OpenGL glDrawElements()调用与基本逻辑代码相比有多费力

How taxing are OpenGL glDrawElements() calls compared to basic logic code?

本文关键字:代码 glDrawElements 调用 OpenGL      更新时间:2023-10-16

我计划对OpenGL程序进行一些优化(它不需要优化,但我这样做是为了它)。出于好奇,OpenGL绘图函数与基本逻辑代码相比有多贵?目前,我正在开始一个游戏,屏幕上布满了正方形,以表示2D块状景观。这意味着一个正方形(两个三角形)的绘制调用被调用多次。目前,我计划添加一些代码,查看当前帧中块的位置,并将它们分组在一起。例如,如果有一列有7个块高,我可以调用一个函数,在整个屏幕上绘制一个1 x 7的矩形,依此类推,而不是执行7个单独的drawBlock()函数(包含glDrawElements()调用)。

如果计算绘制内容的代码实际上比单独绘制块消耗了更多的CPU,我就不会麻烦这么做了。

glDrawElements(或任何其他OpenGL渲染命令)的成本实际上无法估计。这是因为它的成本在很大程度上取决于您在绘图调用之间更改的OpenGL状态。调用OpenGL状态更改函数(基本上,任何不是某种形式的glGet或某种形式的glDraw的OpenGL函数)的成本都会相对较快。但这将使下一次抽签的速度变慢。

这段关于OpenGL性能的视频显示了哪些状态更改在绘制时比其他状态更改更昂贵。真正好的部分大约在31分钟后开始。

如果在绘制调用之间没有更改任何OpenGL状态,则绘制调用相对较快。不同的状态对绘制调用有不同的影响。从最快到最慢(根据NVIDIA上面的介绍,请谨慎对待):

  • 非UBO统一更新
  • 顶点缓冲区绑定(不更改格式)
  • UBO绑定
  • 顶点格式更改
  • 纹理绑定
  • 片段后处理状态更改
  • 着色器程序更改
  • 渲染目标开关

现在,平局调用将比"基本逻辑"更昂贵。它们并不便宜,即使它们之间没有状态变化。如果效率对你的代码很重要,那么分组你的方块是个好主意。

实际数字高度依赖于平台和供应商。不同操作系统上的驱动程序体系结构差异很大,其中一些系统比其他系统更高效。除此之外,驱动程序实现和硬件可能会导致巨大的性能差异。例如,在同一平台上,使用类似的硬件,一个供应商的draw call吞吐量比另一个供应商高10-20倍。

基于此,以下任何数字都只是一个非常粗略的数量级。你真的需要自己根据你关心的配置来衡量这一点。

有了所有这些免责声明,我预计一个draw调用可以在100条指令(CPU周期)的范围内处理。这种情况下,您只需要进行背靠背的绘制调用,并且管道中没有其他瓶颈。

正如@NicolBolas已经指出的,处理draw调用最昂贵的部分通常是处理延迟的状态更改。大多数情况下,在绘制调用之间会发生状态更改。在这种情况下,对于相对便宜的状态更改(如绑定纹理或缓冲区,或更改某些属性),通常只需要100条指令。

切换帧缓冲区通常非常昂贵,并且在某些平台上非常昂贵。除此之外,我在过去优化和基准测试状态变化时测量的数字显示出的顺序与@NicolBolas回答中的列表截然不同。但同样,这是高度依赖于平台和供应商/硬件的。

还有几个方面使衡量这一点变得有些棘手:

  • 大部分CPU时间可能不会在线程中消耗掉。许多驱动程序是多线程的,这意味着处理OpenGL调用所需的大部分工作都被卸载到辅助线程。如果您的应用程序没有使用所有的CPU核心,并且您没有受到功率/热限制的限制,这意味着许多驱动程序工作可以并行进行,而不会大大降低应用程序的速度。但特别是在移动设备和笔记本电脑上,性能通常受到功耗的限制,因此驱动程序开销仍然会降低速度
  • 驱动程序消耗的CPU时间只是降低应用程序代码速度的一部分。另一个考虑因素是缓存污染。如果在OpenGL实现处理绘制调用时,应用程序使用的缓存内容被收回,则您自己的代码将获得更多的缓存未命中,并且运行速度会变慢。因此,测量OpenGL调用内部花费的时间只能显示部分情况