Vulkan之DescriptorSet

我们通过vertexBuffer将顶点数据传送给shader,这是因为不同的顶点它的值是不同的,因此需要一个个的传递。那么对于所有顶点都共享的相同的数据呢?比如说mvp矩阵,纹理图像,这些都是顶点的属性,如果每个顶点都传递一份,那么这个内存估计就该爆炸了。因此vulkan提出了descriptorSet的概念,用来解决共享数据的传递问题。描述符集是着色器自由访问缓冲区和图像资源的一种方式。

它主要由下面的三个部分组成:
在管线创建时指定描述符的布局结构
从描述符对象池中分配描述符集合
在渲染阶段绑定描述符集合

1) descriptorSetLayout

由于在创建pipeline的时候要提前准备空间,所以要首先创建描述符集的布局

1
2
3
4
5
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout(
VkDevice device,
const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDescriptorSetLayout* pSetLayout);

1
2
3
4
5
6
7
typedef struct VkDescriptorSetLayoutCreateInfo {
VkStructureType sType; // VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO
const void* pNext; // nullptr
VkDescriptorSetLayoutCreateFlags flags; // 一般不设置
uint32_t bindingCount; // 绑定的个数
const VkDescriptorSetLayoutBinding* pBindings; // 数组指针,内容是各个binding的layout
} 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
6
typedef 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
2
3
4
5
6
7
typedef struct VkDescriptorSetLayoutBinding {
uint32_t binding; // 绑定点,对应于shader中的layout(binding = 1) uniform sampler2D texSampler;
VkDescriptorType descriptorType; // 描述符的类型
uint32_t descriptorCount; // 描述符的个数
VkShaderStageFlags stageFlags; // 应用在shader的那个阶段,是vs,fs还是都用等等
const VkSampler* pImmutableSamplers; // nullptr
} VkDescriptorSetLayoutBinding;

2)创建descriptorSet pool并分配descriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorPool(
VkDevice device,
const VkDescriptorPoolCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDescriptorPool* pDescriptorPool);


typedef struct VkDescriptorPoolCreateInfo {
VkStructureType sType; //VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO
const void* pNext; // nullptr
VkDescriptorPoolCreateFlags flags; // bitmask
uint32_t maxSets; // pool里面能分配的descriptorSet的最大的数量
uint32_t poolSizeCount; // pPoolSizes的个数
const VkDescriptorPoolSize* pPoolSizes; // VkDescriptorPoolSize结构的数组
} VkDescriptorPoolCreateInfo;


typedef struct VkDescriptorPoolSize {
VkDescriptorType type; // descriptor的类型
uint32_t descriptorCount; //descriptor的个数
} VkDescriptorPoolSize;

3)从pool中分配descriptorSet

1
2
3
4
5
6
7
8
9
10
11
12
13
VKAPI_ATTR VkResult VKAPI_CALL vkAllocateDescriptorSets(
VkDevice device,
const VkDescriptorSetAllocateInfo* pAllocateInfo,
VkDescriptorSet* pDescriptorSets);


typedef struct VkDescriptorSetAllocateInfo {
VkStructureType sType; // VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO
const void* pNext;
VkDescriptorPool descriptorPool; // pool
uint32_t descriptorSetCount; // 描述符集的个数,一般等于swapChainImages的个数
const VkDescriptorSetLayout* pSetLayouts; // 数组,里面包含了descriptorSetCount个descriptorSetLayout
} VkDescriptorSetAllocateInfo;

描述符集创建后,还需要对其进行进一步的配置,一般来讲,我们在swapchain中有几个缓冲,一般都会对应着几个descriptorSet。

通过循环swapChainImages,进行配置

1
2
3
4
5
6
VKAPI_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
12
typedef 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
5
typedef struct VkDescriptorBufferInfo {
VkBuffer buffer; // buffer
VkDeviceSize offset; // buffer起始的偏移量
VkDeviceSize range; // buffer的字节大小
} VkDescriptorBufferInfo;

这个就是图像的标准三件套了。

1
2
3
4
5
typedef struct VkDescriptorImageInfo {
VkSampler sampler;
VkImageView imageView;
VkImageLayout imageLayout;
} VkDescriptorImageInfo;

pDescriptorCopies数组用于两组描述符之间的copy

1
2
3
4
5
6
7
8
9
10
11
typedef 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void createDescriptorSetLayout() {
VkDescriptorSetLayoutBinding uboLayoutBinding{};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.pImmutableSamplers = nullptr;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

VkDescriptorSetLayoutBinding samplerLayoutBinding{};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.pImmutableSamplers = nullptr;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

std::array<VkDescriptorSetLayoutBinding, 2> bindings = { uboLayoutBinding, samplerLayoutBinding };
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();

if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor set layout!");
}
}

void createDescriptorPool() {
std::array<VkDescriptorPoolSize, 2> poolSizes{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(swapChainImages.size());
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(swapChainImages.size());

VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = static_cast<uint32_t>(swapChainImages.size());

if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor pool!");
}
}

void createDescriptorSets() {
std::vector<VkDescriptorSetLayout> layouts(swapChainImages.size(), descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(swapChainImages.size());
allocInfo.pSetLayouts = layouts.data();

descriptorSets.resize(swapChainImages.size());
if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate descriptor sets!");
}

for (size_t i = 0; i < swapChainImages.size(); i++) {
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);

VkDescriptorImageInfo imageInfo{};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = textureImageView;
imageInfo.sampler = textureSampler;

std::array<VkWriteDescriptorSet, 2> descriptorWrites{};

descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;

descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &imageInfo;

vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}
}
显示 Gitment 评论