首页 > 解决方案 > Vulkan - 我应该什么时候创建一个新的管道?

问题描述

所以我想在 Vulkan 中渲染两个独立的网格。我正在涉足纹理,第一个网格使用其中的 4 个,而第二个使用 5 个。我正在做索引绘制。

为简单起见,每个网格都有自己的统一缓冲区和采样器数组,它们被打包到单独的描述符集中,每个描述符集都有一个用于 UBO 的绑定和另一个用于采样器的绑定。以下代码针对每个网格运行,其中descriptorSet是与单个网格关联的描述符集。filepaths是网格特别使用的图像路径的向量。

std::vector<VkWriteDescriptorSet> descriptorWrites;
descriptorWrites.resize(2);

VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = buffers[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);

descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSet;
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;

std::vector<VkDescriptorImageInfo> imageInfos;
imageInfos.resize(filepaths.size());
for (size_t j = 0; j < filepaths.size(); j++) {
    imageInfos[j].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageInfos[j].imageView = imageViews[j];
    imageInfos[j].sampler = samplers[j];
}
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSet;
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = imageInfos.size();
descriptorWrites[1].pImageInfo = imageInfos.data();

vkUpdateDescriptorSets(devicesHandler->device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);

因此,为了告诉 Vulkan 这些描述符集是如何布局的,我当然需要两个描述符集布局,即每个网格一个,由于不同的大小,它们在采样器的绑定方面有所不同filepaths

// <Stuff for binding 0 for UBO here>
// ...
VkDescriptorSetLayoutBinding layoutBinding = {};
layoutBinding.binding = 1;
layoutBinding.descriptorCount = filepaths.size();
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

现在,当我创建管道时,我需要提供管道布局。我这样做如下,layouts填充到向量中的网格的描述符集布局在哪里:

VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = layouts.size();
pipelineLayoutInfo.pSetLayouts = layouts.data();

最后在渲染之前我绑定了适当的描述符集。

我天真地认为定义管道布局的方式是可行的(只需获取所有涉及的布局并将它们传递pSetLayouts),但它不起作用。我得到的错误是:

 descriptorSet #0 being bound is not compatible with overlapping descriptorSetLayout at index 0 of pipelineLayout 0x6e due to: DescriptorSetLayout 87 has 5 descriptors, but DescriptorSetLayout 88, which comes from pipelineLayout, has 6 descriptors.. The Vulkan spec states: Each element of pDescriptorSets must have been allocated with a VKDescriptorSetLayout that matches (is the same as, or identically defined as) the VkDescriptorSetLayout at set n in layout, where n is the sum of firstSet and the index into pDescriptorSets.

我还注意到,如果我将第二个网格中使用的纹理数量从 5 个减少到 4 个,以便它们与第一个网格中的 4 个匹配,那么它就可以工作。所以我想知道是否需要为布局的每个可能配置创建一个管道?也就是说,一个管道setLayoutCount设置为 4,另一个设置为 5,当我要绘制一个网格或另一个时绑定相应的管道?那是愚蠢的吗?我错过了什么吗?

值得注意的是,如果我单独渲染每个网格,一切都会顺利进行。当我将它们都放在场景中时,问题就出现了。

另外,我知道应该连续分配缓冲区并考虑对齐情况,并且我正在做的事情是一种不好的做法 - 但我还没有处理这个问题。

标签: c++3dvulkan

解决方案


将多个集合布局传递给管道意味着您希望管道能够同时访问两个集合中的所有绑定,例如,着色器可以访问 (set=0, binding=0) 和 (set=1, binding=0),在 (set=0, binding=1) 处有四个纹理,在 (set=1, binding=1) 处有五个纹理。

然后,当您将第二个网格的集合绑定为唯一集合时,您会遇到不兼容性,因为它的布局(5 个纹理)与管道对集合 0(4 个纹理)的预期不同。

所以是的,当你有不同的描述符集布局时,你需要不同的管道。如果您使用管道缓存,则实际上可能会在两个管道之间重用大部分编译。

如果您尝试对两个网格使用相同的管道,那么大概着色器中访问第五个纹理的代码是有条件的,基于统一或其他什么?另一种方法是在绘制 4 纹理网格时绑定一个虚拟纹理;因为它不会被访问,所以它的内容是什么并不重要,它可以是 1x1 等。然后你可以为两个网格使用相同的 5 纹理集布局和相同的管道。


推荐阅读