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.
§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: - For
OpCooperativeMatrixLoadKHR
,OpCooperativeMatrixStoreKHR
,OpCooperativeMatrixLoadNV
andOpCooperativeMatrixStoreNV
instructions, thePointer
andStride
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 theColumnMajor
andRowMajor
decorations). [06324] [08986]
§Image views and buffer views
- The
view_type
of the bound image view must match theDim
operand of theOpImageType
. [07752] - The numeric type of the
format
of the bound image view must match theSampled Type
operand of theOpImageType
. [07753] - For every
OpImageWrite
instruction, the type of theTexel
operand must have at least as many components as the format of the bound image view or buffer view. If the bound image view’s format isFormat::A8_UNORM
, then the type of theTexel
operand must have four components. [04469] [08795] [08796] - The
Sampled Type
operand of theOpTypeImage
declaration must have aWidth
of 64, if and only if the format of the bound image view or buffer view also has a 64-bit component. Otherwise, it must have aWidth
of 32. [04470] [04471] [04472] [04473] - The
samples
of the underlying image of the bound image view must match theMS
operand of theOpImageType
. [08725] [08726] - For a storage image/texel buffer declared with
OpTypeImage
with anUnknown
format:- If it is written to in the shader, the format of the bound image view or buffer view must
have the
FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT
format feature. [07027] [07029] - If it is read from in the shader, the format of the bound image view or buffer view must
have the
FormatFeatures::STORAGE_READ_WITHOUT_FORMAT
format feature. [07028] [07030]
- If it is written to in the shader, the format of the bound image view or buffer view must
have the
- If atomic operations are used on a storage image/texel buffer:
- The bound image view’s format must have the
FormatFeatures::STORAGE_IMAGE_ATOMIC
format feature. [02691] - The bound buffer view’s format must have the
FormatFeatures::STORAGE_TEXEL_BUFFER_ATOMIC
format feature. [07888]
- The bound image view’s format must have the
§Image sampling
If the bound sampler uses Filter::Linear
or SamplerMipmapMode::Linear
:
- The bound image view’s format must have the
FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR
format feature. [04553] [04770]
If the bound sampler uses Filter::Cubic
:
- The bound image view’s format must have the
FormatFeatures::SAMPLED_IMAGE_FILTER_CUBIC
format feature. [02692] - The bound image view’s type and format must support cubic filtering, as indicated in
ImageFormatProperties::filter_cubic
returned fromPhysicalDevice::image_format_properties
. [02694] - If the sampler’s reduction mode is
SamplerReductionMode::Min
orSamplerReductionMode::Max
, the image view type and format must support cubic minmax filtering, as indicated inImageFormatProperties::filter_cubic_minmax
returned fromPhysicalDevice::image_format_properties
. [02695]
If the bound sampler uses depth comparison:
- The bound image view’s format must have the
FormatFeatures::SAMPLED_IMAGE_DEPTH_COMPARISON
format feature. [06479]
If the bound sampler uses unnormalized coordinates:
- The bound image view must have a type of
ImageViewType::Dim1d
orImageViewType::Dim2d
. [08609] - The sampler must not be used in any
OpImageSample*
orOpImageSparseSample*
instructions, that containImplicitLod
,Dref
orProj
in their name. [08610] - The sampler must not be used in any
OpImageSample*
orOpImageSparseSample*
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*
orOpImageSparseSample*
instructions. [06550] - The sampler must not be used with the
ConstOffset
orOffset
image operands. [06551]
§Mesh shading
- If the shader declares the
OutputPoints
execution mode with a value greater than 0, and themaintenance5
feature is not enabled on the device, then the shader must write to a variable decorated withPointSize
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’sOutputVertices
execution mode. [07332] - The
Primitive Count
operand must be less than or equal to the value declared with the shader’sOutputPrimitivesEXT
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:
- The
Rayflags
operand must not contain more than one of: - The
RayOrigin
andRayDirection
operands must not contain infinite or NaN values. [06348] [06351] [06355] [06358] - The
RayTmin
andRayTmax
operands must not contain negative or NaN values, andRayTmin
must be less than or equal toRayTmax
. [06349] [06350] [06351] [06356] [06357] [06358]
For OpRayQueryGenerateIntersectionKHR
instructions:
- The
Hit T
operand must be greater than or equal to the value that would be returned byOpRayQueryGetRayTMinKHR
. [06353] - The
Hit T
operand must be less than or equal to the value that would be returned byOpRayQueryGetIntersectionTKHR
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 givengroup_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 thenonuniformEXT
function in GLSL. [06274] - The
Index
argument of theOpGroupNonUniformQuadBroadcast
instruction. [06276] - The
Id
argument of theOpGroupNonUniformBroadcast
instruction. [06277] - The arguments of the
OpEmitMeshTasksEXT
andOpSetMeshOutputsEXT
instructions. [07117] [07118] - The
Texture Sampled Image
andWeight Image
arguments of theOpImageWeightedSampleQCOM
instruction. [06979] - The
Texture Sampled Image
,Reference Sampled Image
andBlock Size
arguments of theOpImageBlockMatchSADQCOM
andOpImageBlockMatchSSDQCOM
instructions. [06982] - The
Sampled Texture Image
andBox Size
arguments of theOpImageBoxFilterQCOM
instruction. [06990] - The
Target Sampled Image
,Reference Sampled Image
andBlock Size
arguments of anyOpImageBlockMatchWindow*QCOM
orOpImageBlockMatchGather*QCOM
instructions. [09219]
Some operations have specific requirements for control flow within the shader:
- The
OpEmitMeshTasksEXT
andOpSetMeshOutputsEXT
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§
- Descriptor
Binding Requirements - The requirements imposed by a shader on a binding within a descriptor set layout, and on any resource that is bound to that binding.
- Descriptor
Identifier - Descriptor
Requirements - The requirements imposed by a shader on resources bound to a descriptor.
- Entry
Point - Represents a shader entry point in a shader module.
- Entry
Point Info - The information associated with a single entry point in a shader.
- Shader
Module - Contains SPIR-V code with one or more entry points.
- Shader
Module Create Info - Shader
Stages - A set of
ShaderStage
values. - Specialized
Shader Module - A shader module with specialization constants applied.
Enums§
- Shader
Stage - A shader stage within a pipeline.
- Specialization
Constant - The value to provide for a specialization constant, when creating a pipeline.