我们通过vertexBuffer将顶点数据传送给shader,这是因为不同的顶点它的值是不同的,因此需要一个个的传递。那么对于所有顶点都共享的相同的数据呢?比如说mvp矩阵,纹理图像,这些都是顶点的属性,如果每个顶点都传递一份,那么这个内存估计就该爆炸了。因此vulkan提出了descriptorSet的概念,用来解决共享数据的传递问题。描述符集是着色器自由访问缓冲区和图像资源的一种方式。
它主要由下面的三个部分组成:
在管线创建时指定描述符的布局结构
从描述符对象池中分配描述符集合
在渲染阶段绑定描述符集合
1) descriptorSetLayout
由于在创建pipeline的时候要提前准备空间,所以要首先创建描述符集的布局1
2
3
4
5VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout(
VkDevice device,
const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDescriptorSetLayout* pSetLayout);
1 | typedef struct VkDescriptorSetLayoutCreateInfo { |
flag参数:这两个参数非常有意思,
VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR表示descriptorSet不被允许使用这个布局,他的descriptors 必须用vkCmdPushDescriptorSetKHR来push
VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT表示指定必须从使用该VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT位集创建的描述符池中分配使用此布局的描述符集,使用此位集创建的描述符集布局对每个阶段和每个管道布局的最大描述符数具有备用限制。非UpdateAfterBind仅限制在没有此标志的情况下创建的集合中的计数描述符。UpdateAfterBind限制会计算所有描述符,但是该限制可能会高于非UpdateAfterBind限制。
这个值一般不需要设置。1
2
3
4
5
6typedef enum VkDescriptorSetLayoutCreateFlagBits {
VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT = 0x00000002,
VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR = 0x00000001,
VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkDescriptorSetLayoutCreateFlagBits;
1 | typedef struct VkDescriptorSetLayoutBinding { |
2)创建descriptorSet pool并分配descriptor
1 | VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorPool( |
3)从pool中分配descriptorSet
1 | VKAPI_ATTR VkResult VKAPI_CALL vkAllocateDescriptorSets( |
描述符集创建后,还需要对其进行进一步的配置,一般来讲,我们在swapchain中有几个缓冲,一般都会对应着几个descriptorSet。
通过循环swapChainImages,进行配置1
2
3
4
5
6VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSets(
VkDevice device,
uint32_t descriptorWriteCount,
const VkWriteDescriptorSet* pDescriptorWrites,
uint32_t descriptorCopyCount,
const VkCopyDescriptorSet* pDescriptorCopies);
主要是两组参数:pDescriptorWrites和pDescriptorCopies
pDescriptorWrites用于更新描述符1
2
3
4
5
6
7
8
9
10
11
12typedef struct VkWriteDescriptorSet {
VkStructureType sType; // VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET
const void* pNext; //
VkDescriptorSet dstSet; // 要更新的描述符集对象
uint32_t dstBinding; // 绑定点
uint32_t dstArrayElement; // pBufferInfo(pImageInfo,pTexelBufferView)中的起始元素,
uint32_t descriptorCount; // 描述符的个数
VkDescriptorType descriptorType; // 描述符的类型
const VkDescriptorImageInfo* pImageInfo; // 指定描述符引用的Image
const VkDescriptorBufferInfo* pBufferInfo; // 指定描述符引用的buffer
const VkBufferView* pTexelBufferView; // 指定描述符引用的缓冲视图
} VkWriteDescriptorSet;
这三个结构如下所示:1
2
3
4
5typedef struct VkDescriptorBufferInfo {
VkBuffer buffer; // buffer
VkDeviceSize offset; // buffer起始的偏移量
VkDeviceSize range; // buffer的字节大小
} VkDescriptorBufferInfo;
这个就是图像的标准三件套了。1
2
3
4
5typedef struct VkDescriptorImageInfo {
VkSampler sampler;
VkImageView imageView;
VkImageLayout imageLayout;
} VkDescriptorImageInfo;
pDescriptorCopies数组用于两组描述符之间的copy1
2
3
4
5
6
7
8
9
10
11typedef struct VkCopyDescriptorSet {
VkStructureType sType;
const void* pNext;
VkDescriptorSet srcSet;
uint32_t srcBinding;
uint32_t srcArrayElement;
VkDescriptorSet dstSet;
uint32_t dstBinding;
uint32_t dstArrayElement;
uint32_t descriptorCount;
} VkCopyDescriptorSet;
4)实际的更新位置
大多数情况下,对于每一帧数据,他的descriptorSet都是不同的,因此一般我们都是在draw的时候,更新descriptorSet绑定的buffer或者Image资源。
5)具体代码例子
1 | void createDescriptorSetLayout() { |