From f04fef0067a834bd71d006320267d41abb99ae20 Mon Sep 17 00:00:00 2001 From: John Tur Date: Wed, 29 Apr 2026 05:39:18 -0400 Subject: [PATCH 1/5] Support BGR subpixel layout --- crates/gpui_linux/src/linux/wayland/client.rs | 12 ++++++++++-- crates/gpui_linux/src/linux/wayland/window.rs | 12 ++++++++++++ crates/gpui_linux/src/linux/x11/window.rs | 11 ++++++++++- crates/gpui_wgpu/src/shaders.wgsl | 3 ++- crates/gpui_wgpu/src/shaders_subpixel.wgsl | 5 ++++- crates/gpui_wgpu/src/wgpu_renderer.rs | 12 ++++++++++-- crates/gpui_windows/src/directx_renderer.rs | 6 ++++++ crates/gpui_windows/src/shaders.hlsl | 9 +++++++-- 8 files changed, 61 insertions(+), 9 deletions(-) diff --git a/crates/gpui_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index 3b8ca0f45025b7..e6f0489ee34435 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -185,6 +185,7 @@ pub struct InProgressOutput { scale: Option, position: Option>, size: Option>, + subpixel: Option, } impl InProgressOutput { @@ -195,6 +196,7 @@ impl InProgressOutput { name: self.name.clone(), scale, bounds: Bounds::new(position, size), + subpixel: self.subpixel, }) } else { None @@ -207,6 +209,7 @@ pub struct Output { pub name: Option, pub scale: i32, pub bounds: Bounds, + pub subpixel: Option, } pub(crate) struct WaylandClientState { @@ -1166,8 +1169,13 @@ impl Dispatch for WaylandClientStatePtr { wl_output::Event::Scale { factor } => { in_progress_output.scale = Some(factor); } - wl_output::Event::Geometry { x, y, .. } => { - in_progress_output.position = Some(point(DevicePixels(x), DevicePixels(y))) + wl_output::Event::Geometry { + x, y, subpixel, .. + } => { + in_progress_output.position = Some(point(DevicePixels(x), DevicePixels(y))); + if let WEnum::Value(subpixel) = subpixel { + in_progress_output.subpixel = Some(subpixel); + } } wl_output::Event::Mode { width, height, .. } => { in_progress_output.size = Some(size(DevicePixels(width), DevicePixels(height))) diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index 9862d307ef0f92..5d53496c057f1a 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -403,6 +403,16 @@ impl WaylandWindowState { || self.background_appearance != WindowBackgroundAppearance::Opaque } + fn update_subpixel_layout(&mut self) { + use wayland_client::protocol::wl_output::Subpixel; + let is_bgr = self + .display + .as_ref() + .and_then(|(_, output)| output.subpixel) + .is_some_and(|s| s == Subpixel::HorizontalBgr); + self.renderer.set_subpixel_layout(is_bgr); + } + pub fn primary_output_scale(&mut self) -> i32 { let mut scale = 1; let mut current_output = self.display.take(); @@ -864,6 +874,7 @@ impl WaylandWindowStatePtr { state.outputs.insert(id, output.clone()); let scale = state.primary_output_scale(); + state.update_subpixel_layout(); // We use `PreferredBufferScale` instead to set the scale if it's available if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE { @@ -876,6 +887,7 @@ impl WaylandWindowStatePtr { state.outputs.remove(&output.id()); let scale = state.primary_output_scale(); + state.update_subpixel_layout(); // We use `PreferredBufferScale` instead to set the scale if it's available if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE { diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 285ba8802db744..a4273e6d630fa7 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -702,7 +702,7 @@ impl X11WindowState { xcb_flush(xcb); - let renderer = { + let mut renderer = { let raw_window = RawWindow { connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection( xcb, @@ -725,6 +725,15 @@ impl X11WindowState { WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? }; + if let Some(client_rc) = client.0.upgrade() { + let client_state = client_rc.borrow(); + let is_bgr = client_state + ._resource_database + .get_string("Xft.rgba", "Xft.Rgba") + .is_some_and(|v| v.eq_ignore_ascii_case("bgr")); + renderer.set_subpixel_layout(is_bgr); + } + // Set max window size hints based on the GPU's maximum texture dimension. // This prevents the window from being resized larger than what the GPU can render. let max_texture_size = renderer.max_texture_size(); diff --git a/crates/gpui_wgpu/src/shaders.wgsl b/crates/gpui_wgpu/src/shaders.wgsl index 12ce7d29b0b816..b700697f47b932 100644 --- a/crates/gpui_wgpu/src/shaders.wgsl +++ b/crates/gpui_wgpu/src/shaders.wgsl @@ -87,7 +87,8 @@ struct GammaParams { gamma_ratios: vec4, grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, - pad: vec2, + is_bgr: u32, + pad: u32, } @group(0) @binding(0) var globals: GlobalParams; diff --git a/crates/gpui_wgpu/src/shaders_subpixel.wgsl b/crates/gpui_wgpu/src/shaders_subpixel.wgsl index 7acbd2e3d2e68e..37face0c482ac4 100644 --- a/crates/gpui_wgpu/src/shaders_subpixel.wgsl +++ b/crates/gpui_wgpu/src/shaders_subpixel.wgsl @@ -38,7 +38,10 @@ fn vs_subpixel_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_i @fragment fn fs_subpixel_sprite(input: SubpixelSpriteOutput) -> SubpixelSpriteFragmentOutput { - let sample = textureSample(t_sprite, s_sprite, input.tile_position).rgb; + var sample = textureSample(t_sprite, s_sprite, input.tile_position).rgb; + if (gamma_params.is_bgr != 0u) { + sample = sample.bgr; + } let alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, gamma_params.subpixel_enhanced_contrast, gamma_params.gamma_ratios); // Alpha clip after using the derivatives. diff --git a/crates/gpui_wgpu/src/wgpu_renderer.rs b/crates/gpui_wgpu/src/wgpu_renderer.rs index 39b6f3f7bdfee9..7377c73fea1030 100644 --- a/crates/gpui_wgpu/src/wgpu_renderer.rs +++ b/crates/gpui_wgpu/src/wgpu_renderer.rs @@ -50,7 +50,8 @@ struct GammaParams { gamma_ratios: [f32; 4], grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, - _pad: [f32; 2], + is_bgr: u32, + _pad: u32, } #[derive(Clone, Debug)] @@ -146,6 +147,7 @@ pub struct WgpuRenderer { max_buffer_size: u64, storage_buffer_alignment: u64, rendering_params: RenderingParameters, + is_bgr: bool, dual_source_blending: bool, adapter_info: wgpu::AdapterInfo, transparent_alpha_mode: wgpu::CompositeAlphaMode, @@ -475,6 +477,7 @@ impl WgpuRenderer { max_buffer_size, storage_buffer_alignment, rendering_params, + is_bgr: false, dual_source_blending, adapter_info, transparent_alpha_mode, @@ -1016,6 +1019,10 @@ impl WgpuRenderer { resources.path_msaa_view = path_msaa_view; } + pub fn set_subpixel_layout(&mut self, is_bgr: bool) { + self.is_bgr = is_bgr; + } + pub fn update_transparency(&mut self, transparent: bool) { let new_alpha_mode = if transparent { self.transparent_alpha_mode @@ -1147,7 +1154,8 @@ impl WgpuRenderer { gamma_ratios: self.rendering_params.gamma_ratios, grayscale_enhanced_contrast: self.rendering_params.grayscale_enhanced_contrast, subpixel_enhanced_contrast: self.rendering_params.subpixel_enhanced_contrast, - _pad: [0.0; 2], + is_bgr: self.is_bgr as u32, + _pad: 0, }; let globals = GlobalParams { diff --git a/crates/gpui_windows/src/directx_renderer.rs b/crates/gpui_windows/src/directx_renderer.rs index 2955b23429bda6..b5c4d3bf34a946 100644 --- a/crates/gpui_windows/src/directx_renderer.rs +++ b/crates/gpui_windows/src/directx_renderer.rs @@ -32,6 +32,7 @@ pub(crate) struct FontInfo { pub gamma_ratios: [f32; 4], pub grayscale_enhanced_contrast: f32, pub subpixel_enhanced_contrast: f32, + pub is_bgr: bool, } pub(crate) struct DirectXRenderer { @@ -195,6 +196,8 @@ impl DirectXRenderer { viewport_size: [resources.viewport.Width, resources.viewport.Height], grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast, subpixel_enhanced_contrast: self.font_info.subpixel_enhanced_contrast, + is_bgr: self.font_info.is_bgr as u32, + _pad: [0; 3], }], )?; unsafe { @@ -741,6 +744,7 @@ impl DirectXRenderer { gamma_ratios: gpui::get_gamma_correction_ratios(render_params.GetGamma()), grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(), subpixel_enhanced_contrast: render_params.GetEnhancedContrast(), + is_bgr: render_params.GetPixelGeometry() == DWRITE_PIXEL_GEOMETRY_BGR, } }) } @@ -961,6 +965,8 @@ struct GlobalParams { viewport_size: [f32; 2], grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, + is_bgr: u32, + _pad: [u32; 3], } struct PipelineState { diff --git a/crates/gpui_windows/src/shaders.hlsl b/crates/gpui_windows/src/shaders.hlsl index 646cfd61cc37c3..d40c7241bd0f23 100644 --- a/crates/gpui_windows/src/shaders.hlsl +++ b/crates/gpui_windows/src/shaders.hlsl @@ -5,6 +5,8 @@ cbuffer GlobalParams: register(b0) { float2 global_viewport_size; float grayscale_enhanced_contrast; float subpixel_enhanced_contrast; + uint is_bgr; + uint3 global_pad; }; Texture2D t_sprite: register(t0); @@ -420,11 +422,11 @@ float4 gradient_color(Background background, // checkerboard float size = background.gradient_angle_or_pattern_height; float2 relative_position = position - bounds.origin; - + float x_index = floor(relative_position.x / size); float y_index = floor(relative_position.y / size); float should_be_colored = (x_index + y_index) % 2.0; - + color = solid_color; color.a *= saturate(should_be_colored); break; @@ -1157,6 +1159,9 @@ MonochromeSpriteVertexOutput subpixel_sprite_vertex(uint vertex_id: SV_VertexID, SubpixelSpriteFragmentOutput subpixel_sprite_fragment(MonochromeSpriteFragmentInput input) { float3 sample = t_sprite.Sample(s_sprite, input.tile_position).rgb; + if (is_bgr) { + sample = sample.bgr; + } float3 alpha_corrected = apply_contrast_and_gamma_correction3(sample, input.color.rgb, subpixel_enhanced_contrast, gamma_ratios); SubpixelSpriteFragmentOutput output; From ebff3b4ea23fe0bb49ec4583754a6209457584b9 Mon Sep 17 00:00:00 2001 From: John Tur Date: Wed, 29 Apr 2026 05:46:21 -0400 Subject: [PATCH 2/5] Fix field name --- crates/gpui_linux/src/linux/x11/client.rs | 4 ++-- crates/gpui_linux/src/linux/x11/window.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 07d1596a2f7b68..cb016158fcafb6 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -188,7 +188,7 @@ pub struct X11ClientState { xkb_device_id: i32, client_side_decorations_supported: bool, pub(crate) x_root_index: usize, - pub(crate) _resource_database: Database, + pub(crate) resource_database: Database, pub(crate) atoms: XcbAtoms, pub(crate) windows: HashMap, pub(crate) mouse_focused_window: Option, @@ -525,7 +525,7 @@ impl X11Client { xkb_device_id, client_side_decorations_supported, x_root_index, - _resource_database: resource_database, + resource_database, atoms, windows: HashMap::default(), mouse_focused_window: None, diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index a4273e6d630fa7..f8b33155013fb5 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -728,7 +728,7 @@ impl X11WindowState { if let Some(client_rc) = client.0.upgrade() { let client_state = client_rc.borrow(); let is_bgr = client_state - ._resource_database + .resource_database .get_string("Xft.rgba", "Xft.Rgba") .is_some_and(|v| v.eq_ignore_ascii_case("bgr")); renderer.set_subpixel_layout(is_bgr); From 2a81ed82b07471e7cb735ffcc2faa1508734eed9 Mon Sep 17 00:00:00 2001 From: John Tur Date: Wed, 29 Apr 2026 05:47:07 -0400 Subject: [PATCH 3/5] Fmt --- crates/gpui_linux/src/linux/wayland/client.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/gpui_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index e6f0489ee34435..f91efa53e8d391 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -1169,9 +1169,7 @@ impl Dispatch for WaylandClientStatePtr { wl_output::Event::Scale { factor } => { in_progress_output.scale = Some(factor); } - wl_output::Event::Geometry { - x, y, subpixel, .. - } => { + wl_output::Event::Geometry { x, y, subpixel, .. } => { in_progress_output.position = Some(point(DevicePixels(x), DevicePixels(y))); if let WEnum::Value(subpixel) = subpixel { in_progress_output.subpixel = Some(subpixel); From b8f7c39313c1f86de3691b0a3b59231c655b030a Mon Sep 17 00:00:00 2001 From: John Tur Date: Wed, 29 Apr 2026 07:52:17 -0400 Subject: [PATCH 4/5] Fix double borrow on X11 --- crates/gpui_linux/src/linux/x11/client.rs | 5 +++++ crates/gpui_linux/src/linux/x11/window.rs | 12 ++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index cb016158fcafb6..c71b1545296bef 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -1599,6 +1599,10 @@ impl LinuxClient for X11Client { let appearance = state.common.appearance; let compositor_gpu = state.compositor_gpu.take(); let supports_xinput_gestures = state.supports_xinput_gestures; + let is_bgr = dbg!(state + .resource_database + .get_string("Xft.rgba", "Xft.Rgba")) + .is_some_and(|v| v.eq_ignore_ascii_case("bgr")); let window = X11Window::new( handle, X11ClientStatePtr(Rc::downgrade(&self.0)), @@ -1615,6 +1619,7 @@ impl LinuxClient for X11Client { appearance, parent_window, supports_xinput_gestures, + is_bgr, )?; check_reply( || "Failed to set XdndAware property", diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index f8b33155013fb5..b44ac2faf41821 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -425,6 +425,7 @@ impl X11WindowState { appearance: WindowAppearance, parent_window: Option, supports_xinput_gestures: bool, + is_bgr: bool, ) -> anyhow::Result { let x_screen_index = params .display_id @@ -725,14 +726,7 @@ impl X11WindowState { WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)? }; - if let Some(client_rc) = client.0.upgrade() { - let client_state = client_rc.borrow(); - let is_bgr = client_state - .resource_database - .get_string("Xft.rgba", "Xft.Rgba") - .is_some_and(|v| v.eq_ignore_ascii_case("bgr")); - renderer.set_subpixel_layout(is_bgr); - } + renderer.set_subpixel_layout(is_bgr); // Set max window size hints based on the GPU's maximum texture dimension. // This prevents the window from being resized larger than what the GPU can render. @@ -892,6 +886,7 @@ impl X11Window { appearance: WindowAppearance, parent_window: Option, supports_xinput_gestures: bool, + is_bgr: bool, ) -> anyhow::Result { let ptr = X11WindowStatePtr { state: Rc::new(RefCell::new(X11WindowState::new( @@ -910,6 +905,7 @@ impl X11Window { appearance, parent_window, supports_xinput_gestures, + is_bgr, )?)), callbacks: Rc::new(RefCell::new(Callbacks::default())), xcb: xcb.clone(), From 82efb9febeca4949d142dde6b8f66d1e6cbd4414 Mon Sep 17 00:00:00 2001 From: John Tur Date: Wed, 29 Apr 2026 07:56:37 -0400 Subject: [PATCH 5/5] Fmt --- crates/gpui_linux/src/linux/x11/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index c71b1545296bef..03272a99051aa7 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -1599,9 +1599,9 @@ impl LinuxClient for X11Client { let appearance = state.common.appearance; let compositor_gpu = state.compositor_gpu.take(); let supports_xinput_gestures = state.supports_xinput_gestures; - let is_bgr = dbg!(state + let is_bgr = state .resource_database - .get_string("Xft.rgba", "Xft.Rgba")) + .get_string("Xft.rgba", "Xft.Rgba") .is_some_and(|v| v.eq_ignore_ascii_case("bgr")); let window = X11Window::new( handle,