将数据映射到着色器
所有 SPIR-V 内容都是用 glslangValidator 生成的。
本章将介绍 Vulkan 中的 SPIR-V 与 shader 进行数据映射的方法,应用程序使用从 vkAllocateMemory
申请的 VkDeviceMemory
来映射数据,让 SPIR-V 着色器知道怎么使用这些数据。
在 Vulkan 核心功能中,主要有 5 种方法将数据从程序映射到 SPIR-V 中:
- 输入属性
- 描述符
- 描述符类型
- VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
- VK_DESCRIPTOR_TYPE_SAMPLER 和 VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
- VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
- VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
- VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
- VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER
- VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER
- VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT
- 描述符类型
- 推送常量
- 特殊化常量
- 物理存储缓冲区
输入属性
Vulkan 中唯一具有输入属性的着色器阶段是顶点着色器 ( VK_SHADER_STAGE_VERTEX_BIT
),需要在创建VkPipeline
时显式声明,然后在绘制前将 VkBuffer
绑定到要映射的数据。其他着色器阶段(例如片段着色器)也有输入属性,但值是由在它之前运行的阶段输出的。
在调用vkCreateGraphicsPipelines
之前 ,VkPipelineVertexInputStateCreateInfo
需要填入VkVertexInputAttributeDescription
数组,来映射输入属性到着色器。
GLSL 顶点着色器示例:
#version 450
layout(location = 0) in vec3 inPosition;
void main() {
gl_Position = vec4(inPosition, 1.0);
}
location 0 处只有一个输入属性,对应生成的 SPIR-V:
Name 18 "inPosition"
Decorate 18(inPosition) Location 0
17: TypePointer Input 16(fvec3)
18(inPosition): 17(ptr) Variable Input
19: 16(fvec3) Load 18(inPosition)
在这个例子中,VkVertexInputAttributeDescription
的内容为:
VkVertexInputAttributeDescription input = {};
input.location = 0;
input.binding = 0;
input.format = VK_FORMAT_R32G32B32_SFLOAT; // maps to vec3
input.offset = 0;
下面要做的是在调用绘制前绑定顶点缓冲区和索引缓冲区(可选)。
“顶点缓冲区”需在创建 VkBuffer
时使用VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
。
vkBeginCommandBuffer();
// ...
vkCmdBindVertexBuffer();
vkCmdDraw();
// ...
vkCmdBindVertexBuffer();
vkCmdBindIndexBuffer();
vkCmdDrawIndexed();
// ...
vkEndCommandBuffer();
更多信息请阅读顶点输入数据处理过程。
描述符
资源描述符 是将统一缓冲区(uniform buffer)、存储缓冲区(storage buffer)、采样器等数据映射到任何着色器阶段的主要方法。描述符一种的抽象理解是着色器可以使用的内存指针。
Vulkan 中有各种 描述符类型,Vulkan Spec有每种描述符类型的详细描述。
描述符被分组到 描述符 集合中,描述符集合绑定到着色器,即使描述符集中只有一个描述符,也要绑定整个VkDescriptorSet
到着色器。
举例
下例中有 3 个描述符集:
着色器的 GLSL:
// Note - only set 0 and 2 are used in this shader
layout(set = 0, binding = 0) uniform sampler2D myTextureSampler;
layout(set = 0, binding = 2) uniform uniformBuffer0 {
float someData;
} ubo_0;
layout(set = 0, binding = 3) uniform uniformBuffer1 {
float moreData;
} ubo_1;
layout(set = 2, binding = 0) buffer storageBuffer {
float myResults;
} ssbo;
相应的 SPIR-V:
Decorate 19(myTextureSampler) DescriptorSet 0
Decorate 19(myTextureSampler) Binding 0
MemberDecorate 29(uniformBuffer0) 0 Offset 0
Decorate 29(uniformBuffer0) Block
Decorate 31(ubo_0) DescriptorSet 0
Decorate 31(ubo_0) Binding 2
MemberDecorate 38(uniformBuffer1) 0 Offset 0
Decorate 38(uniformBuffer1) Block
Decorate 40(ubo_1) DescriptorSet 0
Decorate 40(ubo_1) Binding 3
MemberDecorate 44(storageBuffer) 0 Offset 0
Decorate 44(storageBuffer) BufferBlock
Decorate 46(ssbo) DescriptorSet 2
Decorate 46(ssbo) Binding 0
描述符的绑定是在记录命令缓冲区时完成的,描述符必须在调用draw/dispatch时绑定,以下伪代码:
vkBeginCommandBuffer();
// ...
vkCmdBindPipeline(); // Binds shader
// One possible way of binding the two sets
vkCmdBindDescriptorSets(firstSet = 0, pDescriptorSets = &descriptor_set_c);
vkCmdBindDescriptorSets(firstSet = 2, pDescriptorSets = &descriptor_set_b);
vkCmdDraw(); // or dispatch
// ...
vkEndCommandBuffer();
绑定结果如下:
描述符类型
Vulkan Spec 有一个 着色器资源和存储对应类别 表,描 述了每个描述符类型需要如何映射到 SPIR-V。
下面展示了 GLSL 和 SPIR-V 映射到每个描述符类型 的示例。
对于 GLSL,更多信息参阅: GLSL Spec - 12.2.4. Vulkan Only: Samplers, Images, Textures, and Buffers
存储图像(Storage Image)
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
// VK_FORMAT_R32_UINT
layout(set = 0, binding = 0, r32ui) uniform uimage2D storageImage;
// example usage for reading and writing in GLSL
const uvec4 texel = imageLoad(storageImage, ivec2(0, 0));
imageStore(storageImage, ivec2(1, 1), texel);
OpDecorate %storageImage DescriptorSet 0
OpDecorate %storageImage Binding 0
%r32ui = OpTypeImage %uint 2D 0 0 0 2 R32ui
%ptr = OpTypePointer UniformConstant %r32ui
%storageImage = OpVariable %ptr UniformConstant
采样器和采样图像(Sampler and Sampled Image)
VK_DESCRIPTOR_TYPE_SAMPLER`和`VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE
layout(set = 0, binding = 0) uniform sampler samplerDescriptor;
layout(set = 0, binding = 1) uniform texture2D sampledImage;
// example usage of using texture() in GLSL
vec4 data = texture(sampler2D(sampledImage, samplerDescriptor), vec2(0.0, 0.0));
OpDecorate %sampledImage DescriptorSet 0
OpDecorate %sampledImage Binding 1
OpDecorate %samplerDescriptor DescriptorSet 0
OpDecorate %samplerDescriptor Binding 0
%image = OpTypeImage %float 2D 0 0 0 1 Unknown
%imagePtr = OpTypePointer UniformConstant %image
%sampledImage = OpVariable %imagePtr UniformConstant
%sampler = OpTypeSampler
%samplerPtr = OpTypePointer UniformConstant %sampler
%samplerDescriptor = OpVariable %samplerPtr UniformConstant
%imageLoad = OpLoad %image %sampledImage
%samplerLoad = OpLoad %sampler %samplerDescriptor
%sampleImageType = OpTypeSampledImage %image
%1 = OpSampledImage %sampleImageType %imageLoad %samplerLoad
%textureSampled = OpImageSampleExplicitLod %v4float %1 %coordinate Lod %float_0
组合图像采样器(Combined Image Sampler)
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
在某些驱动实现中,使用组合图像采样器来进行图像采样 可能性能更高。
layout(set = 0, binding = 0) uniform sampler2D combinedImageSampler;
// example usage of using texture() in GLSL
vec4 data = texture(combinedImageSampler, vec2(0.0, 0.0));
OpDecorate %combinedImageSampler DescriptorSet 0
OpDecorate %combinedImageSampler Binding 0
%imageType = OpTypeImage %float 2D 0 0 0 1 Unknown
%sampleImageType = OpTypeSampledImage imageType
%ptr = OpTypePointer UniformConstant %sampleImageType
%combinedImageSampler = OpVariable %ptr UniformConstant
%load = OpLoad %sampleImageType %combinedImageSampler
%textureSampled = OpImageSampleExplicitLod %v4float %load %coordinate Lod %float_0