diff --git a/src/video/CMakeLists.txt b/src/video/CMakeLists.txt --- a/src/video/CMakeLists.txt +++ b/src/video/CMakeLists.txt @@ -31,5 +31,6 @@ add_files( dedicated_v.h null_v.cpp null_v.h + video_driver.cpp video_driver.hpp ) diff --git a/src/video/allegro_v.cpp b/src/video/allegro_v.cpp --- a/src/video/allegro_v.cpp +++ b/src/video/allegro_v.cpp @@ -479,62 +479,16 @@ void VideoDriver_Allegro::InputLoop() void VideoDriver_Allegro::MainLoop() { - 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 (;;) { InteractiveRandom(); // randomness PollEvent(); if (_exit_game) return; - 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; - - this->InputLoop(); - ::InputLoop(); - UpdateWindows(); - this->CheckPaletteAnim(); - + if (this->Tick()) { this->Paint(); } - - /* 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); - } - } + this->SleepTillNextTick(); } } diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -656,11 +656,6 @@ void VideoDriver_Cocoa::InputLoop() /** Main game loop. */ void VideoDriver_Cocoa::GameLoop() { - 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 { @@ -674,52 +669,10 @@ void VideoDriver_Cocoa::GameLoop() break; } - - 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; - - this->InputLoop(); - ::InputLoop(); - UpdateWindows(); - this->CheckPaletteAnim(); - + if (this->Tick()) { this->Paint(); } - - /* 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); - } - } + this->SleepTillNextTick(); } } } diff --git a/src/video/dedicated_v.cpp b/src/video/dedicated_v.cpp --- a/src/video/dedicated_v.cpp +++ b/src/video/dedicated_v.cpp @@ -236,10 +236,6 @@ static void DedicatedHandleKeyInput() void VideoDriver_Dedicated::MainLoop() { - 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) signal(SIGTERM, DedicatedSignalHandler); @@ -283,43 +279,8 @@ void VideoDriver_Dedicated::MainLoop() if (!_dedicated_forks) DedicatedHandleKeyInput(); - 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(); - } - - /* Don't sleep when fast forwarding (for desync debugging) */ - if (!_ddc_fastforward) { - /* 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()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } else { - /* 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); - } - } - } + _fast_forward = _ddc_fastforward; + this->Tick(); + this->SleepTillNextTick(); } } diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -786,42 +786,7 @@ void VideoDriver_SDL::LoopOnce() return; } - 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. */ - this->UnlockVideoBuffer(); - GameLoop(); - this->LockVideoBuffer(); - } - - /* 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; - - this->InputLoop(); - ::InputLoop(); - UpdateWindows(); - this->CheckPaletteAnim(); - + if (VideoDriver::Tick()) { if (_draw_mutex != nullptr && !HasModalProgress()) { _draw_signal->notify_one(); } else { @@ -832,27 +797,12 @@ 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) { - /* 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) { - this->UnlockVideoBuffer(); - std::this_thread::sleep_for(next_tick - now); - this->LockVideoBuffer(); - } - } + this->SleepTillNextTick(); #endif } void VideoDriver_SDL::MainLoop() { - cur_ticks = std::chrono::steady_clock::now(); - last_realtime_tick = cur_ticks; - next_game_tick = cur_ticks; - if (_draw_threaded) { /* Initialise the mutex first, because that's the thing we *need* * directly in the newly created thread. */ diff --git a/src/video/sdl2_v.h b/src/video/sdl2_v.h --- a/src/video/sdl2_v.h +++ b/src/video/sdl2_v.h @@ -67,11 +67,6 @@ private: */ bool edit_box_focused; - 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; std::unique_lock draw_lock; diff --git a/src/video/sdl_v.cpp b/src/video/sdl_v.cpp --- a/src/video/sdl_v.cpp +++ b/src/video/sdl_v.cpp @@ -704,11 +704,6 @@ void VideoDriver_SDL::InputLoop() void VideoDriver_SDL::MainLoop() { - 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; if (_draw_threaded) { /* Initialise the mutex first, because that's the thing we *need* @@ -746,61 +741,14 @@ void VideoDriver_SDL::MainLoop() while (PollEvent() == -1) {} if (_exit_game) break; - 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. */ - this->UnlockVideoBuffer(); - GameLoop(); - this->LockVideoBuffer(); - } - - /* 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; - - this->InputLoop(); - ::InputLoop(); - UpdateWindows(); - this->CheckPaletteAnim(); - + if (this->Tick()) { if (_draw_mutex != nullptr && !HasModalProgress()) { _draw_signal->notify_one(); } else { this->Paint(); } } - - /* 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) { - this->UnlockVideoBuffer(); - std::this_thread::sleep_for(next_tick - now); - this->LockVideoBuffer(); - } - } + this->SleepTillNextTick(); } if (_draw_mutex != nullptr) { diff --git a/src/video/video_driver.cpp b/src/video/video_driver.cpp new file mode 100644 --- /dev/null +++ b/src/video/video_driver.cpp @@ -0,0 +1,83 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file video_driver.cpp Common code between video driver implementations. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../gfx_func.h" +#include "../progress.h" +#include "../thread.h" +#include "../window_func.h" +#include "video_driver.hpp" + +bool VideoDriver::Tick() +{ + auto cur_ticks = std::chrono::steady_clock::now(); + + /* If more than a millisecond has passed, increase the _realtime_tick. */ + if (cur_ticks - this->last_realtime_tick > std::chrono::milliseconds(1)) { + auto delta = std::chrono::duration_cast(cur_ticks - this->last_realtime_tick); + _realtime_tick += delta.count(); + this->last_realtime_tick += delta; + } + + if (cur_ticks >= this->next_game_tick || (_fast_forward && !_pause_mode)) { + if (_fast_forward && !_pause_mode) { + this->next_game_tick = cur_ticks + this->GetGameInterval(); + } else { + this->next_game_tick += this->GetGameInterval(); + /* Avoid next_game_tick getting behind more and more if it cannot keep up. */ + if (this->next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) this->next_game_tick = cur_ticks; + } + + /* The game loop is the part that can run asynchronously. + * The rest except sleeping can't. */ + this->UnlockVideoBuffer(); + ::GameLoop(); + this->LockVideoBuffer(); + + /* For things like dedicated server, don't run a separate draw-tick. */ + if (!this->HasGUI()) { + ::InputLoop(); + UpdateWindows(); + this->next_draw_tick = this->next_game_tick; + } + } + + /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */ + if (this->HasGUI() && cur_ticks >= this->next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) { + this->next_draw_tick += this->GetDrawInterval(); + /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */ + if (this->next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) this->next_draw_tick = cur_ticks; + + this->InputLoop(); + ::InputLoop(); + UpdateWindows(); + this->CheckPaletteAnim(); + + return true; + } + + return false; +} + +void VideoDriver::SleepTillNextTick() +{ + /* 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(this->next_draw_tick, this->next_game_tick); + auto now = std::chrono::steady_clock::now(); + + if (next_tick > now) { + this->UnlockVideoBuffer(); + std::this_thread::sleep_for(next_tick - now); + this->LockVideoBuffer(); + } + } +} diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -189,6 +189,17 @@ protected: */ virtual void CheckPaletteAnim() {} + /** + * Run the game for a single tick, processing boththe game-tick and draw-tick. + * @returns True if the driver should redraw the screen. + */ + bool Tick(); + + /** + * Sleep till the next tick is about to happen. + */ + void SleepTillNextTick(); + std::chrono::steady_clock::duration GetGameInterval() { return std::chrono::milliseconds(MILLISECONDS_PER_TICK); @@ -198,6 +209,10 @@ protected: { return std::chrono::microseconds(1000000 / _settings_client.gui.refresh_rate); } + + 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; }; #endif /* VIDEO_VIDEO_DRIVER_HPP */ diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1156,10 +1156,6 @@ void VideoDriver_Win32::InputLoop() void VideoDriver_Win32::MainLoop() { MSG mesg; - 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; @@ -1210,61 +1206,14 @@ void VideoDriver_Win32::MainLoop() /* Flush GDI buffer to ensure we don't conflict with the drawing thread. */ GdiFlush(); - 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 game loop is the part that can run asynchronously. - * The rest except sleeping can't. */ - this->UnlockVideoBuffer(); - GameLoop(); - this->LockVideoBuffer(); - } - - /* 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; - - this->InputLoop(); - ::InputLoop(); - UpdateWindows(); - CheckPaletteAnim(); - + if (this->Tick()) { if (_draw_mutex != nullptr && !HasModalProgress()) { _draw_signal->notify_one(); } else { this->Paint(); } } - - /* 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) { - this->UnlockVideoBuffer(); - std::this_thread::sleep_for(next_tick - now); - this->LockVideoBuffer(); - } - } + this->SleepTillNextTick(); } if (_draw_threaded) {