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

How to improve texture access performance in OpenGL shaders?

条件

我使用 OpenGL 3PyOpenGL

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

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

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

代码

两个着色器都以

开头
#version 300 es

in vec3 vin_position;

out vec4 vin_pos;

uniform mat4 rotation_matrix;

uniform float coefficients[199];

uniform sampler2D principal_components;

更快的是

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;
}

慢一点的

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;
}

它们的主要区别:

我想,对齐的数据访问起来会更快。

问题

备注

Why is data not accessed faster?

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

如果您使用这些纹理的结果进行光照计算,它会显得很快,因为光照计算的成本会被内存获取的延迟所隐藏。您正在获取一次提取的结果,执行 multiply/add,然后进行另一次提取。太慢了。

Is there ability to link texture chunk with vertex?

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

因此,让每个纹理访问连贯一致对性能有帮助。也就是说,相邻的顶点将访问相邻的纹素,从而使纹理获取的缓存效率更高。但是无法知道将考虑哪些顶点 "neighbors"。纹理调配布局依赖于实现,因此即使您知道顶点处理的顺序,也无法调整纹理以利用它的局部优势。

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

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

How can I increase performance of it?

停止获取这么多纹理。

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

您的算法看起来很像通过 "poses" 调色板进行的顶点变形,系数指定了应用于每个姿势的权重。如果是这种情况,那么您的大部分系数要么为 0 要么小得可以忽略不计的可能性很大。如果是这样,那么您正在浪费 大量 访问纹理的时间,只是为了将它们的贡献变成无。

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