如何提高 OpenGL 着色器中的纹理访问性能?

Cha*_*lie 1 opengl performance textures glsl

状况

我用OpenGL 3PyOpenGL

我有大约 50,000 (53'490) 个顶点,每个顶点都有 199 个vec3属性来确定它们的位移。不可能将此数据存储为常规顶点属性,因此我使用纹理。

问题是:非并行化C函数计算顶点位移的速度与计算顶点位移的速度一样快GLSL,在某些情况下甚至更快。我已经检查过:问题是纹理读取,我不明白如何优化它。

我写了两个不同的着色器。一种在约 0.09 秒内计算出新模型,另一种在约 0.12 秒内计算出新模型(包括属性分配,这两种情况都是相同的)。

代码

两个着色器都以

#version 300 es

in vec3 vin_position;

out vec4 vin_pos;

uniform mat4 rotation_matrix;

uniform float coefficients[199];

uniform sampler2D principal_components;
Run Code Online (Sandbox Code Playgroud)

速度较快的是

void main(void) {
    int c_pos = gl_VertexID;
    int texture_size = 8192;
    ivec2 texPos = ivec2(c_pos % texture_size, c_pos / texture_size);
    vec4 tmp = vec4(0.0);
    for (int i = 0; i < 199; i++) {
        tmp += texelFetch(principal_components, texPos, 0) * coefficients[i];
        c_pos += 53490;
        texPos = ivec2(c_pos % texture_size, c_pos / texture_size);
    }
    gl_Position = rotation_matrix
        * vec4(vin_position + tmp.xyz, 246006.0);
    vin_pos = gl_Position;
}
Run Code Online (Sandbox Code Playgroud)

较慢的那一个

void main(void) {
    int texture_size = 8192;
    int columns = texture_size - texture_size % 199;
    int c_pos = gl_VertexID * 199;
    ivec2 texPos = ivec2(c_pos % columns, c_pos / columns);
    vec4 tmp = vec3(0.0);
    for (int i = 0; i < 199; i++) {
        tmp += texelFetch(principal_components, texPos, 0) * coefficients[i];
        texPos.x++;
    }
    gl_Position = rotation_matrix
        * vec4(vin_position + tmp.xyz, 246006.0);
    vin_pos = gl_Position;
}
Run Code Online (Sandbox Code Playgroud)

它们之间的主要区别思想:

  • 在第一种情况下,顶点的属性按以下方式存储:
    • 所有顶点的第一个属性
    • 所有顶点的第二个属性
    • ...
    • 所有顶点的最后一个属性
  • 在第二种情况下,顶点的属性以另一种方式存储:
    • 第一个顶点的所有属性
    • 第二个顶点的所有属性
    • ...
    • 最后一个顶点的所有属性
  • 同样在第二个示例中,数据也是对齐的,以便每个顶点的所有属性仅存储在一行中。这意味着如果我知道某个顶点的第一个属性的行和列,我只需要增加x纹理坐标的分量

我认为,对齐的数据访问速度会更快。

问题

  • 为什么数据访问速度不快?
  • 我怎样才能提高它的性能?
  • 是否能够将纹理块与顶点链接起来?
  • 是否有关于数据对齐的建议,以及有关 GPU(Intel HD、nVidia GeForce)中缓存的相关文章?

笔记

  • coefficients数组从帧到帧改变,否则没有问题:我可以预先计算模型并且很高兴

Nic*_*las 5

为什么数据访问速度不快?

因为 GPU 并不神奇。GPU 通过并行执行计算来提高性能。执行 100 万次纹素提取,无论如何发生,都不会很快。

如果您使用这些纹理的结果来进行照明计算,它会显得很快,因为照明计算的成本将被内存获取的延迟隐藏。您正在获取提取的结果,进行乘法/加法,然后进行另一次提取。那很慢。

是否能够将纹理块与顶点链接起来?

即使有(也没有),那又有什么帮助呢?GPU并行执行操作。这意味着同时处理多个顶点,每个顶点访问 200 个纹理。

因此,提高性能的方法就是使每个纹理访问保持一致。也就是说,相邻的顶点将访问相邻的纹理像素,从而使纹理获取的缓存效率更高。但无法知道哪些顶点将被视为“邻居”。纹理混合布局依赖于实现,因此即使您确实知道顶点处理的顺序,您也无法调整纹理以充分利用它。

最好的方法是放弃顶点着色器和纹理访问,转而使用计算着色器和 SSBO。这样,您就可以通过设置工作组大小来直接了解访问的位置。借助 SSBO,您可以以任何方式布置阵列,从而为每个波前提供最佳的访问位置。

但这样的事情就相当于在裂开的伤口上贴上创可贴。

我怎样才能提高它的性能?

停止进行如此多的纹理获取。

我是认真的。虽然有多种方法可以降低您正在做的事情的成本,但最有效的解决方案是更改您的算法,以便它不需要做那么多工作。

您的算法看起来可疑地像通过“姿势”调色板进行顶点变形,系数指定应用于每个姿势的权重。如果是这种情况,那么大多数系数很可能为 0 或小到可以忽略不计。如果是这样,那么您就浪费了大量时间访问纹理,结果却将它们的贡献化为乌有。

如果大多数系数都是 0,那么最好的办法是选择一些任意的数字作为可以影响结果的最大系数数。例如,8。您将 8 个索引和系数的数组作为制服发送到着色器。然后您遍历该数组,仅获取 8 次。而且你可能只需要 4 个就可以逃脱惩罚。