Improve accuracy of frame rate limiter
This commit is contained in:
parent
4c7353a3a4
commit
4e8744f047
@ -58,6 +58,8 @@
|
|||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
#include "io/rich_presence.hpp"
|
#include "io/rich_presence.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
@ -98,8 +100,8 @@ MainLoop::MainLoop(unsigned parent_pid, bool download_assets)
|
|||||||
: m_abort(false), m_request_abort(false), m_paused(false),
|
: m_abort(false), m_request_abort(false), m_paused(false),
|
||||||
m_ticks_adjustment(0), m_parent_pid(parent_pid)
|
m_ticks_adjustment(0), m_parent_pid(parent_pid)
|
||||||
{
|
{
|
||||||
m_curr_time = 0;
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
m_prev_time = 0;
|
m_prev_time = std::chrono::steady_clock::now();
|
||||||
m_throttle_fps = true;
|
m_throttle_fps = true;
|
||||||
m_allow_large_dt = false;
|
m_allow_large_dt = false;
|
||||||
m_frame_before_loading_world = false;
|
m_frame_before_loading_world = false;
|
||||||
@ -188,7 +190,7 @@ extern "C" void reset_network_body()
|
|||||||
* too low (the frame rate too high), the process will sleep to reach the
|
* too low (the frame rate too high), the process will sleep to reach the
|
||||||
* maximum frame rate.
|
* maximum frame rate.
|
||||||
*/
|
*/
|
||||||
float MainLoop::getLimitedDt()
|
double MainLoop::getLimitedDt()
|
||||||
{
|
{
|
||||||
m_prev_time = m_curr_time;
|
m_prev_time = m_curr_time;
|
||||||
|
|
||||||
@ -215,7 +217,6 @@ float MainLoop::getLimitedDt()
|
|||||||
resume_mainloop();
|
resume_mainloop();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
float dt = 0;
|
|
||||||
|
|
||||||
// In profile mode without graphics, run with a fixed dt of 1/60
|
// In profile mode without graphics, run with a fixed dt of 1/60
|
||||||
if ((ProfileWorld::isProfileMode() && GUIEngine::isNoGraphics()) ||
|
if ((ProfileWorld::isProfileMode() && GUIEngine::isNoGraphics()) ||
|
||||||
@ -224,9 +225,7 @@ float MainLoop::getLimitedDt()
|
|||||||
return 1.0f/60.0f;
|
return 1.0f/60.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
while( 1 )
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
{
|
|
||||||
m_curr_time = StkTime::getMonoTimeMs();
|
|
||||||
if (m_prev_time > m_curr_time)
|
if (m_prev_time > m_curr_time)
|
||||||
{
|
{
|
||||||
m_prev_time = m_curr_time;
|
m_prev_time = m_curr_time;
|
||||||
@ -252,7 +251,7 @@ float MainLoop::getLimitedDt()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dt = (float)(m_curr_time - m_prev_time);
|
double dt = convertToTime(m_curr_time, m_prev_time);
|
||||||
// On a server (i.e. without graphics) the frame rate can be under
|
// On a server (i.e. without graphics) the frame rate can be under
|
||||||
// 1 ms, i.e. dt = 0. Additionally, the resolution of a sleep
|
// 1 ms, i.e. dt = 0. Additionally, the resolution of a sleep
|
||||||
// statement is not that precise either: if the sleep statement
|
// statement is not that precise either: if the sleep statement
|
||||||
@ -265,13 +264,13 @@ float MainLoop::getLimitedDt()
|
|||||||
while (dt == 0)
|
while (dt == 0)
|
||||||
{
|
{
|
||||||
StkTime::sleep(1);
|
StkTime::sleep(1);
|
||||||
m_curr_time = StkTime::getMonoTimeMs();
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
if (m_prev_time > m_curr_time)
|
if (m_prev_time > m_curr_time)
|
||||||
{
|
{
|
||||||
Log::error("MainLopp", "System clock keeps backwards!");
|
Log::error("MainLopp", "System clock keeps backwards!");
|
||||||
m_prev_time = m_curr_time;
|
m_prev_time = m_curr_time;
|
||||||
}
|
}
|
||||||
dt = (float)(m_curr_time - m_prev_time);
|
dt = convertToTime(m_curr_time, m_prev_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
const World* const world = World::getWorld();
|
const World* const world = World::getWorld();
|
||||||
@ -301,41 +300,11 @@ float MainLoop::getLimitedDt()
|
|||||||
!m_allow_large_dt)
|
!m_allow_large_dt)
|
||||||
{
|
{
|
||||||
/* time 3 internal substeps take */
|
/* time 3 internal substeps take */
|
||||||
const float MAX_ELAPSED_TIME = 3.0f*1.0f / 60.0f*1000.0f;
|
const double MAX_ELAPSED_TIME = 3.0f*1.0f / 60.0f*1000.0f;
|
||||||
if (dt > MAX_ELAPSED_TIME) dt = MAX_ELAPSED_TIME;
|
if (dt > MAX_ELAPSED_TIME) dt = MAX_ELAPSED_TIME;
|
||||||
}
|
}
|
||||||
if (!m_throttle_fps || ProfileWorld::isProfileMode()) break;
|
|
||||||
|
|
||||||
// Throttle fps if more than maximum, which can reduce
|
dt *= 0.001;
|
||||||
// the noise the fan on a graphics card makes.
|
|
||||||
// When in menus, reduce FPS much, it's not necessary to push to the
|
|
||||||
// maximum for plain menus
|
|
||||||
#ifdef IOS_STK
|
|
||||||
// For iOS devices most at locked at 60, for new iPad Pro supports 120
|
|
||||||
// which is currently m_max_fps
|
|
||||||
const int max_fps =
|
|
||||||
UserConfigParams::m_swap_interval == 2 ? UserConfigParams::m_max_fps :
|
|
||||||
UserConfigParams::m_swap_interval == 1 ? 60 :
|
|
||||||
30;
|
|
||||||
#else
|
|
||||||
const int max_fps = (irr_driver->isRecording() &&
|
|
||||||
UserConfigParams::m_limit_game_fps )
|
|
||||||
? UserConfigParams::m_record_fps
|
|
||||||
: UserConfigParams::m_max_fps;
|
|
||||||
#endif
|
|
||||||
const int current_fps = (int)(1000.0f / dt);
|
|
||||||
if (!m_throttle_fps || current_fps <= max_fps ||
|
|
||||||
ProfileWorld::isProfileMode() ) break;
|
|
||||||
|
|
||||||
int wait_time = 1000 / max_fps - 1000 / current_fps;
|
|
||||||
if (wait_time < 1) wait_time = 1;
|
|
||||||
|
|
||||||
PROFILER_PUSH_CPU_MARKER("Throttle framerate", 0, 0, 0);
|
|
||||||
StkTime::sleep(wait_time);
|
|
||||||
PROFILER_POP_CPU_MARKER();
|
|
||||||
} // while(1)
|
|
||||||
|
|
||||||
dt *= 0.001f;
|
|
||||||
return dt;
|
return dt;
|
||||||
} // getLimitedDt
|
} // getLimitedDt
|
||||||
|
|
||||||
@ -427,10 +396,10 @@ void MainLoop::updateRace(int ticks, bool fast_forward)
|
|||||||
*/
|
*/
|
||||||
void MainLoop::run()
|
void MainLoop::run()
|
||||||
{
|
{
|
||||||
m_curr_time = StkTime::getMonoTimeMs();
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
// DT keeps track of the leftover time, since the race update
|
// DT keeps track of the leftover time, since the race update
|
||||||
// happens in fixed timesteps
|
// happens in fixed timesteps
|
||||||
float left_over_time = 0;
|
double left_over_time = 0;
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
HANDLE parent = 0;
|
HANDLE parent = 0;
|
||||||
@ -483,11 +452,12 @@ void MainLoop::run()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
PROFILER_PUSH_CPU_MARKER("Main loop", 0xFF, 0x00, 0xF7);
|
PROFILER_PUSH_CPU_MARKER("Main loop", 0xFF, 0x00, 0xF7);
|
||||||
|
TimePoint frame_start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
left_over_time += getLimitedDt();
|
left_over_time += getLimitedDt();
|
||||||
int num_steps = stk_config->time2Ticks(left_over_time);
|
int num_steps = stk_config->time2Ticks(left_over_time);
|
||||||
float dt = stk_config->ticks2Time(1);
|
float dt = stk_config->ticks2Time(1);
|
||||||
left_over_time -= num_steps * dt ;
|
left_over_time -= num_steps * dt;
|
||||||
|
|
||||||
// Shutdown next frame if shutdown request is sent while loading the
|
// Shutdown next frame if shutdown request is sent while loading the
|
||||||
// world
|
// world
|
||||||
@ -676,8 +646,8 @@ void MainLoop::run()
|
|||||||
// in CutsceneWorld::enterRaceOverState
|
// in CutsceneWorld::enterRaceOverState
|
||||||
// Reset the timer for correct time for cutscene
|
// Reset the timer for correct time for cutscene
|
||||||
m_frame_before_loading_world = false;
|
m_frame_before_loading_world = false;
|
||||||
m_curr_time = StkTime::getMonoTimeMs();
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
left_over_time = 0.0f;
|
left_over_time = 0.0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,8 +678,8 @@ void MainLoop::run()
|
|||||||
{
|
{
|
||||||
// irr_driver->getDevice()->run() loads the world
|
// irr_driver->getDevice()->run() loads the world
|
||||||
m_frame_before_loading_world = false;
|
m_frame_before_loading_world = false;
|
||||||
m_curr_time = StkTime::getMonoTimeMs();
|
m_curr_time = std::chrono::steady_clock::now();
|
||||||
left_over_time = 0.0f;
|
left_over_time = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abort)
|
if (abort)
|
||||||
@ -725,6 +695,31 @@ void MainLoop::run()
|
|||||||
gp->sendActions();
|
gp->sendActions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimePoint frame_end = std::chrono::steady_clock::now();
|
||||||
|
double frame_time = convertToTime(frame_end, frame_start) * 0.001;
|
||||||
|
const double current_fps = 1.0 / frame_time;
|
||||||
|
const double max_fps =
|
||||||
|
(irr_driver->isRecording() && UserConfigParams::m_limit_game_fps) ?
|
||||||
|
UserConfigParams::m_record_fps : UserConfigParams::m_max_fps;
|
||||||
|
|
||||||
|
// Throttle fps if more than maximum, which can reduce
|
||||||
|
// the noise the fan on a graphics card makes.
|
||||||
|
// No need to throttle if vsync is on (m_swap_interval == 1) as
|
||||||
|
// endScene handles fps according to monitor refresh rate
|
||||||
|
if ((UserConfigParams::m_swap_interval == 0 ||
|
||||||
|
GUIEngine::isNoGraphics()) &&
|
||||||
|
m_throttle_fps && !ProfileWorld::isProfileMode() &&
|
||||||
|
current_fps > max_fps)
|
||||||
|
{
|
||||||
|
double wait_time = 1.0 / max_fps - 1.0 / current_fps;
|
||||||
|
std::chrono::nanoseconds wait_time_ns(
|
||||||
|
(uint64_t)(wait_time * 1000.0 * 1000.0 * 1000.0));
|
||||||
|
PROFILER_PUSH_CPU_MARKER("Throttle framerate", 0, 0, 0);
|
||||||
|
std::this_thread::sleep_for(wait_time_ns);
|
||||||
|
PROFILER_POP_CPU_MARKER();
|
||||||
|
}
|
||||||
|
|
||||||
PROFILER_POP_CPU_MARKER(); // MainLoop pop
|
PROFILER_POP_CPU_MARKER(); // MainLoop pop
|
||||||
PROFILER_SYNC_FRAME();
|
PROFILER_SYNC_FRAME();
|
||||||
} // while !m_abort
|
} // while !m_abort
|
||||||
@ -761,33 +756,6 @@ void MainLoop::renderGUI(int phase, int loop_index, int loop_size)
|
|||||||
irr_driver->getDevice()->setEventReceiver(NULL);
|
irr_driver->getDevice()->setEventReceiver(NULL);
|
||||||
irr_driver->getDevice()->run();
|
irr_driver->getDevice()->run();
|
||||||
irr_driver->getDevice()->setEventReceiver(GUIEngine::EventHandler::get());
|
irr_driver->getDevice()->setEventReceiver(GUIEngine::EventHandler::get());
|
||||||
return;
|
|
||||||
// Rendering past phase 7000 causes the minimap to not work
|
|
||||||
// on higher graphical settings
|
|
||||||
if (phase > 7000)
|
|
||||||
{
|
|
||||||
m_request_abort = !irr_driver->getDevice()->run();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t now = StkTime::getMonoTimeMs();
|
|
||||||
float dt = (now - m_curr_time)/1000.0f;
|
|
||||||
|
|
||||||
if (dt < 1.0 / 30.0f) return;
|
|
||||||
|
|
||||||
m_curr_time = now;
|
|
||||||
|
|
||||||
// TODO: remove debug output
|
|
||||||
//Log::verbose("mainloop", "Rendergui t %llu dt %f phase %d index %d / %d",
|
|
||||||
// now, dt, phase, loop_index, loop_size);
|
|
||||||
|
|
||||||
irr_driver->update(dt, /*is_loading*/true);
|
|
||||||
GUIEngine::update(dt);
|
|
||||||
m_request_abort = !irr_driver->getDevice()->run();
|
|
||||||
|
|
||||||
//TODO: remove debug output
|
|
||||||
// uint64_t now2 = StkTime::getMonoTimeMs();
|
|
||||||
// Log::verbose("mainloop", " duration t %llu dt %llu", now, now2-now);
|
|
||||||
#endif
|
#endif
|
||||||
} // renderGUI
|
} // renderGUI
|
||||||
/* EOF */
|
/* EOF */
|
||||||
|
@ -23,12 +23,14 @@
|
|||||||
#include "utils/synchronised.hpp"
|
#include "utils/synchronised.hpp"
|
||||||
#include "utils/types.hpp"
|
#include "utils/types.hpp"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
/** Management class for the whole gameflow, this is where the
|
/** Management class for the whole gameflow, this is where the
|
||||||
main-loop is */
|
main-loop is */
|
||||||
class MainLoop
|
class MainLoop
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
using TimePoint = std::chrono::steady_clock::time_point;
|
||||||
/** True if the main loop should exit. */
|
/** True if the main loop should exit. */
|
||||||
std::atomic_bool m_abort;
|
std::atomic_bool m_abort;
|
||||||
|
|
||||||
@ -48,11 +50,18 @@ private:
|
|||||||
|
|
||||||
Synchronised<int> m_ticks_adjustment;
|
Synchronised<int> m_ticks_adjustment;
|
||||||
|
|
||||||
uint64_t m_curr_time;
|
TimePoint m_curr_time;
|
||||||
uint64_t m_prev_time;
|
TimePoint m_prev_time;
|
||||||
unsigned m_parent_pid;
|
unsigned m_parent_pid;
|
||||||
float getLimitedDt();
|
double getLimitedDt();
|
||||||
void updateRace(int ticks, bool fast_forward);
|
void updateRace(int ticks, bool fast_forward);
|
||||||
|
double convertToTime(const TimePoint& cur, const TimePoint& prev) const
|
||||||
|
{
|
||||||
|
auto duration = cur - prev;
|
||||||
|
auto value =
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
|
||||||
|
return value.count() / (1000.0 * 1000.0);
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
MainLoop(unsigned parent_pid, bool download_assets = false);
|
MainLoop(unsigned parent_pid, bool download_assets = false);
|
||||||
~MainLoop();
|
~MainLoop();
|
||||||
|
Loading…
Reference in New Issue
Block a user