Использование библиотеки std::chrono для настройки fps приложения, но странное поведение

Я написал код ниже, используя библиотеку std::chrono C++, и я пытаюсь исправить приложение FPSна 60, но я получаю 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 ответа

Временное разрешение std::chrono зависит от системы:

По истечении интервала ожидания поток готов к работе. Если вы укажете 0 миллисекунд, поток освободит оставшуюся часть своего временного интервала, но останется готовым. Обратите внимание, что готовый поток не гарантированно запускается немедленно. Следовательно, поток может не запускаться до тех пор, пока не истечет некоторое время после окончания интервала ожидания.

  • Стандартная библиотека C++ не дает лучших гарантий для sleep_for независимо от того, какую ОС вы используете:

30.3.2 / 7: Эффект: блокирует вызывающий поток на относительное время ожидания (...)

Последствие:

  • С FPS, установленным на 60, будет кадр каждые 16,6 мс. Так что при условии, что ваш app_logic() очень быстро, ваш поток будет спать не менее 15,6 мс. Если для выполнения логики требуется 1 мс, вы будете точно на 60 FPS.
  • Однако, согласно документации API, если [время ожидания] больше одного тика, но меньше двух, время ожидания может быть где-то между одним и двумя тиками, так что среднее время ожидания будет между 15,6 и 31,2 мс, что означает и наоборот, ваш FPS будет между 60 и 32 FPS. Это объясняет, почему вы достигаете только 50 FPS.

  • Когда вы устанавливаете FPS на 100, каждые 10 мс должен быть кадр. Это ниже точности таймера. Там может быть не спать вообще. Если ни один другой поток не готов к запуску, функция немедленно вернется, так что вы получите максимальную пропускную способность. Если вы установите более высокий FPS, вы будете в точно такой же ситуации, как ожидаемое время ожидания всегда будет ниже точности таймера. Следовательно, результат не улучшится.

Задача решена:)

    #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

Я должен был добавить timeBeginPeriod() а также timeEndPeriod() на платформе Windows, благодаря этому удивительному, затерянному на ветру веб-сайту http://www.geisswerks.com/ryan/FAQS/timing.html от Райана Гейсса.

Подробности:

Поскольку мы не можем на самом деле достичь желаемого значения частоты кадров (очень немного выше или ниже, но до 1000 кадров в секунду и ниже до 1 кадра в секунду благодаря timeXPeriod(1)), поэтому я использовал некоторую дополнительную переменную дамп fps для настройки целевого числа кадров в секунду. мы увеличиваем и уменьшаем его..., что позволит нам контролировать фактический fps приложения, чтобы он достиг нашего реального целевого fps как среднего (вы можете включить и отключить это, используя флаг 'enable_fps_oscillation'), это исправляет проблему для fps = 60, потому что мы не можем поразить его ( +/-0,5), но если мы установим fps = 500, мы поразим его, и нам не нужно колебаться ниже и выше

Другие вопросы по тегам