OpenTTD Source  20241120-master-g6d3adc6169
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 
27 
28 void VideoDriver::GameLoop()
29 {
30  this->next_game_tick += this->GetGameInterval();
31 
32  /* Avoid next_game_tick getting behind more and more if it cannot keep up. */
33  auto now = std::chrono::steady_clock::now();
34  if (this->next_game_tick < now - ALLOWED_DRIFT * this->GetGameInterval()) this->next_game_tick = now;
35 
36  {
37  std::lock_guard<std::mutex> lock(this->game_state_mutex);
38 
39  ::GameLoop();
40  }
41 }
42 
43 void VideoDriver::GameThread()
44 {
45  while (!_exit_game) {
46  this->GameLoop();
47 
48  auto now = std::chrono::steady_clock::now();
49  if (this->next_game_tick > now) {
50  std::this_thread::sleep_for(this->next_game_tick - now);
51  } else {
52  /* Ensure we yield this thread if drawings wants to take a lock on
53  * the game state. This is mainly because most OSes have an
54  * optimization that if you unlock/lock a mutex in the same thread
55  * quickly, it will never context switch even if there is another
56  * thread waiting to take the lock on the same mutex. */
57  std::lock_guard<std::mutex> lock(this->game_thread_wait_mutex);
58  }
59  }
60 }
61 
67 {
68  /* If we are not called from the game-thread, ignore this request. */
69  if (std::this_thread::get_id() != this->game_thread.get_id()) return;
70 
71  this->game_state_mutex.unlock();
72 
73  {
74  /* See GameThread() for more details on this lock. */
75  std::lock_guard<std::mutex> lock(this->game_thread_wait_mutex);
76  }
77 
78  this->game_state_mutex.lock();
79 }
80 
81 /* static */ void VideoDriver::GameThreadThunk(VideoDriver *drv)
82 {
83  drv->GameThread();
84 }
85 
87 {
88  if (this->is_game_threaded) {
89  this->is_game_threaded = StartNewThread(&this->game_thread, "ottd:game", &VideoDriver::GameThreadThunk, this);
90  }
91 
92  Debug(driver, 1, "using {}thread for game-loop", this->is_game_threaded ? "" : "no ");
93 }
94 
96 {
97  if (!this->is_game_threaded) return;
98 
99  this->game_thread.join();
100 }
101 
103 {
104  if (!this->is_game_threaded && std::chrono::steady_clock::now() >= this->next_game_tick) {
105  this->GameLoop();
106 
107  /* For things like dedicated server, don't run a separate draw-tick. */
108  if (!this->HasGUI()) {
109  ::InputLoop();
110  ::UpdateWindows();
111  this->next_draw_tick = this->next_game_tick;
112  }
113  }
114 
115  auto now = std::chrono::steady_clock::now();
116  if (this->HasGUI() && now >= this->next_draw_tick) {
117  this->next_draw_tick += this->GetDrawInterval();
118  /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
119  if (this->next_draw_tick < now - ALLOWED_DRIFT * this->GetDrawInterval()) this->next_draw_tick = now;
120 
121  /* Locking video buffer can block (especially with vsync enabled), do it before taking game state lock. */
122  this->LockVideoBuffer();
123 
124  {
125  /* Tell the game-thread to stop so we can have a go. */
126  std::lock_guard<std::mutex> lock_wait(this->game_thread_wait_mutex);
127  std::lock_guard<std::mutex> lock_state(this->game_state_mutex);
128 
129  /* Keep the interactive randomizer a bit more random by requesting
130  * new values when-ever we can. */
131  InteractiveRandom();
132 
133  this->DrainCommandQueue();
134 
135  while (this->PollEvent()) {}
136  this->InputLoop();
137 
138  /* Check if the fast-forward button is still pressed. */
139  if (fast_forward_key_pressed && !_networking && _game_mode != GM_MENU) {
140  ChangeGameSpeed(true);
141  this->fast_forward_via_key = true;
142  } else if (this->fast_forward_via_key) {
143  ChangeGameSpeed(false);
144  this->fast_forward_via_key = false;
145  }
146 
147  ::InputLoop();
148 
149  /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
150  if (_game_mode == GM_BOOTSTRAP || _switch_mode == SM_NONE || HasModalProgress()) {
151  ::UpdateWindows();
152  }
153 
154  this->PopulateSystemSprites();
155  }
156 
157  this->CheckPaletteAnim();
158  this->Paint();
159 
160  this->UnlockVideoBuffer();
161 
162  /* Wait till the first successful drawing tick before marking the driver as operational. */
163  static bool first_draw_tick = true;
164  if (first_draw_tick) {
165  first_draw_tick = false;
167  }
168  }
169 }
170 
172 {
173  auto next_tick = this->next_draw_tick;
174  auto now = std::chrono::steady_clock::now();
175 
176  if (!this->is_game_threaded) {
177  next_tick = min(next_tick, this->next_game_tick);
178  }
179 
180  if (next_tick > now) {
181  std::this_thread::sleep_for(next_tick - now);
182  }
183 }
184 
189 /* static */ std::string VideoDriver::GetCaption()
190 {
191  return fmt::format("OpenTTD {}", _openttd_revision);
192 }
static void MarkVideoDriverOperational()
Mark the current video driver as operational.
Definition: driver.cpp:206
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,...)
Ouptut 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:3048
bool _networking
are we in networking mode?
Definition: network.cpp:65
bool HasModalProgress()
Check if we are currently in a modal progress state.
Definition: progress.h:17
bool StartNewThread(std::thread *thr, const char *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