Module shader

Source
Expand description

A program that is run on the device.

In Vulkan, shaders are grouped in shader modules. Each shader module is built from SPIR-V code and can contain one or more entry points. Note that for the moment the official GLSL-to-SPIR-V compiler does not support multiple entry points.

The vulkano library can parse and introspect SPIR-V code, but it does not fully validate the code. You are encouraged to use the vulkano-shaders crate that will generate Rust code that wraps around vulkano’s shaders API.

§Shader interface

Vulkan has specific rules for interfacing shaders with each other, and with other parts of a program.

§Endianness

The Vulkan specification requires that a Vulkan implementation has runtime support for the types u8, u16, u32, u64 as well as their signed versions, as well as f32 and f64 on the host, and that the representation and endianness of these types matches those on the device. This means that if you have for example a Subbuffer<u32>, you can be sure that it is represented the same way on the host as it is on the device, and you don’t need to worry about converting the endianness.

§Layout of data

When buffers, push constants or other user-provided data are accessed in shaders, the shader expects the values inside to be laid out in a specific way. For every uniform buffer, storage buffer or push constant block, the SPIR-V specification requires the SPIR-V code to provide the Offset decoration for every member of a struct, indicating where it is placed relative to the start of the struct. If there are arrays or matrices among the variables, the SPIR-V code must also provide an ArrayStride or MatrixStride decoration for them, indicating the number of bytes between the start of each element in the array or column in the matrix. When providing data to shaders, you must make sure that your data is placed at the locations indicated within the SPIR-V code, or the shader will read the wrong data and produce nonsense.

GLSL does not require you to give explicit offsets and/or strides to your variables (although it has the option to provide them if you wish). Instead, the shader compiler automatically assigns every variable an offset, increasing in the order you declare them in. To know the exact offsets that will be used, so that you can lay out your data appropriately, you must know the alignment rules that the shader compiler uses. The shader compiler will always give a variable the smallest offset that fits the alignment rules and doesn’t overlap with the previous variable. The shader compiler uses default alignment rules depending on the type of block, but you can specify another layout by using the layout qualifier.

§Alignment rules

The offset of each variable from the start of a block, matrix or array must be a multiple of a certain number, which is called its alignment. The stride of an array or matrix must likewise be a multiple of this number. An alignment is always a power-of-two value. Regardless of whether the offset/stride is provided manually in the compiled SPIR-V code, or assigned automatically by the shader compiler, all variable offsets/strides in a shader must follow these alignment rules.

Three sets of alignment rules are supported by Vulkan. Each one has a GLSL qualifier that you can place in front of a block, to make the shader compiler use that layout for the block. If you don’t provide this qualifier, it will use a default alignment.

  • Scalar alignment (GLSL qualifier: layout(scalar), requires the GL_EXT_scalar_block_layout GLSL extension). This is the same as the C alignment, expressed in Rust with the #[repr(C)] attribute. The shader compiler does not use this alignment by default, so you must use the GLSL qualifier. You must also enable the scalar_block_layout feature in Vulkan.
  • Base alignment, also known as std430 (GLSL qualifier: layout(std430)). The shader compiler uses this alignment by default for all shader data except uniform buffers. If you use the base alignment for a uniform buffer, you must also enable the uniform_buffer_standard_layout feature in Vulkan.
  • Extended alignment, also known as std140 (GLSL qualifier: layout(std140)). The shader compiler uses this alignment by default for uniform buffers.

Each alignment type is a subset of the ones above it, so if something adheres to the extended alignment rules, it also follows the rules for the base and scalar alignments.

In all three of these alignment rules, a primitive/scalar value with a size of N bytes has an alignment of N, meaning that it must have an offset that is a multiple of its size, like in C or Rust. For example, a float (like a Rust f32) has a size of 4 bytes, and an alignment of 4.

The differences between the alignment rules are in how compound types (vectors, matrices, arrays and structs) are expected to be laid out. For a compound type with an element whose alignment is N, the scalar alignment considers the alignment of the compound type to be also N. However, the base and extended alignments are stricter:

GLSL typeScalarBaseExtended
primitiveNNN
vec2NN * 2N * 2
vec3NN * 4N * 4
vec4NN * 4N * 4
arrayNNmax(N, 16)
structNmaxNmaxmax(Nmax, 16)

In the base and extended alignment, the alignment of a vector is the size of the whole vector, rather than the size of its individual elements as is the case in the scalar alignment. But note that, because alignment must be a power of two, the alignment of vec3 cannot be N * 3; it must be N * 4, the same alignment as vec4. This means that it is not possible to tightly pack multiple vec3 values (e.g. in an array); there will always be empty padding between them.

In both the scalar and base alignment, the alignment of arrays and their elements is equal to the alignment of the contained type. In the extended alignment, however, the alignment is always at least 16 (the size of a vec4). Therefore, the minimum stride of the array can be much greater than the element size. For example, in an array of float, the stride must be at least 16, even though a float itself is only 4 bytes in size. Every float element will be followed by at least 12 bytes of unused space.

A matrix matCxR is considered equivalent to an array of column vectors vecR[C]. In the base and extended alignments, that means that if the matrix has 3 rows, there will be one element’s worth of padding between the column vectors. In the extended alignment, the alignment is also at least 16, further increasing the amount of padding between the column vectors.

The rules for structs are similar to those of arrays. When the members of the struct have different alignment requirements, the alignment of the struct as a whole is the maximum of the alignments of its members. As with arrays, in the extended alignment, the alignment of a struct is at least 16.

§Safety

The following general safety requirements apply to the descriptors in a shader, and to the resources that were bound to them. They apply to all shader types, and must be met at the moment the shader executes on the device.

Vulkano will validate many of these requirements, but it is only able to do so when the resources involved are statically known. This means that either the descriptor binding must not be arrayed, or if it is arrayed, that the array must be indexed only by constants. If the array index is dynamic (meaning that it depends on values that are inputs to the shader), then Vulkano cannot check these requirements, and you must ensure them yourself.

Some requirements, such as the validity of pointers to device memory, cannot be validated by Vulkano at all.

§Descriptors

  • If a descriptor set binding was created with DescriptorBindingFlags::PARTIALLY_BOUND, then if the shader accesses a descriptor in that binding, the descriptor must be initialized and contain a valid resource.

§Buffers and memory accesses

  • If the robust_buffer_access feature is not enabled on the device, then the shader must not access any values outside the range of the buffer, as specified when writing the descriptor set. [06935] [06936]
  • If any PhysicalStorageBuffer pointers to device memory are dereferenced in the shader, then:
    • The pointer must point to valid memory of the correct type.
    • The pointer must be aligned to a multiple of the largest scalar type within the type that it points to. [06314]
    • If the instruction has Aligned as one of its memory operands, the pointer must be aligned to the specified alignment. [06315]
  • For OpCooperativeMatrixLoadKHR, OpCooperativeMatrixStoreKHR, OpCooperativeMatrixLoadNV and OpCooperativeMatrixStoreNV instructions, the Pointer and Stride operands must both be aligned to the minimum of either 16 bytes or the number of bytes per row/column of the matrix (depending on the ColumnMajor and RowMajor decorations). [06324] [08986]

§Image views and buffer views

§Image sampling

If the bound sampler uses Filter::Linear or SamplerMipmapMode::Linear:

If the bound sampler uses Filter::Cubic:

If the bound sampler uses depth comparison:

If the bound sampler uses unnormalized coordinates:

  • The bound image view must have a type of ImageViewType::Dim1d or ImageViewType::Dim2d. [08609]
  • The sampler must not be used in any OpImageSample* or OpImageSparseSample* instructions, that contain ImplicitLod, Dref or Proj in their name. [08610]
  • The sampler must not be used in any OpImageSample* or OpImageSparseSample* instructions, that include an LOD bias or offset operand. [08611]

If the bound sampler has a sampler YCbCr conversion:

  • The sampler must only be used in OpImageSample* or OpImageSparseSample* instructions. [06550]
  • The sampler must not be used with the ConstOffset or Offset image operands. [06551]

§Mesh shading

  • If the shader declares the OutputPoints execution mode with a value greater than 0, and the maintenance5 feature is not enabled on the device, then the shader must write to a variable decorated with PointSize for each output point. [09218]

For OpSetMeshOutputsEXT instructions:

  • The Vertex Count operand must be less than or equal to the value declared with the shader’s OutputVertices execution mode. [07332]
  • The Primitive Count operand must be less than or equal to the value declared with the shader’s OutputPrimitivesEXT execution mode. [07333]

§Acceleration structures, ray queries and ray tracing

  • Acceleration structures that are used as operands to an instruction must have been built as a top-level acceleration structure. [06352] [06359] [06365] [07709]
  • In any top-level acceleration structure, the pointers that refer to the contained bottom-level acceleration structure instances must point to valid bottom-level acceleration structures.

For OpRayQueryInitializeKHR and OpTraceRayKHR instructions:

For OpRayQueryGenerateIntersectionKHR instructions:

  • The Hit T operand must be greater than or equal to the value that would be returned by OpRayQueryGetRayTMinKHR. [06353]
  • The Hit T operand must be less than or equal to the value that would be returned by OpRayQueryGetIntersectionTKHR for the current committed intersection. [06353]

For OpReportIntersectionKHR instructions:

  • The Hit Kind operand must be between 0 and 127 inclusive. [06998]

§Dynamically uniform values and control flow

In a shader, a value (expression, variable) is dynamically uniform if its value is the same for all shader invocations within an invocation group. What counts as an invocation group depends on the type of shader being executed:

  • For compute, task and mesh shaders, an invocation group is the same as the (local) workgroup. A single dispatch command value spawns one distinct invocation group for every element in the product of the given group_counts argument.
  • For all other graphics shaders, an invocation group is all shaders invoked by a single draw command. For indirect draws, each element of the indirect buffer creates one draw call.
  • For ray tracing shaders, an invocation group is an implementation-dependent subset of the shaders invoked by a single ray tracing command.

Vulkan and SPIR-V assume that certain values within a shader are dynamically uniform, and will optimize the generated shader code accordingly. If such a value is not actually dynamically uniform, this results in undefined behavior. This concerns the following values:

  • The index into an arrayed descriptor binding. If the index is not dynamically uniform, you must explicitly mark it with the NonUniform decoration in SPIR-V, or the nonuniformEXT function in GLSL. [06274]
  • The Index argument of the OpGroupNonUniformQuadBroadcast instruction. [06276]
  • The Id argument of the OpGroupNonUniformBroadcast instruction. [06277]
  • The arguments of the OpEmitMeshTasksEXT and OpSetMeshOutputsEXT instructions. [07117] [07118]
  • The Texture Sampled Image and Weight Image arguments of the OpImageWeightedSampleQCOM instruction. [06979]
  • The Texture Sampled Image, Reference Sampled Image and Block Size arguments of the OpImageBlockMatchSADQCOM and OpImageBlockMatchSSDQCOM instructions. [06982]
  • The Sampled Texture Image and Box Size arguments of the OpImageBoxFilterQCOM instruction. [06990]
  • The Target Sampled Image, Reference Sampled Image and Block Size arguments of any OpImageBlockMatchWindow*QCOM or OpImageBlockMatchGather*QCOM instructions. [09219]

Some operations have specific requirements for control flow within the shader:

  • The OpEmitMeshTasksEXT and OpSetMeshOutputsEXT instructions must be executed uniformly within the invocation group. That means that, either all shader invocations within the invocation group must execute the instruction, or none of them must execute it. [07117] [07118]
  • If the PointSize built-in is written to, then all execution paths must write to it. [09190]

Modules§

reflect
Extraction of information from SPIR-V modules, that is needed by the rest of Vulkano.
spirv
Parsing and analysis utilities for SPIR-V shader binaries.

Structs§

DescriptorBindingRequirements
The requirements imposed by a shader on a binding within a descriptor set layout, and on any resource that is bound to that binding.
DescriptorIdentifier
DescriptorRequirements
The requirements imposed by a shader on resources bound to a descriptor.
EntryPoint
Represents a shader entry point in a shader module.
EntryPointInfo
The information associated with a single entry point in a shader.
ShaderModule
Contains SPIR-V code with one or more entry points.
ShaderModuleCreateInfo
ShaderStages
A set of ShaderStage values.
SpecializedShaderModule
A shader module with specialization constants applied.

Enums§

ShaderStage
A shader stage within a pipeline.
SpecializationConstant
The value to provide for a specialization constant, when creating a pipeline.