高精度睡眠或如何产生CPU并保持精确的帧速率

时间:2011-01-29 14:33:37

标签: c# .net sleep

我在2D游戏引擎上工作,该引擎具有一个名为LimitFrameRate的功能,以确保游戏运行速度不会太快以至于用户无法玩游戏。在这个游戏引擎中,游戏的速度与帧速率有关。因此通常人们希望将帧速率限制为大约60 fps。这个函数的代码相对简单:计算我们应该在下一帧开始工作之前剩余的时间量,将其转换为毫秒,睡眠该毫秒数(可能是0),重复直到它恰好是正确的时间,然后退出。这是代码:

public virtual void LimitFrameRate(int fps)
{
  long freq;
  long frame;
  freq = System.Diagnostics.Stopwatch.Frequency;
  frame = System.Diagnostics.Stopwatch.GetTimestamp();
  while ((frame - previousFrame) * fps < freq)
  {
     int sleepTime = (int)((previousFrame * fps + freq - frame * fps) * 1000 / (freq * fps));
     System.Threading.Thread.Sleep(sleepTime);
     frame = System.Diagnostics.Stopwatch.GetTimestamp();
  }
  previousFrame = frame;
}

当然,我发现由于某些系统上睡眠功能的不精确性,帧速率与预期完全不同。睡眠功能的精度只有大约15毫秒,所以你不能等待。奇怪的是,有些系统使用此代码可以实现完美的帧速率,并且可以完美地实现一系列帧速率。但其他系统则没有。我可以删除睡眠功能,然后其他系统将达到帧速率,但随后它们会占用CPU。

我已阅读其他有关睡眠功能的文章:

编码器是做什么的?我不是要求保证帧速率(换句话说,保证睡眠时间),只是一般行为。我希望能够睡眠(例如)7毫秒来为操作系统提供一些CPU,并且通常在7毫秒或更短的时间内返回控制(只要它恢复一些CPU时间),如果需要的话更有时候,那没关系。所以我的问题如下:

  1. 为什么睡眠在某些Windows环境中完美而精确地工作,而在其他环境中却没有? (有没有办法在所有环境中获得相同的行为?)
  2. 如何在不占用C#代码的CPU的情况下实现一般精确帧速率?

3 个答案:

答案 0 :(得分:5)

您可以使用timeBeginPeriod来提高计时器/睡眠准确度。请注意,这会对系统产生全局影响,并可能会增加功耗 您可以在程序开头调用timeBeginPeriod(1)。在您观察到更高计时器精度的系统上,另一个正在运行的程序可能会这样做 我不打算计算睡眠时间,只需在循环中使用sleep(1)

但即使只有16ms的精度,您也可以编写代码,以便随着时间的推移误差平均。这就是我要做的。编写代码并不困难,并且应该很少适应当前的代码。

或者您可以切换到使移动与经过时间成比例的代码。但即使在这种情况下,你也应该实现一个帧速率限制器,这样你就不会得到无用的高帧率和不必要的功耗。

编辑:根据此答案中的想法和评论,制定了接受的答案。

答案 1 :(得分:2)

几乎所有的游戏引擎都会通过传递自上一帧以来的时间来处理更新并且有移动等等...按时间成比例地运行,除此之外的任何其他实现都是错误的。

虽然CodeInChaos的建议可以回答你的问题,并且在某些情况下可能会部分起作用,但这只是一种不好的做法。

将帧速率限制为所需的60fps仅在计算机运行速度较快时才有效。然而,第二个背景任务占用了一些处理器能力(例如,virusscanner启动)并且你的游戏降到60fps以下,一切都会慢得多。即使你的游戏在35fps上完全可以玩,这也会让玩游戏变得不可能,因为一切都快了一半。

睡眠之类的东西不会有帮助,因为它们会暂停你的进程以支持另一个进程,该进程必须首先停止,sleep(1ms)只意味着在1ms之后你的进程返回队列等待运行权限,睡眠(1ms)因此可以轻松地花费15ms,具体取决于其他运行过程及其优先级。

所以我的建议是你尽快在你所有的更新方法中使用某种“elapsedSeconds”变量,你构建它的时间越早,它的工作就越少,这也将确保与MONO的兼容性。

如果您的引擎有2个部分,比如物理和渲染引擎,并且您希望以不同的帧速率运行它们,那么只需看看自上一帧以来已经过了多少时间,然后决定更新物理引擎或者不是,只要你在计算中加入自上次更新以来的时间就可以了。

也不会比你搬东西更频繁。绘制完全相同的屏幕2次是浪费,所以如果屏幕上可以改变的唯一方法就是更新物理引擎,然后保持渲染引擎和物理引擎更新同步。

答案 2 :(得分:0)

基于对CodeInChaos&#39;的想法和评论回答,这是我到达的最终代码。我最初编辑了这个答案,但CodeInChaos建议这是一个单独的答案。

public virtual void LimitFrameRate(int fps)
{
   long freq;
   long frame;
   freq = System.Diagnostics.Stopwatch.Frequency;
   frame = System.Diagnostics.Stopwatch.GetTimestamp();
   while ((frame - fpsStartTime) * fps < freq * fpsFrameCount)
   {
      int sleepTime = (int)((fpsStartTime * fps + freq * fpsFrameCount - frame * fps) * 1000 / (freq * fps));
      if (sleepTime > 0) System.Threading.Thread.Sleep(sleepTime);
      frame = System.Diagnostics.Stopwatch.GetTimestamp();
   }
   if (++fpsFrameCount > fps)
   {
      fpsFrameCount = 0;
      fpsStartTime = frame;
   }
}