Pipelines
The following figure shows a block diagram of the Vulkan pipelines. Some Vulkan commands specify geometric objects to be drawn or computational work to be performed, while others specify state controlling how objects are handled by the various pipeline stages, or control data transfer between memory organized as images and buffers. Commands are effectively sent through a processing pipeline, either a graphics pipeline, a ray tracing pipeline, or a compute pipeline.
The graphics pipeline can be operated in two modes, as either primitive shading or mesh shading pipeline.
Primitive Shading
The first stage of the graphics pipeline (Input Assembler) assembles vertices to form geometric primitives such as points, lines, and triangles, based on a requested primitive topology. In the next stage (Vertex Shader) vertices can be transformed, computing positions and attributes for each vertex. If tessellation and/or geometry shaders are supported, they can then generate multiple primitives from a single input primitive, possibly changing the primitive topology or generating additional attribute data in the process.
Cluster Culling Shading
When using the Cluster Culling Shader, a compute-like shader will perform cluster-based culling, a set of new built-in output variables are used to express visible cluster, in addition, a new built-in function is used to emit these variables from the cluster culling shader to the Input Assembler(IA) stage, then IA can use these variables to fetches vertices of visible cluster and drive vertex shader to work.
Mesh Shading
When using the mesh shading pipeline input primitives are not assembled implicitly, but explicitly through the (Mesh Shader). The work on the mesh pipeline is initiated by the application drawing a set of mesh tasks.
If an optional (Task Shader) is active, each task triggers the execution of a task shader workgroup that will generate a new set of tasks upon completion. Each of these spawned tasks, or each of the original dispatched tasks if no task shader is present, triggers the execution of a mesh shader workgroup that produces an output mesh with a variable-sized number of primitives assembled from vertices stored in the output mesh.
Common
The final resulting primitives are clipped to a clip volume in preparation for the next stage, Rasterization. The rasterizer produces a series of fragments associated with a region of the framebuffer, from a two-dimensional description of a point, line segment, or triangle. These fragments are processed by fragment operations to determine whether generated values will be written to the framebuffer. Fragment shading determines the values to be written to the framebuffer attachments. Framebuffer operations then read and write the color and depth/stencil attachments of the framebuffer for a given subpass of a render pass instance. The attachments can be used as input attachments in the fragment shader in a later subpass of the same render pass.
The compute pipeline is a separate pipeline from the graphics pipeline, which operates on one-, two-, or three-dimensional workgroups which can read from and write to buffer and image memory.
This ordering is meant only as a tool for describing Vulkan, not as a strict rule of how Vulkan is implemented, and we present it only as a means to organize the various operations of the pipelines. Actual ordering guarantees between pipeline stages are explained in detail in the synchronization chapter.
Each pipeline is controlled by a monolithic object created from a description of all of the shader stages and any relevant fixed-function stages. Linking the whole pipeline together allows the optimization of shaders based on their input/outputs and eliminates expensive draw time state validation.
A pipeline object is bound to the current state using vkCmdBindPipeline. Any pipeline object state that is specified as dynamic is not applied to the current state when the pipeline object is bound, but is instead set by dynamic state setting commands.
If the commandBufferInheritance
feature is not enabled, then no
state, including dynamic state, is inherited from one command buffer to
another.
If the commandBufferInheritance
feature is enabled, then all graphics and compute state that is valid at the
end of the command buffer executed in a queue is inherited and valid at
beginning of the command buffer next executed in the same queue.
This applies to both primary and secondary command buffers, where a primary
command buffer submitted to a queue will inherit state from the previously
submitted command buffer to that queue, secondary command buffers will
inherit state from the primary or seconard command buffer they are executed
in, and after a seconard command buffer is executed, its state inherited by
the primary or secondary command buffer that executed it.
Command buffers executed in one queue do not inherit state from any command
buffers executed in another queue.
Multiple Pipeline Creation
Multiple pipelines can be created in a single call by commands such as vkCreateExecutionGraphPipelinesAMDX, vkCreateRayTracingPipelinesKHR, vkCreateRayTracingPipelinesNV, vkCreateComputePipelines, and vkCreateGraphicsPipelines.
The creation commands are passed an array pCreateInfos
of
Vk*PipelineCreateInfo
structures specifying parameters of each
pipeline to be created, and return a corresponding array of handles in
pPipelines
.
Each element index i of pPipelines
is created based on the
corresponding element i of pCreateInfos
.
Applications can group together similar pipelines to be created in a single call, and implementations are encouraged to look for reuse opportunities when creating a group.
When attempting to create many pipelines in a single command, it is possible
that creation may fail for a subset of them.
In this case, the corresponding elements of pPipelines
will be
VK_NULL_HANDLE.
If creation fails for a pipeline despite valid arguments (for example, due
to out of memory errors), the VkResult code returned by the pipeline
creation command will indicate why.
The implementation will attempt to create all pipelines, and only return
VK_NULL_HANDLE values for those that actually failed.
If creation fails for a pipeline that has the
VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT
set in its
Vk*PipelineCreateInfo
, pipelines at an index in the pPipelines
array greater than or equal to that of the failing pipeline will be
VK_NULL_HANDLE.
If creation fails for multiple pipelines, the returned VkResult must
be the return value of any one of the pipelines which did not succeed.
An application can reliably clean up from a failed call by iterating over
the pPipelines
array and destroying every element that is not
VK_NULL_HANDLE.
If the entire command fails and no pipelines are created, all elements of
pPipelines
will be VK_NULL_HANDLE.
Compute Pipelines
Compute pipelines consist of a single static compute shader stage and the pipeline layout.
The compute pipeline represents a compute shader and is created by calling
vkCreateComputePipelines
with module
and pName
selecting an entry point from a shader
module, where that entry point defines a valid compute shader, in the
VkPipelineShaderStageCreateInfo structure contained within the
VkComputePipelineCreateInfo structure.
If a compute pipeline is going to be used in Device-Generated Commands by specifying its pipeline token with
VkBindPipelineIndirectCommandNV, then that pipeline’s associated
metadata must be saved at a specified buffer device address for later use
in indirect command generation.
The buffer device address must be specified at the time of compute pipeline
creation with VkComputePipelineIndirectBufferInfoNV structure in the
pNext
chain of VkComputePipelineCreateInfo.
Graphics Pipelines
Graphics pipelines consist of multiple shader stages, multiple fixed-function pipeline stages, and a pipeline layout.
Valid Combinations of Stages for Graphics Pipelines
Primitive processing can be handled either on a per primitive basis by the vertex, tessellation, and geometry shader stages, or on a per mesh basis using task and mesh shader stages. If the pipeline includes a mesh shader stage, it uses the mesh pipeline, otherwise it uses the primitive pipeline.
If a task shader is omitted, the task shading stage is skipped.
If tessellation shader stages are omitted, the tessellation shading and fixed-function stages of the pipeline are skipped.
If a geometry shader is omitted, the geometry shading stage is skipped.
If a fragment shader is omitted, fragment color outputs have undefined: values, and the fragment depth value is determined by Fragment Operations state. This can be useful for depth-only rendering.
Presence of a shader stage in a pipeline is indicated by including a valid
VkPipelineShaderStageCreateInfo with module
and pName
selecting an entry point from a shader module, where that entry point is
valid for the stage specified by stage
.
Presence of some of the fixed-function stages in the pipeline is implicitly derived from enabled shaders and provided state. For example, the fixed-function tessellator is always present when the pipeline has valid Tessellation Control and Tessellation Evaluation shaders.
- Depth/stencil-only rendering in a subpass with no color attachments
- Active Pipeline Shader Stages
- Vertex Shader
- Required: Fixed-Function Pipeline Stages
- Active Pipeline Shader Stages
- Color-only rendering in a subpass with no depth/stencil attachment
- Active Pipeline Shader Stages
- Vertex Shader
- Fragment Shader
- Required: Fixed-Function Pipeline Stages
- Active Pipeline Shader Stages
- Rendering pipeline with tessellation and geometry shaders
- Active Pipeline Shader Stages
- Vertex Shader
- Tessellation Control Shader
- Tessellation Evaluation Shader
- Geometry Shader
- Fragment Shader
- Required: Fixed-Function Pipeline Stages
- Active Pipeline Shader Stages
- Rendering pipeline with task and mesh shaders
- Active Pipeline Shader Stages
- Task Shader
- Mesh Shader
- Fragment Shader
- Required: Fixed-Function Pipeline Stages
- Active Pipeline Shader Stages
Graphics Pipeline Shader Groups
Graphics pipelines can contain multiple shader groups that can be bound
individually.
Each shader group behaves as if it was a pipeline using the shader group’s
state.
When the pipeline is bound by regular means, it behaves as if the state of
group 0
is active, use vkCmdBindPipelineShaderGroupNV to bind an
individual shader group.
The primary purpose of shader groups is allowing the device to bind different pipeline state using Device-Generated Commands.
Ray Tracing Pipelines
Ray tracing pipelines consist of multiple shader stages, fixed-function traversal stages, and a pipeline layout.
Ray tracing pipelines can contain more shaders than a graphics or compute pipeline, so to allow parallel compilation of shaders within a pipeline, an application can choose to defer compilation until a later point in time.
Pipeline Destruction
Pipeline Derivatives
A pipeline derivative is a child pipeline created from a parent pipeline, where the child and parent are expected to have much commonality.
The goal of derivative pipelines is that they be cheaper to create using the parent as a starting point, and that it be more efficient (on either host or device) to switch/bind between children of the same parent.
A derivative pipeline is created by setting the
VK_PIPELINE_CREATE_DERIVATIVE_BIT
flag in the
Vk*PipelineCreateInfo
structure.
If this is set, then exactly one of basePipelineHandle
or
basePipelineIndex
members of the structure must have a valid
handle/index, and specifies the parent pipeline.
If basePipelineHandle
is used, the parent pipeline must have already
been created.
If basePipelineIndex
is used, then the parent is being created in the
same command.
VK_NULL_HANDLE acts as the invalid handle for
basePipelineHandle
, and -1 is the invalid index for
basePipelineIndex
.
If basePipelineIndex
is used, the base pipeline must appear earlier
in the array.
The base pipeline must have been created with the
VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT
flag set.
Pipeline Cache
Creating a Pipeline Cache
Merging Pipeline Caches
Retrieving Pipeline Cache Data
Pipeline Cache Header
Applications can store the data retrieved from the pipeline cache, and use these data, possibly in a future run of the application, to populate new pipeline cache objects. The results of pipeline compiles, however, may depend on the vendor ID, device ID, driver version, and other details of the device. To enable applications to detect when previously retrieved data is incompatible with the device, the pipeline cache data must begin with a valid pipeline cache header.
Structures described in this section are not part of the Vulkan API and are only used to describe the representation of data elements in pipeline cache data. Accordingly, the valid usage clauses defined for structures defined in this section do not define valid usage conditions for APIs accepting pipeline cache data as input, as providing invalid pipeline cache data as input to any Vulkan API commands will result in the provided pipeline cache data being ignored.
Destroying a Pipeline Cache
Pipeline Binaries
Generating the Pipeline Key
Creating Pipeline Binaries
Retrieving Pipeline Binary Data
Releasing Captured Pipeline Binary Data
Destroying Pipeline Binaries
Specialization Constants
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.
Specialization constants are useful to allow a compute shader to have its local workgroup size changed at runtime by the user, for example.
Each VkPipelineShaderStageCreateInfo structure contains a
pSpecializationInfo
member, which can be NULL
to indicate no
specialization constants, or point to a VkSpecializationInfo
structure.
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[] =
{
{
.constantID = 13,
.offset = 0 * sizeof(uint32_t),
.size = sizeof(uint32_t)
},
{
.constantID = 42,
.offset = 1 * sizeof(uint32_t),
.size = sizeof(uint32_t)
},
{
.constantID = 3,
.offset = 2 * sizeof(uint32_t),
.size = sizeof(uint32_t)
}
};
const uint32_t data[] = { 16, 8, 4 }; // our workgroup size is 16x8x4
const VkSpecializationInfo info =
{
.mapEntryCount = 3,
.pMapEntries = entries,
.dataSize = 3 * sizeof(uint32_t),
.pData = data,
};
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 value.
Now to specialize the above via the specialization constants mechanism:
struct SpecializationData {
int32_t data0;
float data1;
};
const VkSpecializationMapEntry entries[] =
{
{
.constantID = 0,
.offset = offsetof(SpecializationData, data0),
.size = sizeof(SpecializationData::data0)
},
{
.constantID = 12,
.offset = offsetof(SpecializationData, data1),
.size = sizeof(SpecializationData::data1)
}
};
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 =
{
.mapEntryCount = 2,
.pMapEntries = entries,
.dataSize = sizeof(data),
.pdata = &data,
};
It is legal for a SPIR-V module with specializations to be compiled into a pipeline where no specialization information 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.
Pipeline Libraries
A pipeline library is a special pipeline that was created using the
VK_PIPELINE_CREATE_LIBRARY_BIT_KHR
and cannot be bound, instead it
defines a set of pipeline state which can be linked into other pipelines.
For ray tracing pipelines this includes shaders and shader groups.
For graphics pipelines this includes distinct library types defined by
VkGraphicsPipelineLibraryFlagBitsEXT.
The application must maintain the lifetime of a pipeline library based on
the pipelines that link with it.
This linkage is achieved by using the following structure within the appropriate creation mechanisms:
Pipelines created with VK_PIPELINE_CREATE_LIBRARY_BIT_KHR
libraries
can depend on other pipeline libraries in
VkPipelineLibraryCreateInfoKHR.
A pipeline library is considered in-use, as long as one of the linking pipelines is in-use. This applies recursively if a pipeline library includes other pipeline libraries.
Pipeline Binding
Interaction With Shader Objects
If the shaderObject
feature is enabled,
applications can use both pipelines and shader objects
at the same time.
The interaction between pipelines and shader objects is described in
Interaction with Pipelines.
Dynamic State
When a pipeline object is bound, any pipeline object state that is not specified as dynamic is applied to the command buffer state. Pipeline object state that is specified as dynamic is not applied to the command buffer state at this time.
Instead, dynamic state can be modified at any time and persists for the lifetime of the command buffer, or until modified by another dynamic state setting command, or made invalid by binding a pipeline in which that state is statically specified.
If the commandBufferInheritance
feature is enabled, all valid state from the previously executed command
buffer in the queue is inherited into the next command buffer executed in
the same queue.
This inherited state does not need to be set again before draw or dispatch
commands.
When a pipeline object is bound, the following applies to each state parameter:
- If the state is not specified as dynamic in the new pipeline object, then that command buffer state is overwritten by the state in the new pipeline object. Before any draw or dispatch call with this pipeline there must not have been any calls to any of the corresponding dynamic state setting commands after this pipeline was bound.
- If the state is specified as dynamic in the new pipeline object, then that command buffer state is not disturbed. Before any draw or dispatch call with this pipeline there must have been at least one call to each of the corresponding dynamic state setting commands. The state-setting commands must be recorded after command buffer recording was begun, or after the last command binding a pipeline object with that state specified as static, whichever was the latter.
- If the state is not included (corresponding pointer in
VkGraphicsPipelineCreateInfo was
NULL
or was ignored) in the new pipeline object, then that command buffer state is not disturbed. For example, mesh shading pipelines do not include vertex input state and therefore do not disturb any such command buffer state.
Dynamic state that does not affect the result of operations can be left undefined:.
For example, if blending is disabled by the pipeline object state then the dynamic color blend constants do not need to be specified in the command buffer, even if this state is specified as dynamic in the pipeline object.
Applications running on Vulkan implementations advertising an
VkPhysicalDeviceDriverProperties::conformanceVersion
less than
1.3.8.0 should be aware that rebinding the currently bound pipeline object
may not reapply static state.