引擎架构之资源管理
资源管理类似 OpenGL 中的上下文,需要提供接口用来申请 Buffer、Image、RenderPass、FrameBuffer、Pipeline、DescriptorSet 等资源,并且在应用结束时,负责统一清理这些资源。
DescriptorSet 管理
资源管理中,最重要的一项是对于 DescriptorSet 的管理,这里我们看下如何对 DescriptorSet 资源进行统一管理:
- 抽象出
DescriptorSetInfo
结构作为对于 DescriptorSet 的核心描述; - DescriptorSetLayout、DescriptorPool 都会解析该结构,然后进行生成;
- 对于每一帧的 DescriptorSet[] 数组,根据 DescriptorSetLayout、DescriptorPool 创建 DescriptorSet,并根据 DescriptorSetInfo 更新 DescriptorSet;
下面我们先来看下 DescriptorSetInfo 的基本结构与使用方式,再一一拆解其中使用到的函数:
DescriptorSetInfo 的基本结构
// 1、保存 DescriptorSet 的信息
struct DescriptorSetInfo
{
std::vector<BufferAttachment> buffers;
std::vector<TextureAttachment> textures;
std::vector<TextureArrayAttachment> textureArrays;
};
// 2、关联 buffer 对象和 DescriptorInfo 描述对象
struct BufferAttachment
{
DescriptorInfo dInfo;
VulkanBuffer buffer;
uint32_t offset;
uint32_t size;
};
struct TextureAttachment
{
DescriptorInfo dInfo;
VulkanTexture texture;
};
struct TextureArrayAttachment
{
DescriptorInfo dInfo;
std::vector<VulkanTexture> textures;
};
// 3、描述 buffer 对象的类型和被使用的阶段
struct DescriptorInfo
{
VkDescriptorType type;
VkShaderStageFlags shaderStageFlags;
};
// 4、描述不同的 buffer 对象,主要是 handle 和 memory,以及辅助信息
struct VulkanBuffer
{
VkBuffer buffer;
VkDeviceSize size;
VkDeviceMemory memory;
/* Permanent mapping to CPU address space (see VulkanResources::addBuffer) */
void* ptr;
};
struct VulkanImage final
{
VkImage image = nullptr;
VkDeviceMemory imageMemory = nullptr;
VkImageView imageView = nullptr;
};
// Aggregate structure for passing around the texture data
struct VulkanTexture final
{
uint32_t width;
uint32_t height;
uint32_t depth;
VkFormat format;
VulkanImage image;
VkSampler sampler;
// Offscreen buffers require VK_IMAGE_LAYOUT_GENERAL && static textures have VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
VkImageLayout desiredLayout;
};
这里的核心是 XXXAttachment
,用来将对于缓存对象的描述信息和真实的 buffer、image 对象联系起来。
DescriptorSetInfo 的使用方法
DescriptorSetInfo
基本的使用规则有两条:
- 首先根据
DescriptorSetInfo
初始化DescriptorSetLayout
和DescriptorPool
,然后依次填充每个swapChainImage
所需要的DescriptorSets
; DescriptorSetInfo
中可以一开始不填写具体的VulkanBuffer/VulkanTexture
,只需要在addDescriptorSet()
之前填充好就行;
// 1. 配置 DescriptorSetInfo 描述信息
DescriptorSetInfo dsInfo = {
.buffers = {
// 1-1. uniform buffer 需要根据不同的 swapChainImage 来确定,故此时空缺
uniformBufferAttachment(VulkanBuffer {}, 0, uniformBufferSize, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT),
sceneData_.vertexBuffer_,
sceneData_.indexBuffer_,
// 1-2. drawData buffer 同上
storageBufferAttachment(VulkanBuffer {}, 0, shapesSize, VK_SHADER_STAGE_VERTEX_BIT),
storageBufferAttachment(sceneData_.material_, 0, (uint32_t)sceneData_.material_.size, VK_SHADER_STAGE_FRAGMENT_BIT),
storageBufferAttachment(sceneData_.transforms_, 0, (uint32_t)sceneData_.transforms_.size, VK_SHADER_STAGE_VERTEX_BIT),
},
.textures = textureAttachments,
.textureArrays = { sceneData_.allMaterialTextures }
};
// 2. 创建 DescriptorSetLayout 和 DescriptorPool
descriptorSetLayout_ = ctx.resources.addDescriptorSetLayout(dsInfo);
descriptorPool_ = ctx.resources.addDescriptorPool(dsInfo, (uint32_t)imgCount);
// 3. 创建并更新 DescriptorSet
for (size_t i = 0; i != imgCount; i++)
{
// 3-1. 创建 uniform 和 drawData buffer
uniforms_[i] = ctx.resources.addUniformBuffer(uniformBufferSize);
shape_[i] = ctx.resources.addStorageBuffer(shapesSize);
uploadBufferData(ctx.vkDev, shape_[i].memory, 0, sceneData_.shapes_.data(), shapesSize);
// 3-2. 关联 DescriptorSetInfo
dsInfo.buffers[0].buffer = uniforms_[i];
dsInfo.buffers[3].buffer = shape_[i];
// 3-3. 创建并更新 DescriptorSet
descriptorSets_[i] = ctx.resources.addDescriptorSet(descriptorPool_, descriptorSetLayout_);
ctx.resources.updateDescriptorSet(descriptorSets_[i], dsInfo);
}
DescriptorSetXXX 相关的函数
和 DescriptorSet 创建相关的函数总共有四个,下面一一分析:
addDescriptorSetLayout,根据 DescriptorSetInfo,创建描述符布局信息;
VkDescriptorSetLayout VulkanResources::addDescriptorSetLayout(const DescriptorSetInfo& dsInfo) { VkDescriptorSetLayout descriptorSetLayout; uint32_t bindingIdx = 0; std::vector<VkDescriptorSetLayoutBinding> bindings; // 1. 创建 bindings,bindingIdx 从 0 开始递增 for (const auto& b: dsInfo.buffers) { bindings.push_back(descriptorSetLayoutBinding(bindingIdx++, b.dInfo.type, b.dInfo.shaderStageFlags)); } for (const auto& i: dsInfo.textures) { bindings.push_back(descriptorSetLayoutBinding(bindingIdx++, i.dInfo.type /*VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER*/, i.dInfo.shaderStageFlags)); } for (const auto& t: dsInfo.textureArrays) { bindings.push_back(descriptorSetLayoutBinding(bindingIdx++, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, t.dInfo.shaderStageFlags, static_cast<uint32_t>(t.textures.size()))); } // 2. 根据 bindings 创建 DescriptorSetLayout const VkDescriptorSetLayoutCreateInfo layoutInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = nullptr, .flags = 0, .bindingCount = static_cast<uint32_t>(bindings.size()), .pBindings = bindings.size() > 0 ? bindings.data() : nullptr }; if (vkCreateDescriptorSetLayout(vkDev.device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { printf("Failed to create descriptor set layout\n"); exit(EXIT_FAILURE); } allDSLayouts.push_back(descriptorSetLayout); return descriptorSetLayout; } inline VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(uint32_t binding, VkDescriptorType descriptorType, VkShaderStageFlags stageFlags, uint32_t descriptorCount = 1) { return VkDescriptorSetLayoutBinding{ .binding = binding, .descriptorType = descriptorType, .descriptorCount = descriptorCount, .stageFlags = stageFlags, .pImmutableSamplers = nullptr }; }
addDescriptorPool,根据 DescriptorSetInfo 和 swapChainImageCount 来创建描述符集分配池,其中最关键的属性是 DescriptorSet 的数量和不同类型的 Descriptor 的数量;
VkDescriptorPool VulkanResources::addDescriptorPool(const DescriptorSetInfo& dsInfo, uint32_t dSetCount) { // 1. 统计 uniform、storage、sampler 的数量 uint32_t uniformBufferCount = 0; uint32_t storageBufferCount = 0; uint32_t samplerCount = static_cast<uint32_t>(dsInfo.textures.size()); for(const auto& ta : dsInfo.textureArrays) samplerCount += static_cast<uint32_t>(ta.textures.size()); for(const auto& b: dsInfo.buffers) { if (b.dInfo.type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) uniformBufferCount++; if (b.dInfo.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) storageBufferCount++; } std::vector<VkDescriptorPoolSize> poolSizes; /* printf("Allocating pool[%d | %d | %d]\n", (int)uniformBufferCount, (int)storageBufferCount, (int)samplerCount); */ // 2. 配置 DescriptorPool 所需的数量信息 if (uniformBufferCount) poolSizes.push_back(VkDescriptorPoolSize{ .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = dSetCount * uniformBufferCount }); if (storageBufferCount) poolSizes.push_back(VkDescriptorPoolSize{ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount = dSetCount * storageBufferCount }); if (samplerCount) poolSizes.push_back(VkDescriptorPoolSize{ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = dSetCount * samplerCount }); // 3. 创建 DescriptorPool const VkDescriptorPoolCreateInfo poolInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, .flags = 0, .maxSets = static_cast<uint32_t>(dSetCount), .poolSizeCount = static_cast<uint32_t>(poolSizes.size()), .pPoolSizes = poolSizes.empty() ? nullptr : poolSizes.data() }; VkDescriptorPool descriptorPool = VK_NULL_HANDLE; if (vkCreateDescriptorPool(vkDev.device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { printf("Cannot allocate descriptor pool\n"); exit(EXIT_FAILURE); } allDPools.push_back(descriptorPool); return descriptorPool; }
addDescriptorSet,根据 descriptorPool 描述符集分配池和 descriptorSetLayout 描述符布局信息,创建 DescriptorSet 描述符集;
VkDescriptorSet VulkanResources::addDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout dsLayout) { VkDescriptorSet descriptorSet; const VkDescriptorSetAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pNext = nullptr, .descriptorPool = descriptorPool, .descriptorSetCount = 1, .pSetLayouts = &dsLayout }; if (vkAllocateDescriptorSets(vkDev.device, &allocInfo, &descriptorSet) != VK_SUCCESS) { printf("Cannot allocate descriptor set\n"); exit(EXIT_FAILURE); } return descriptorSet; }
updateDescriptorSet,根据 DescriptorSetInfo,更新 DescriptorSet 描述符集;
void VulkanResources::updateDescriptorSet(VkDescriptorSet ds, const DescriptorSetInfo& dsInfo) { uint32_t bindingIdx = 0; std::vector<VkWriteDescriptorSet> descriptorWrites; // 1. 分类型存储 Descriptor 描述符信息 std::vector<VkDescriptorBufferInfo> bufferDescriptors(dsInfo.buffers.size()); std::vector<VkDescriptorImageInfo> imageDescriptors(dsInfo.textures.size()); std::vector<VkDescriptorImageInfo> imageArrayDescriptors; // 2. 保存 buffer 信息 for (size_t i = 0 ; i < dsInfo.buffers.size() ; i++) { BufferAttachment b = dsInfo.buffers[i]; bufferDescriptors[i] = VkDescriptorBufferInfo { .buffer = b.buffer.buffer, .offset = b.offset, .range = (b.size > 0) ? b.size : VK_WHOLE_SIZE }; descriptorWrites.push_back(bufferWriteDescriptorSet(ds, &bufferDescriptors[i], bindingIdx++, b.dInfo.type)); } // 3. 保存 texture 信息 for(size_t i = 0 ; i < dsInfo.textures.size() ; i++) { VulkanTexture t = dsInfo.textures[i].texture; imageDescriptors[i] = VkDescriptorImageInfo { .sampler = t.sampler, .imageView = t.image.imageView, .imageLayout = /* t.texture.layout */ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; descriptorWrites.push_back(imageWriteDescriptorSet(ds, &imageDescriptors[i], bindingIdx++)); } // 4. 保存 textureArray 信息,注意,这里的 taOffsets 是为了后面和 imageArrayDescriptors.data() 指针配合,用来偏移起始位置 uint32_t taOffset = 0; std::vector<uint32_t> taOffsets(dsInfo.textureArrays.size()); for (size_t ta = 0 ; ta < dsInfo.textureArrays.size() ; ta++) { taOffsets[ta] = taOffset; for (size_t j = 0 ; j < dsInfo.textureArrays[ta].textures.size() ; j++) { VulkanTexture t = dsInfo.textureArrays[ta].textures[j]; VkDescriptorImageInfo imageInfo = { .sampler = t.sampler, .imageView = t.image.imageView, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; imageArrayDescriptors.push_back(imageInfo); // item 'taOffsets[ta] + j' } taOffset += static_cast<uint32_t>(dsInfo.textureArrays[ta].textures.size()); } for (size_t ta = 0 ; ta < dsInfo.textureArrays.size() ; ta++) { VkWriteDescriptorSet writeSet = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ds, .dstBinding = bindingIdx++, .dstArrayElement = 0, .descriptorCount = static_cast<uint32_t>(dsInfo.textureArrays[ta].textures.size()), .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = imageArrayDescriptors.data() + taOffsets[ta] }; descriptorWrites.push_back(writeSet); } // 4. 更新绑定 vkUpdateDescriptorSets(vkDev.device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); } inline VkWriteDescriptorSet bufferWriteDescriptorSet(VkDescriptorSet ds, const VkDescriptorBufferInfo* bi, uint32_t bindIdx, VkDescriptorType dType) { return VkWriteDescriptorSet { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, ds, bindIdx, 0, 1, dType, nullptr, bi, nullptr }; } inline VkWriteDescriptorSet imageWriteDescriptorSet(VkDescriptorSet ds, const VkDescriptorImageInfo* ii, uint32_t bindIdx) { return VkWriteDescriptorSet { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, ds, bindIdx, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, ii, nullptr, nullptr }; } typedef struct VkWriteDescriptorSet { VkStructureType sType; const void* pNext; VkDescriptorSet dstSet; uint32_t dstBinding; uint32_t dstArrayElement; uint32_t descriptorCount; VkDescriptorType descriptorType; const VkDescriptorImageInfo* pImageInfo; const VkDescriptorBufferInfo* pBufferInfo; const VkBufferView* pTexelBufferView; } VkWriteDescriptorSet;