跳到主要内容

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 的工作原理。

push_constant_offset

管线布局兼容性

Vulkan Spec将推送常量的兼容性定义为:

提示

假设它们是使用相同的推送常量范围创建的。

这意味着在与管线相关的指令执行vkCmdDrawvkCmdDispatch 等)前,最后一个vkCmdPushConstantsvkCmdBindPipeline 中使用的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()