T11: swap R↔B between Servo RGBA and GPUI BGRA so colours stop inverting
This commit is contained in:
@@ -110,13 +110,19 @@ impl WebSurfaceFrame {
|
||||
let image = if parts.rgba_bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let bytes_hash = rgba_hash(&parts.rgba_bytes);
|
||||
Some(resolve_render_image(
|
||||
parts.width,
|
||||
parts.height,
|
||||
parts.rgba_bytes,
|
||||
bytes_hash,
|
||||
)?)
|
||||
// Servo's `read_pixels(gl::RGBA, gl::UNSIGNED_BYTE)`
|
||||
// writes R-G-B-A in memory order. GPUI's `RenderImage` is
|
||||
// documented as "in BGRA format" and uploads via
|
||||
// `MTLPixelFormat::BGRA8Unorm`, which reads B-G-R-A. Hand
|
||||
// the bytes across unchanged and the Metal sampler treats
|
||||
// R as B (and vice versa) — every coloured pixel renders
|
||||
// with R and B swapped. Swap once here so the rest of the
|
||||
// pipeline (dedup hash, image buffer, GPU upload) all
|
||||
// operate on the same BGRA representation.
|
||||
let mut bytes = parts.rgba_bytes;
|
||||
swap_red_blue_in_place(&mut bytes);
|
||||
let bytes_hash = rgba_hash(&bytes);
|
||||
Some(resolve_render_image(parts.width, parts.height, bytes, bytes_hash)?)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -241,6 +247,15 @@ pub(super) enum WebSurfaceError {
|
||||
InvalidFrameBuffer { width: u32, height: u32 },
|
||||
}
|
||||
|
||||
/// Swap byte 0 and byte 2 of every 4-byte pixel, converting Servo's
|
||||
/// RGBA8 output into GPUI's expected BGRA8 in place. ~0.8 ms on a
|
||||
/// 1080p frame; cheap relative to the AHash pass that follows.
|
||||
fn swap_red_blue_in_place(bytes: &mut [u8]) {
|
||||
for pixel in bytes.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn rgba_hash(bytes: &[u8]) -> u64 {
|
||||
let mut hasher = AHasher::default();
|
||||
hasher.write(bytes);
|
||||
|
||||
@@ -327,6 +327,46 @@ fn click_survives_viewport_bounds_change_before_drain() -> Result<(), Box<dyn Er
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Servo's `read_pixels(gl::RGBA, gl::UNSIGNED_BYTE)` writes R-G-B-A
|
||||
/// in memory order. GPUI's `RenderImage` is documented as "in BGRA
|
||||
/// format" and the macOS Metal atlas uploads bytes as
|
||||
/// `MTLPixelFormat::BGRA8Unorm` (B-G-R-A in memory). If the bytes
|
||||
/// crossed the boundary unchanged, the Metal sampler would read the R
|
||||
/// byte as B and vice versa — every coloured pixel would render with
|
||||
/// R and B swapped. `WebSurfaceFrame::from_live_frame` is responsible
|
||||
/// for swapping the two channels before handing the buffer to GPUI.
|
||||
///
|
||||
/// This test fed `(R=255, G=0, B=0, A=255)` — one red RGBA pixel —
|
||||
/// through the live-frame conversion and asserts the resulting
|
||||
/// `RenderImage` carries `(B=0, G=0, R=255, A=255)` byte-for-byte.
|
||||
#[test]
|
||||
fn live_frame_swaps_red_and_blue_bytes_for_gpui_bgra() -> Result<(), Box<dyn Error>> {
|
||||
use crate::services::servo_live::ServoLiveFrame;
|
||||
use crate::shell::web_surface_frame::WebSurfaceFrame;
|
||||
use crate::shell::web_surface_geometry::WebSurfaceScrollOffset;
|
||||
|
||||
let rgba_red_pixel = vec![255u8, 0, 0, 255];
|
||||
let live = ServoLiveFrame::for_test(1, 1, rgba_red_pixel);
|
||||
let frame = WebSurfaceFrame::from_live_frame(
|
||||
"https://example.com/".to_string(),
|
||||
WebSurfaceScrollOffset::default(),
|
||||
100,
|
||||
live,
|
||||
)?;
|
||||
|
||||
let image = frame.image.as_ref().ok_or("software frame must produce a RenderImage")?;
|
||||
let bytes = image.as_bytes(0).ok_or("RenderImage must expose its first frame's bytes")?;
|
||||
assert_eq!(
|
||||
bytes,
|
||||
&[0u8, 0, 255, 255],
|
||||
"Servo's RGBA red pixel must be swapped to BGRA (B=0, G=0, R=255, A=255) \
|
||||
before GPUI uploads it as BGRA8Unorm. If this assert says \
|
||||
`[255, 0, 0, 255]`, the swap is missing and every coloured \
|
||||
pixel on the page renders with R and B exchanged.",
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn web_bounds() -> Bounds<gpui::Pixels> {
|
||||
Bounds::new(point(px(0.0), px(0.0)), size(px(640.0), px(480.0)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user