【问题标题】:How to improve texture access performance in OpenGL shaders?如何提高 OpenGL 着色器中的纹理访问性能?
【发布时间】:2016-08-14 10:22:13
【问题描述】:

条件

我使用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;
}

它们之间的主要区别:

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

我想,对齐的数据会被更快地访问。

问题

  • 为什么不能更快地访问数据?
  • 如何提高它的性能?
  • 是否可以将纹理块与顶点链接?
  • 是否有关于数据对齐的建议,以及关于 GPU(Intel HD、nVidia GeForce)中缓存的良好相关文章?

注意事项

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

【问题讨论】:

  • 性能差异可能与着色器的执行方式有关。通常一堆着色器并行执行相同的命令。当所有第一个属性彼此靠近存储时,并行着色器读取相邻纹素的机会很高,这将更快。
  • 我在您的代码中看到了很多预计算的潜力。 c_pos 的演变仅取决于 VertexID。 coefficientsdeviations 的乘法也可以很简单地合并。而且因为迭代总是通过相同的 199 个元素,甚至可以与 principal_components LUT 合并。这意味着您可以保存 199 次纹理查找和 2×199 次乘法。这……很多。
  • @BDL 是的,我也想过这个问题。我将尝试不同的对齐方式,但我猜测存在一些关于更佳间隔的建议,或者我可以将纹理块分配给特定顶点。
  • @datenwolf coefficients 参数在每一帧中都不同,所以我唯一能做的就是预先计算coefficientsdeviations 的乘积。我已经这样做了,现在我有 ~0.09s vs ~0.12s,所以差异仍然存在。谢谢,我会更改有问题的代码!
  • coefficients 中有多少是零或可以忽略不计?

标签: performance opengl glsl textures


【解决方案1】:

为什么不能更快地访问数据?

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

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

是否有能力将纹理块与顶点链接?

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

那么,让每个纹理访问连贯一致会有助于提高性能。也就是说,相邻顶点将访问相邻的纹素,从而使纹理获取更高效。但是没有办法知道哪些顶点将被视为“邻居”。并且纹理混合布局依赖于实现,因此即使您确实知道顶点处理的顺序,也无法调整纹理以利用它。

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

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

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

停止进行这么多的纹理提取。

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

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

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

【讨论】:

  • 感谢您的回答!它为我澄清了很多。虽然,我使用系数来变形模型,所以它们中的大多数都是非零的,我会根据你的建议尝试以另一种方式存储它们
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多