如何在游戏循环中计算直到最后一刻

时间:2015-01-27 02:11:15

标签: opengl optimization 3d game-engine vsync

作为优化我的3D游戏/模拟引擎的一部分,我正在尝试使引擎自我优化。

基本上,我的计划是这样的。首先,让引擎测量每帧的CPU周期数。然后测量各子系统消耗的CPU周期数(最小值,平均值,最大值)。

鉴于此信息,在帧循环中的几个特定点,引擎可以估计有多少“额外CPU周期”可用于执行现在有效执行的“可选处理”(相关数据在现在缓存),但如果当前帧存在CPU周期不足的危险,则可能会延迟到后续帧。

我们的想法是尽可能地保持领先于游戏的工作,因此可以处理每个可能的CPU周期来处理“要求严格的帧”(如“单帧中的多次碰撞”)而不会失败在vsync的最新可能时刻之前及时调用glXSwapBuffers()来交换后台/前台缓冲区。


上面的分析假设交换后/前缓冲区是确保恒定帧速率的基本要求。我已经看到声称这不是唯一的方法,但我不明白逻辑。

我在glXSwapBuffers()之前和之后捕获了64位CPU时钟周期时间,发现帧的时钟周期变化大约2,000,000!这似乎是由于事实上glXSwapBuffers()在vsync(它可以交换缓冲区)之前不会阻塞,而是立即返回。

然后我在glXSwapBuffers()之前立即添加了glFinish(),这将变化减少到大约100,000个CPU时钟周期......但是glFinish()被阻止了100,000到900,000个CPU时钟周期(可能取决于工作量) nvidia驱动程序必须在它可以交换缓冲区之前完成。由于glXSwapBuffers()可能需要多长时间来完成处理和交换缓冲区,我想知道是否有任何“智能方法”都有希望。


最重要的是,我不确定如何实现我的目标,这似乎相当简单,似乎并没有过多地询问底层子系统(例如OpenGL驱动程序)。但是,即使在glXSwapBuffers()之前的glFinish(),我仍然会在“帧时间”中看到大约1,600,000个周期变化。我可以平均测量的“每帧CPU时钟周期”速率并假设平均产生实际帧速率,但是如果有这么大的变化,我的计算可能实际上导致我的引擎跳过帧错误地假设它可以依赖于这些值。

我将非常感谢所涉及的各种GLX / OpenGL功能的具体细节,或者在实践中可能比我正在尝试的更好的方法。

PS:当核心速度减慢或加速时,CPU的CPU时钟频率不会发生变化。因此,这不是我问题的根源。

2 个答案:

答案 0 :(得分:1)

这是我的建议:在渲染结束时,只需调用交换缓冲区函数,并在需要时让它阻塞。实际上,你应该有一个执行所有OpenGL API调用的线程,只有那个。如果要执行另一个计算(例如物理,游戏逻辑),则使用其他线程,操作系统将在渲染线程等待vsync时让这些线程运行。

此外,如果有些人禁用vsync,他们希望看到他们每秒可以实现多少帧。但是根据你的方法,似乎禁用vsync只会让fps大约为60左右。

答案 1 :(得分:0)

我会尝试重新解释您的问题(如果我错过了一些您可以告诉我的内容,我可以更新答案):

鉴于 T 是您在Vsync事件发生之前可以使用的时间,您希望使用 1xT 秒(或接近1的位置)制作帧。

但是,即使您能够编写任务代码以便他们可以利用缓存局部性来实现完全确定的时间行为(您事先知道每个任务需要多长时间以及您可以使用多长时间)等等你可以在理论上实现以下时间:

0.96xT

0.84xT

0.99xT


你必须处理一些事实:

  1. 你不知道T(你试图弄它,它似乎是hic-cup:那些依赖于司机!)
  2. 时间错误
  3. 不同的CPU架构:您可以测量某个功能的CPU周期,但是在另一个CPU上,由于更好/更差的预处理或流水线操作,该功能需要更少或更多的周期。
  4. 即使在相同的CPU上运行,另一个任务也可能污染预先输出算法,因此相同的功能不一定会产生相同的CPU周期(取决于之前调用的函数和prefetech algorihtm!)
  5. 操作系统可以通过暂停您的应用程序来运行某些后台进程,在任何时候干扰,这将增加您填充的时间。任务有效地让你错过了Vsync事件(即使你的"预测"时间合理,如 0.85xT
  6. 有时您仍然可以获得

    的时间

    1.3xT

    同时你没有使用所有可能的CPU功率(当你错过了一个Vsync事件时,你基本上浪费了你的帧时间,因此它浪费了CPU能力)


    您仍然可以解决;)

    缓冲帧:存储最多2/3帧的渲染调用(不再需要!你已经添加了一些延迟,某些GPU驱动程序会做类似的事情来改善并行性并降低功耗! )之后,你使用游戏循环闲置或做晚期工作。

    使用这种方法超过 1xT 是合理的。因为你有一些"缓冲帧"。

    让我们看一个简单的例子

    • 您为 0.95xT 计划了任务,但由于程序运行的CPU不同于您用于开发程序的CPU,因为不同的架构,您的帧需要 1.3xT
    • 没问题,你知道背后有一些帧,所以你仍然可以开心,但现在你必须启动 1xT - 0.3xT 任务,更好地使用一些安全边际,因此您启动 0.6xT 而不是 0.7xT 的任务。
    • 操作确实出错了,框架再次 1.3xT 现在你耗尽了你的框架储备,只需做一个简单的更新并提交GL调用,你的程序预测 0.4xT < / em>的
    • 您的程序对以下帧感到惊讶 0.3xT ,即使您计划的工作时间超过 2xT ,您还有3帧在渲染线程中排队。
    • 由于您有一些框架并且还有较晚的作品,因此您需要安排 1,5xT
    • 的更新

    通过引入一点延迟,您可以利用全部CPU功耗,当然,如果您测量大多数时候您的队列缓冲超过2帧,您可以将池减少到2而不是3,这样可以节省一些延迟。


    当然,这假设您以同步方式完成所有工作(除了延迟GL cals)。您仍然可以在必要时使用一些额外的线程(文件加载或其他繁重的任务)来提高性能(如果需要)。