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