|
1 | 1 | /*!
|
2 | 2 | Backend for [SPIR-V][spv] (Standard Portable Intermediate Representation).
|
3 | 3 |
|
| 4 | +# Layout of values in `uniform` buffers |
| 5 | +
|
| 6 | +WGSL's ["Internal Layout of Values"][ilov] rules specify how each WGSL type |
| 7 | +should be stored in `uniform` and `storage` buffers, and Naga IR adheres to |
| 8 | +these rules. The SPIR-V we generate must access values in that form, even when |
| 9 | +it is not what Vulkan would use normally. Fortunately the rules for `storage` |
| 10 | +buffers match Vulkan's, but some adjustments must be made when emitting SPIR-V |
| 11 | +for `uniform` buffers. |
| 12 | +
|
| 13 | +## Padding in two-row matrices |
| 14 | +
|
| 15 | +In Vulkan's ["extended layout"][extended-layout] (also known as std140) used |
| 16 | +for `uniform` buffers, matrices are defined in terms of arrays of their vector |
| 17 | +type, and arrays are defined to have an alignment equal to the alignment of |
| 18 | +their element type rounded up to a multiple of 16. This means that each column |
| 19 | +of the vector has a minimum alignment of 16. WGSL, and consequently Naga IR, on |
| 20 | +the other hand defines each column to have an alignment equal to the alignment |
| 21 | +of the vector type, without being rounded up to 16. |
| 22 | +
|
| 23 | +To compensate for this, for any `struct` used as a `uniform` buffer which |
| 24 | +contains a two-row matrix, we declare an additional "std140 compatible" type |
| 25 | +in which each column of the matrix has been decomposed into the containing |
| 26 | +struct. For example, the following WGSL struct type: |
| 27 | +
|
| 28 | +```ignore |
| 29 | +struct Baz { |
| 30 | + m: mat3x2<f32>, |
| 31 | +} |
| 32 | +``` |
| 33 | +
|
| 34 | +is rendered as the SPIR-V struct type: |
| 35 | +
|
| 36 | +```ignore |
| 37 | +OpTypeStruct %v2float %v2float %v2float |
| 38 | +``` |
| 39 | +
|
| 40 | +This has the effect that struct indices in Naga IR for such types do not |
| 41 | +correspond to the struct indices used in SPIR-V. A mapping of struct indices |
| 42 | +for these types is maintained in [`Std140CompatTypeInfo`]. |
| 43 | +
|
| 44 | +Additionally, any two-row matrices that are declared directly as uniform |
| 45 | +buffers without being wrapped in a struct are declared as a struct containing a |
| 46 | +vector member for each column. Any array of a two-row matrix in a uniform |
| 47 | +buffer is declared as an array of a struct containing a vector member for each |
| 48 | +column. Any struct or array within a uniform buffer which contains a member or |
| 49 | +whose base type requires requires a std140 compatible type declaration, itself |
| 50 | +requires a std140 compatible type declaration. |
| 51 | +
|
| 52 | +Whenever a value of such a type is [`loaded`] we insert code to convert the |
| 53 | +loaded value from the std140 compatible type to the regular type. This occurs |
| 54 | +in `BlockContext::write_checked_load`, making use of the wrapper function |
| 55 | +defined by `Writer::write_wrapped_convert_from_std140_compat_type`. For matrices |
| 56 | +that have been decomposed as separate columns in the containing struct, we load |
| 57 | +each column separately then composite the matrix type in |
| 58 | +`BlockContext::maybe_write_load_uniform_matcx2_struct_member`. |
| 59 | +
|
| 60 | +Whenever a column of a matrix that has been decomposed into its containing |
| 61 | +struct is [`accessed`] with a constant index we adjust the emitted access chain |
| 62 | +to access from the containing struct instead, in `BlockContext::write_access_chain`. |
| 63 | +
|
| 64 | +Whenever a column of a uniform buffer two-row matrix is [`dynamically accessed`] |
| 65 | +we must first load the matrix type, converting it from its std140 compatible |
| 66 | +type as described above, then access the column using the wrapper function |
| 67 | +defined by `Writer::write_wrapped_matcx2_get_column`. This is handled by |
| 68 | +`BlockContext::maybe_write_uniform_matcx2_dynamic_access`. |
| 69 | +
|
| 70 | +Note that this approach differs somewhat from the equivalent code in the HLSL |
| 71 | +backend. For HLSL all structs containing two-row matrices (or arrays of such) |
| 72 | +have their declarations modified, not just those used as uniform buffers. |
| 73 | +Two-row matrices and arrays of such only use modified type declarations when |
| 74 | +used as uniform buffers, or additionally when used as struct member in any |
| 75 | +context. This avoids the need to convert struct values when loading from uniform |
| 76 | +buffers, but when loading arrays and matrices from uniform buffers or from any |
| 77 | +struct the conversion is still required. In contrast, the approach used here |
| 78 | +always requires converting *any* affected type when loading from a uniform |
| 79 | +buffer, but consistently *only* when loading from a uniform buffer. As a result |
| 80 | +this also means we only have to handle loads and not stores, as uniform buffers |
| 81 | +are read-only. |
| 82 | +
|
4 | 83 | [spv]: https://www.khronos.org/registry/SPIR-V/
|
| 84 | +[ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout |
| 85 | +[extended-layout]: https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources-layout |
| 86 | +[`loaded`]: crate::Expression::Load |
| 87 | +[`accessed`]: crate::Expression::AccessIndex |
| 88 | +[`dynamically accessed`]: crate::Expression::Access |
5 | 89 | */
|
6 | 90 |
|
7 | 91 | mod block;
|
@@ -461,6 +545,12 @@ enum WrappedFunction {
|
461 | 545 | left_type_id: Word,
|
462 | 546 | right_type_id: Word,
|
463 | 547 | },
|
| 548 | + ConvertFromStd140CompatType { |
| 549 | + r#type: Handle<crate::Type>, |
| 550 | + }, |
| 551 | + MatCx2GetColumn { |
| 552 | + r#type: Handle<crate::Type>, |
| 553 | + }, |
464 | 554 | }
|
465 | 555 |
|
466 | 556 | /// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids.
|
@@ -721,6 +811,20 @@ impl BlockContext<'_> {
|
721 | 811 | }
|
722 | 812 | }
|
723 | 813 |
|
| 814 | +/// Information about a type for which we have declared a std140 layout |
| 815 | +/// compatible variant, because the type is used in a uniform but does not |
| 816 | +/// adhere to std140 requirements. The uniform will be declared using the |
| 817 | +/// type `type_id`, and the result of any `Load` will be immediately converted |
| 818 | +/// to the base type. This is used for matrices with 2 rows, as well as any |
| 819 | +/// arrays or structs containing such matrices. |
| 820 | +pub struct Std140CompatTypeInfo { |
| 821 | + /// ID of the std140 compatible type declaration. |
| 822 | + type_id: Word, |
| 823 | + /// For structs, a mapping of Naga IR struct member indices to the indices |
| 824 | + /// used in the generated SPIR-V. For non-struct types this will be empty. |
| 825 | + member_indices: Vec<u32>, |
| 826 | +} |
| 827 | + |
724 | 828 | pub struct Writer {
|
725 | 829 | physical_layout: PhysicalLayout,
|
726 | 830 | logical_layout: LogicalLayout,
|
@@ -760,6 +864,7 @@ pub struct Writer {
|
760 | 864 | constant_ids: HandleVec<crate::Expression, Word>,
|
761 | 865 | cached_constants: crate::FastHashMap<CachedConstant, Word>,
|
762 | 866 | global_variables: HandleVec<crate::GlobalVariable, GlobalVariable>,
|
| 867 | + std140_compat_uniform_types: crate::FastHashMap<Handle<crate::Type>, Std140CompatTypeInfo>, |
763 | 868 | binding_map: BindingMap,
|
764 | 869 |
|
765 | 870 | // Cached expressions are only meaningful within a BlockContext, but we
|
|
0 commit comments