描述符集布局

下一步,我们在应用程序的代码中定义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);

        ...
}