Glsl Vulkan:统一缓冲区的奇怪性能

Glsl Vulkan:统一缓冲区的奇怪性能,glsl,vulkan,Glsl,Vulkan,我的片段着色器的一个输入是5个结构的数组。着色器基于5个结构中的每个结构计算颜色。最后,将这5种颜色相加以生成最终输出。数组的总大小为1440字节。为了适应统一缓冲区的对齐,统一缓冲区的大小更改为1920字节 1-如果我将5个结构的数组定义为统一缓冲区数组,则渲染需要5毫秒(通过Nsight Graphics测量)。统一缓冲区的内存属性为“VK_内存_属性_主机_可见_位| VK_内存_属性_主机_相干_位”。glsl中的统一缓冲区定义如下 layout(set=0,binding=0) uni

我的片段着色器的一个输入是5个结构的数组。着色器基于5个结构中的每个结构计算颜色。最后,将这5种颜色相加以生成最终输出。数组的总大小为1440字节。为了适应统一缓冲区的对齐,统一缓冲区的大小更改为1920字节

1-如果我将5个结构的数组定义为统一缓冲区数组,则渲染需要5毫秒(通过Nsight Graphics测量)。统一缓冲区的内存属性为“VK_内存_属性_主机_可见_位| VK_内存_属性_主机_相干_位”。glsl中的统一缓冲区定义如下

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;
}
layout(set=0,binding=0)统一统一结构{A A;}us[];
布局(位置=0)输出vec4 c;
void main()
{
vec4列=vec4(0);
对于(int i=0;i<5;i++)
col+=func(us[ununiformext(i)]);
c=col;
}
此外,我正在使用“GL\u EXT\u ununiform\u qualifier”扩展来访问统一缓冲区数组。对我来说,这似乎是最简单的方法,但也有其他的实现方式

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

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

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


在我看来,4应该是最快的方式,这在测试中是正确的。那么1应该是下一个。2和3都应该比1慢。但是,2或3都不比1慢。相比之下,它们比1快得多。你知道为什么使用统一缓冲阵列会减慢渲染速度吗?是因为它是主机可见缓冲区吗?

说到UBO,有两种硬件:一种是UBO专用硬件,另一种不是。对于那些UBO不是专用硬件的GPU,UBO实际上只是一个
只读的
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撒谎来加速事情的发展。表达式
ununiformext(i)
表示表达式
i
不是。这是不正确的。执行此循环的每个着色器调用都将生成值从0到4的
i
表达式。任何调用的表达式
i
的每个动态实例在代码中的该位置的值都与其他位置的值相同


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

您是否考虑过一种明显的可能性,即您只有一个统一的缓冲区来存储5个
a
s的数组?这样:
layout(set=0,binding=0)统一的统一结构{A[5];}us抱歉没有在问题中澄清。我已经考虑过按照你的建议将方括号放入统一结构中。我没有这么做的原因是“5”实际上在程序启动之前未确定。当用户运行程序时,他/她会告诉程序将有多少个结构。程序启动后,该数字在整个生命周期内保持不变。总之,处理这个问题的唯一方法是使用存储缓冲区或GL_EXT_ununiform_限定符。如果有更好的方法,我们将不胜感激。@user3677630:GL\u EXT\u ununiform\u限定符与此无关。在这里使用它是错误的,因为你所做的一切都是不统一的。至于数组的大小,这听起来像是一个专门化常量可以解决的问题。明白了。我对“GL_EXT_ununiform_限定符”的理解是错误的。特化常数正是我需要的。我将测试它的性能。谢谢更改到您的解决方案比使用存储缓冲区稍微快一点。