OpenTTD Source 20250612-master-gb012d9e3dc
video_driver.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include "../core/random_func.hpp"
12#include "../network/network.h"
13#include "../blitter/factory.hpp"
14#include "../debug.h"
15#include "../driver.h"
16#include "../fontcache.h"
17#include "../gfx_func.h"
18#include "../gfxinit.h"
19#include "../progress.h"
20#include "../rev.h"
21#include "../thread.h"
22#include "../window_func.h"
23#include "video_driver.hpp"
24
25#include "../safeguards.h"
26
29
30void VideoDriver::GameLoop()
31{
32 this->next_game_tick += this->GetGameInterval();
33
34 /* Avoid next_game_tick getting behind more and more if it cannot keep up. */
35 auto now = std::chrono::steady_clock::now();
36 if (this->next_game_tick < now - ALLOWED_DRIFT * this->GetGameInterval()) this->next_game_tick = now;
37
38 {
39 std::lock_guard<std::mutex> lock(this->game_state_mutex);
40
41 ::GameLoop();
42 }
43}
44
45void VideoDriver::GameThread()
46{
47 while (!_exit_game) {
48 this->GameLoop();
49
50 auto now = std::chrono::steady_clock::now();
51 if (this->next_game_tick > now) {
52 std::this_thread::sleep_for(this->next_game_tick - now);
53 } else {
54 /* Ensure we yield this thread if drawings wants to take a lock on
55 * the game state. This is mainly because most OSes have an
56 * optimization that if you unlock/lock a mutex in the same thread
57 * quickly, it will never context switch even if there is another
58 * thread waiting to take the lock on the same mutex. */
59 std::lock_guard<std::mutex> lock(this->game_thread_wait_mutex);
60 }
61 }
62}
63
69{
70 /* If we are not called from the game-thread, ignore this request. */
71 if (std::this_thread::get_id() != this->game_thread.get_id()) return;
72
73 this->game_state_mutex.unlock();
74
75 {
76 /* See GameThread() for more details on this lock. */
77 std::lock_guard<std::mutex> lock(this->game_thread_wait_mutex);
78 }
79
80 this->game_state_mutex.lock();
81}
82
83/* static */ void VideoDriver::GameThreadThunk(VideoDriver *drv)
84{
85 drv->GameThread();
86}
87
89{
90 if (this->is_game_threaded) {
91 this->is_game_threaded = StartNewThread(&this->game_thread, "ottd:game", &VideoDriver::GameThreadThunk, this);
92 }
93
94 Debug(driver, 1, "using {}thread for game-loop", this->is_game_threaded ? "" : "no ");
95}
96
98{
99 if (!this->is_game_threaded) return;
100
101 this->game_thread.join();
102}
103
105{
106 if (!this->is_game_threaded && std::chrono::steady_clock::now() >= this->next_game_tick) {
107 this->GameLoop();
108
109 /* For things like dedicated server, don't run a separate draw-tick. */
110 if (!this->HasGUI()) {
111 ::InputLoop();
113 this->next_draw_tick = this->next_game_tick;
114 }
115 }
116
117 auto now = std::chrono::steady_clock::now();
118 if (this->HasGUI() && now >= this->next_draw_tick) {
119 this->next_draw_tick += this->GetDrawInterval();
120 /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
121 if (this->next_draw_tick < now - ALLOWED_DRIFT * this->GetDrawInterval()) this->next_draw_tick = now;
122
123 /* Locking video buffer can block (especially with vsync enabled), do it before taking game state lock. */
124 this->LockVideoBuffer();
125
126 {
127 /* Tell the game-thread to stop so we can have a go. */
128 std::lock_guard<std::mutex> lock_wait(this->game_thread_wait_mutex);
129 std::lock_guard<std::mutex> lock_state(this->game_state_mutex);
130
131 /* Keep the interactive randomizer a bit more random by requesting
132 * new values when-ever we can. */
133 InteractiveRandom();
134
135 this->DrainCommandQueue();
136
137 while (this->PollEvent()) {}
138 this->InputLoop();
139
140 /* Check if the fast-forward button is still pressed. */
141 if (fast_forward_key_pressed && !_networking && _game_mode != GM_MENU) {
142 ChangeGameSpeed(true);
143 this->fast_forward_via_key = true;
144 } else if (this->fast_forward_via_key) {
145 ChangeGameSpeed(false);
146 this->fast_forward_via_key = false;
147 }
148
149 ::InputLoop();
150
151 /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
152 if (_game_mode == GM_BOOTSTRAP || _switch_mode == SM_NONE || HasModalProgress()) {
154 }
155
156 this->PopulateSystemSprites();
157 }
158
159 this->CheckPaletteAnim();
160 this->Paint();
161
162 this->UnlockVideoBuffer();
163
164 /* Wait till the first successful drawing tick before marking the driver as operational. */
165 static bool first_draw_tick = true;
166 if (first_draw_tick) {
167 first_draw_tick = false;
169 }
170 }
171}
172
174{
175 auto next_tick = this->next_draw_tick;
176 auto now = std::chrono::steady_clock::now();
177
178 if (!this->is_game_threaded) {
179 next_tick = min(next_tick, this->next_game_tick);
180 }
181
182 if (next_tick > now) {
183 std::this_thread::sleep_for(next_tick - now);
184 }
185}
186
191/* static */ std::string VideoDriver::GetCaption()
192{
193 return fmt::format("OpenTTD {}", _openttd_revision);
194}
static void MarkVideoDriverOperational()
Mark the current video driver as operational.
Definition driver.cpp:207
The base of all video drivers.
virtual void InputLoop()
Handle input logic, is CTRL pressed, should we fast-forward, etc.
virtual void PopulateSystemSprites()
Populate all sprites in cache.
bool fast_forward_key_pressed
The fast-forward key is being pressed.
void Tick()
Give the video-driver a tick.
void SleepTillNextTick()
Sleep till the next tick is about to happen.
void StartGameThread()
Start the loop for game-tick.
virtual bool HasGUI() const
Whether the driver has a graphical user interface with the end user.
static std::string GetCaption()
Get the caption to use for the game's title bar.
void StopGameThread()
Stop the loop for the game-tick.
virtual bool PollEvent()
Process a single system event.
virtual void Paint()
Paint the window.
virtual void UnlockVideoBuffer()
Unlock a previously locked video buffer.
bool fast_forward_via_key
The fast-forward was enabled by key press.
virtual void CheckPaletteAnim()
Process any pending palette animation.
virtual bool LockVideoBuffer()
Make sure the video buffer is ready for drawing.
void GameLoopPause()
Pause the game-loop for a bit, releasing the game-state lock.
void DrainCommandQueue()
Execute all queued commands.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
SwitchMode _switch_mode
The next mainloop command.
Definition gfx.cpp:49
void UpdateWindows()
Update the continuously changing contents of the windows, such as the viewports.
Definition window.cpp:3095
bool _networking
are we in networking mode?
Definition network.cpp:67
bool HasModalProgress()
Check if we are currently in a modal progress state.
Definition progress.h:17
bool StartNewThread(std::thread *thr, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition thread.h:47
bool _video_vsync
Whether we should use vsync (only if active video driver supports HW acceleration).
bool _video_hw_accel
Whether to consider hardware accelerated video drivers on startup.
Base of all video drivers.
std::mutex lock
synchronization for playback status fields
Definition win32_m.cpp:35