使用std::chrono限制帧率

4
std::chrono::system_clock::time_point m_BeginFrame = std::chrono::system_clock::now();
std::chrono::system_clock::time_point m_EndFrame = std::chrono::system_clock::now();
std::chrono::nanoseconds m_WorkTime = std::chrono::nanoseconds::zero();
std::chrono::nanoseconds m_WaitTime = std::chrono::nanoseconds::zero();
auto invFpsLimit = std::chrono::nanoseconds(1e9 / fpsLimit());

// main loop
while (!glfwWindowShouldClose(m_pScreen->glfwWindow()))
{
    m_WaitTime = m_BeginFrame - m_EndFrame;
    m_EndFrame = std::chrono::system_clock::now();
    m_WorkTime = m_EndFrame - m_BeginFrame;

    // if need sleep
    if (m_WorkTime < invFpsLimit)
    {
        std::this_thread::sleep_until(m_BeginFrame + invFpsLimit);
        m_DeltaTime = invFpsLimit;
    }
    else
    {
       m_DeltaTime = m_WorkTime;
    }

    m_BeginFrame = std::chrono::system_clock::now();

    TRACE("   %f   %u   %u   %u",
            ((float)1e9 / (float)(m_WaitTime.count() + m_WorkTime.count())),
            m_WorkTime.count(),
            m_WaitTime.count(),
            invFpsLimit.count());


    // think and opengl stuff...
}

我试图使用这段代码限制帧率,但是Fraps显示约为56-57的值。

58.848454   3175500   13817300   16666666
55.259304   3314200   14782300   16666666
58.923698   2321700   14649400   16666666
56.200928   2167400   15625900   16666666
52.774071   3188200   15760500   16666666
50.600887   4899700   14862800   16666666
65.011055   2347500   13034500   16666666
54.966499   2611700   15581200   16666666
55.605911   2653400   15330300   16666666
56.476437   2386100   15320400   16666666
55.280689   2581400   15508100   16666666
56.437550   2355400   15363300   16666666
47.170700   5535900   15663700   16666666
64.713608   3039700   12413000   16666666
56.136570   2941100   14872600   16666666
53.444496   3624200   15086800   16666666
60.074493   2397500   14248500   16666666
51.737339   3777100   15551300   16666666
59.369026   3665800   13178000   16666666
59.355282   3543900   13303800   16666666
54.243759   3961000   14474300   16666666
45.764706   7823500   14027400   16666666
61.242237   6239400   10089200   16666666
73.396652   2013800   11610800   16666666
50.531849   3824000   15965500   16666666
62.895454   2796000   13103400   16666666
60.611572   2360500   14138000   16666666
56.074242   2241700   15591800   16666666
55.143208   2454800   15679800   16666666

我尝试过将clock更改为system_clock,使用sleep_for而不是sleep_until,但都没有起作用。是否有一些关于std clocks的特定知识或我看不到的工作代码?我的fps是被什么东西削弱了吗?

2
如果您启用垂直同步,您可以“免费”获得此功能。 - tkausl
3个回答

5

通过使用invFpsLimit而不是system_clock::now()来更新m_BeginFramem_EndFrame可以更好地完成任务。可能会像这样:

#include <iostream>
#include <thread>

int fpsLimit() {return 60;}

int
main()
{
    using namespace std::chrono;
    using dsec = duration<double>;
    auto invFpsLimit = duration_cast<system_clock::duration>(dsec{1./fpsLimit()});
    auto m_BeginFrame = system_clock::now();
    auto m_EndFrame = m_BeginFrame + invFpsLimit;
    unsigned frame_count_per_second = 0;
    auto prev_time_in_seconds = time_point_cast<seconds>(m_BeginFrame);
    while (true)
    {
        // Do drawing work ...

        // This part is just measuring if we're keeping the frame rate.
        // It is not necessary to keep the frame rate.
        auto time_in_seconds = time_point_cast<seconds>(system_clock::now());
        ++frame_count_per_second;
        if (time_in_seconds > prev_time_in_seconds)
        {
            std::cerr << frame_count_per_second << " frames per second\n";
            frame_count_per_second = 0;
            prev_time_in_seconds = time_in_seconds;
        }

        // This part keeps the frame rate.
        std::this_thread::sleep_until(m_EndFrame);
        m_BeginFrame = m_EndFrame;
        m_EndFrame = m_BeginFrame + invFpsLimit;
    }
}

在C++17及以上版本中,我建议使用round构造invFpsLimit,而不是duration_cast,以获得稍微更好的精度。
auto invFpsLimit = round<system_clock::duration>(dsec{1./fpsLimit()});

这段代码运行良好,可以稳定地达到60帧每秒。不幸的是,我需要一个名为m_DeltaTime的变量来计算移动(在写文章时我删除了它,以免用无用的数据混淆您,但现在已添加)。使用你的实现,m_DeltaTime将始终是invFpsLimit,但实际上有时会更大,这会导致我的运动出现抽搐。 - n0lavar
哦,我的错,计算起来很简单,只需要在 sleep_until 后加上 m_DeltaTime = std::chrono::system_clock::now() - m_BeginFrame; 就可以了。效果很好,谢谢! - n0lavar

1

For getting the delta you can do this:

unsgined deltaTime = 1000.0f / frame_count_per_fps; // In milliseconds

0

sleep_until的文档中写道:

由于调度或资源争用延迟,该函数可能会阻塞时间比睡眠时间更长。

如果您确实需要精确的fps,请使用vSync,或者将代码中的if块替换为以下循环,直到帧时间用完为止:

while (m_WorkTime < invFpsLimit)
{
    m_EndFrame = std::chrono::system_clock::now();
    m_WorkTime = m_EndFrame - m_BeginFrame;
}

谢谢,我会尝试使用vSync。 主动等待不是一个好的选择,它会占用太多的CPU时间。 - n0lavar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接