Support BGR subpixel layout (#55174)

Release Notes:

- Added text rendering support for BGR subpixel layouts.
This commit is contained in:
John Tur authored and GitHub committed 2026-04-29 12:26:19 +00:00
1 parent 348a9b815b
commit f01d6816cb
9 files changed
+62 -11

No files matched your search

@@ -185,6 +185,7 @@ pub struct InProgressOutput {
scale: Option<i32>,
position: Option<Point<DevicePixels>>,
size: Option<Size<DevicePixels>>,
subpixel: Option<wl_output::Subpixel>,
}
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<String>,
pub scale: i32,
pub bounds: Bounds<DevicePixels>,
pub subpixel: Option<wl_output::Subpixel>,
}
pub(crate) struct WaylandClientState {
@@ -1166,8 +1169,11 @@ impl Dispatch<wl_output::WlOutput, ()> 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)))
@@ -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 {
+7 -2
View File
@@ -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<xproto::Window, WindowRef>,
pub(crate) mouse_focused_window: Option<xproto::Window>,
@@ -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,
@@ -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 = 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",
+6 -1
View File
@@ -425,6 +425,7 @@ impl X11WindowState {
appearance: WindowAppearance,
parent_window: Option<X11WindowStatePtr>,
supports_xinput_gestures: bool,
is_bgr: bool,
) -> anyhow::Result<Self> {
let x_screen_index = params
.display_id
@@ -702,7 +703,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 +726,8 @@ impl X11WindowState {
WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)?
};
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();
@@ -883,6 +886,7 @@ impl X11Window {
appearance: WindowAppearance,
parent_window: Option<X11WindowStatePtr>,
supports_xinput_gestures: bool,
is_bgr: bool,
) -> anyhow::Result<Self> {
let ptr = X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new(
@@ -901,6 +905,7 @@ impl X11Window {
appearance,
parent_window,
supports_xinput_gestures,
is_bgr,
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb: xcb.clone(),
+2 -1
View File
@@ -87,7 +87,8 @@ struct GammaParams {
gamma_ratios: vec4<f32>,
grayscale_enhanced_contrast: f32,
subpixel_enhanced_contrast: f32,
pad: vec2<f32>,
is_bgr: u32,
pad: u32,
}
@group(0) @binding(0) var<uniform> globals: GlobalParams;
+4 -1
View File
@@ -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.
+10 -2
View File
@@ -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 {
@@ -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<T> {
+7 -2
View File
@@ -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<float4> 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;