GLSL:结构数组与 OpenGL 缓冲区中的数组结构
GLSL: Array of Structs vs Struct of Arrays in OpenGL Buffers
现在,当您浏览 Internet 上的不同资源时,如果您要按顺序处理大型数组,数组结构似乎是一种非常高效的数据存储方式。
例如在 C++ 中
struct CoordFrames
{
float* x_pos;
float* y_pos;
float* z_pos;
float* scaleFactor;
float* x_quat;
float* y_quat;
float* z_quat;
float* w_quat;
};
允许比
的数组更快地处理大型数组(多亏了 SIMD)
struct CoordFrame
{
glm::vec3 position;
float scaleFactor;
glm::quat quaternion;
};
GPU 是为大规模并行计算而设计的处理器。 SIMD在这里是一个"must have"。所以结论是数组结构在这里最有用。
但是...
我从来没有在任何地方见过这样的 GLSL 着色器(我觉得这不对):
#define NUM_POINT_LIGHTS 16
uniform float point_light_x[NUM_POINT_LIGHTS];
uniform float point_light_y[NUM_POINT_LIGHTS];
uniform float point_light_z[NUM_POINT_LIGHTS];
uniform float point_light_radius[NUM_POINT_LIGHTS];
uniform float point_light_color_r[NUM_POINT_LIGHTS];
uniform float point_light_color_g[NUM_POINT_LIGHTS];
uniform float point_light_color_b[NUM_POINT_LIGHTS];
uniform float point_light_power[NUM_POINT_LIGHTS];
或类似的东西也不常见:
#define NUM_POINT_LIGHTS 16
uniform vec3 point_light_pos[NUM_POINT_LIGHTS];
uniform float point_light_radius[NUM_POINT_LIGHTS];
uniform vec3 point_light_color[NUM_POINT_LIGHTS];
uniform float point_light_power[NUM_POINT_LIGHTS];
每个人,包括我,似乎更喜欢这样写 GLSL:
#define NUM_POINT_LIGHTS 16
struct PointLight
{
vec3 origin;
float radius;
vec3 color;
float power;
};
uniform PointLight pointLights[NUM_POINT_LIGHTS];
此外,在阅读有关顶点数组数据的原始 OpenGl Wiki 时,我突然想知道,interleaved data should be preferred:
As a general rule, you should use interleaved attributes wherever possible.
问题:尝试在 GLSL 着色器中使用数组结构是否有意义?
什么是真的? GPU 是否针对我们喜欢编写着色器的方式进行了高度优化,这真的没有任何区别吗?
虽然我目前没有确切的数字,但我认为总体上不会有帮助。
许多现代 GPU 确实使用 SoA 格式。然而,数组部分通常是着色器的多次调用,当查看单个调用时,就好像您在没有 SIMD 的情况下执行一样。因此,特别是对于统一变量,变量的 SoA 布局没有显着的性能差异。
其他一些 GPU 实际上具有 AoS 布局。例如,Intel Sandy Bridge(Core 2011 版)在一个内核上同时执行 2 个顶点着色器,但有一个 8 宽的 SIMD 单元,基本上是 2 个 vec4 的布局。因此,使用向量可以使编译器更容易优化您的代码。
如果我们看一下 SoA 在 CPU 上的好处,两大好处是
- 通过仅访问包含您需要的成员的缓存行来提高缓存利用率。
- 能够使用 SIMD 指令轻松加载和存储数据。
更好的缓存利用率对于GPU来说基本一样。然而,无论如何,您经常会为单次绘制操作优化数据结构,因此您不会遗漏任何成员来提高缓存利用率。尽管在渲染阴影贴图时将一系列材质作为 AoS 可能仍然很浪费。
使用 SIMD 指令的问题要小得多,因为从单个着色器调用的角度来看,您并没有真正使用 SIMD,因此对您的加载和存储没有限制。根据架构的不同,可能会有一些指令加载多个元素,但例如对于 AMD GCN 架构,您可以在之后使用单独加载的变量,因此可以只加载整个结构并使用它。
我猜想,如果您的计算量有限,这并不重要,如果您的带宽有限,您应该减少加载数据的大小,您可以使用 SoA 布局来实现该目标。
如果它只是 16 盏灯的阵列,我不会担心,因为它非常小,可能不会真正使用大量带宽。
至于交错属性,这可能非常依赖 GPU。例如,对于 Sandy Bridge,有 2 个顶点着色器调用,您可以通过交错处理这两个顶点来获得更好的局部性。
但是,在单个内核可以同时执行 64 个着色器的 AMD GCN 上,即使您不交错属性,您也可能会获得良好的局部性,因为每个属性都应该填充整个缓存行(假设如果进行索引渲染,顶点会很接近)。
请记住,性能特征可能因 GPU、驱动程序和您要执行的操作而异。没有什么能比得上特定问题的良好基准。
现在,当您浏览 Internet 上的不同资源时,如果您要按顺序处理大型数组,数组结构似乎是一种非常高效的数据存储方式。
例如在 C++ 中
struct CoordFrames
{
float* x_pos;
float* y_pos;
float* z_pos;
float* scaleFactor;
float* x_quat;
float* y_quat;
float* z_quat;
float* w_quat;
};
允许比
的数组更快地处理大型数组(多亏了 SIMD)struct CoordFrame
{
glm::vec3 position;
float scaleFactor;
glm::quat quaternion;
};
GPU 是为大规模并行计算而设计的处理器。 SIMD在这里是一个"must have"。所以结论是数组结构在这里最有用。
但是...
我从来没有在任何地方见过这样的 GLSL 着色器(我觉得这不对):
#define NUM_POINT_LIGHTS 16 uniform float point_light_x[NUM_POINT_LIGHTS]; uniform float point_light_y[NUM_POINT_LIGHTS]; uniform float point_light_z[NUM_POINT_LIGHTS]; uniform float point_light_radius[NUM_POINT_LIGHTS]; uniform float point_light_color_r[NUM_POINT_LIGHTS]; uniform float point_light_color_g[NUM_POINT_LIGHTS]; uniform float point_light_color_b[NUM_POINT_LIGHTS]; uniform float point_light_power[NUM_POINT_LIGHTS];
或类似的东西也不常见:
#define NUM_POINT_LIGHTS 16 uniform vec3 point_light_pos[NUM_POINT_LIGHTS]; uniform float point_light_radius[NUM_POINT_LIGHTS]; uniform vec3 point_light_color[NUM_POINT_LIGHTS]; uniform float point_light_power[NUM_POINT_LIGHTS];
每个人,包括我,似乎更喜欢这样写 GLSL:
#define NUM_POINT_LIGHTS 16 struct PointLight { vec3 origin; float radius; vec3 color; float power; }; uniform PointLight pointLights[NUM_POINT_LIGHTS];
此外,在阅读有关顶点数组数据的原始 OpenGl Wiki 时,我突然想知道,interleaved data should be preferred:
As a general rule, you should use interleaved attributes wherever possible.
问题:尝试在 GLSL 着色器中使用数组结构是否有意义?
什么是真的? GPU 是否针对我们喜欢编写着色器的方式进行了高度优化,这真的没有任何区别吗?
虽然我目前没有确切的数字,但我认为总体上不会有帮助。
许多现代 GPU 确实使用 SoA 格式。然而,数组部分通常是着色器的多次调用,当查看单个调用时,就好像您在没有 SIMD 的情况下执行一样。因此,特别是对于统一变量,变量的 SoA 布局没有显着的性能差异。
其他一些 GPU 实际上具有 AoS 布局。例如,Intel Sandy Bridge(Core 2011 版)在一个内核上同时执行 2 个顶点着色器,但有一个 8 宽的 SIMD 单元,基本上是 2 个 vec4 的布局。因此,使用向量可以使编译器更容易优化您的代码。
如果我们看一下 SoA 在 CPU 上的好处,两大好处是
- 通过仅访问包含您需要的成员的缓存行来提高缓存利用率。
- 能够使用 SIMD 指令轻松加载和存储数据。
更好的缓存利用率对于GPU来说基本一样。然而,无论如何,您经常会为单次绘制操作优化数据结构,因此您不会遗漏任何成员来提高缓存利用率。尽管在渲染阴影贴图时将一系列材质作为 AoS 可能仍然很浪费。
使用 SIMD 指令的问题要小得多,因为从单个着色器调用的角度来看,您并没有真正使用 SIMD,因此对您的加载和存储没有限制。根据架构的不同,可能会有一些指令加载多个元素,但例如对于 AMD GCN 架构,您可以在之后使用单独加载的变量,因此可以只加载整个结构并使用它。
我猜想,如果您的计算量有限,这并不重要,如果您的带宽有限,您应该减少加载数据的大小,您可以使用 SoA 布局来实现该目标。
如果它只是 16 盏灯的阵列,我不会担心,因为它非常小,可能不会真正使用大量带宽。
至于交错属性,这可能非常依赖 GPU。例如,对于 Sandy Bridge,有 2 个顶点着色器调用,您可以通过交错处理这两个顶点来获得更好的局部性。
但是,在单个内核可以同时执行 64 个着色器的 AMD GCN 上,即使您不交错属性,您也可能会获得良好的局部性,因为每个属性都应该填充整个缓存行(假设如果进行索引渲染,顶点会很接近)。
请记住,性能特征可能因 GPU、驱动程序和您要执行的操作而异。没有什么能比得上特定问题的良好基准。