分析 OpenGL 应用程序 - 当驱动程序阻塞 CPU 端时

Posted

技术标签:

【中文标题】分析 OpenGL 应用程序 - 当驱动程序阻塞 CPU 端时【英文标题】:Profiling OpenGL application - when the driver is blocking the CPU side 【发布时间】:2015-04-24 02:50:11 【问题描述】:

我制作了一个游戏内图形分析器(CPU 和 GPU),但我不知道如何处理 Nvidia 驱动程序的一个奇怪行为。

以下是正常情况的屏幕截图: 在这里你可以看到连续 3 帧,GPU 在顶部,CPU 在底部。两个图都是同步的。

“END FRAME”栏只包含对SwapBuffers 的调用。在 GPU 完成所有工作之前它会阻塞似乎很奇怪,但这就是驱动程序有时在 vsync 开启时选择做的事情,并且所有工作(CPU 和 GPU)都可以在 16 毫秒内完成(AMD 也是如此)。我的猜测是这样做是为了最大限度地减少输入延迟。

现在我的问题是它并不总是这样做。根据框架中发生的情况,图表有时如下所示: 这里实际发生的是,第一个 OpenGL 调用是阻塞的,而不是对SwapBuffers 的调用。在这种特殊情况下,阻塞调用是glBufferData。如果我添加一个可以做到这一点的虚拟代码(创建一个统一缓冲区,用随机值加载它并销毁它),它会更加明显:

这是一个问题,因为这意味着图表中的条形可能会无缘无故变得非常大。看到这一点的人可能会得出关于某些代码运行缓慢的错误结论。

所以我的问题是,我该如何处理这种情况?我需要一种方法来始终显示有意义的 CPU 计时。

添加一个加载统一缓冲区的虚拟代码不是很优雅,并且可能不适用于未来版本的驱动程序(如果驱动程序只阻塞绘制调用怎么办?)。

glClientWaitSync 同步看起来也不是一件好事,因为如果帧速率下降,驱动程序将停止阻塞以允许 CPU 和 GPU 帧并行运行,我需要检测到这一点停止拨打glClientWaitSync(但我不知道该怎么做。)

(欢迎提出更好的标题建议。)

编辑:当 GPU 成为瓶颈时,如果没有 vsync,会发生以下情况: GPU 帧比 CPU 帧花费的时间更长,因此驱动程序决定在 glBufferData 期间阻塞 CPU,直到 GPU 赶上。

条件不一样,但问题是:CPU时序“错误”,因为驱动程序做了一些OpenGL功能块。这实际上可能比打开 vsync 的例子更容易理解。

【问题讨论】:

好吧,GL 可能会在任何时候阻塞,出于任何特定于实现的原因。 OTOH,即使 vsync 开启,SwapBuffers 也不能保证阻塞。例如,Windows 上的 nvidia 驱动程序甚至有一个配置选项,它可以在必须阻止之前提前缓冲多少帧 - 但是,这也不是一个可靠的最小值,如果你在一帧中做了很多工作,它可能会更早地阻止,或强制进行一些隐式或显式同步。我曾经观察到一个奇怪的问题,即 nvidia 驱动程序在 1 帧和 2 帧延迟之间交替,产生生涩的动画,而平均速度仍为 60 fps。 我真的不知道如何解释我在您的任何屏幕截图中在 CPU 分析器中看到的内容,因此您在编辑中所做的更改并没有它们应有的洞察力。不过,我彻底概述了 VSYNC 导致不可预知的阻塞行为的原因……您可能会发现这很有用。 在上一个截图中,GPU 帧比 CPU 帧花费的时间更长,因此驱动程序会在某个时刻阻塞 CPU 以等待 GPU。这可能发生在 SwapBuffer 或其他地方,比如屏幕截图。但是,是的,您的回答很有见地,谢谢。 你正在使用 imgui ,不错的库 【参考方案1】:

这实际上按预期工作。在调用SwapBuffers (...) 期间,由于 VSYNC 引起的阻塞不一定要发生,VSYNC 导致阻塞的原因有很多,而且它们几乎完全不受您的控制。

当交换链充满等待交换的后备缓冲区时(通常您只有 1 个后备缓冲区),在交换完成之前不得执行修改帧缓冲区的命令。这会导致管道停顿,并且是第一次罢工。请记住,即使管道停止,GL 仍可能在此状态下排队命令。

在大多数平台上,没有 API 允许您显式请求窗口系统交换链中的后备缓冲区数量。您可以请求 singledouble- 缓冲,驱动程序可能会将双缓冲解释为 2 或更多(您将看到标记为 “启用三重缓冲” 在某些驱动程序中)。

第二次打击来自被称为“提前渲染”的东西。这是 GL 在拒绝接受新命令之前将排队的特定于驱动程序的工作量。再说一次,您作为 OpenGL 软件的开发者,对此没有任何控制权。在某些驱动程序中,您可以深入挖掘并手动配置它。增加该值将允许 CPU 在管道停止时排队更多工作,但往往会增加延迟(特别是 D3D 实现它的方式,它禁止丢帧)。

一旦渲染管道停止等待缓冲区交换并且您耗尽了渲染提前限制,即罢工三。调用线程将阻塞下一个 GL 命令,直到 VBLANK 滚动并疏通管道。


glClientWaitSync (...),正如您所描述的,将有效地消除所有提前渲染。这可能有助于最大限度地减少时序变化,但如果您无法达到刷新率,则会对整体帧率产生负面影响。

自适应 VSYNC 应该是您追求的第一件事。在支持此功能的驱动程序上,您可以通过设置负交换间隔来启用它,当您无法维持刷新率时,它将避免阻塞。实际上,自适应 VSYNC 的目的是在绘制速度过快时限制渲染。如果您的绘图速度超过了您的显示器可以处理的速度,那么分析 GL API 调用似乎并不是特别重要。

在最坏的情况下,您始终可以完全禁用 VSYNC。在像 Windows Vista 这样的现代合成窗口管理器中,无论您是否启用 VSYNC,都可以在窗口模式下防止撕裂。在这种情况下,VSYNC 确实可以节省电力,将其关闭以进行更准确的分析可能是一个可以接受的折衷方案。您可以轻松实现自己的节流机制,以防止引擎以高得离谱的帧速率进行绘制,而不会引入 VSYNC 带来的不可预测的行为。

【讨论】:

我想补充一点,这种不确定的计时行为会在虚拟现实系统的各个环节之间产生严重的影响。由于对 VR 重新燃起的兴趣,并且由于约翰·卡马克和迈克尔·阿布拉什等受人尊敬的人的推动,我们现在已经看到(供应商特定的)扩展添加到 OpenGL,允许更精细地控制时序和 VSync 行为。还有一些人 (*cough* me *cough*) 正在研究实时编程方法,以通过精确的程序执行时间来获得开环同步。 @datenwolf 这些扩展是什么?也许其中一个可以帮助我控制这种行为。 @Jerem:我在这里主要考虑 ..._NV_delay_before_swap 扩展,它允许引入 CPU 延迟,这将在下一次计划交换之前的指定时间结束(通常与垂直同步)会发生。这样做的主要用途是允许 VR 系统将 HMD 输入集成到最终渲染输出中,以便可以通过稍微扭曲/移动呈现的图像来补偿渲染期间发生的头部运动。 OculusVR 将此方法称为 timewarp,但要工作,您需要在进行最终输入集成时精确计时,然后再进行交换。 这可能是一个很好的解决方案:如果我可以调用 wglDelayBeforeSwapNV 让它等到下一次计划交换之前的很短的时间,然后调用 SwapBuffers,也许所有的等待都将在对 wglDelayBeforeSwapNV 的调用中。文档说,如果 vsync 关闭,它不会等待,但出于好奇,我仍然会尝试。谢谢。 不幸的是,即使打开了垂直同步,它也不能像我预期的那样工作:游戏几乎在每一帧都错过了垂直同步(帧速率下降到 ~40)。我尝试将等待时间设置为 1ms、100us 和 10us 但每次结果都是一样的 :(

以上是关于分析 OpenGL 应用程序 - 当驱动程序阻塞 CPU 端时的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法防止 OpenGL 分析器附加到我的应用程序?

防止 OpenGL 缓冲帧

使用 OpenGL 和 Qt 进行科学可视化

我的OpenGL学习进阶之旅当你运行OpenGL程序的时候,程序并不绘制任何内容,并且白屏和黑屏的时候怎么排查?

我的OpenGL学习进阶之旅当你运行OpenGL程序的时候,程序并不绘制任何内容,并且白屏和黑屏的时候怎么排查?

Redis时延问题分析及应对