【问题标题】:Vulkan: weird performance of uniform bufferVulkan:统一缓冲区的奇怪性能
【发布时间】:2020-09-19 21:02:32
【问题描述】:

我的片段着色器的输入之一是一个由 5 个结构组成的数组。着色器根据 5 个结构中的每一个计算颜色。最后,将这 5 种颜色相加得到最终输出。数组的总大小为 1440 字节。为了适应统一缓冲区的对齐,统一缓冲区的大小变为 1920 字节。

1- 如果我将 5 个结构的数组定义为统一缓冲区数组,则渲染需要 5ms(由 Nsight Graphics 测量)。统一缓冲区的内存属性是 'VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT'。 glsl中的uniform缓冲区定义如下

layout(set=0,binding=0) uniform UniformStruct { A a; } us[];

layout(location=0) out vec4 c;
    
void main() 
{
    vec4 col = vec4(0); 
    for (int i = 0; i < 5; i++)   
      col += func(us[nonuniformEXT(i)]);
    c = col;
}

此外,我正在使用“GL_EXT_nonuniform_qualifier”扩展来访问统一缓冲区数组。这对我来说似乎是最直接的方式,但还有其他实现方式。

2- 我可以将渲染从一个 vkCmdDraw 拆分为五个 vkCmdDraw,将帧缓冲区的混合模式从覆盖更改为添加,并在片段着色器中定义一个统一缓冲区而不是统一缓冲区数组。在 CPU 方面,我将描述符类型从 UNIFORM_BUFFER 更改为 UNIFORM_BUFFER_DYNAMICS。在每个 vkCmdDraw 之前,我绑定了动态统一缓冲区和相应的偏移量。在片段着色器中,移除了 for 循环。虽然看起来它应该比第一种方法慢,但它比第一种方法快得多。 5 次绘制总共只需要 2 毫秒的渲染时间。

3- 如果我将 5 个结构的数组定义为存储缓冲区并执行一个 vkCmdDraw,则渲染只需 1.4ms。换句话说,如果我将数组从统一缓冲区数组更改为存储缓冲区,但其他任何内容都保持为 1,它会变得更快。

4- 如果我在 glsl 中将 5 个结构的数组定义为一个全局常量并执行一个 vkCmdDraw,则渲染只需要 0.5ms。

在我看来,4应该是最快的方式,在测试中也是如此。那么 1 应该是下一个。 2 和 3 都应该比 1 慢。但是,2 或 3 都不比 1 慢。相比之下,它们比 1 快得多。有什么想法为什么使用统一缓冲区数组会减慢渲染速度?是不是因为它是主机可见缓冲区?

【问题讨论】:

  • 您是否考虑过一种明显的可能性,即您只有一个统一的缓冲区来存储 5 个 As 的数组?像这样:layout(set=0,binding=0) uniform UniformStruct { A a[5]; } us;

标签: glsl vulkan


【解决方案1】:

说到 UBO,有两种硬件:UBO 是专用硬件的类型和非专用硬件的类型。对于 UBO 不是专用硬件的 GPU,UBO 实际上只是 readonly SSBO。您通常可以分辨出区别,因为专用于 UBO 的硬件对它们的大小限制与 SSBO 的大小限制不同。

对于基于硬件的专用 UBO(如果我没记错的话,NVIDIA 仍在使用它),每个 UBO 代表从内存上传到一个特定着色器阶段的所有调用都可以访问的一大块常量数据。

对于这种硬件,UBO 数组基本上是从这个常量数据块的段中创建一个数组。并且某些硬件具有多个常量数据块,因此使用非常量表达式进行索引很棘手。这就是为什么对此类索引的非恒定访问是 Vulkan 的一个可选功能。

相比之下,包含一个数组的 UBO 只是一个大 UBO。它的特别之处仅在于它有多大。通过 UBO 中的数组进行索引与对任何数组进行索引没有区别。对于此类访问的索引的统一性没有特殊的规定。

所以停止使用 UBO 数组,而只使用包含数据数组的 单个 UBO:

layout(set=0,binding=0) uniform UniformStruct { A a[5]; } us;

它还将避免由于对齐、额外的描述符、额外的缓冲区等导致的额外填充。

但是,您也可以通过不对 Vulkan 撒谎来加快速度。表达式nonuniformEXT(i) 表明表达式i 不是dynamically uniform。这是不正确的。执行此循环的每个着色器调用都将生成值从 0 到 4 的 i 表达式。任何调用的表达式 i 的每个动态实例在代码中的该位置都将具有相同的值。

因此i 是动态统一的,因此告诉 Vulkan 它不是没有帮助。

【讨论】:

  • 很抱歉没有在问题中澄清它。我已经考虑按照您的建议将方括号放入统一结构中。我没有这样做的原因是“5”实际上在程序启动之前并没有确定。当用户运行程序时,他/她告诉程序会有多少个结构。程序启动后,这个数字在整个生命周期中是恒定的。恕我直言,处理此问题的唯一方法是使用存储缓冲区或 GL_EXT_nonuniform_qualifier。任何更好的方法将不胜感激。
  • @user3677630: GL_EXT_nonuniform_qualifier 与此无关。在这里使用它是错误的,因为你所做的一切都是不统一的。至于数组的大小,这听起来像是一个特化常数可以解决的问题。
  • 知道了。我对“GL_EXT_nonuniform_qualifier”的理解是错误的。专业化常数正是我所需要的。我将测试它的性能。谢谢!
  • 更改为您的解决方案比使用存储缓冲区稍快。
猜你喜欢
  • 2018-03-23
  • 1970-01-01
  • 2021-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-07
  • 1970-01-01
相关资源
最近更新 更多