为什么用单个 bufferData 调用替换这些矩阵转换会慢得多?

Why is replacing these matrix transformations with a single bufferData call so much slower?

我正在尝试优化绘制精灵的着色器,我最初有这样的东西:

// this matrix will convert from pixels to clip space
var matrix = m3.projection(this.camera.viewportWidth / this.camera.scale, this.camera.viewportHeight / this.camera.scale);

// this matrix will translate our quad to dstX, dstY
matrix = m3.translate(matrix, dstX, dstY);

// this matrix will scale our 1 unit quad
// from 1 unit to texWidth, texHeight units
matrix = m3.scale(matrix, dstWidth, dstHeight);

gl.uniformMatrix3fv(attribs.matrixLocation, false, matrix);

以上代码灵感来自本教程:https://webglfundamentals.org/webgl/lessons/webgl-2d-drawimage.html

这行得通,但我已经保存了相机矩阵变换,所以我想避免在每一帧都进行所有这些矩阵变换。每个 m3.whatever 调用都会分配一个新数组,所以我想用以下内容替换它:

gl.bindBuffer(gl.ARRAY_BUFFER, attribs.positionBuffer);

attribs.positionsQuad[0] = dstX;
attribs.positionsQuad[1] = dstY + dstHeight;
attribs.positionsQuad[2] = dstX;
attribs.positionsQuad[3] = dstY;
attribs.positionsQuad[4] = dstX + dstWidth;
attribs.positionsQuad[5] = dstY + dstHeight;

attribs.positionsQuad[6] = dstX + dstWidth;
attribs.positionsQuad[7] = dstY + dstHeight;
attribs.positionsQuad[8] = dstX;
attribs.positionsQuad[9] = dstY;
attribs.positionsQuad[10] = dstX + dstWidth;
attribs.positionsQuad[11] = dstY;

gl.bufferData(gl.ARRAY_BUFFER, attribs.positionsQuad, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(attribs.positionLocation, 2, gl.FLOAT, false, 0, 0);

gl.uniformMatrix3fv(attribs.matrixLocation, false, camera.ClipTransform);

这也有效,但现在我的帧速率非常尖锐。有人知道为什么吗?我试着分析它,它确实说我的图像绘图着色器现在变慢了,但我不确定这是怎么回事。我用写入单个预分配数组然后传输它来替换一堆矩阵分配和转换,现在速度慢多了?

似乎很多帧率跳跃可能是由于垃圾收集器运行,但即使这样对我来说也没有意义。对于最初的解决方案,应该有更多的垃圾,考虑到我每帧分配和丢弃大量数组以及所有这些矩阵转换。现在我根本没有分配,那么为什么 GC 使用率现在会飙升?

有没有更好的方法来完成这个?我已经在这里上传了我的整个着色器以供参考:https://pastebin.com/tdCYpDqv

对于大多数图形 API 命令,发生的情况是命令在命令缓冲区中编码,在某些时候(异步)这些缓冲区由图形驱动程序同步到 GPU。对于可预测的命令缓冲区,需要复制所有数据以放入缓冲区。

现在您的代码存在一个问题,即您正在设置数据并立即要求 GPU 从中提取数据,这需要对整个缓冲区进行硬同步。驱动程序期望制服需要同步但不一定需要数组缓冲区,使用提示(DYNAMICSTREAMSTATIC 绘制)并没有真正做太多(实际上在大多数情况下 STATIC_DRAW 即使对于动态数据也更快)。

当这些硬同步发生时,您几乎总是在拖延管道,这意味着 GPU 需要等待所有数据传输完毕,然后才能继续做它正在做的事情。您可以通过使用双倍甚至三倍缓冲(为下一帧写入数据但渲染当前帧等)来避免这种情况。

然而尽管如此,试图优化 6 个四边形的绘制是非常有问题的,因为(在这种情况下)我们在这里谈论不可估量的差异,改变一个其他事情可能会改变帧时间,但它并没有说明可伸缩性,因为您实际上只是在测量(通常是静态的)开销而不是实际性能。