Dhia Hassen
Dhia Hassen

Reputation: 526

Using std::chrono library to adjust the application fps but getting weird behavior

I wrote the code bellow using std::chrono c++ library , what i am trying to do is to fix the application's FPSon 60 , but i am getting 50 FPS, not a performance issue for sure because i am computing nothing . but it is certainly an invalid usage or a bug .

the TARGET_FPS macro is set to the target FPSthat i want to get , then the console window displays the real actual FPS , these following lines shows the values i set TARGET_FPSto , and each is associated to the final FPS.

 TARGET_FPS---->FPS

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

Even if i define TARGET_FPS to 1000000000 i get 100 FPS, even when i define it to 458 or whatever value more than 100 i will get 100 FPSas output .

#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

Upvotes: 2

Views: 1287

Answers (2)

Dhia Hassen
Dhia Hassen

Reputation: 526

Problem solved :)

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

I had to add timeBeginPeriod() and timeEndPeriod() on windows platform , thanks to this awesome , lost-in-the-wind website http://www.geisswerks.com/ryan/FAQS/timing.html from Ryan Geiss .

Details :

Because we can't actually hit the exact fps that we want ( very slightly above or bellow , but up to 1000 fps and down to 1 fps thanks to timeXPeriod(1) ) therefore i used some extra dump fps variable to adjust the target fps i am seeking, increasing it and decreasing it .., that will let us control the actual application fps to hits our real target fps as an average (you can enable and disable this using 'enable_fps_oscillation' flag ) this fixes an issue for fps = 60 because we can't hit it ( +/-0.5 ) , but if we set fps = 500 we hit it and we dont need to oscillate bellow and above it

Upvotes: 2

Christophe
Christophe

Reputation: 73366

The timing resolution of std::chrono is system dependent:

  • In this answer to another question, you'll find a code snippet to determine the approximate timing resolution of your platform.
  • On windows 7, the default timer resolution is 15.6 ms
  • In addition, the windows API sleep on which the c++ standard library has to rely, does not guarantee that the thread will resume execution immediately after the waiting time:

After the sleep interval has passed, the thread is ready to run. If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready. Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the sleep interval elapses.

  • The C++ standard library doesn't give better guarantees for sleep_for, whatever OS you are using:

30.3.2/7: Effect: Blocks the calling thread for the relative timeout (...)

Consequence:

  • With FPS set to 60, there would be a frame every 16.6 ms. So assuming that your app_logic() is ultra fast, your thread will sleep at least 15.6 ms. If the logic takes 1 ms to execute, you'd be exactly at 60 FPS.
  • However, according to the API documentation, if [wait time] is greater than one tick but less than two, the wait can be anywhere between one and two ticks, so that the average sleep time will be between 15.6 and 31.2 ms, whic means, inversely, that your FPS will be between 60 and 32 FPS. This explains why you only achieve 50 FPS.

  • When you set FPS to 100, there should be a frame every 10ms. This is below the timer accuracy. There might be no sleep at all. If no other thread is ready to run, the function will return immediately, so that you will be at your maximum throughput. If you set a higher FPS, you'd be in exactly the same situation as the expected waiting time would always be below the timer accuracy. The result will therefore not improve.

Upvotes: 2

Related Questions