Vulkan 学习(14)---- 描述符集
目录
- Vulkan DescriptorSet
- 描述符布局和管线布局
- 创建和使用描述符集
- 参考代码
Vulkan DescriptorSet
Vulkan
中,描述符是一种在着色器中访问资源(比如缓冲区,图像,采样器等)的机制或者协议
每个描述符对应一个资源,代表 GPU
内存中的资源,比如 Uniform Buffer
, storage Buffer
, Texture
,Sampler
等
Vulkan
描述符集(VkDescriptorSet
)表示着色器可以与之交互的资源的集合,着色器是通过描述符读取和解析资源中的数据,着色器中的绑定点和相应的描述符集中的绑定点必须一一对应
Vulkan
描述符集是不能被直接创建的,首先需要从一个特定的缓冲池中被分配得到。这个池子叫做描述符池(VkDescriptorPool
),类似于内存池的概念
VkDescriptorPool
负责分配新的 Descriptor
对象,换句话说,它相当于一组描述符的集合,新的描述符就是从这些描述符中分配得到的
VkDescriptorPool
对于内存分配效率较低的场合是非常有用的,它可以直接分配出多组描述符而不需要调用全局同步操作
在创建 DescriptorSet
之前,需要定义 DescriptorSet
的布局(VkDescriporSetLayout
),布局指定了描述符的类型,数量,绑定点等信息
描述符布局和管线布局
VkDescriporSetLayout
和 VkPipelineLayout
的关系是什么:
管线布局是对着色器资源绑定的全局描述,代表了图形管线可以访问的所有资源的集合
创建 Vulkan
渲染管线的时候需要设置管线布局,它描述了渲染过程中着色器如何访问资源,包括描述符集和推送常量等
管线布局可以包括一个或者多个描述符布局和推送常量描述(推送常量是可以更新着色器中的常量数据),下面是创建管线布局的结构体:
typedef struct VkPipelineLayoutCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineLayoutCreateFlags flags;
uint32_t setLayoutCount;
const VkDescriptorSetLayout* pSetLayouts;
uint32_t pushConstantRangeCount;
const VkPushConstantRange* pPushConstantRanges;
} VkPipelineLayoutCreateInfo;
无论是管线布局还是描述符布局,本质都是对资源的一种描述,其本身并不占用资源
创建和使用描述符集
创建和使用描述符集
- 定义描述符集布局
- 使用
VkDescriptorSetLayoutBinding
结构体定义每个描述符的类型、数量和绑定点 - 调用
vkCreateDescriptorSetLayout
创建描述符集布局
- 创建描述符池
- 使用
VkDescriptorPoolSize
指定描述符池的每种描述符类型的数量 - 调用
vkCreateDescriptorPool
创建描述符池
- 分配描述符集
- 调用
vkAllocateDescriptorSets
从描述符池中分配描述符集
- 更新描述符集
- 使用
VkWriteDescriptorSet
结构体更新描述符集中的描述符,绑定实际的资源(比如缓冲区、纹理等)
- 绑定描述符集
- 在渲染过程中,调用
vkCmdBindDescriptorSets
将描述符集绑定到管线,着色器就可以访问描述符集中指定的资源
参考代码
- 定义描述符集布局
创建一个含有三个绑定点的VkDescriptorSetLayout
,相应的要创建三个VkDescriptorSetLayoutBinding
void createComputeDescriptorSetLayout() {
std::array<VkDescriptorSetLayoutBinding, 3> layoutBindings{};
layoutBindings[0].binding = 0; // 绑定点 0
layoutBindings[0].descriptorCount = 1;
layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
layoutBindings[0].pImmutableSamplers = nullptr;
layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
layoutBindings[1].binding = 1; // 绑定点 1
layoutBindings[1].descriptorCount = 1;
layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
layoutBindings[1].pImmutableSamplers = nullptr;
layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
layoutBindings[2].binding = 2;// 绑定点 2
layoutBindings[2].descriptorCount = 1;
layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
layoutBindings[2].pImmutableSamplers = nullptr;
layoutBindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 3;
layoutInfo.pBindings = layoutBindings.data();
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &computeDescriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create compute descriptor set layout!");
}
}
对应的 GLSL
代码为:
layout (binding = 0) uniform ParameterUBO {
float deltaTime;
} ubo;
layout(std140, binding = 1) readonly buffer ParticleSSBOIn {
Particle particlesIn[ ];
};
layout(std140, binding = 2) buffer ParticleSSBOOut {
Particle particlesOut[ ];
};
- 创建描述符池
DescriptorPool
根据 VkDescriptorSet
的类型,分别创建一个分配 UniformBuffer
和 storageBuffer
的 descriptorPool
,
注意要定义最大分配的 descriptorCount
void createDescriptorPool() {
std::array<VkDescriptorPoolSize, 2> poolSizes{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT) * 2;
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 2;
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor pool!");
}
}
- 分配描述符集
VkDescriptorSet
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
allocInfo.pSetLayouts = layouts.data();
computeDescriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
if (vkAllocateDescriptorSets(device, &allocInfo, computeDescriptorSets.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate descriptor sets!");
}
- 更新描述符集
通过VkWriteDescriptorSet
更新DescriptorSet
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo uniformBufferInfo{};
uniformBufferInfo.buffer = uniformBuffers[i];
uniformBufferInfo.offset = 0;
uniformBufferInfo.range = sizeof(UniformBufferObject);
std::array<VkWriteDescriptorSet, 3> descriptorWrites{};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = computeDescriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &uniformBufferInfo;
VkDescriptorBufferInfo storageBufferInfoLastFrame{};
storageBufferInfoLastFrame.buffer = shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT];
storageBufferInfoLastFrame.offset = 0;
storageBufferInfoLastFrame.range = sizeof(Particle) * PARTICLE_COUNT;
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = computeDescriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pBufferInfo = &storageBufferInfoLastFrame;
VkDescriptorBufferInfo storageBufferInfoCurrentFrame{};
storageBufferInfoCurrentFrame.buffer = shaderStorageBuffers[i];
storageBufferInfoCurrentFrame.offset = 0;
storageBufferInfoCurrentFrame.range = sizeof(Particle) * PARTICLE_COUNT;
descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[2].dstSet = computeDescriptorSets[i];
descriptorWrites[2].dstBinding = 2;
descriptorWrites[2].dstArrayElement = 0;
descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrites[2].descriptorCount = 1;
descriptorWrites[2].pBufferInfo = &storageBufferInfoCurrentFrame;
vkUpdateDescriptorSets(device, 3, descriptorWrites.data(), 0, nullptr);
}
- 绑定描述符集
void recordComputeCommandBuffer(VkCommandBuffer commandBuffer) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording compute command buffer!");
}
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSets[currentFrame], 0, nullptr);
vkCmdDispatch(commandBuffer, PARTICLE_COUNT / 256, 1, 1);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record compute command buffer!");
}
}