OpenGL:glFlush()与glFinish()的区别

125

我在区分如何调用 glFlush()glFinish() 时遇到了麻烦。

官方文档说明 glFlush()glFinish() 将把所有缓冲操作推送到OpenGL,以确保它们都被执行,不同之处在于glFlush()会立即返回而glFinish()会阻塞直到所有操作完成。

根据定义,我认为如果我使用 glFlush(),可能会遇到向OpenGL提交的操作多于其可以执行的问题。所以,我尝试将我的 glFinish() 替换为 glFlush(),结果我的程序运行良好(就我所知),帧率、资源使用等一切都没变。

因此,我想知道这两个函数之间是否有很大的区别,或者我的代码使它们没有任何不同之处。另外我还以为 OpenGL 会有类似 glIsDone() 这样的函数来检查 glFlush() 的所有缓冲命令是否已经完成(这样就不会比它们能够被执行得更快地向OpenGL发送操作),但是我找不到这样的函数。

我的代码是典型的游戏循环:

while (running) {
    process_stuff();
    render_stuff();
}
8个回答

107
请注意,这些命令自OpenGL早期就存在。glFlush确保之前的OpenGL命令必须在有限时间内完成(OpenGL 2.1规范,第245页)。如果您直接画到前缓冲区,则应确保OpenGL驱动程序开始绘制而无需过多延迟。您可以考虑一个复杂的场景,屏幕上逐个出现物体,当您在每个对象后调用glFlush时,它将确保完成对象的绘制。但是,在使用双缓冲时,glFlush实际上几乎没有任何效果,因为更改不会显示,直到您交换缓冲区为止。
glFinish直到从先前发出的所有命令的效果完全实现才返回。这意味着程序的执行在此处等待,直到每个像素都被绘制并且OpenGL没有其他任务可执行。如果您直接渲染到前缓冲区,则在使用操作系统调用截取屏幕截图之前应该调用glFinish。对于双缓冲来说,它的用处远不及它在单缓冲下的用处大,因为您看不到强制完成的更改。
因此,如果您使用双缓冲,您可能既不需要glFlush也不需要glFinish。SwapBuffers隐式地将OpenGL调用定向到正确的缓冲区,无需先调用glFlush。而且不要担心过多压力OpenGL驱动程序:glFlush不会因为太多命令而出错。虽然不保证此调用立即返回(无论意味着什么),但它可以花费任何时间来处理您的命令。

1
你是说glFlush“必须在有限时间内完成”,但后来又说,glFlush不会因为“它可以花费任何需要处理您的命令的时间”而被阻塞?(大写字母是我的)这两者不是互相排斥的吗? - Praxiteles
1
只要它在某个时间点完成,它就符合有限时间标准。一些驱动程序完全不执行此调用。 - chrisvarnz
SwapBuffers是上下文特定的,只需要刷新调用线程的上下文。如果渲染多个上下文,则每个上下文都应该手动刷新,因为通过另一个上下文交换缓冲区时无法保证会发生刷新。 - Andreas
当我使用一些片段着色器编辑器时,我发现有些统一变量在某些帧中没有得到更新(例如,当触摸时,时间统一变量可能会出现小延迟)。在这里,调用glFinish(在更新统一变量后调用)可以解决这个问题吗? - Mehran

29

我曾经也混淆过这两个命令的作用,但是这张图片让我一清二楚:

Flush vs Finish

显然,有些GPU驱动程序只有在积累了一定数量的命令后才会将其发送到硬件。在这个例子中,这个数字是5

这张图展示了已经发出的各种OpenGL命令(A、B、C、D、E...)。正如我们在顶部看到的那样,这些命令还没有被发出,因为队列还没有满。

在中间,我们可以看到glFlush()如何影响排队的命令。它告诉驱动程序将所有排队的命令发送到硬件(即使队列还没有满)。这不会阻塞调用线程。它只是向驱动程序发出信号,表示我们可能不会再发送任何其他命令。因此,等待队列填满将是浪费时间。

在底部,我们看到了一个使用glFinish()的示例。它几乎与glFlush()做相同的事情,除了它会使调用线程等待直到所有命令都被硬件处理。

图片来源于书目《Advanced Graphics Programming Using OpenGL》。


2
我确实知道glFlushglFinish的作用,但我无法理解那张图片在说什么。左边和右边是什么?此外,那张图片是以公共领域发布还是在某些许可证下允许您在互联网上发布? - Nicol Bolas
我应该再详细说明一下。 关于许可证:实际上我不太确定。在做研究时,我在谷歌上偶然发现了这个PDF文件。 - Tara

26

正如其他答案所暗示的那样,根据规范,实际上没有好的答案。 glFlush() 的一般意图是,在调用它之后,主机 CPU 将没有任何与 OpenGL 相关的工作要做 - 命令将被推送到图形硬件。 glFinish() 的一般意图是,在它返回后,没有剩余的工作,结果应该对所有适当的非 OpenGL API(例如从帧缓冲区读取、截图等)可用。这是否真的发生取决于驱动程序。规范允许大量的可移植性。


9
如果你没有看到任何性能差异,那就意味着你做错了什么。正如其他人所提到的,你不需要调用 glFinish,但如果你确实调用了它,那么你自动失去了 GPU 和 CPU 可以实现的并行性。现在让我深入解释一下:
实际上,你提交给驱动程序的所有工作都会被批处理,并且可能会在很长时间后(例如在 SwapBuffer 时)发送到硬件。
因此,如果你调用 glFinish,你实际上是强制驱动程序将命令推送到 GPU(它批处理到那时为止,并且从未要求 GPU 工作),并阻塞 CPU,直到推送的命令完全执行。因此,在 GPU 工作的整个时间内,CPU 不工作(至少在此线程上)。而在 CPU 执行其工作(主要是批处理命令)的所有时间里,GPU 不执行任何操作。因此,glFinish 应该会影响你的性能。(这只是一个近似值,因为驱动程序可能会开始让 GPU 在某些命令上工作,如果已经批处理了很多命令。尽管如此,这并不是典型情况,因为命令缓冲区足够大,可以容纳相当多的命令)。
那么,为什么你要调用 glFinish 呢?我使用它的唯一情况是当我遇到驱动程序错误时。确实,如果您发送到硬件的命令之一导致 GPU 崩溃,则最简单的选择是在每个绘制后调用 glFinish。这样,你就可以缩小触发崩溃的确切命令。
顺便说一句,像 Direct3D 这样的 API 根本不支持 Finish 概念。

7
“那么为什么你要调用glFinish呢?”如果你正在一个线程上加载资源(如纹理),并在另一个线程上使用它们进行渲染,通过wglShareLists或类似方式共享资源,那么你需要在向渲染线程发出信号表明已经异步加载完成后调用glFinish。这样才能确保渲染线程不会在资源未完全加载的情况下启动渲染。 - Marco Mp
正如我下面所说的,这似乎是一个驱动程序错误的解决方法。我找不到任何规范提到这个要求。在Windows上,驱动程序必须获取锁,在Mac上,您必须执行CGLLockContext。但使用glFinish似乎非常错误。无论如何,要知道纹理何时有效,您都必须进行自己的锁定或事件处理。 - starmole
1
OpenCL OpenGL互操作文档建议使用glFinish进行同步。在调用clEnqueueAcquireGLObjects之前,应用程序必须确保访问mem_objects中指定的对象的任何挂起的GL操作已经完成。可以通过在所有具有对这些对象的挂起引用的GL上下文上发出并等待glFinish命令的完成来实现可移植性。 - Mark Essel
2
你不必调用glFinish,可以使用同步对象。 - chrisvarnz
只是补充一下,自原回答以来:一些GL实现开始使用flush/finish在不同线程上的共享上下文之间进行同步:https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/OpenGLESApplicationDesign/OpenGLESApplicationDesign.html - 最值得注意的是苹果IOS。我认为这本质上是API的另一种误用。但这是对我的原始答案的一个警告:在某些实现中,需要使用finish/flush。但是如何以及为什么是实现定义的,而不是OpenGL规范。 - starmole

8

请看这里。简而言之,它说:

glFinish()与glFlush()具有相同的效果,但还会阻塞直到所有提交的命令都被执行。

另一篇文章描述了其他差异:

  • 交换函数(用于双缓冲应用程序)会自动刷新命令,因此不需要调用glFlush
  • glFinish会强制OpenGL执行未完成的命令,这是个坏主意(例如使用VSync时)

总之,这意味着在使用双缓冲时甚至不需要这些函数,除非您的交换缓冲实现不会自动刷新命令。


是的,但文件说两者具有相同的效果,只有一些关于窗口系统的差异,例如。但无论如何,我已经编辑了我的答案 ;) - AndiDog
很好的指出了细微差别。它澄清了在调用glFlush()和THEN glFinish()之间是否有必要的问题 - (在阅读上面的所有内容后我们产生了这个问题)。 - Praxiteles

7

glFlush实际上可以追溯到客户端服务器模型。您通过管道将所有的gl命令发送到gl服务器。该管道可能会缓冲。就像任何文件或网络I/O可能会缓冲一样。glFlush只是说“现在发送缓冲区,即使它还没有完全填满!”在本地系统上,这几乎永远不需要,因为本地的OpenGL API不太可能自己缓冲并直接发出指令。而且,所有导致实际渲染的命令都会隐式刷新。

另一方面,glFinish是为性能测量而设计的。有点像对GL服务器的PING。它往返一个命令并等待服务器响应“我闲置了”。

如今,现代的本地驱动程序有相当有创意的想法,关于何时认为自己空闲。是“所有像素都被绘制”还是“我的命令队列有空间”?此外,由于许多旧程序在代码中无缘无故地添加了glFlush和glFinish作为迷信编码方式,许多现代驱动程序将它们忽略为“优化”。但事实上,我们不能责怪他们。

因此,总的来说:在实践中把glFinish和glFlush都视为无操作,除非您正在编写针对古老的远程SGI OpenGL服务器的代码。


4
错误。就像我在上面的回答中留下的评论一样:如果您正在一个线程上加载资源(例如纹理),并在另一个线程上使用它们进行渲染,通过wglShareLists或类似方式共享资源,则需要在向渲染线程发出信号之前调用glFinish,以便让其自由渲染已经异步加载的内容。而且,它不是像你说的那样无操作。 - Marco Mp
真的吗?你可以用一些规范链接支持在使用共享对象之前调用glFinish的需要吗?我完全可以看到需要这样做的有缺陷的驱动程序。而那就回到了我所说的问题。人们使用glFinish来解决有缺陷的驱动程序的问题。因此,正常工作的驱动程序为了提高性能而忽略它。最初的规范用例——剖析(和单缓冲渲染)已经不再适用。 - starmole
2
个人经验是处理损坏的纹理,此外还有这个链接:http://higherorderfun.com/blog/2011/05/26/multi-thread-opengl-texture-loading/ 如果你仔细想一下,这是有道理的,因为它与在线程之间使用互斥锁或其他同步API并没有太大区别 - 在一个上下文可以使用共享资源之前,另一个必须完成该资源的加载,因此需要确保缓冲区已被清空。我认为这更多是规范的边角案例,而不是错误,但我没有证据支持这种说法 :) - Marco Mp
非常棒的答案,谢谢。特别是“许多旧程序在代码中随意使用glFlush和glFinish,就像巫术一样”。 - akauppi
这真的是没有操作吗?glFinish和/或glFlush对于高强度图像处理或基于GPU的数学协处理非常有价值,不是吗?例如,我们在调用glFinish之前不会从像素缓冲区中将GPU计算结果读取到CPU,以确保所有像素计算都已写入。我们有什么遗漏的吗? - Praxiteles

4

似乎没有一种查询缓冲区状态的方法。有这个苹果扩展, 可以起到相同的作用,但似乎不是跨平台的(我还没有尝试过)。初步看来,在flush之前,你需要推送fence命令; 然后,您可以查询该fence在缓冲区中移动时的状态。

我想知道是否可以在缓冲命令之前使用flush,但在开始渲染下一帧之前调用finish。这将允许您在GPU工作时开始处理下一帧,但如果它在您返回之前没有完成,finish将阻塞以确保一切都处于新状态。

我还没有尝试过这个,但很快就会。

我已经在一个旧应用程序上尝试过它,该应用程序具有相当平均的CPU和GPU使用率。(最初使用了finish。)

当我将它改为在结尾处使用flush和在开头处使用finish时,没有立即出现问题。(一切看起来都很好!)程序的响应性增加了,可能是因为CPU没有等待GPU而停顿。绝对是更好的方法。
相比之下,我从帧的开头删除了finished,只留下了flush,并且它表现相同。
所以我会建议使用flushfinish,因为在调用finish时缓冲区为空,不会有性能损失。如果缓冲区已满,你应该想要finish

0
问题是:您是否希望在执行OpenGL命令时继续运行代码,还是只在OpenGL命令执行完成后运行代码。
这在某些情况下很重要,例如在网络延迟的情况下,只有在图像绘制完成或类似操作完成后才输出特定的控制台输出。

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