Vulkan DescriptorSet(三)【Bindless】
Bindless
背景简介
传统管线的渲染流程,可以简化为:
BindFrameBuffer(); // FrameBuffer
BindProgram(); // Shader
SetStates(); // PipelineState
BindTexture(); // Shader resrouces
BindBuffer(); // Shader resrouces
BindVertexBuffer() // Vertex Buffer
BindIndexBuffer(); // Index Buffer
Draw(); // DrawCall (Linear / Indexed)
资源需要以 DrawCall 的粒度来组织,Vulkan Descriptor 的出现可以让资源绑定进行分组预处理,如按照 Frequency-Based 方式进行组织(set 序号越大更新越频繁),如:
- set = 0:绑定 Global、Per-View、Per-Frame 资源
- set = 1:绑定 Material 资源
- set = 2:绑定 Per-Obeject,Per-Draw 资源
以上图为例,Shader 资源的绑定指令变成以下方式,这种组织方式可以有效的减少资源切换带来的开销,但是组织的粒度依旧围绕在单个 DrawCall。
// bindDescriptorSets(firstSet, pSets)
bindDescriptorSets(0, set[0, 1, 3])
draw();
bindDescriptorSets(2, set[4])
draw();
bindDescriptorSets(1, set[2, 5])
draw()
随着 DrawIndirect
方式出现,DrawCall 可以通过 Buffer 组织,IndirectDraw
可以单指令下发大量的 DrawCall,带来的典型挑战就是数据合并,其中 Texture 因为需要预先占用一个(slot,binding),需要更灵活的资源声明方式,因为 slot + binding 通常是规定的,变体的方式过于臃肿,数组或者 sparse resources 是一个不错的选择,这边先讨论数组。
Texture 数组的几种方式:
- TextureArray
- 固定数组
- Runtime-sized 数组
对应 Shader 声明:
layout (set = 0, binding = 0) uniform texture2DArray textures;
layout (set = 0, binding = 1) uniform texture2D textures0[4];
layout (set = 0, binding = 2) uniform texture2D textures1[];
- TextureArray: Format、Extent 等需要保持一致,限制太大
- 固定数组:无 TextureArray 的限制,但是大小编译时确定,灵活性受限
- Runtime-sized 数组:运行时指定
创建 Bindless DescriptorSet
主要步骤:
- 查询 Feature 支持情况,需要使用
VkPhysicalDeviceFeatures2
VkPhysicalDeviceDescriptorIndexingFeatures phyIndexingFeatures = {};
phyIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
VkPhysicalDeviceFeatures2 phyFeatures = {};
phyFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
phyFeatures.pNext = &phyIndexingFeatures;
vkGetPhysicalDeviceFeatures2(pDev, &phyFeatures);
- 使能 Device Feature,主要涉及:
runtimeDescriptorArray
:允许支持 SPV 的 Runtime-sized 数组
descriptorBindingVariableDescriptorCount
:用于使能VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT
VkPhysicalDeviceDescriptorIndexingFeatures enabledIndexingFeature{};
enabledIndexingFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
enabledIndexingFeature.runtimeDescriptorArray = phyIndexingFeatures.runtimeDescriptorArray;
enabledIndexingFeature.descriptorBindingVariableDescriptorCount = phyIndexingFeatures.descriptorBindingVariableDescriptorCount;
VkDeviceCreateInfo devInfo = {};
devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
devInfo.pNext = &enabledIndexingFeature;
- 创建 DescriptorSetLayout,
VkDescriptorSetLayoutBindingFlagsCreateInfo
使能 Layout 对于每个 LayoutBinding 设置 flag,主要涉及VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT
引入几个限制:- 无法使用 (UNIFORM / STORAGE) DYNAMIC 类型
- 该 Bit 置位时,
VkDescriptorSetLayoutBinding::descriptorCount
表示最大可能支持的 Descriptor 数量 - 当前 LayoutBinding 必须保证 binding 号最大,即保证相关 Descriptor 位于队尾
VkDescriptorSetLayoutBindingFlagsCreateInfo setLayoutBindingFlags{};
setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
setLayoutBindingFlags.bindingCount = static_cast<uint32_t>(bindingFlags.size());
setLayoutBindingFlags.pBindingFlags = bindingFlags.data();
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.pNext = &setLayoutBindingFlags;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();
- 申请 DescriptorSet,需要通过
VkDescriptorSetVariableDescriptorCountAllocateInfo::pDescriptorCounts
信息,告知需要申请的实际使用的 Descriptor 数量。
VkDescriptorSetVariableDescriptorCountAllocateInfo variableDescriptorCountAllocInfo = {};
variableDescriptorCountAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO;
variableDescriptorCountAllocInfo.descriptorSetCount = 1;
variableDescriptorCountAllocInfo.pDescriptorCounts = &variableDescriptors;
VkDescriptorSetAllocateInfo setInfo = {};
setInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
setInfo.pNext = &variableDescriptorCountAllocInfo;
- 更新 DescriptorSet,按照使用情况将对应的 Buffer,Image,Sampler,BufferView 等更新到对应的 index。
基础示例
- 使用 1 次 IndirectDraw,在屏幕绘制 4 个方块,每个方块分别使用一种 Material
- 利用 bindless 特性在 (0, 2) 位置绑定 4 张格式、大小存在差异的纹理
- 利用 storage buffer 合并 Material 参数
- 利用 Per-Instance VertexBuffer 传递 ObjectID
代码地址
- 地址:sample/VulkanBindless/src/VulkanBindlessSample.cpp
本文含有隐藏内容,请 开通VIP 后查看