From f28177003499ec9c5052d1883fee62b148451c59 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 9 Jul 2026 01:37:35 -0400 Subject: [PATCH] gpui: Store GPU-facing bools as PaddedBool32 to avoid uninitialized padding (#60482) `PolychromeSprite` in `crates/gpui/src/scene.rs` is `#[repr(C)]` and had a `grayscale: bool` field followed by 3 compiler-inserted padding bytes that were never written. The wgpu renderer's `instance_bytes` reinterprets `&[PolychromeSprite]` as `&[u8]` via `slice::from_raw_parts` and passes it to `queue.write_buffer`, so those uninitialized padding bytes were exposed behind a shared `&[u8]` on every frame that draws an image or emoji, which is undefined behavior. Rather than widening the field to a raw `u32` (which would suggest values other than 0 and 1 are meaningful), this introduces `PaddedBool32`: a `#[repr(transparent)]` wrapper around `u32` whose only public constructor is `From`, so the 0-or-1 invariant is enforced by the type while the layout has no padding. `Underline.wavy`, which was already a raw `u32` for the same reason, is converted too. cbindgen emits the wrapper as `typedef uint32_t PaddedBool32;`, so the generated Metal header and shaders are unchanged. The WGSL and HLSL shaders already declared these fields as `u32`/`uint`; their `& 0xFFu` masks, which existed to ignore the garbage padding bytes, are now simplified to plain comparisons. Release Notes: - N/A --- crates/gpui/src/scene.rs | 18 ++++++++++++++++-- crates/gpui/src/window.rs | 8 ++++---- crates/gpui_wgpu/src/shaders.wgsl | 4 ++-- crates/gpui_windows/src/shaders.hlsl | 2 +- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index bc7f5d79eac..ea2ad7b2b90 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -22,6 +22,20 @@ pub type PathVertex_ScaledPixels = PathVertex; #[expect(missing_docs)] pub type DrawOrder = u32; +/// A boolean stored as a `u32` so that GPU-facing structs contain no +/// compiler-inserted padding bytes, which would be undefined behavior to +/// reinterpret as `&[u8]` when writing instance buffers. Guaranteed to be +/// `0` or `1` by construction; shaders read it as a `u32`/`uint`. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct PaddedBool32(u32); + +impl From for PaddedBool32 { + fn from(value: bool) -> Self { + PaddedBool32(value as u32) + } +} + #[derive(Default)] #[expect(missing_docs)] pub struct Scene { @@ -511,7 +525,7 @@ pub struct Underline { pub content_mask: ContentMask, pub color: Hsla, pub thickness: ScaledPixels, - pub wavy: u32, + pub wavy: PaddedBool32, } impl From for Primitive { @@ -701,7 +715,7 @@ impl From for Primitive { pub struct PolychromeSprite { pub order: DrawOrder, pub pad: u32, - pub grayscale: bool, + pub grayscale: PaddedBool32, pub opacity: f32, pub bounds: Bounds, pub content_mask: ContentMask, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 62a8011d4e6..7059cf863a6 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3812,7 +3812,7 @@ impl Window { content_mask: self.snapped_content_mask(), color: style.color.unwrap_or_default().opacity(element_opacity), thickness, - wavy: if style.wavy { 1 } else { 0 }, + wavy: style.wavy.into(), }); } @@ -3842,7 +3842,7 @@ impl Window { content_mask: self.snapped_content_mask(), thickness: self.snap_stroke(style.thickness), color: style.color.unwrap_or_default().opacity(opacity), - wavy: 0, + wavy: false.into(), }); } @@ -4002,7 +4002,7 @@ impl Window { self.next_frame.scene.insert_primitive(PolychromeSprite { order: 0, pad: 0, - grayscale: false, + grayscale: false.into(), bounds, corner_radii: Default::default(), content_mask, @@ -4117,7 +4117,7 @@ impl Window { self.next_frame.scene.insert_primitive(PolychromeSprite { order: 0, pad: 0, - grayscale, + grayscale: grayscale.into(), bounds, content_mask, corner_radii, diff --git a/crates/gpui_wgpu/src/shaders.wgsl b/crates/gpui_wgpu/src/shaders.wgsl index 933b88e84d7..747a34d81e0 100644 --- a/crates/gpui_wgpu/src/shaders.wgsl +++ b/crates/gpui_wgpu/src/shaders.wgsl @@ -1191,7 +1191,7 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4 { } let underline = b_underlines[input.underline_id]; - if ((underline.wavy & 0xFFu) == 0u) + if (underline.wavy == 0u) { return blend_color(input.color, input.color.a); } @@ -1305,7 +1305,7 @@ fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4 { let distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); var color = sample; - if ((sprite.grayscale & 0xFFu) != 0u) { + if (sprite.grayscale != 0u) { let grayscale = dot(color.rgb, GRAYSCALE_FACTORS); color = vec4(vec3(grayscale), sample.a); } diff --git a/crates/gpui_windows/src/shaders.hlsl b/crates/gpui_windows/src/shaders.hlsl index 89c12489b24..483f0399385 100644 --- a/crates/gpui_windows/src/shaders.hlsl +++ b/crates/gpui_windows/src/shaders.hlsl @@ -1249,7 +1249,7 @@ float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Targe float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); float4 color = sample; - if ((sprite.grayscale & 0xFFu) != 0u) { + if (sprite.grayscale != 0u) { float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS); color = float4(grayscale, sample.a); }