From eb9b1ad68d84ddbebb3d9e50f3ec8d3ad195c75c Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 17 Feb 2021 15:19:33 +0100 Subject: [PATCH] Change: sleep till the next tick in the main loop Sleep for 1ms (which is always (a lot) more than 1ms) is just randomly guessing and hoping you hit your deadline, give or take. But given we can calculate when our next frame is happening, we can just sleep for that exact amount. As these values are often a bit larger, it is also more likely the OS can schedule us back in close to our requested target. This means it is more likely we hit our deadlines, which makes the FPS a lot more stable. --- src/video/allegro_v.cpp | 9 ++++++++- src/video/cocoa/cocoa_v.mm | 9 ++++++++- src/video/dedicated_v.cpp | 9 +++++++-- src/video/sdl2_v.cpp | 13 ++++++++++--- src/video/sdl_v.cpp | 13 ++++++++++--- src/video/win32_v.cpp | 17 ++++++++++++----- 6 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/video/allegro_v.cpp b/src/video/allegro_v.cpp index de329db9ed..800cce40cc 100644 --- a/src/video/allegro_v.cpp +++ b/src/video/allegro_v.cpp @@ -521,8 +521,15 @@ void VideoDriver_Allegro::MainLoop() DrawSurfaceToScreen(); } + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ if (!_fast_forward || _pause_mode) { - CSleep(1); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto next_tick = std::min(next_draw_tick, next_game_tick); + auto now = std::chrono::steady_clock::now(); + + if (next_tick > now) { + std::this_thread::sleep_for(next_tick - now); + } } } } diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index 20f3904aec..7c8981f617 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -704,8 +704,15 @@ void VideoDriver_Cocoa::GameLoop() this->Draw(); } + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ if (!_fast_forward || _pause_mode) { - CSleep(1); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto next_tick = std::min(next_draw_tick, next_game_tick); + auto now = std::chrono::steady_clock::now(); + + if (next_tick > now) { + std::this_thread::sleep_for(next_tick - now); + } } } } diff --git a/src/video/dedicated_v.cpp b/src/video/dedicated_v.cpp index 7b1abbadd7..845b0bf8cf 100644 --- a/src/video/dedicated_v.cpp +++ b/src/video/dedicated_v.cpp @@ -311,9 +311,14 @@ void VideoDriver_Dedicated::MainLoop() /* Sleep longer on a dedicated server, if the game is paused and no clients connected. * That can allow the CPU to better use deep sleep states. */ if (_pause_mode != 0 && !HasClients()) { - CSleep(100); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else { - CSleep(1); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto now = std::chrono::steady_clock::now(); + + if (next_game_tick > now) { + std::this_thread::sleep_for(next_game_tick - now); + } } } } diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index a8d8fc3e10..7c04b8743e 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -824,10 +824,17 @@ void VideoDriver_SDL::LoopOnce() /* Emscripten is running an event-based mainloop; there is already some * downtime between each iteration, so no need to sleep. */ #ifndef __EMSCRIPTEN__ + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ if (!_fast_forward || _pause_mode) { - if (_draw_mutex != nullptr) draw_lock.unlock(); - CSleep(1); - if (_draw_mutex != nullptr) draw_lock.lock(); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto next_tick = std::min(next_draw_tick, next_game_tick); + auto now = std::chrono::steady_clock::now(); + + if (next_tick > now) { + if (_draw_mutex != nullptr) draw_lock.unlock(); + std::this_thread::sleep_for(next_tick - now); + if (_draw_mutex != nullptr) draw_lock.lock(); + } } #endif } diff --git a/src/video/sdl_v.cpp b/src/video/sdl_v.cpp index 11da9c5a31..9f28b641c1 100644 --- a/src/video/sdl_v.cpp +++ b/src/video/sdl_v.cpp @@ -782,10 +782,17 @@ void VideoDriver_SDL::MainLoop() } } + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ if (!_fast_forward || _pause_mode) { - if (_draw_mutex != nullptr) draw_lock.unlock(); - CSleep(1); - if (_draw_mutex != nullptr) draw_lock.lock(); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto next_tick = std::min(next_draw_tick, next_game_tick); + auto now = std::chrono::steady_clock::now(); + + if (next_tick > now) { + if (_draw_mutex != nullptr) draw_lock.unlock(); + std::this_thread::sleep_for(next_tick - now); + if (_draw_mutex != nullptr) draw_lock.lock(); + } } } diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 2fe5f67f0f..42fcf3f4bc 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1258,13 +1258,20 @@ void VideoDriver_Win32::MainLoop() CheckPaletteAnim(); } + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ if (!_fast_forward || _pause_mode) { - /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ - GdiFlush(); + /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */ + auto next_tick = std::min(next_draw_tick, next_game_tick); + auto now = std::chrono::steady_clock::now(); - if (_draw_mutex != nullptr) draw_lock.unlock(); - CSleep(1); - if (_draw_mutex != nullptr) draw_lock.lock(); + if (next_tick > now) { + /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ + GdiFlush(); + + if (_draw_mutex != nullptr) draw_lock.unlock(); + std::this_thread::sleep_for(next_tick - now); + if (_draw_mutex != nullptr) draw_lock.lock(); + } } }