【问题标题】:C++ How to make precise frame rate limit?C ++如何进行精确的帧速率限制?
【发布时间】:2025-12-21 12:40:07
【问题描述】:

我正在尝试使用 C++ 创建游戏,我想为 fps 创建限制,但我总是得到比我想要的更多或更少的 fps。当我查看具有 fps 限制的游戏时,它总是精确的帧率。尝试使用Sleep() std::this_thread::sleep_for(sleep_until)。例如Sleep(0.01-deltaTime) 获得 100 fps 但最终得到 +-90fps。 当任何睡眠都不精确时,这些游戏如何如此精确地处理 fps? 我知道我可以使用无限循环来检查时间是否过去,但它正在使用 CPU 的全部功能,但我想在没有 VSync 的情况下将 CPU 使用率降低到这个限制。

【问题讨论】:

  • 有 std::this_thread::sleep_until(timepoint) 不是 std::this_thread::sleep_for(sleep_untill)。显示你的代码,你听起来很困惑。
  • 查看media players such as VLC的源代码。几乎可以肯定不会使用任何类型的sleep()
  • 如果它是用于“C++”的,为什么要标记为“C”?
  • 在 C 中我会使用clock()(虽然它不是“墙上时间”)并匹配一般吞吐量,而不是尝试对单个帧进行计时/控制。如果系统能跟上,那就太好了。千万不要在游戏中浪费时间睡觉,一定有更好的事情要做。
  • 启用垂直同步以显示器的刷新率绘制并防止撕裂。查看glfwSwapInterval()

标签: c++ windows opengl glfw


【解决方案1】:

是的,睡眠通常是不准确的。这就是为什么您的睡眠时间少于完成帧所需的实际时间。例如,如果您还需要 5 毫秒来完成帧,则休眠 4 毫秒。睡眠后,只需对帧的其余部分进行自旋锁定。类似的东西

float TimeRemaining = NextFrameTime - GetCurrentTime();
Sleep(ConvertToMilliseconds(TimeRemaining) - 1);
while (GetCurrentTime() < NextFrameTime) {};

【讨论】:

    【解决方案2】:

    您可以在开始时记录时间点,为其添加固定持续时间并休眠,直到计算出的时间点出现在每个循环的结束(或开始)。示例:

    #include <chrono>
    #include <iostream>
    #include <ratio>
    #include <thread>
    
    template<std::intmax_t FPS>
    class frame_rater {
    public:
        frame_rater() :                 // initialize the object keeping the pace
            time_between_frames{1},     // std::ratio<1, FPS> seconds
            tp{std::chrono::steady_clock::now()}
        {}
    
        void sleep() {
            // add to time point
            tp += time_between_frames;
    
            // and sleep until that time point
            std::this_thread::sleep_until(tp);
        }
    
    private:
        // a duration with a length of 1/FPS seconds
        std::chrono::duration<double, std::ratio<1, FPS>> time_between_frames;
    
        // the time point we'll add to in every loop
        std::chrono::time_point<std::chrono::steady_clock, decltype(time_between_frames)> tp;
    };
    
    // this should print ~10 times per second pretty accurately
    int main() {
        frame_rater<10> fr; // 10 FPS
        while(true) {
            std::cout << "Hello world\n";
            fr.sleep();                   // let it sleep any time remaining
        }
    }
    

    【讨论】:

    • 是否可以对其进行编辑以将变量作为“FPS”传递?
    • @Baptiste 是的,当然。例如,您可以从this instead 中挑选一些想法
    【解决方案3】:

    在实现任何基于调度程序睡眠的等待时要非常小心。

    大多数 OS 调度程序在等待时具有较高的延迟周转时间,而没有明确定义的时间间隔或信号将线程带回准备运行状态。

    睡眠本身并不是不准确的,你只是在解决问题上完全错了。如果您可以访问 DXGI 的 Waitable Swapchain 之类的东西,您可以同步到 DWM 的当前队列并获得真正可靠的低延迟时间。

    您无需忙于等待以获得准确的计时,等待计时器将为您提供一个同步对象来重新安排您的线程。

    无论您做什么,都不要在生产代码中使用当前接受的答案。这里有一个你想避免的极端情况,睡眠 (0) 不会为更高优先级的线程提供 CPU 时间。我见过很多游戏开发者尝试使用 Sleep (0),但它会给您带来严重的问题。

    【讨论】:

    【解决方案4】:

    接受的答案听起来很糟糕。不准确,会烧CPU!

    Thread.Sleep 不准确,因为您必须告诉它准确(默认情况下准确度约为 15 毫秒 - 意味着如果您告诉它休眠 1 毫秒,它可能会休眠 15 毫秒)。

    您可以通过 Win32 API 调用 timeBeginPeriod 和 timeEndPeriod 函数来做到这一点。

    查看 MSDN 了解更多详情 -> https://docs.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

    (我会评论已接受的答案,但仍然没有 50 声望)

    【讨论】:

      【解决方案5】:

      您可以将 const fps 变量设置为您想要的帧速率,然后如果自上次更新后经过的时间等于或大于 1 / desired_fps,您可以更新您的游戏。 这可能会奏效。 示例:

      const /*or constexpr*/ int fps{60};
      
      // then at update loop.
      while(running)
      {
          // update the game timer.
          timer->update();
      
          // check for any events.
      
          if(timer->ElapsedTime() >= 1 / fps)
          {
              // do your updates and THEN renderer.
          }
      }
      
      

      【讨论】:

        【解决方案6】:

        使用计时器。
        某些操作系统可以提供特殊功能。例如,对于 Windows,您可以使用 SetTimer 并处理其 WM_TIMER 消息。

        然后计算定时器的频率。 100 fps 意味着计时器必须每 0.01 秒触发一次事件。

        在此计时器事件的事件处理程序中,您可以进行渲染。

        如果渲染速度低于所需频率,则使用同步标志OpenGL sync,如果之前的渲染未完成,则丢弃计时器事件。

        【讨论】: