Vulkan:统一缓冲区的奇怪性能
Vulkan: weird performance of uniform buffer
我的片段着色器的输入之一是一个包含 5 个结构的数组。着色器根据 5 个结构中的每一个计算颜色。最后,将这 5 种颜色相加产生最终输出。数组的总大小为 1440 字节。为了适应统一缓冲区的对齐方式,统一缓冲区的大小更改为1920字节。
1-如果我将5个结构的数组定义为统一缓冲区数组,则渲染需要5ms(由Nsight Graphics测量)。统一缓冲区的内存 属性 是 'VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT'。 glsl中uniform buffer定义如下
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 快得多。知道为什么使用统一缓冲区数组会减慢渲染速度吗?是因为它是主机可见缓冲区吗?
当谈到 UBO 时,有两种硬件:一种 UBO 是专用硬件,另一种不是。对于 UBO 不是专用硬件的 GPU,UBO 实际上只是一个 readonly
SSBO。您通常可以分辨出区别,因为 UBO 专用的硬件对它们的大小限制与 SSBO 的大小限制不同。
对于专门的 hardware-based UBO(如果我没记错的话,NVIDIA 仍在使用),每个 UBO 代表从内存上传到一大块常量数据,特定着色器阶段的所有调用都可以访问这些数据。
对于这种硬件,UBO 数组基本上是从这个常量数据块的段中创建一个数组。并且某些硬件具有多个常量数据块,因此使用 non-constant 表达式进行索引很棘手。这就是为什么 non-constant 访问此类索引是 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 个结构的数组。着色器根据 5 个结构中的每一个计算颜色。最后,将这 5 种颜色相加产生最终输出。数组的总大小为 1440 字节。为了适应统一缓冲区的对齐方式,统一缓冲区的大小更改为1920字节。
1-如果我将5个结构的数组定义为统一缓冲区数组,则渲染需要5ms(由Nsight Graphics测量)。统一缓冲区的内存 属性 是 'VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT'。 glsl中uniform buffer定义如下
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 快得多。知道为什么使用统一缓冲区数组会减慢渲染速度吗?是因为它是主机可见缓冲区吗?
当谈到 UBO 时,有两种硬件:一种 UBO 是专用硬件,另一种不是。对于 UBO 不是专用硬件的 GPU,UBO 实际上只是一个 readonly
SSBO。您通常可以分辨出区别,因为 UBO 专用的硬件对它们的大小限制与 SSBO 的大小限制不同。
对于专门的 hardware-based UBO(如果我没记错的话,NVIDIA 仍在使用),每个 UBO 代表从内存上传到一大块常量数据,特定着色器阶段的所有调用都可以访问这些数据。
对于这种硬件,UBO 数组基本上是从这个常量数据块的段中创建一个数组。并且某些硬件具有多个常量数据块,因此使用 non-constant 表达式进行索引很棘手。这就是为什么 non-constant 访问此类索引是 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 它不是这样没有帮助。