如何提高 OpenGL 着色器中的纹理访问性能?
How to improve texture access performance in OpenGL shaders?
条件
我使用 OpenGL 3
和 PyOpenGL
。
我有大约 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
纹理坐标的分量
我想,对齐的数据访问起来会更快。
问题
- 为什么数据访问速度不快?
- 如何提高它的性能?
- 是否有能力 link 带顶点的纹理块?
- 是否有关于数据对齐的建议,关于 GPU(Intel HD、nVidia GeForce)缓存的相关文章?
备注
coefficients
数组逐帧更改,否则没有问题:我可以预先计算模型并且很高兴
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 个就可以逃脱。
条件
我使用 OpenGL 3
和 PyOpenGL
。
我有大约 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
纹理坐标的分量
我想,对齐的数据访问起来会更快。
问题
- 为什么数据访问速度不快?
- 如何提高它的性能?
- 是否有能力 link 带顶点的纹理块?
- 是否有关于数据对齐的建议,关于 GPU(Intel HD、nVidia GeForce)缓存的相关文章?
备注
coefficients
数组逐帧更改,否则没有问题:我可以预先计算模型并且很高兴
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 个就可以逃脱。