We have been using uniform buffers to communicate uniform data to our shader programs. One disadvantage of these is that when we have multiple objects to render with different uniform values, we must allocate multiple uniform buffers and descriptor sets.
Vulkan provides an alternative, but limited, method for supplying data to shaders called push constants. These values are encoded directly into the recorded stream of commands, which makes it easy to change them on a per-mesh or per-pass basis.
To access push constants in our shader code, we use the following syntax:
layout (push_constant) uniform constants {
mat4 MVP;
vec4 color;
} pcBuffer;
In our C++ code, we define a struct with the same layout
struct PCBuffer {
alignas(16) mat4 MVP;
alignas(16) vec4 color;
};
When recording the command buffer, we can set the values of the push constants
using the vkCmdPushConstants
function.
For example,
PCBuffer pConsts = { projMat * viewMat * model->toWorld, model->color };
vkCmdPushConstants (
cmdBuf, // command buffer
pipelineLayout, // graphics-pipeline layout
stages, // shader stages where constants are used
0, // offset of push-constant range being updated
sizeof(PCBuffer), // size of push-constant range being updated
&pConsts); // pointer to push-constant data
In order to use push constants, we have to include information about them
when defining the layout for the graphics pipeline. The
VkPipelineLayoutCreateInfo
struct has fields for specifying
push constant ranges.
VkPushConstantRange pcRange = {
stages, // shader stages where constants are used
0, // offset of push-constant range being updated
sizeof(PCBuffer), // size of push-constant range being updated
};
VkPipelineLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
...
layoutInfo.pushConstantRangeCount = 1;
layoutInfo.pPushConstantRanges = &pcRange;
Note that the stages that you specify in the
VkPushConstantRange
struct
must match the stages specified in the
vkCmdPushConstants
function call.
Push constants have the advantages over uniform buffers in that they are fast
and do not require memory-backed resources. The disadvantages are that they
can only hold a small amount of data and they require recording the commands
each time we render a frame (we cannot pre-record
the command buffer, unless the push constants never change).
The Vulkan specification requires that implementations provide at least 128 bytes
of push-constants and most implementations will provide more.[1]
You can use the limits
method from the application class to determine if there is
sufficient space for push constants:
if (app->limits()->maxPushConstantsSize < sizeof(PCBuffer)) {
ERROR("insufficient space for push constants");
}