Push 常量
Push Constants
称为推送常量,Vulkan 规范是这么定义Push Constants
的:
可通过 API 写入,用于在着色器中访问的一小组值。推送常量允许应用程序无需创建缓冲区或修改\绑定描述符集,而直接设置着色器中使用的值。
如何使用推送常量
着色器代码
从着色器的角度来看,推送常量类似于 uniform 缓冲区。规范提供了 Vulkan 和 SPIR-V 之间的 push constant 接口信息。
一个简单的 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 的不同之处在于offset:
- 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;
下图展示了 push constant offsets 的工作原理。
管线布局兼容性
Vulkan Spec将推送常量的兼容性定义为:
假设它们是使用相同的推送常量范围创建的。
这意味着在与管线相关的指令执行(vkCmdDraw
、vkCmdDispatch
等)前,最后一个vkCmdPushConstants
和vkCmdBindPipeline
中使用的VkPipelineLayout
必须具有相同的 VkPushConstantRange
。
推送常量的生命周期
推送常量的生命周期需要注意的是边界场景,下面给出一些常见的示例,说明一些无效的推送常量用法。
有一些 CTS用例用来测试这个场景:dEQP-VK.pipeline.push_constant.lifetime.*
绑定描述符集不影响推送常量
由于推送常量不与描述符绑定,因此使用vkCmdBindDescriptorSets
不会影响推 送常量的生命周期。
混合绑定点
可以使用两个不同的VkPipelineBindPoint
,每个VkPipelineBindPoint
的shader 使用不同的推送常量:
// 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()