描述符集布局
下一步,我们在应用程序的代码中定义UBO:
struct UniformBufferObject {
glm::mat4 model;
glm::mat4 view;
glm::mat4 proj;
};
通过GLM库我们可以准确地匹配我们在着色器中使用变量类型,可以放心地直接使用memcpy函数复制UniformBufferObject结构体的数据VkBuffer中。
我们需要在管线创建时提供着色器使用的每一个描述符绑定信息。我们添加了一个叫做createDescriptorSetLayout的函数,来完成这项工作。并在管线创建前调用它:
void initVulkan() {
...
createDescriptorSetLayout();
createGraphicsPipeline();
...
}
...
void createDescriptorSetLayout() {
}
我们需要使用VkDescriptorSetLayoutBinding结构体来描述每一个绑定:
void createDescriptorSetLayout() {
VkDescriptorSetLayoutBinding uboLayoutBinding = {};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
}
binding和descriptorType成员变量用于指定着色器使用的描述符绑定和描述符类型。这里我们指定的是一个uniform缓冲对象。着色器变量可以用来表示uniform缓冲对象数组,descriptorCount成员变量用来指定数组中元素的个数。我们可以使用数组来指定骨骼动画使用的所有变换矩阵。在这里,我们的MVP矩阵只需要一个uniform缓冲对象,所以我们将descriptorCount的值设置为1。
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
我们还需要指定描述符在哪一个着色器阶段被使用。stageFlags成员变量可以指定通过VkShaderStageFlagBits组合或VK_SHADER_STAGE_ALL_GRAPHICS指定描述符被使用的着色器阶段。在这里,我们只在顶点着色器使用描述符。
uboLayoutBinding.pImmutableSamplers = nullptr; // Optional
pImmutableSamplers成员变量仅用于和图像采样相关的描述符。这里我们先将其设置为默认值,之后的章节会对它进行介绍。
所有的描述符绑定被组合进一个VkDescriptorSetLayout对象。我们在pipelineLayout成员变量的定义上面定义descriptorSetLayout成员变量:
VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout
调用vkCreateDescriptorSetLayout函数创建VkDescriptorSetLayout对象。vkCreateDescriptorSetLayout函数以VkDescriptorSetLayoutCreateInfo结构体作为参数:
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("failed to create descriptor set layout!");
}
我们需要在管线创建时指定着色器需要使用的描述符集布局。管线布局对象指定了管线使用的描述符集布局。修改VkPipelineLayoutCreateInfo结构体信息引用布局对象:
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
读者可能会有疑问,明明一个VkDescriptorSetLayout对象就包含了所有要使用的描述符绑定,为什么这里还可以指定多个VkDescriptorSetLayout对象,我们会在下一章节作出解释。
描述符布局对象可以在应用程序的整个生命周期使用,即使使用了新的管线对象。通常我们在应用程序退出前才清除它:
void cleanup() {
cleanupSwapChain();
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
...
}