使用std :: chrono库来调整​​应用程序fps,但会产生奇怪的行为

时间:2017-09-16 21:37:22

标签: c++ windows thread-sleep frame-rate chrono

我使用std :: chrono c ++库编写了下面的代码,我想做的是 在60上修复应用程序的FPS,但我得到50 FPS,而不是确定性能问题 因为我一无所有。但它肯定是无效的用法或错误。

TARGET_FPS宏设置为我想要的目标FPS,然后是控制台窗口 显示实际的实际FPS,以下这些行显示我设置为TARGET_FPS的值,并且每个值都与最终的FPS相关联。

 TARGET_FPS---->FPS

       60----->50
       90----->50
       100----->100
     1000----->100
     10000----->100
   whatever ----->100

即使我将TARGET_FPS定义为1000000000,我也得到100 FPS,即使我将其定义为458或任何超过100的值,我也会得到100 FPS作为输出。

#include <chrono> /// to use std::chrono namespace 
#include <iostream> /// for console output
#include <thread> /// for std::this_thread::sleep_for()
#define TARGET_FPS 60// our target FPS    
using frame_len_type = std::chrono::duration<float,std::ratio<1,TARGET_FPS>>; /// this is the     duration that defines the length of a frame
using fsecond = std::chrono::duration<float>; /// this duration    represents once second and uses 'float' type as internal representation
const frame_len_type target_frame_len(1); /// we will define this    constant here , to represent on frame duration ( defined to avoid    construction inside a loop )
void app_logic(){ /** ... All application logic goes here ... **/}
int main() /// our main function !
{
    using sys_clock = std::chrono::system_clock; /// simplify the type    name to make the code readable
     sys_clock::time_point frame_begin,frame_end; /// we will use these time points to point to frame begin and end
     while (true)
     {
      frame_begin = sys_clock::now(); /// there we go !
      app_logic(); /// lets be logical here :)
      frame_end = sys_clock::now(); /// we are done so quick !
      std::this_thread::sleep_for( target_frame_len-    (frame_end.time_since_epoch()-frame_begin.time_since_epoch()) ); /// we will take a rest that is equal to what we where supposed to take to finish the actual target frame length
      std::cout<< fsecond(1) / ( sys_clock::now() - frame_begin) <<std::endl; /// this will show ass the current FPS
     }
    return 0; /// return to OS
} /// end of code

2 个答案:

答案 0 :(得分:2)

std :: chrono的时序分辨率取决于系统:

  

休眠间隔过后,线程就可以运行了。如果   你指定0毫秒,线程将放弃余数   它的时间片但仍然准备好了。请注意,就绪线程不是   保证立即运行。因此,线程可能无法运行   直到睡眠间隔过去一段时间后。

  • 无论您使用何种操作系统,C ++标准库都无法为sleep_for提供更好的保证:
  

30.3.2 / 7:效果:阻止调用线程的相对超时(...)

后果:

  • 当FPS设置为60时,每16.6 ms会有一帧。因此,假设您的app_logic()超快,您的线程将至少休眠15.6毫秒。如果逻辑需要1 ms才能执行,那么你的精确度将达到60 FPS。
  • 但是,根据API文档,如果[等待时间]大于一个刻度但小于2,则等待可以是一到两个刻度之间的任何位置,以便平均睡眠时间在15.6到31.2毫秒之间,相反,你的FPS将在60到32 FPS之间。这就解释了为什么你只能达到50 FPS。

  • 当您将FPS设置为100时,每10ms应该有一个帧。这低于计时器精度。可能根本没有睡眠。如果没有其他线程准备好运行,该函数将立即返回,这样您将达到最大吞吐量。如果设置更高的FPS,则情况与预期等待时间始终低于计时器精度的情况完全相同。因此结果不会改善。

答案 1 :(得分:2)

问题解决了:)

    #include <chrono> /// to use std::chrono namespace
    #include <iostream> /// for console output
    #include <thread> /// for std::this_thread::sleep_for()
    #include <windows.h>
    #define TARGET_FPS 500 /// our target fps as a macro
    const float target_fps = (float)TARGET_FPS; /// our target fps
    float tmp_target_fps = target_fps;  /// used to adjust the target fps depending on the actual real fps to reach the real target fps
    using frame_len_type = std::chrono::duration<float,std::ratio<1,TARGET_FPS>>; /// this is the     duration that defines the length of a frame
    using fsecond = std::chrono::duration<float>; /// this duration    represents once second and uses 'float' type as internal representation
    fsecond target_frame_len(1.0f/tmp_target_fps); /// we will define this    constant here , to represent on frame duration ( defined to avoid    construction inside a loop )
    bool enable_fps_oscillation = true;
    void app_logic()
    {
        /** ... All application logic goes here ... **/
    }
    class HeighResolutionClockKeeper
    {
    private :
        bool using_higher_res_timer;
    public :
        HeighResolutionClockKeeper() : using_higher_res_timer(false) {}
        void QueryHeighResolutionClock()
        {
            if (timeBeginPeriod(1) != TIMERR_NOCANDO)
            {
                using_higher_res_timer = true;
            }
        }
        void FreeHeighResolutionClock()
        {
            if (using_higher_res_timer)
            {
                timeEndPeriod(1);
            }
        }
        ~HeighResolutionClockKeeper()
        {
            FreeHeighResolutionClock(); /// if exception is thrown , if not this wont cause problems thanks to the flag we put
        }
    };
    int main() /// our main function !
    {
        HeighResolutionClockKeeper MyHeighResolutionClockKeeper;
        MyHeighResolutionClockKeeper.QueryHeighResolutionClock();
        using sys_clock = std::chrono::system_clock; /// simplify the type    name to make the code readable
        sys_clock::time_point frame_begin,frame_end; /// we will use these time points to point to frame begin and end
        sys_clock::time_point start_point = sys_clock::now();
        float accum_fps = 0.0f;
        int frames_count = 0;
        while (true)
        {
            frame_begin = sys_clock::now(); /// there we go !
            app_logic(); /// lets be logical here :)
            frame_end = sys_clock::now(); /// we are done so quick !
            std::this_thread::sleep_for( target_frame_len-    (frame_end.time_since_epoch()-frame_begin.time_since_epoch()) ); /// we will take a rest that is equal to what we where supposed to take to finish the actual target frame length
            float fps =  fsecond(1) / ( sys_clock::now() - frame_begin) ; /// this will show ass the current FPS

    /// obviously we will not be able to hit the exact FPS  we want se we need to oscillate around until we
    /// get a very close average FPS by time .
            if (fps < target_fps) /// our real fps is less than what we want
                tmp_target_fps += 0.01; /// lets asl for more !
            else if (fps > target_fps ) /// it is more than what we want
                tmp_target_fps -=0.01; /// lets ask for less
            if(enable_fps_oscillation == true)
            {
             /// now we will adjust our target frame length for match the new target FPS
                target_frame_len = fsecond(1.0f/tmp_target_fps);
           /// used to calculate average FPS
                accum_fps+=fps;
                frames_count++;
                /// each 1 second
                if( (sys_clock::now()-start_point)>fsecond(1.0f)) /// show average each 1 sec
                {
                    start_point=sys_clock::now();
                    std::cout<<accum_fps/frames_count<<std::endl; /// it is getting more close each time to our target FPS
                }
            }
            else
            {
                /// each frame
                std::cout<<fps<<std::endl;
            }
        }
        MyHeighResolutionClockKeeper.FreeHeighResolutionClock();
        return 0; /// return to OS
    } /// end of code

我必须在Windows平台上添加timeBeginPeriod()timeEndPeriod(),感谢来自http://www.geisswerks.com/ryan/FAQS/timing.html的这个非常棒的,迷失方向的网站Ryan Geiss

详细信息:

因为我们实际上无法达到我们想要的精确fps(非常略高于或低于,但由于timeXPeriod(1)而高达1000 fps并且低至1 fps)因此我使用了一些额外的转储fps变量调整我们分区的目标fps,增加它并减少它...,这将让我们控制实际的应用程序fps以平均值命中我们的真实目标fps(你可以使用&#39; enable_fps_oscillation&#39启用和禁用它;标志)这解决了fps = 60的问题,因为我们无法击中它(+/- 0.5),但是如果我们设置fps = 500,我们就会击中它而我们不需要在它下面振动