diff --git a/src/framerate_gui.cpp b/src/framerate_gui.cpp index 0afb533f6aa1f..26a40c4516016 100644 --- a/src/framerate_gui.cpp +++ b/src/framerate_gui.cpp @@ -189,7 +189,7 @@ namespace { PerformanceData(1), // PFE_ACC_GL_AIRCRAFT PerformanceData(1), // PFE_GL_LANDSCAPE PerformanceData(1), // PFE_GL_LINKGRAPH - PerformanceData(GL_RATE), // PFE_DRAWING + PerformanceData(1000.0 / 30), // PFE_DRAWING PerformanceData(1), // PFE_ACC_DRAWWORLD PerformanceData(60.0), // PFE_VIDEO PerformanceData(1000.0 * 8192 / 44100), // PFE_SOUND @@ -468,7 +468,7 @@ struct FramerateWindow : Window { this->speed_gameloop.SetRate(gl_rate / _pf_data[PFE_GAMELOOP].expected_rate, 1.0); if (this->small) return; // in small mode, this is everything needed - this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _pf_data[PFE_DRAWING].expected_rate); + this->rate_drawing.SetRate(_pf_data[PFE_DRAWING].GetRate(), _settings_client.gui.refresh_rate); int new_active = 0; for (PerformanceElement e = PFE_FIRST; e < PFE_MAX; e++) { diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index 8fb0800ac7916..7ef9937330740 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -1188,7 +1188,7 @@ struct GenWorldStatus { StringID cls; uint current; uint total; - int timer; + std::chrono::steady_clock::time_point timer; }; static GenWorldStatus _gws; @@ -1294,7 +1294,7 @@ void PrepareGenerateWorldProgress() _gws.current = 0; _gws.total = 0; _gws.percent = 0; - _gws.timer = 0; // Forces to paint the progress window immediately + _gws.timer = std::chrono::steady_clock::now() - std::chrono::milliseconds(MODAL_PROGRESS_REDRAW_TIMEOUT * 2); // Ensure we draw on first update } /** @@ -1329,7 +1329,7 @@ static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uin } /* Don't update the screen too often. So update it once in every once in a while... */ - if (!_network_dedicated && _gws.timer != 0 && _realtime_tick - _gws.timer < MODAL_PROGRESS_REDRAW_TIMEOUT) return; + if (!_network_dedicated && std::chrono::steady_clock::now() - _gws.timer < std::chrono::milliseconds(MODAL_PROGRESS_REDRAW_TIMEOUT)) return; /* Percentage is about the number of completed tasks, so 'current - 1' */ _gws.percent = percent_table[cls] + (percent_table[cls + 1] - percent_table[cls]) * (_gws.current == 0 ? 0 : _gws.current - 1) / _gws.total; @@ -1365,7 +1365,7 @@ static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uin _modal_progress_work_mutex.lock(); _modal_progress_paint_mutex.unlock(); - _gws.timer = _realtime_tick; + _gws.timer = std::chrono::steady_clock::now(); } /** diff --git a/src/gfx.cpp b/src/gfx.cpp index 786bbb25b55c3..9f42a7b530f51 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -1470,9 +1470,8 @@ void DrawDirtyBlocks() _modal_progress_paint_mutex.unlock(); _modal_progress_work_mutex.unlock(); - /* Wait a while and update _realtime_tick so we are given the rights */ + /* Wait a while and hope the modal gives us a bit of time to draw the GUI. */ if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT); - _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT; /* Modal progress thread may need blitter access while we are waiting for it. */ VideoDriver::GetInstance()->ReleaseBlitterLock(); diff --git a/src/openttd.cpp b/src/openttd.cpp index b68f378cf65c7..728228209c81f 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -1465,7 +1465,6 @@ void GameLoop() if (_game_mode == GM_BOOTSTRAP) { /* Check for UDP stuff */ if (_network_available) NetworkBackgroundLoop(); - InputLoop(); return; } @@ -1505,8 +1504,6 @@ void GameLoop() if (!_pause_mode && HasBit(_display_opt, DO_FULL_ANIMATION)) DoPaletteAnimations(); - InputLoop(); - SoundDriver::GetInstance()->MainLoop(); MusicLoop(); } diff --git a/src/settings_type.h b/src/settings_type.h index 3c325a0cc0310..11298cfa70561 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -147,6 +147,7 @@ struct GUISettings { byte starting_colour; ///< default color scheme for the company to start a new game with bool show_newgrf_name; ///< Show the name of the NewGRF in the build vehicle window bool auto_remove_signals; ///< automatically remove signals when in the way during rail construction + uint16 refresh_rate; ///< How often we refresh the screen (time between draw-ticks). uint16 console_backlog_timeout; ///< the minimum amount of time items should be in the console backlog before they will be removed in ~3 seconds granularity. uint16 console_backlog_length; ///< the minimum amount of items in the console backlog before items will be removed. diff --git a/src/table/settings.ini b/src/table/settings.ini index 1c68e6b7313ca..7ad7e22577881 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -3354,6 +3354,16 @@ def = 100 min = 10 max = 65500 +[SDTC_VAR] +var = gui.refresh_rate +type = SLE_UINT16 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +def = 60 +min = 10 +max = 1000 +cat = SC_EXPERT +startup = true + [SDTC_BOOL] var = sound.news_ticker flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC diff --git a/src/video/allegro_v.cpp b/src/video/allegro_v.cpp index b62486a98abdf..83039fdc6e231 100644 --- a/src/video/allegro_v.cpp +++ b/src/video/allegro_v.cpp @@ -24,6 +24,7 @@ #include "../core/math_func.hpp" #include "../framerate_type.h" #include "../thread.h" +#include "../window_func.h" #include "allegro_v.h" #include @@ -445,34 +446,16 @@ void VideoDriver_Allegro::Stop() if (--_allegro_instance_count == 0) allegro_exit(); } -#if defined(UNIX) || defined(__OS2__) -# include /* gettimeofday */ - -static uint32 GetTime() -{ - struct timeval tim; - - gettimeofday(&tim, nullptr); - return tim.tv_usec / 1000 + tim.tv_sec * 1000; -} -#else -static uint32 GetTime() -{ - return GetTickCount(); -} -#endif - - void VideoDriver_Allegro::MainLoop() { - uint32 cur_ticks = GetTime(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; + auto cur_ticks = std::chrono::steady_clock::now(); + auto last_realtime_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; CheckPaletteAnim(); for (;;) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness PollEvent(); @@ -491,11 +474,32 @@ void VideoDriver_Allegro::MainLoop() _fast_forward = 0; } - cur_ticks = GetTime(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks; + } + + GameLoop(); + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks; bool old_ctrl_pressed = _ctrl_pressed; @@ -511,16 +515,22 @@ void VideoDriver_Allegro::MainLoop() if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - GameLoop(); - + InputLoop(); UpdateWindows(); CheckPaletteAnim(); + DrawSurfaceToScreen(); - } else { - CSleep(1); - NetworkDrawChatMessage(); - DrawMouseCursor(); - DrawSurfaceToScreen(); + } + + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ + if (!_fast_forward || _pause_mode) { + /* 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 4c6ce6cab2cf4..cf2512b04aaf4 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -36,6 +36,7 @@ #include "../../gfx_func.h" #include "../../thread.h" #include "../../core/random_func.hpp" +#include "../../progress.h" #include "../../settings_type.h" #include "../../window_func.h" #include "../../window_gui.h" @@ -634,19 +635,14 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) /** Main game loop. */ void VideoDriver_Cocoa::GameLoop() { - uint32 cur_ticks = GetTick(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; - -#ifdef _DEBUG - uint32 et0 = GetTick(); - uint32 st = 0; -#endif + auto cur_ticks = std::chrono::steady_clock::now(); + auto last_realtime_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; for (;;) { @autoreleasepool { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness while (this->PollEvent()) {} @@ -669,11 +665,32 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) _fast_forward = 0; } - cur_ticks = GetTick(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks; + } + + ::GameLoop(); + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks; bool old_ctrl_pressed = _ctrl_pressed; @@ -682,34 +699,25 @@ static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height) if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - ::GameLoop(); - + InputLoop(); UpdateWindows(); this->CheckPaletteAnim(); - this->Draw(); - } else { -#ifdef _DEBUG - uint32 st0 = GetTick(); -#endif - CSleep(1); -#ifdef _DEBUG - st += GetTick() - st0; -#endif - NetworkDrawChatMessage(); - DrawMouseCursor(); + this->Draw(); } - } - } -#ifdef _DEBUG - uint32 et = GetTick(); + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ + if (!_fast_forward || _pause_mode) { + /* 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(); - DEBUG(driver, 1, "cocoa_v: nextEventMatchingMask took %i ms total", _tEvent); - DEBUG(driver, 1, "cocoa_v: game loop took %i ms total (%i ms without sleep)", et - et0, et - et0 - st); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop total) is %f%%", (double)_tEvent / (double)(et - et0) * 100); - DEBUG(driver, 1, "cocoa_v: (nextEventMatchingMask total)/(game loop without sleep total) is %f%%", (double)_tEvent / (double)(et - et0 - st) * 100); -#endif + 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 d237e2d9e39fb..53351458a19dd 100644 --- a/src/video/dedicated_v.cpp +++ b/src/video/dedicated_v.cpp @@ -21,6 +21,7 @@ #include "../core/random_func.hpp" #include "../saveload/saveload.h" #include "../thread.h" +#include "../window_func.h" #include "dedicated_v.h" #ifdef __OS2__ @@ -195,14 +196,6 @@ static bool InputWaiting() return select(STDIN + 1, &readfds, nullptr, nullptr, &tv) > 0; } -static uint32 GetTime() -{ - struct timeval tim; - - gettimeofday(&tim, nullptr); - return tim.tv_usec / 1000 + tim.tv_sec * 1000; -} - #else static bool InputWaiting() @@ -210,11 +203,6 @@ static bool InputWaiting() return WaitForSingleObject(_hInputReady, 1) == WAIT_OBJECT_0; } -static uint32 GetTime() -{ - return GetTickCount(); -} - #endif static void DedicatedHandleKeyInput() @@ -248,8 +236,9 @@ static void DedicatedHandleKeyInput() void VideoDriver_Dedicated::MainLoop() { - uint32 cur_ticks = GetTime(); - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; + auto cur_ticks = std::chrono::steady_clock::now(); + auto last_realtime_tick = cur_ticks; + auto next_game_tick = cur_ticks; /* Signal handlers */ #if defined(UNIX) @@ -290,17 +279,30 @@ void VideoDriver_Dedicated::MainLoop() } while (!_exit_game) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness if (!_dedicated_forks) DedicatedHandleKeyInput(); - cur_ticks = GetTime(); - _realtime_tick += cur_ticks - prev_cur_ticks; - if (cur_ticks >= next_tick || cur_ticks < prev_cur_ticks || _ddc_fastforward) { - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || _ddc_fastforward) { + if (_ddc_fastforward) { + next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks; + } GameLoop(); + InputLoop(); UpdateWindows(); } @@ -309,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/null_v.cpp b/src/video/null_v.cpp index 49f39415373bd..35dd7b0752591 100644 --- a/src/video/null_v.cpp +++ b/src/video/null_v.cpp @@ -10,6 +10,7 @@ #include "../stdafx.h" #include "../gfx_func.h" #include "../blitter/factory.hpp" +#include "../window_func.h" #include "null_v.h" #include "../safeguards.h" @@ -48,6 +49,7 @@ void VideoDriver_Null::MainLoop() for (i = 0; i < this->ticks; i++) { GameLoop(); + InputLoop(); UpdateWindows(); } } diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index d41f79d5492d6..bdfdbdaf19bb0 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -734,7 +734,6 @@ void VideoDriver_SDL::LoopOnce() uint32 mod; int numkeys; const Uint8 *keys; - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness while (PollEvent() == -1) {} @@ -768,11 +767,36 @@ void VideoDriver_SDL::LoopOnce() _fast_forward = 0; } - cur_ticks = SDL_GetTicks(); - if (SDL_TICKS_PASSED(cur_ticks, next_tick) || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + } else { + next_game_tick += std::chrono::milliseconds(MILLISECONDS_PER_TICK); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - std::chrono::milliseconds(ALLOWED_DRIFT * MILLISECONDS_PER_TICK)) next_game_tick = cur_ticks; + } + + /* The gameloop is the part that can run asynchronously. The rest + * except sleeping can't. */ + if (_draw_mutex != nullptr) draw_lock.unlock(); + GameLoop(); + if (_draw_mutex != nullptr) draw_lock.lock(); + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks; bool old_ctrl_pressed = _ctrl_pressed; @@ -787,48 +811,40 @@ void VideoDriver_SDL::LoopOnce() (keys[SDL_SCANCODE_DOWN] ? 8 : 0); if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - /* The gameloop is the part that can run asynchronously. The rest - * except sleeping can't. */ - if (_draw_mutex != nullptr) draw_lock.unlock(); - - GameLoop(); - - if (_draw_mutex != nullptr) draw_lock.lock(); - + InputLoop(); UpdateWindows(); this->CheckPaletteAnim(); - } else { - /* Release the thread while sleeping */ - if (_draw_mutex != nullptr) { - draw_lock.unlock(); - CSleep(1); - draw_lock.lock(); + + if (_draw_mutex != nullptr && !HasModalProgress()) { + _draw_signal->notify_one(); } else { + Paint(); + } + } + /* Emscripten is running an event-based mainloop; there is already some * downtime between each iteration, so no need to sleep. */ #ifndef __EMSCRIPTEN__ - CSleep(1); -#endif + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ + if (!_fast_forward || _pause_mode) { + /* 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(); } - - NetworkDrawChatMessage(); - DrawMouseCursor(); - } - - /* End of the critical part. */ - if (_draw_mutex != nullptr && !HasModalProgress()) { - _draw_signal->notify_one(); - } else { - /* Oh, we didn't have threads, then just draw unthreaded */ - Paint(); } +#endif } void VideoDriver_SDL::MainLoop() { - cur_ticks = SDL_GetTicks(); - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + last_realtime_tick = cur_ticks; + next_game_tick = cur_ticks; this->CheckPaletteAnim(); diff --git a/src/video/sdl2_v.h b/src/video/sdl2_v.h index d1c4d957cae32..b1afb6d96266b 100644 --- a/src/video/sdl2_v.h +++ b/src/video/sdl2_v.h @@ -62,9 +62,10 @@ class VideoDriver_SDL : public VideoDriver { */ bool edit_box_focused; - uint32 cur_ticks; - uint32 last_cur_ticks; - uint32 next_tick; + std::chrono::steady_clock::time_point cur_ticks; + std::chrono::steady_clock::time_point last_realtime_tick; + std::chrono::steady_clock::time_point next_game_tick; + std::chrono::steady_clock::time_point next_draw_tick; int startup_display; std::thread draw_thread; diff --git a/src/video/sdl_v.cpp b/src/video/sdl_v.cpp index f67d4680125e7..7c75ebfeaaa1b 100644 --- a/src/video/sdl_v.cpp +++ b/src/video/sdl_v.cpp @@ -21,6 +21,7 @@ #include "../core/math_func.hpp" #include "../fileio_func.h" #include "../framerate_type.h" +#include "../window_func.h" #include "sdl_v.h" #include #include @@ -648,9 +649,10 @@ void VideoDriver_SDL::Stop() void VideoDriver_SDL::MainLoop() { - uint32 cur_ticks = SDL_GetTicks(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; + auto cur_ticks = std::chrono::steady_clock::now(); + auto last_realtime_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; uint32 mod; int numkeys; Uint8 *keys; @@ -690,7 +692,6 @@ void VideoDriver_SDL::MainLoop() DEBUG(driver, 1, "SDL: using %sthreads", _draw_threaded ? "" : "no "); for (;;) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping InteractiveRandom(); // randomness while (PollEvent() == -1) {} @@ -719,11 +720,36 @@ void VideoDriver_SDL::MainLoop() _fast_forward = 0; } - cur_ticks = SDL_GetTicks(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks; + } + + /* The gameloop is the part that can run asynchronously. The rest + * except sleeping can't. */ + if (_draw_mutex != nullptr) draw_lock.unlock(); + GameLoop(); + if (_draw_mutex != nullptr) draw_lock.lock(); + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks; bool old_ctrl_pressed = _ctrl_pressed; @@ -745,33 +771,29 @@ void VideoDriver_SDL::MainLoop() #endif if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - /* The gameloop is the part that can run asynchronously. The rest - * except sleeping can't. */ - if (_draw_mutex != nullptr) draw_lock.unlock(); - - GameLoop(); - - if (_draw_mutex != nullptr) draw_lock.lock(); - + InputLoop(); UpdateWindows(); _local_palette = _cur_palette; - } else { - /* Release the thread while sleeping */ - if (_draw_mutex != nullptr) draw_lock.unlock(); - CSleep(1); - if (_draw_mutex != nullptr) draw_lock.lock(); - NetworkDrawChatMessage(); - DrawMouseCursor(); + if (_draw_mutex != nullptr && !HasModalProgress()) { + _draw_signal->notify_one(); + } else { + CheckPaletteAnim(); + DrawSurfaceToScreen(); + } } - /* End of the critical part. */ - if (_draw_mutex != nullptr && !HasModalProgress()) { - _draw_signal->notify_one(); - } else { - /* Oh, we didn't have threads, then just draw unthreaded */ - CheckPaletteAnim(); - DrawSurfaceToScreen(); + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ + if (!_fast_forward || _pause_mode) { + /* 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/video_driver.hpp b/src/video/video_driver.hpp index bc445d92d1b45..732050c769cd0 100644 --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -13,7 +13,9 @@ #include "../driver.h" #include "../core/geometry_type.hpp" #include "../core/math_func.hpp" +#include "../settings_type.h" #include "../zoom_type.h" +#include #include extern std::string _ini_videodriver; @@ -126,6 +128,8 @@ class VideoDriver : public Driver { } protected: + const uint ALLOWED_DRIFT = 5; ///< How many times videodriver can miss deadlines without it being overly compensated. + /** * Get the resolution of the main screen. */ @@ -151,6 +155,16 @@ class VideoDriver : public Driver { _cur_resolution.height = ClampU(res.height * 3 / 4, DEFAULT_WINDOW_HEIGHT, UINT16_MAX / 2); } } + + std::chrono::steady_clock::duration GetGameInterval() + { + return std::chrono::milliseconds(MILLISECONDS_PER_TICK); + } + + std::chrono::steady_clock::duration GetDrawInterval() + { + return std::chrono::microseconds(1000000 / _settings_client.gui.refresh_rate); + } }; #endif /* VIDEO_VIDEO_DRIVER_HPP */ diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 8ccdd803ef2b4..4ffbc9a211bc4 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1133,9 +1133,10 @@ static void CheckPaletteAnim() void VideoDriver_Win32::MainLoop() { MSG mesg; - uint32 cur_ticks = GetTickCount(); - uint32 last_cur_ticks = cur_ticks; - uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK; + auto cur_ticks = std::chrono::steady_clock::now(); + auto last_realtime_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; std::thread draw_thread; std::unique_lock draw_lock; @@ -1176,8 +1177,6 @@ void VideoDriver_Win32::MainLoop() CheckPaletteAnim(); for (;;) { - uint32 prev_cur_ticks = cur_ticks; // to check for wrapping - while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) { InteractiveRandom(); // randomness /* Convert key messages to char messages if we want text input. */ @@ -1198,11 +1197,39 @@ void VideoDriver_Win32::MainLoop() _fast_forward = 0; } - cur_ticks = GetTickCount(); - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) { - _realtime_tick += cur_ticks - last_cur_ticks; - last_cur_ticks = cur_ticks; - next_tick = cur_ticks + MILLISECONDS_PER_TICK; + cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - last_realtime_tick); + _realtime_tick += delta.count(); + last_realtime_tick += delta; + } + + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks; + } + + /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ + GdiFlush(); + + /* The game loop is the part that can run asynchronously. + * The rest except sleeping can't. */ + if (_draw_threaded) draw_lock.unlock(); + GameLoop(); + if (_draw_threaded) draw_lock.lock(); + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks; bool old_ctrl_pressed = _ctrl_pressed; @@ -1222,30 +1249,30 @@ void VideoDriver_Win32::MainLoop() if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); + if (_force_full_redraw) MarkWholeScreenDirty(); + /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ GdiFlush(); - /* The game loop is the part that can run asynchronously. - * The rest except sleeping can't. */ - if (_draw_threaded) draw_lock.unlock(); - GameLoop(); - if (_draw_threaded) draw_lock.lock(); - - if (_force_full_redraw) MarkWholeScreenDirty(); - + InputLoop(); UpdateWindows(); CheckPaletteAnim(); - } else { - /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ - GdiFlush(); + } - /* Release the thread while sleeping */ - if (_draw_threaded) draw_lock.unlock(); - CSleep(1); - if (_draw_threaded) draw_lock.lock(); + /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */ + if (!_fast_forward || _pause_mode) { + /* 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(); - NetworkDrawChatMessage(); - DrawMouseCursor(); + 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(); + } } }