Использование библиотеки 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 зависит от системы:

  • В этот ответ на другой вопрос вы найдете фрагмент кода для определения приблизительного временного разрешения вашей платформы.
  • На Windows 7, разрешение таймера по умолчанию составляет 15,6 мс
  • В дополнение Windows API Sleep , на который должна полагаться стандартная библиотека c ++, не гарантирует, что поток возобновит выполнение сразу после времени ожидания:

По истечении интервала ожидания поток готов к работе. Если
Вы указываете 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, вы будете в точно такой же ситуации, как ожидаемое время ожидания всегда будет ниже точности таймера. Следовательно, результат не улучшится.

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

Я должен был добавить 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, мы поразим его, и нам не нужно будет колебаться ниже и выше

2

По вопросам рекламы [email protected]