opengl 如何在GPU上通过大而密集的矩阵向量乘法计算顶点位置?

wgxvkvu9  于 2022-11-04  发布在  其他
关注(0)|答案(1)|浏览(180)

我的顶点位置V∈ R ³应该是一个大的稠密矩阵B∈ R ³与一个向量z∈ R相乘的结果:

V = B z

在我的例子中,k≈300,所以据我所知,这太大了,不足以将第i个顶点的相关行B存储为顶点属性。
目前,我正在顶点着色器中计算乘法,方法是将z设置为uniform,将B打包到一个正方形纹理中,然后使用texelFetch和for循环。

uniform int n; // number of vertices
uniform int k; // size of z
uniform int s; // size of texture square where B is packed
uniform float z[512];
uniform sampler2D tex;
in float id; // index of vertex
out vec3 v;

void main()
{
  v = vec3(0,0,0);
  for(int j = 0;j < k; j++)
  {
    int index = int(id)*k+j;
    int si = index % s;
    int sj = int((index - si)/s);
    v = v + texelFetch(tex,ivec2(si,sj),0).xyz*z[j];
  }

在我的MacBook Pro M1上,当n≈50,000,k≈100时,这一方法相当有效。增加任何一个值,我都会开始丢帧。
在顶点着色器中执行此操作是个好主意吗?
我的计算类似于blendshape。这些通常是如何在GPU上计算的?
理想的情况下,我希望坚持使用opengl,如果没有,有没有opencl或者其他的方法可以最好地实现这一点?

thigvfpy

thigvfpy1#

我对你的着色器有点困惑。z从未使用过,但可以访问数组q。我想这是一个打字错误吧?
抛开着色器类型不谈,根据您是计算型还是内存型,您还可能会将一些可能的实质性优化留在桌面上:
1.你的si/sj计算使用整数除法/除法余数。尽量避免在热循环中进行这些昂贵的计算。相反,在循环之前执行一次这些计算,然后在每次迭代中递增si,然后进行简单的测试,如果你已经到达了if (si >= s) { si = 0; ++sj; }行的末尾,我知道他们说避免在着色器中进行分支,但是像这样简单的条件语句要么根本不分支,要么至少比整数除法更便宜。衡量哪一个更快。)
1.你说你的矩阵是稠密的。那么你的输入向量是z/q吗?如果有很多元素是0,那么跳过矩阵中要乘以0的部分可能更明智。要么显式地测试输入向量元素中的0,要么将输入向量作为非零元素的列表传递。(包含非零值及其索引的数组,非零值的个数。)这样做是否值得取决于内存带宽对性能的限制程度;如果超过一半的元素,那么绝对值得尝试,但是考虑到读取庞大矩阵的成本,即使是少量的零,也可能值得尝试。
1.最后,您不需要指定如何得到巨型矩阵B。如果您可以分解其计算,那么如果它可以减少需要在顶点着色器中读取的数据量,那么这可能是值得的。

着色器类型

决定使用昂贵的顶点着色器还是计算着色器的一个主要因素是你是否要用相同的值重新运行计算。如果这些都是一次性计算,你可能会坚持使用顶点着色器。如果你的z/q向量和B矩阵在多个渲染通道中是恒定的,您将需要缓存结果。
计算着色器还可以给予您更好地控制并行性,并且您不必在将矩阵打包到纹理中时浪费时间:只需使用数组。
我所说的 * 更好地控制并行性 * 是指:您可以在一个工作组中计算每个向量元素,而不是按顺序执行每个矩阵乘法切片,其中工作组中的每个工作项计算几个切片,然后工作组在本地/组内存中执行并行缩减,以累积最终结果。这通常更有效地平均占用所有GPU的着色器单元。

API/平台选择

如果你的目标是macOS和其他苹果平台,你可能要考虑使用金属,而不是OpenGL和OpenCL,因为后两个被苹果标记为不推荐使用,而工具(分析、调试......)支持与Metal相比不存在。(是的,这是令人难以置信的恼人。)在其他平台上,OpenGL也提供计算着色器,但苹果在计算着色器之前停止实现新的OpenGL功能,因此,在macOS中实现它们的唯一方法是通过OpenCL或Metal。(例如,使用Metal可以让您确定着色器的性能是受计算还是内存限制,从而更好地指导优化。

相关问题