简体   繁体   English

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

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

I work on a 2D game engine that has a function called LimitFrameRate to ensure that the game does not run so fast that a user cannot play the game. 我在2D游戏引擎上工作,该引擎具有一个名为LimitFrameRate的功能,以确保游戏运行速度不快,用户无法玩游戏。 In this game engine the speed of the game is tied to the frame rate. 在这个游戏引擎中,游戏的速度与帧速率有关。 So generally one wants to limit the frame rate to about 60 fps. 因此通常人们希望将帧速率限制为大约60 fps。 The code of this function is relatively simple: calculate the amount of time remaining before we should start work on the next frame, convert that to milliseconds, sleep for that number of milliseconds (which may be 0), repeat until it's exactly the right time, then exit. 这个函数的代码相对简单:计算我们应该在下一帧开始工作之前剩余的时间量,将其转换为毫秒,睡眠该毫秒数(可能是0),重复直到它恰好是正确的时间,然后退出。 Here's the code: 这是代码:

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;
}

Of course I have found that due to the imprecise nature of the sleep function on some systems, the frame rate comes out quite differently than expected. 当然,我发现由于某些系统上睡眠功能的不精确性,帧速率与预期完全不同。 The precision of the sleep function is only about 15 milliseconds, so you can't wait less than that. 睡眠功能的精度只有大约15毫秒,所以你不能等待。 The strange thing is that some systems achieve a perfect frame rate with this code and can achieve a range of frame rates perfectly. 奇怪的是,有些系统使用此代码可以实现完美的帧速率,并且可以完美地实现一系列帧速率。 But other systems don't. 但其他系统则没有。 I can remove the sleep function and then the other systems will achieve the frame rate, but then they hog the CPU. 我可以删除睡眠功能,然后其他系统将实现帧速率,但随后它们占用了CPU。

I have read other articles about the sleep function: 我读过有关睡眠功能的其他文章:

What's a coder to do? 编码器是做什么的? I'm not asking for a guaranteed frame rate (or guaranteed sleep time, in other words), just a general behavior. 我不是要求保证帧速率(换句话说,保证睡眠时间),只是一般行为。 I would like to be able to sleep (for example) 7 milliseconds to yield some CPU to the OS and have it generally return control in 7 milliseconds or less (so long as it gets some of its CPU time back), and if it takes more sometimes, that's OK. 我希望能够睡眠(例如)7毫秒来为操作系统提供一些CPU,并且通常在7毫秒或更短的时间内返回控制(只要它恢复一些CPU时间),如果需要的话更有时候,那没关系。 So my questions are as follows: 所以我的问题如下:

  1. Why does sleep work perfectly and precisely in some Windows environments and not in others? 为什么睡眠在某些Windows环境中完美而精确地工作而不在其他环境中? (Is there some way to get the same behavior in all environments?) (有没有办法在所有环境中获得相同的行为?)
  2. How to I achieve a generally precise frame rate without hogging the CPU from C# code? 如何在不占用C#代码的CPU的情况下实现通常精确的帧速率?

You can use timeBeginPeriod to increase the timer/sleep accuracy. 您可以使用timeBeginPeriod来提高计时器/睡眠精度。 Note that this globally affects the system and might increase the power consumption. 请注意,这会对系统产生全局影响,并可能会增加功耗。
You can call timeBeginPeriod(1) at the beginning of your program. 您可以在程序开头调用timeBeginPeriod(1) On the systems where you observed the higher timer accuracy another running program probably did that. 在您观察到更高计时器精度的系统上,另一个正在运行的程序可能会这样做。
And I wouldn't bother calculating the sleep time and just use sleep(1) in a loop. 我不打算计算睡眠时间,只是在循环中使用sleep(1)

But even with only 16ms precision you can write your code so that the error averages out over time. 但即使只有16ms的精度,您也可以编写代码,以便随着时间的推移平均误差。 That's what I'd do. 这就是我要做的。 Isn't hard to code and should work with few adaptions to your current code. 编写代码并不困难,并且应该很少适应当前的代码。

Or you can switch to code that makes the movement proportional to the elapsed time. 或者您可以切换到使移动与经过时间成比例的代码。 But even in this case you should implement a frame-rate limiter so you don't get uselessly high framerates and unnecessarily consume power. 但即使在这种情况下,您也应该实现帧速率限制器,这样您就不会获得无用的高帧率和不必要的功耗。

Edit: Based on ideas and comments in this answer, the accepted answer was formulated. 编辑:根据这个答案中的想法和评论,制定了接受的答案。

Almost all game engines handle updates by passing the time since last frame and having movement etc... behave proportionally to time, any other implementation than this is faulty. 几乎所有的游戏引擎都通过传递自上一帧以来的时间来处理更新并且具有移动等......与时间成比例的行为,除此之外的任何其他实现都是错误的。

Although CodeInChaos suggestion is answers your question, and might work partially in some scenarios it's just a plain bad practice. 尽管CodeInChaos建议可以解答您的问题,并且在某些情况下可能部分有效,但这只是一个简单的不良做法。

Limiting the framerate to your desired 60fps will work only when a computer is running faster. 将帧速率限制为所需的60fps仅在计算机运行速度较快时才有效。 However the second that a background task eats up some processor power (for example the virusscanner starts) and your game drops below 60fps everything will go much slower. 然而,第二个背景任务占用了一些处理器能力(例如,virusscanner启动)并且你的游戏降到60fps以下,一切都会慢得多。 Even though your game could be perfectly playable on 35fps this will make it impossible to play the game because everything goes half as fast. 即使你的游戏在35fps上可以完美播放,这也会让游戏变得不可能,因为一切都快了一半。

Things like sleep are not going to help because they halt your process in favor of another process, that process must first be halted, sleep(1ms) just means that after 1ms your process is returned to the queue waiting for permission to run, sleep(1ms) therefor can easily take 15ms depending on the other running processes and their priorities. 像睡眠这样的东西不会有帮助,因为它们会暂停你的过程,而不是另一个过程,该过程必须首先停止,睡眠(1ms)只是意味着在1ms之后你的进程返回队列等待运行的权限,睡眠(因此,根据其他运行过程及其优先级,可以轻松地占用15ms。

So my suggestions is that you as quickly as possible at some sort of "elapsedSeconds" variable that you use in all your update methods, the earlier you built it in, the less work it is, this will also ensure compatibility with MONO. 所以我的建议是你尽快在你所有的更新方法中使用的某种“elapsedSeconds”变量,你构建它的时间越早,它的工作就越少,这也将确保与MONO的兼容性。

If you have 2 parts of your engine, say a physics and a render engine and you want to run these at different framerates, then just see how much time has passed since the last frame, and then decide to update the physics engine or not, as long as you incorporate the time since last update in your calculations you will be fine. 如果你的引擎有2个部分,比如物理和渲染引擎,你想以不同的帧速率运行它们,那么只要看看自上一帧以来经过了多少时间,然后决定是否更新物理引擎,只要你在计算中加入自上次更新以来的时间就可以了。

Also never draw more often than you're moving something. 也不会比你搬东西更经常画画。 It's a waste to draw 2 times the exact same screen, so if the only way something can change on screen is by updating your physics engine, then keep render engine and physics engine updates in sync. 绘制完全相同的屏幕2次是浪费,所以如果屏幕上可以改变的唯一方法是更新物理引擎,那么保持渲染引擎和物理引擎更新同步。

Based on ideas and comments on CodeInChaos' answer, this was the final code I arrived at. 根据对CodeInChaos回答的想法和评论,这是我到达的最终代码。 I originally edited that answer, but CodeInChaos suggested this be a separate answer. 我最初编辑了这个答案,但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