From c81c6e5eb7a0e991039b7662868e87a8d2fe3415 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 17 Feb 2021 15:04:46 +0100 Subject: Add: draw the screen at a steady pace, also during fast-forward During fast-forward, the game was drawing as fast as it could. This means that the fast-forward was limited also by how fast we could draw, something that people in general don't expect. To give an extreme case, if you are fully zoomed out on a busy map, fast-forward would be mostly limited because of the time it takes to draw the screen. By decoupling the draw-tick and game-tick, we can keep the pace of the draw-tick the same while speeding up the game-tick. To use the extreme case as example again, if you are fully zoomed out now, the screen only redraws 33.33 times per second, fast-forwarding or not. This means fast-forward is much more likely to go at the same speed, no matter what you are looking at. --- src/openttd.cpp | 3 --- src/video/allegro_v.cpp | 25 ++++++++++++-------- src/video/cocoa/cocoa_v.mm | 24 +++++++++++-------- src/video/dedicated_v.cpp | 2 ++ src/video/null_v.cpp | 2 ++ src/video/sdl2_v.cpp | 57 +++++++++++++++++++++------------------------- src/video/sdl2_v.h | 3 ++- src/video/sdl_v.cpp | 51 +++++++++++++++++++++-------------------- src/video/win32_v.cpp | 43 +++++++++++++++++++--------------- 9 files changed, 114 insertions(+), 96 deletions(-) diff --git a/src/openttd.cpp b/src/openttd.cpp index b68f378cf..728228209 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/video/allegro_v.cpp b/src/video/allegro_v.cpp index 493e5a81d..7a51415c7 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 @@ -449,7 +450,8 @@ void VideoDriver_Allegro::MainLoop() { auto cur_ticks = std::chrono::steady_clock::now(); auto last_realtime_tick = cur_ticks; - auto next_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; CheckPaletteAnim(); @@ -481,8 +483,14 @@ void VideoDriver_Allegro::MainLoop() last_realtime_tick += delta; } - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode)) { - next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + + GameLoop(); + } + + if (cur_ticks >= next_draw_tick) { + next_draw_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); bool old_ctrl_pressed = _ctrl_pressed; @@ -498,16 +506,15 @@ void VideoDriver_Allegro::MainLoop() if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - GameLoop(); - + InputLoop(); UpdateWindows(); CheckPaletteAnim(); + DrawSurfaceToScreen(); - } else { + } + + if (!_fast_forward || _pause_mode) { CSleep(1); - NetworkDrawChatMessage(); - DrawMouseCursor(); - DrawSurfaceToScreen(); } } } diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index c9e6768a8..d065630b7 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -636,7 +636,8 @@ void VideoDriver_Cocoa::GameLoop() { auto cur_ticks = std::chrono::steady_clock::now(); auto last_realtime_tick = cur_ticks; - auto next_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; for (;;) { @autoreleasepool { @@ -672,8 +673,14 @@ void VideoDriver_Cocoa::GameLoop() last_realtime_tick += delta; } - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode)) { - next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + + ::GameLoop(); + } + + if (cur_ticks >= next_draw_tick) { + next_draw_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); bool old_ctrl_pressed = _ctrl_pressed; @@ -682,16 +689,15 @@ void VideoDriver_Cocoa::GameLoop() if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged(); - ::GameLoop(); - + InputLoop(); UpdateWindows(); this->CheckPaletteAnim(); + this->Draw(); - } else { + } + + if (!_fast_forward || _pause_mode) { CSleep(1); - NetworkDrawChatMessage(); - DrawMouseCursor(); - this->Draw(); } } } diff --git a/src/video/dedicated_v.cpp b/src/video/dedicated_v.cpp index 06b359880..057c0daa8 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__ @@ -295,6 +296,7 @@ void VideoDriver_Dedicated::MainLoop() next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); GameLoop(); + InputLoop(); UpdateWindows(); } diff --git a/src/video/null_v.cpp b/src/video/null_v.cpp index 49f394153..35dd7b075 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 6db220470..157ffcd64 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -776,8 +776,18 @@ void VideoDriver_SDL::LoopOnce() last_realtime_tick += delta; } - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode)) { - next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + + /* 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(); + } + + if (cur_ticks >= next_draw_tick) { + next_draw_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); bool old_ctrl_pressed = _ctrl_pressed; @@ -792,48 +802,33 @@ 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 { -/* 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 + Paint(); } - - 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(); +/* Emscripten is running an event-based mainloop; there is already some + * downtime between each iteration, so no need to sleep. */ +#ifndef __EMSCRIPTEN__ + if (!_fast_forward || _pause_mode) { + if (_draw_mutex != nullptr) draw_lock.unlock(); + CSleep(1); + if (_draw_mutex != nullptr) draw_lock.lock(); } +#endif } void VideoDriver_SDL::MainLoop() { cur_ticks = std::chrono::steady_clock::now(); last_realtime_tick = cur_ticks; - next_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 6b5d3dd14..b1afb6d96 100644 --- a/src/video/sdl2_v.h +++ b/src/video/sdl2_v.h @@ -64,7 +64,8 @@ private: std::chrono::steady_clock::time_point cur_ticks; std::chrono::steady_clock::time_point last_realtime_tick; - std::chrono::steady_clock::time_point next_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 fa0294200..1c88dfbcd 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 @@ -650,7 +651,8 @@ void VideoDriver_SDL::MainLoop() { auto cur_ticks = std::chrono::steady_clock::now(); auto last_realtime_tick = cur_ticks; - auto next_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; uint32 mod; int numkeys; Uint8 *keys; @@ -727,8 +729,18 @@ void VideoDriver_SDL::MainLoop() last_realtime_tick += delta; } - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode)) { - next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + + /* 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(); + } + + if (cur_ticks >= next_draw_tick) { + next_draw_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); bool old_ctrl_pressed = _ctrl_pressed; @@ -750,33 +762,22 @@ 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 (!_fast_forward || _pause_mode) { + if (_draw_mutex != nullptr) draw_lock.unlock(); + CSleep(1); + if (_draw_mutex != nullptr) draw_lock.lock(); } } diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 57f146e5e..8a80344c4 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1135,7 +1135,8 @@ void VideoDriver_Win32::MainLoop() MSG mesg; auto cur_ticks = std::chrono::steady_clock::now(); auto last_realtime_tick = cur_ticks; - auto next_tick = cur_ticks; + auto next_game_tick = cur_ticks; + auto next_draw_tick = cur_ticks; std::thread draw_thread; std::unique_lock draw_lock; @@ -1205,8 +1206,21 @@ void VideoDriver_Win32::MainLoop() last_realtime_tick += delta; } - if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode)) { - next_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) { + next_game_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); + + /* 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 (cur_ticks >= next_draw_tick) { + next_draw_tick = cur_ticks + std::chrono::milliseconds(MILLISECONDS_PER_TICK); bool old_ctrl_pressed = _ctrl_pressed; @@ -1226,30 +1240,23 @@ 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 { + } + + if (!_fast_forward || _pause_mode) { /* 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(); + if (_draw_mutex != nullptr) draw_lock.unlock(); CSleep(1); - if (_draw_threaded) draw_lock.lock(); - - NetworkDrawChatMessage(); - DrawMouseCursor(); + if (_draw_mutex != nullptr) draw_lock.lock(); } } -- cgit v1.2.3-70-g09d2