Skip to content

Conversation

jamienicol
Copy link
Contributor

Connections
SPIR-V fix for #4371 (leave open for GLSL)

Description
Vulkan's default uniform buffer "extended layout" (std140) defines matrices in terms of arrays of their vector type, and defines arrays to have an alignment rounded up to a multiple of 16. WGSL/Naga IR defines matrices to have the same alignment as their vector type, not rounded up. For a matCx2 this means Naga expects each column to have an alignment of 8, whereas Vulkan expects 16.

To fix this, for each matCx2 struct member in a uniform buffer we decompose matrix such that each column is a vector member of the containing struct. For matrices used directly as uniform buffers, we create a struct that contains a vector member for each column. For arrays of matrices, we declare an array of a struct containing a vector member for each column.

We only use these alternative type declarations for uniform buffers, and when loading a value of such a type we convert it to the original type. This is in contrast to the approach used in the HLSL backend where all structs containing matCx2 have a modified layout. This allows the HLSL backend to avoid having to convert a struct after loading one, but it still must convert matrices or arrays of matrices when loading from uniform buffers, or when accessing values of these types that are struct members in any address space. The approach taken here means we consistently must convert two-row matrices, or arrays or structs containing two-row matrices, but only when loading from uniform buffers. It also allows us to ignore stores altogether, as uniform buffers are read only.

Testing
Extended struct_layout tests to handle additional cases, and remove expected fail for vulkan. Made existing hlsl_mat_cx[23].wgsl snapshot tests run on SPIR-V too.

Squash or Rebase?

Rebase

Checklist

  • Run cargo fmt.
  • Run taplo format.
  • Run cargo clippy --tests. If applicable, add:
    • --target wasm32-unknown-unknown
  • Run cargo xtask test to run tests.
  • If this contains user-facing changes, add a CHANGELOG.md entry.

@jamienicol jamienicol marked this pull request as draft September 18, 2025 10:16
Extend existing tests to use dynamic indexing of matrices' columns as
well static indexing. Add new tests for arrays of matrices.
Two-row matrices in uniform buffers in Vulkan/SPIR-V's default
"extended" (std140) layout do not adhere to WGSL/Naga IR's layout
rules - each column is aligned to a minimum of 16 bytes rather than
just the vector size.

To work around this, we emit additional std140 compatible type
declarations for each type used in a uniform buffer that requires
one. Any two-row matrix struct member is decomposed into the
containing struct as separate vectors for each column. Two-row
matrices used directly as uniform buffers are wrapped in a struct with
a vector member for each column, and arrays (of arrays, etc) of
two-row matrices are declared as arrays (of arrays) of structs
containing a vector member for each column.

When loading such a value from a uniform buffer, we convert from the
std140 compatible type to the regular type immediately after loading.
Accesses of a uniform two-row matrix's columns using constant indices
are rewritten to access the containing struct's vector members
directly. Accesses using dynamic indices are implemented by loading
and converting the matrix, extracting each column then `switch`ing on
the index to select the correct column.

We can now remove the expected failure annotation for the Vulkan
backend on the uniform_input struct_layout test, as it now passes. We
additionally make the hlsl_mat_cx*.wgsl snapshot tests run for the
SPIR-V backend too, and remove the "hlsl" prefix from the name, as
they are no longer just relevant for HLSL.
@jamienicol jamienicol marked this pull request as ready for review September 18, 2025 10:58
@jamienicol
Copy link
Contributor Author

@jimblandy I know we previously discussed my approach here of using alternative type declarations for uniform types, vs always declaring structs with a modified type, and you had a preference for the latter. After testing out both I found this approach much cleaner. There was less special casing as the rules for when we need to convert a type are more consistent, and being able to ignore stores altogether makes life much easier. (And FWIW I'm not convinced stores are implemented correctly in HLSL, but I still need to test that and file a bug if I'm right). I've tried to document that decision both in this PR and in the module-level comment.)

@andyleiserson andyleiserson self-assigned this Sep 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants