繁体   English   中英

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

[英]High precision sleep or How to yield CPU and maintain precise frame rate

我在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的情况下实现通常精确的帧速率?

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

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

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

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

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

尽管CodeInChaos建议可以解答您的问题,并且在某些情况下可能部分有效,但这只是一个简单的不良做法。

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

像睡眠这样的东西不会有帮助,因为它们会暂停你的过程,而不是另一个过程,该过程必须首先停止,睡眠(1ms)只是意味着在1ms之后你的进程返回队列等待运行的权限,睡眠(因此,根据其他运行过程及其优先级,可以轻松地占用15ms。

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

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

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

根据对CodeInChaos回答的想法和评论,这是我到达的最终代码。 我最初编辑了这个答案,但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;
   }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM