Vulkan - 我应该什么时候创建新管道?

Vulkan - when should I create a new pipeline?

所以我想在 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 个匹配,那么它就可以工作。所以我想知道是否需要为每个可能的布局配置创建一个管道?也就是一个pipeline setLayoutCount 设置为4,另一个pipeline设置为5,然后在我要绘制一个或另一个网格时绑定相应的?那是愚蠢的吗?我错过了什么吗?

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

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

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

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

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

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