推送常量
如何使用
着色器代码
从着色器的角度来看,推送常量类似于统一缓冲区。规范提供了 Vulkan 和 SPIR-V 之间的推送常量接口的详细信息。
一个简单的 GLSL 片段着色器示例 (在线尝试)
layout(push_constant, std430) uniform pc {
vec4 data;
};
layout(location = 0) out vec4 outColor;
void main() {
outColor = data;
}
当查看反汇编的 SPIR-V 的一部分时
OpMemberDecorate %pc 0 Offset 0
OpDecorate %pc Block
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%pc = OpTypeStruct %v4float
%pc_ptr = OpTypePointer PushConstant %pc
%pc_var = OpVariable %pc_ptr PushConstant
%pc_v4float_ptr = OpTypePointer PushConstant %v4float
%access_chain = OpAccessChain %pc_v4float_ptr %pc_var %int_0
它与 Vulkan 规范的描述相匹配,即具有 Block
装饰的 OpTypeStruct
类型。
管线布局
当调用 vkCreatePipelineLayout
时,需要在 VkPipelineLayoutCreateInfo 中设置推送常量范围。
一个使用上述着色器的示例
VkPushConstantRange range = {};
range.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
range.offset = 0;
range.size = 16; // %v4float (vec4) is defined as 16 bytes
VkPipelineLayoutCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
create_info.pNext = NULL;
create_info.flags = 0;
create_info.setLayoutCount = 0;
create_info.pushConstantRangeCount = 1;
create_info.pPushConstantRanges = ⦥
VkPipelineLayout pipeline_layout;
vkCreatePipelineLayout(device, &create_info, NULL, &pipeline_layout);
在记录时更新
最后,需要使用 vkCmdPushConstants 将推送常量的值更新为所需的值。
float data[4] = {0.0f, 1.0f, 2.0f, 3.0f}; // where sizeof(float) == 4 bytes
// vkBeginCommandBuffer()
uint32_t offset = 0;
uint32_t size = 16;
vkCmdPushConstants(commandBuffer, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, offset, size, data);
// draw / dispatch / trace rays / etc
// vkEndCommandBuffer()
偏移量
采用上面的着色器,开发人员可以向推送常量块添加偏移量
layout(push_constant, std430) uniform pc {
- vec4 data;
+ layout(offset = 32) vec4 data;
};
layout(location = 0) out vec4 outColor;
void main() {
outColor = data;
}
与上述反汇编的 SPIR-V 的区别仅在于成员装饰
- OpMemberDecorate %pc 0 Offset 0
+ OpMemberDecorate %pc 0 Offset 32
从这里开始,还需要在每个使用它的着色器阶段的 VkPushConstantRange
中指定 32
的偏移量
VkPushConstantRange range = {};
range.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-range.offset = 0;
+range.offset = 32;
range.size = 16;
下图提供了推送常量偏移量如何工作的可视化表示。

推送常量的生命周期
在 |
绑定描述符集无效
因为推送常量未与描述符绑定,所以使用 vkCmdBindDescriptorSets
对推送常量的生命周期或 管线布局兼容性 没有影响。
混合绑定点
可以在其着色器中使用两个不同的 VkPipelineBindPoint
,每个绑定点对推送常量有不同的用法
// different ranges and therefore not compatible layouts
VkPipelineLayout layout_graphics; // VK_SHADER_STAGE_FRAGMENT_BIT
VkPipelineLayout layout_compute; // VK_SHADER_STAGE_COMPUTE_BIT
// vkBeginCommandBuffer()
vkCmdBindPipeline(pipeline_graphics); // layout_graphics
vkCmdBindPipeline(pipeline_compute); // layout_compute
vkCmdPushConstants(layout_graphics); // VK_SHADER_STAGE_FRAGMENT_BIT
// Still valid as the last pipeline and push constant for graphics are compatible
vkCmdDraw();
vkCmdPushConstants(layout_compute); // VK_SHADER_STAGE_COMPUTE_BIT
vkCmdDispatch(); // valid
// vkEndCommandBuffer()
绑定不兼容的管线
规范说
绑定具有与推送常量布局不兼容的布局的管线不会干扰推送常量值。 |
以下示例有助于说明这一点
// vkBeginCommandBuffer()
vkCmdPushConstants(layout_0);
vkCmdBindPipeline(pipeline_b); // non-compatible with layout_0
vkCmdBindPipeline(pipeline_a); // compatible with layout_0
vkCmdDraw(); // valid
// vkEndCommandBuffer()
// vkBeginCommandBuffer()
vkCmdBindPipeline(pipeline_b); // non-compatible with layout_0
vkCmdPushConstants(layout_0);
vkCmdBindPipeline(pipeline_a); // compatible with layout_0
vkCmdDraw(); // valid
// vkEndCommandBuffer()
// vkBeginCommandBuffer()
vkCmdPushConstants(layout_0);
vkCmdBindPipeline(pipeline_a); // compatible with layout_0
vkCmdBindPipeline(pipeline_b); // non-compatible with layout_0
vkCmdDraw(); // INVALID
// vkEndCommandBuffer()
没有静态推送常量的布局
即使管线布局中存在 VkPushConstantRange
,但着色器中没有推送常量也是有效的,例如
VkPushConstantRange range = {VK_SHADER_STAGE_VERTEX_BIT, 0, 4};
VkPipelineLayoutCreateInfo pipeline_layout_info = {VK_SHADER_STAGE_VERTEX_BIT. 1, &range};
void main() {
gl_Position = vec4(1.0);
}
如果使用上述着色器和管线布局创建 VkPipeline
,则对其调用 vkCmdPushConstants
仍然有效。
可以这样理解,vkCmdPushConstants
与 VkPipelineLayout
的使用相关联,因此它们必须匹配才能调用诸如 vkCmdDraw()
之类的命令。
正如可以绑定着色器从未使用过的描述符集一样,推送常量也是如此。
增量更新
推送常量可以在命令缓冲区的执行过程中进行增量更新。
以下示例展示了一个 vec4
推送常量的值
// vkBeginCommandBuffer()
vkCmdBindPipeline();
vkCmdPushConstants(offset: 0, size: 16, value = [0, 0, 0, 0]);
vkCmdDraw(); // values = [0, 0, 0, 0]
vkCmdPushConstants(offset: 4, size: 8, value = [1 ,1]);
vkCmdDraw(); // values = [0, 1, 1, 0]
vkCmdPushConstants(offset: 8, size: 8, value = [2, 2]);
vkCmdDraw(); // values = [0, 1, 2, 2]
// vkEndCommandBuffer()