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<bool>`, 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
This commit is contained in:
Richard Feldman 2026-07-09 01:37:35 -04:00 committed by GitHub
parent 10504e3ce1
commit f281770034
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 23 additions and 9 deletions

View file

@ -22,6 +22,20 @@ pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
#[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<bool> 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<ScaledPixels>,
pub color: Hsla,
pub thickness: ScaledPixels,
pub wavy: u32,
pub wavy: PaddedBool32,
}
impl From<Underline> for Primitive {
@ -701,7 +715,7 @@ impl From<SubpixelSprite> for Primitive {
pub struct PolychromeSprite {
pub order: DrawOrder,
pub pad: u32,
pub grayscale: bool,
pub grayscale: PaddedBool32,
pub opacity: f32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,

View file

@ -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,

View file

@ -1191,7 +1191,7 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
}
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<f32> {
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<f32>(vec3<f32>(grayscale), sample.a);
}

View file

@ -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);
}