Module vulkano::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.

Modules

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

Structs

Enums