OpenGL:批处理绘制好还是静态VBO更好?

19

从效率角度(或其他重要角度)来看,哪种方案更可取呢?

情境
一个OpenGL应用程序每帧绘制许多不同位置的线条(60 fps)。假设有10条线。或者有100,000条线。答案会有所不同吗?

  • #1 使用永远不会更改的静态VBO,其中包含一条线的2个顶点

每帧将使用一个glDrawArrays调用来绘制每条线,并且在此之间会进行矩阵变换以定位我们的一条线

  • #2 每帧更新包含所有线数据的VBO

每帧仅使用一个绘图调用

1个回答

29

第二个方法效率更高。

改变状态,特别是变换和矩阵操作,往往会导致其他状态的重新计算,通常需要更多的数学运算。

然而,更新几何图形只涉及重写缓冲区。

随着现代视频硬件在非常大的带宽总线上的发展,仅仅发送一些浮点数是微不足道的。它们被设计用于快速移动大量数据,这是工作的副作用。更新顶点缓冲区正是它们经常快速执行的任务。如果我们假设每个点占用32个字节(float4位置和颜色),那么100000条线段就不到6 MB,而PCIe 2.0 x16约为8 GB/s。

在某些情况下,具体取决于驱动程序或显卡处理转换的方式,改变一个可能会导致矩阵乘法和其他值的重新计算,包括转换、裁剪平面等。如果你改变状态、绘制几千个多边形并重复进行,这不是问题,但是当状态更改频繁时,会产生相当大的开销。

批处理是解决此问题的好方法,通过最小化状态更改,可以更有效地绘制大量几何图形。

一个非常明显的例子是#1的最佳情况:变换设置不触发任何其他计算,并且驱动程序缓冲处理得非常完美。要绘制100000条线,你需要:

  • 100000个矩阵设置(在系统RAM中)
  • 100000个矩阵设置调用,带有函数调用开销(传递矩阵到视频驱动程序的缓冲区中)
  • 将100000个矩阵复制到视频RAM中,在单个块中执行
  • 100000个线段绘制调用

仅函数调用开销就足以使性能崩溃。

另一方面,批处理涉及:

  • 100000个点的计算和设置,在系统RAM中
  • 1个VBO复制到视频RAM中。这将是一个大块,但两端都知道可以处理什么。它可以被很好地处理。
  • 1个矩阵设置调用
  • 将1个矩阵复制到视频RAM中
  • 1个绘制调用

你确实复制了更多的数据,但是VBO的内容很可能仍然不如复制矩阵数据那样昂贵。此外,你节省了大量的CPU时间用于函数调用(从200000降至2)。这简化了你、驱动程序(必须缓冲所有内容并检查冗余调用以优化和处理下载)和视频卡(可能需要重新计算)的生活。为了使它更加清晰,请想象一下实现它的简单代码:

1:

for (i = 0; i < 100000; ++i)
{
    matrix = calcMatrix(i);
    setMatrix(matrix);
    drawLines(1, vbo);
}

(现在取消包装)

2:

matrix = calcMatrix();
setMatrix(matrix);
for (i = 0; i < 100000; ++i)
{
    localVBO[i] = point[i];
}
setVBO(localVBO);
drawLines(100000, vbo);

3
这是否意味着将数据写入VBO再渲染总是比使用矩阵变换更好?如果我有10个移动的纹理四边形,那么重新计算对象的坐标、重建VBO、上传和渲染真的比为每个对象使用矩阵平移(我将其优化为仅需2次加法而非64次乘法和48次加法)并进行渲染更优吗? - mk12
1
如果我们假设每个点占用32个字节(float4位置和颜色),那么将其减半几乎不需要任何努力:位置的vec3和无符号字节颜色的vec4。此外,您应该调查缓冲对象流以提高其性能。 - Nicol Bolas
1
@Mk12,你提出了一个更加复杂的问题。你的问题之所以简单是因为每个对象都非常小,即使你有很多对象,顶点数据也很小。一旦你拥有更多的对象,计算它们的位置的成本就变得更加显著,上传成本也是如此。 - Nicol Bolas
4
这只是数学问题。在10个四边形的情况下,成本会发生变化;几何形状更小,你需��增加更多的函数调用开销。对于你的示例案例,大量的点使得决策非常明确。在不同情况下,您需要进行分析并查看瓶颈所在,并相应地进行优化。 - ssube
3
我想指出的一件事是,OpenGL矩阵操作(在后续版本中已被弃用)不会在GPU上执行。它们是由驱动程序执行的。正是这种驱动程序端的执行可能会触发连锁反应的依赖状态更改。更糟糕的是,你会在绘图之间进行状态更改,因此会打断GPU的工作流程;对于超标量处理器来说,需要若干个工作周期才能达到完全效率。因此,最好将许多单个移动放在一个大缓冲区中进行。然而,整个集合的单个运动最好通过矩阵完成。 - datenwolf
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接