GPU shader data layout mismatch: a 12-byte gap that can freeze your computer
Testing WebGPU data layouts with Facet
When sharing configuration data between Rust and WebGPU compute shaders, subtle layout differences can cause GPU threads to spin forever. Matt Keeter discovered that a mat3x3 matrix takes 48 bytes in WGSL but only 36 in Rust, leading to a 12-byte offset error. He built a generic unit test using the facet reflection library and naga to automatically compare struct layouts, catching mismatches without hardcoding fields.
Even more unfortunately, I'm writing bytecode VMs which run in compute shaders, and their failure mode is often "congrats, your GPU now has persistently spinning threads which can only be killed by rebooting your computer".
- genxy
> facet is a library for Rust which provides run-time reflection. By annotating your struct with #[derive(facet::Facet)], you get a SHAPE associated type which can be inspected at runtime.
https://github.com/facet-rs/facet
> The core crates give types a SHAPE associated constant with their kind, layout, fields, documentation, attributes, and type-specific operations. facet-reflect builds, reads, and mutates values through those shapes while preserving their invariants.
> Serialization and integration crates build on that reflection layer: JSON, TOML, YAML, MessagePack, Postcard, ASN.1, XDR, CSV, XML, URL-encoded forms, SQLite, Axum, schema generation, structural diffing, and more.
Side by side comparison with SERDE https://facet.rs/guide/serde/
- flohofwoe
FWIW, there's a WebGPU extension which 'harmonizes' uniform buffer struct layout with other address spaces, but currently it's only implemented in Chromium-based browsers:
Ticket: https://github.com/gpuweb/gpuweb/issues/4973
Caniuse: https://caniuse.com/?search=uniform_buffer_standard_layout
The std140-style layout restrictions are basically a leftover when GPUs used vec4 "register tables" for uniform data.
- scoopr
Nice, always good to have some extra checking.
While most of the time you want the memcpy semantics, but I wonder if naga could be used to compute a facet SHAPE from the shader. Maybe it could be used to "serialise" the cpu format to gpu format, without them needing to be padding/repr-compatible (or even field-reorder-compatible!).
Another option of course would be to parse the struct with naga and just always generate the rust counterpart, then it should always be compatible and up-to-date, but then you have codegen in your build step.