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 theGL_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 thescalar_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 theuniform_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 type | Scalar | Base | Extended |
---|---|---|---|
primitive | N | N | N |
vec2 | N | N * 2 | N * 2 |
vec3 | N | N * 4 | N * 4 |
vec4 | N | N * 4 | N * 4 |
array | N | N | max(N, 16) |
struct | Nmax | Nmax | max(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 struct
s 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
- The requirements imposed by a shader on a binding within a descriptor set layout, and on any resource that is bound to that binding.
- The requirements imposed by a shader on resources bound to a descriptor.
- Represents a shader entry point in a shader module.
- The information associated with a single entry point in a shader.
- Type that contains the definition of an interface between two shader stages, or between the outside and a shader stage.
- Entry of a shader interface definition.
- The type of a variable in a shader interface.
- Contains SPIR-V code with one or more entry points.
- A set of
ShaderStage
values. - A shader module with specialization constants applied.
Enums
- A shader stage within a pipeline.
- The value to provide for a specialization constant, when creating a pipeline.