C++如何进行精确的帧率限制?

C++ How to make precise frame rate limit?

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

你可以记录开始的时间点,给它加上一个固定的持续时间,然后休眠直到计算的时间点出现在每个循环的结束(或开始)。示例:

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

您可以将 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.
    }
}

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

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

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

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

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

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

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

大多数 OS 调度程序有更高的延迟 turn-around 等待没有 well-defined 间隔或信号将线程带回 ready-to-run 状态。

睡眠并不是不准确的per-se,你只是完全错误地解决了这个问题。如果您可以访问 DXGI 的 Waitable Swapchain 之类的东西,您可以同步到 DWM 的当前队列并获得真正可靠的 low-latency 计时。

您不需要 busy-wait 来获得准确的计时,可等待的计时器会给您一个同步对象来重新安排您的线程。

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

接受的答案听起来很糟糕。它不会准确,它会燃烧 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 的声誉)