【问题标题】:Vulkan - when should I create a new pipeline?Vulkan - 我应该什么时候创建一个新的管道?
【发布时间】:2019-04-17 19:45:22
【问题描述】:

所以我想在 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,当我要绘制一个网格或另一个时绑定相应的?那是愚蠢的吗?我错过了什么吗?

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

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

【问题讨论】:

  • 请不要将文字作为图片发布。引用的代码块使用大于后跟 6 个空格。

标签: c++ 3d vulkan


【解决方案1】:

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

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

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

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

【讨论】:

  • 谢谢,这很有意义。我的错误是我认为传递给管道的布局是一种“目录”,可以从中挑选并在任何给定时间绑定哪些布局,而实际上正如您所说的那样,它们都是同时进行的。我想某种“查找或创建”管道管理系统对于一般情况下需要考虑的布局类型是需要考虑的...... - 另外,正确的,片段着色器通过顶点属性索引正确的纹理,从带有“flat”修饰符的顶点着色器传递。
猜你喜欢
  • 2012-11-13
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 2019-02-16
  • 2011-09-11
  • 2015-07-20
相关资源
最近更新 更多