Specialization constants are a mechanism whereby constants in a SPIR-V
module can have their constant value specified at the time the
VkPipeline
is created.
This allows a SPIR-V module to have constants that can be modified while
executing an application that uses the Vulkan API.
![]() | Note |
---|---|
Specialization constants are useful to allow a compute shader to have its local workgroup size changed at runtime by the user, for example. |
Each instance of the VkPipelineShaderStageCreateInfo
structure
contains a parameter pSpecializationInfo
, which can be NULL
to
indicate no specialization constants, or point to a
VkSpecializationInfo
structure.
The VkSpecializationInfo
structure is defined as:
typedef struct VkSpecializationInfo { uint32_t mapEntryCount; const VkSpecializationMapEntry* pMapEntries; size_t dataSize; const void* pData; } VkSpecializationInfo;
mapEntryCount
is the number of entries in the pMapEntries
array.
pMapEntries
is a pointer to an array of
VkSpecializationMapEntry
which maps constant IDs to offsets in
pData
.
dataSize
is the byte size of the pData
buffer.
pData
contains the actual constant values to specialize with.
pMapEntries
points to a structure of type
VkSpecializationMapEntry
.
The VkSpecializationMapEntry
structure is defined as:
typedef struct VkSpecializationMapEntry { uint32_t constantID; uint32_t offset; size_t size; } VkSpecializationMapEntry;
constantID
is the ID of the specialization constant in SPIR-V.
offset
is the byte offset of the specialization constant value
within the supplied data buffer.
size
is the byte size of the specialization constant value within
the supplied data buffer.
If a constantID
value is not a specialization constant ID used in the
shader, that map entry does not affect the behavior of the pipeline.
In human readable SPIR-V:
OpDecorate %x SpecId 13 ; decorate .x component of WorkgroupSize with ID 13 OpDecorate %y SpecId 42 ; decorate .y component of WorkgroupSize with ID 42 OpDecorate %z SpecId 3 ; decorate .z component of WorkgroupSize with ID 3 OpDecorate %wgsize BuiltIn WorkgroupSize ; decorate WorkgroupSize onto constant %i32 = OpTypeInt 32 0 ; declare an unsigned 32-bit type %uvec3 = OpTypeVector %i32 3 ; declare a 3 element vector type of unsigned 32-bit %x = OpSpecConstant %i32 1 ; declare the .x component of WorkgroupSize %y = OpSpecConstant %i32 1 ; declare the .y component of WorkgroupSize %z = OpSpecConstant %i32 1 ; declare the .z component of WorkgroupSize %wgsize = OpSpecConstantComposite %uvec3 %x %y %z ; declare WorkgroupSize
From the above we have three specialization constants, one for each of the x, y & z elements of the WorkgroupSize vector.
Now to specialize the above via the specialization constants mechanism:
const VkSpecializationMapEntry entries[] = { { 13, // constantID 0 * sizeof(uint32_t), // offset sizeof(uint32_t) // size }, { 42, // constantID 1 * sizeof(uint32_t), // offset sizeof(uint32_t) // size }, { 3, // constantID 2 * sizeof(uint32_t), // offset sizeof(uint32_t) // size } }; const uint32_t data[] = { 16, 8, 4 }; // our workgroup size is 16x8x4 const VkSpecializationInfo info = { 3, // mapEntryCount entries, // pMapEntries 3 * sizeof(uint32_t), // dataSize data, // pData };
Then when calling vkCreateComputePipelines
, and passing the
VkSpecializationInfo
we defined as the pSpecializationInfo
parameter of VkPipelineShaderStageCreateInfo
, we will create a compute
pipeline with the runtime specified local workgroup size.
Another example would be that an application has a SPIR-V module that has some platform-dependent constants they wish to use.
In human readable SPIR-V:
OpDecorate %1 SpecId 0 ; decorate our signed 32-bit integer constant OpDecorate %2 SpecId 12 ; decorate our 32-bit floating-point constant %i32 = OpTypeInt 32 1 ; declare a signed 32-bit type %float = OpTypeFloat 32 ; declare a 32-bit floating-point type %1 = OpSpecConstant %i32 -1 ; some signed 32-bit integer constant %2 = OpSpecConstant %float 0.5 ; some 32-bit floating-point constant
From the above we have two specialization constants, one is a signed 32-bit integer and the second is a 32-bit floating-point.
Now to specialize the above via the specialization constants mechanism:
struct SpecializationData { int32_t data0; float data1; }; const VkSpecializationMapEntry entries[] = { { 0, // constantID offsetof(SpecializationData, data0), // offset sizeof(SpecializationData::data0) // size }, { 12, // constantID offsetof(SpecializationData, data1), // offset sizeof(SpecializationData::data1) // size } }; SpecializationData data; data.data0 = -42; // set the data for the 32-bit integer data.data1 = 42.0f; // set the data for the 32-bit floating-point const VkSpecializationInfo info = { 2, // mapEntryCount entries, // pMapEntries sizeof(data), // dataSize &data, // pData };
It is legal for a SPIR-V module with specializations to be compiled into a pipeline where no specialization info was provided. SPIR-V specialization constants contain default values such that if a specialization is not provided, the default value will be used. In the examples above, it would be valid for an application to only specialize some of the specialization constants within the SPIR-V module, and let the other constants use their default values encoded within the OpSpecConstant declarations.