OpenTTD Source 20241224-master-gf74b0cf984
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
28void 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
43void 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();
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()) {
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:3056
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