OpenTTD Source 20250522-master-g467f832c2f
allegro_v.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
15#ifdef WITH_ALLEGRO
16
17#include "../stdafx.h"
18#include "../openttd.h"
19#include "../error_func.h"
20#include "../gfx_func.h"
21#include "../blitter/factory.hpp"
22#include "../core/random_func.hpp"
23#include "../core/math_func.hpp"
24#include "../framerate_type.h"
25#include "../progress.h"
26#include "../thread.h"
27#include "../window_func.h"
28#include "allegro_v.h"
29#include <allegro.h>
30
31#include "../safeguards.h"
32
33#ifdef _DEBUG
34/* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
35 * be triggered, so rereplace the signals and make the debugger useful. */
36#include <signal.h>
37#endif
38
39static FVideoDriver_Allegro iFVideoDriver_Allegro;
40
41static BITMAP *_allegro_screen;
42
43static PointDimension _dirty_rects[100];
44static size_t _num_dirty_rects;
46
47void VideoDriver_Allegro::MakeDirty(int left, int top, int width, int height)
48{
49 if (_num_dirty_rects < std::size(_dirty_rects)) {
50 _dirty_rects[_num_dirty_rects].x = left;
51 _dirty_rects[_num_dirty_rects].y = top;
52 _dirty_rects[_num_dirty_rects].width = width;
53 _dirty_rects[_num_dirty_rects].height = height;
54 }
55 _num_dirty_rects++;
56}
57
59{
61
62 size_t n = _num_dirty_rects;
63 if (n == 0) return;
64
65 _num_dirty_rects = 0;
66 if (n > std::size(_dirty_rects)) {
67 blit(_allegro_screen, screen, 0, 0, 0, 0, _allegro_screen->w, _allegro_screen->h);
68 return;
69 }
70
71 for (size_t i = 0; i < n; i++) {
72 blit(_allegro_screen, screen, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].width, _dirty_rects[i].height);
73 }
74}
75
76
77static void UpdatePalette(uint start, uint count)
78{
79 static PALETTE pal;
80
81 uint end = start + count;
82 for (uint i = start; i != end; i++) {
83 pal[i].r = _local_palette.palette[i].r / 4;
84 pal[i].g = _local_palette.palette[i].g / 4;
85 pal[i].b = _local_palette.palette[i].b / 4;
86 pal[i].filler = 0;
87 }
88
89 set_palette_range(pal, start, end - 1, 1);
90}
91
92static void InitPalette()
93{
94 UpdatePalette(0, 256);
95}
96
98{
99 if (!CopyPalette(_local_palette)) return;
100
102
103 switch (blitter->UsePaletteAnimation()) {
106 break;
107
110 break;
111
113 break;
114
115 default:
116 NOT_REACHED();
117 }
118}
119
120static const Dimension default_resolutions[] = {
121 { 640, 480},
122 { 800, 600},
123 {1024, 768},
124 {1152, 864},
125 {1280, 800},
126 {1280, 960},
127 {1280, 1024},
128 {1400, 1050},
129 {1600, 1200},
130 {1680, 1050},
131 {1920, 1200}
132};
133
134static void GetVideoModes()
135{
136 /* Need to set a gfx_mode as there is NO other way to autodetect for
137 * cards ourselves... and we need a card to get the modes. */
138 set_gfx_mode(_fullscreen ? GFX_AUTODETECT_FULLSCREEN : GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
139
140 _resolutions.clear();
141
142 GFX_MODE_LIST *mode_list = get_gfx_mode_list(gfx_driver->id);
143 if (mode_list == nullptr) {
144 _resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
145 return;
146 }
147
148 GFX_MODE *modes = mode_list->mode;
149
150 for (int i = 0; modes[i].bpp != 0; i++) {
151 uint w = modes[i].width;
152 uint h = modes[i].height;
153 if (w < 640 || h < 480) continue;
154 if (std::ranges::find(_resolutions, Dimension(w, h)) != _resolutions.end()) continue;
155 _resolutions.emplace_back(w, h);
156 }
157
158 SortResolutions();
159
160 destroy_gfx_mode_list(mode_list);
161}
162
163static void GetAvailableVideoMode(uint *w, uint *h)
164{
165 /* No video modes, so just try it and see where it ends */
166 if (_resolutions.empty()) return;
167
168 /* is the wanted mode among the available modes? */
169 if (std::ranges::find(_resolutions, Dimension(*w, *h)) != _resolutions.end()) return;
170
171 /* use the closest possible resolution */
172 uint best = 0;
173 uint delta = Delta(_resolutions[0].width, *w) * Delta(_resolutions[0].height, *h);
174 for (uint i = 1; i != _resolutions.size(); ++i) {
175 uint newdelta = Delta(_resolutions[i].width, *w) * Delta(_resolutions[i].height, *h);
176 if (newdelta < delta) {
177 best = i;
178 delta = newdelta;
179 }
180 }
181 *w = _resolutions[best].width;
182 *h = _resolutions[best].height;
183}
184
185static bool CreateMainSurface(uint w, uint h)
186{
188 if (bpp == 0) UserError("Can't use a blitter that blits 0 bpp for normal visuals");
189 set_color_depth(bpp);
190
191 GetAvailableVideoMode(&w, &h);
192 if (set_gfx_mode(_fullscreen ? GFX_AUTODETECT_FULLSCREEN : GFX_AUTODETECT_WINDOWED, w, h, 0, 0) != 0) {
193 Debug(driver, 0, "Allegro: Couldn't allocate a window to draw on '{}'", allegro_error);
194 return false;
195 }
196
197 /* The size of the screen might be bigger than the part we can actually draw on!
198 * So calculate the size based on the top, bottom, left and right */
199 _allegro_screen = create_bitmap_ex(bpp, screen->cr - screen->cl, screen->cb - screen->ct);
200 _screen.width = _allegro_screen->w;
201 _screen.height = _allegro_screen->h;
202 _screen.pitch = ((uint8_t*)screen->line[1] - (uint8_t*)screen->line[0]) / (bpp / 8);
203 _screen.dst_ptr = _allegro_screen->line[0];
204
205 /* Initialise the screen so we don't blit garbage to the screen */
206 std::fill_n(static_cast<std::byte *>(_screen.dst_ptr), static_cast<size_t>(_screen.height) * _screen.pitch, static_cast<std::byte>(0));
207
208 /* Set the mouse at the place where we expect it */
209 poll_mouse();
210 _cursor.pos.x = mouse_x;
211 _cursor.pos.y = mouse_y;
212
214
215 InitPalette();
216
217 std::string caption = VideoDriver::GetCaption();
218 set_window_title(caption.c_str());
219
220 enable_hardware_cursor();
221 select_mouse_cursor(MOUSE_CURSOR_ARROW);
222 show_mouse(_allegro_screen);
223
225
226 return true;
227}
228
229bool VideoDriver_Allegro::ClaimMousePointer()
230{
231 select_mouse_cursor(MOUSE_CURSOR_NONE);
232 show_mouse(nullptr);
233 disable_hardware_cursor();
234 return true;
235}
236
238{
239 std::vector<int> rates = {};
240
241 int refresh_rate = get_refresh_rate();
242 if (refresh_rate != 0) rates.push_back(refresh_rate);
243
244 return rates;
245}
246
247struct AllegroVkMapping {
248 uint16_t vk_from;
249 uint8_t vk_count;
250 uint8_t map_to;
251};
252
253#define AS(x, z) {x, 1, z}
254#define AM(x, y, z, w) {x, y - x + 1, z}
255
256static const AllegroVkMapping _vk_mapping[] = {
257 /* Pageup stuff + up/down */
258 AM(KEY_PGUP, KEY_PGDN, WKC_PAGEUP, WKC_PAGEDOWN),
259 AS(KEY_UP, WKC_UP),
260 AS(KEY_DOWN, WKC_DOWN),
261 AS(KEY_LEFT, WKC_LEFT),
262 AS(KEY_RIGHT, WKC_RIGHT),
263
264 AS(KEY_HOME, WKC_HOME),
265 AS(KEY_END, WKC_END),
266
267 AS(KEY_INSERT, WKC_INSERT),
268 AS(KEY_DEL, WKC_DELETE),
269
270 /* Map letters & digits */
271 AM(KEY_A, KEY_Z, 'A', 'Z'),
272 AM(KEY_0, KEY_9, '0', '9'),
273
274 AS(KEY_ESC, WKC_ESC),
275 AS(KEY_PAUSE, WKC_PAUSE),
276 AS(KEY_BACKSPACE, WKC_BACKSPACE),
277
278 AS(KEY_SPACE, WKC_SPACE),
279 AS(KEY_ENTER, WKC_RETURN),
280 AS(KEY_TAB, WKC_TAB),
281
282 /* Function keys */
283 AM(KEY_F1, KEY_F12, WKC_F1, WKC_F12),
284
285 /* Numeric part. */
286 AM(KEY_0_PAD, KEY_9_PAD, '0', '9'),
287 AS(KEY_SLASH_PAD, WKC_NUM_DIV),
288 AS(KEY_ASTERISK, WKC_NUM_MUL),
289 AS(KEY_MINUS_PAD, WKC_NUM_MINUS),
290 AS(KEY_PLUS_PAD, WKC_NUM_PLUS),
291 AS(KEY_ENTER_PAD, WKC_NUM_ENTER),
292 AS(KEY_DEL_PAD, WKC_DELETE),
293
294 /* Other non-letter keys */
295 AS(KEY_SLASH, WKC_SLASH),
296 AS(KEY_SEMICOLON, WKC_SEMICOLON),
297 AS(KEY_EQUALS, WKC_EQUALS),
298 AS(KEY_OPENBRACE, WKC_L_BRACKET),
299 AS(KEY_BACKSLASH, WKC_BACKSLASH),
300 AS(KEY_CLOSEBRACE, WKC_R_BRACKET),
301
302 AS(KEY_QUOTE, WKC_SINGLEQUOTE),
303 AS(KEY_COMMA, WKC_COMMA),
304 AS(KEY_MINUS, WKC_MINUS),
305 AS(KEY_STOP, WKC_PERIOD),
306 AS(KEY_TILDE, WKC_BACKQUOTE),
307};
308
309static uint32_t ConvertAllegroKeyIntoMy(char32_t *character)
310{
311 int scancode;
312 int unicode = ureadkey(&scancode);
313
314 uint key = 0;
315
316 for (const auto &map : _vk_mapping) {
317 if (IsInsideBS(scancode, map.vk_from, map.vk_count)) {
318 key = scancode - map.vk_from + map.map_to;
319 break;
320 }
321 }
322
323 if (key_shifts & KB_SHIFT_FLAG) key |= WKC_SHIFT;
324 if (key_shifts & KB_CTRL_FLAG) key |= WKC_CTRL;
325 if (key_shifts & KB_ALT_FLAG) key |= WKC_ALT;
326#if 0
327 Debug(driver, 0, "Scancode character pressed {}", scancode);
328 Debug(driver, 0, "Unicode character pressed {}", unicode);
329#endif
330
331 *character = unicode;
332 return key;
333}
334
335static const uint LEFT_BUTTON = 0;
336static const uint RIGHT_BUTTON = 1;
337
339{
340 poll_mouse();
341
342 bool mouse_action = false;
343
344 /* Mouse buttons */
345 static int prev_button_state;
346 if (prev_button_state != mouse_b) {
347 uint diff = prev_button_state ^ mouse_b;
348 while (diff != 0) {
349 uint button = FindFirstBit(diff);
350 ClrBit(diff, button);
351 if (HasBit(mouse_b, button)) {
352 /* Pressed mouse button */
353 if (_rightclick_emulate && (key_shifts & KB_CTRL_FLAG)) {
354 button = RIGHT_BUTTON;
355 ClrBit(diff, RIGHT_BUTTON);
356 }
357 switch (button) {
358 case LEFT_BUTTON:
359 _left_button_down = true;
360 break;
361
362 case RIGHT_BUTTON:
363 _right_button_down = true;
365 break;
366
367 default:
368 /* ignore rest */
369 break;
370 }
371 } else {
372 /* Released mouse button */
374 _right_button_down = false;
375 _left_button_down = false;
376 _left_button_clicked = false;
377 } else if (button == LEFT_BUTTON) {
378 _left_button_down = false;
379 _left_button_clicked = false;
380 } else if (button == RIGHT_BUTTON) {
381 _right_button_down = false;
382 }
383 }
384 }
385 prev_button_state = mouse_b;
386 mouse_action = true;
387 }
388
389 /* Mouse movement */
390 if (_cursor.UpdateCursorPosition(mouse_x, mouse_y)) {
391 position_mouse(_cursor.pos.x, _cursor.pos.y);
392 }
393 if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true;
394
395 static int prev_mouse_z = 0;
396 if (prev_mouse_z != mouse_z) {
397 _cursor.wheel = (prev_mouse_z - mouse_z) < 0 ? -1 : 1;
398 prev_mouse_z = mouse_z;
399 mouse_action = true;
400 }
401
402 if (mouse_action) HandleMouseEvents();
403
404 poll_keyboard();
405 if ((key_shifts & KB_ALT_FLAG) && (key[KEY_ENTER] || key[KEY_F])) {
406 ToggleFullScreen(!_fullscreen);
407 } else if (keypressed()) {
408 char32_t character;
409 uint keycode = ConvertAllegroKeyIntoMy(&character);
410 HandleKeypress(keycode, character);
411 }
412
413 return false;
414}
415
420int _allegro_instance_count = 0;
421
422std::optional<std::string_view> VideoDriver_Allegro::Start(const StringList &param)
423{
424 if (_allegro_instance_count == 0 && install_allegro(SYSTEM_AUTODETECT, &errno, nullptr)) {
425 Debug(driver, 0, "allegro: install_allegro failed '{}'", allegro_error);
426 return "Failed to set up Allegro";
427 }
428 _allegro_instance_count++;
429
430 this->UpdateAutoResolution();
431
432 install_timer();
433 install_mouse();
434 install_keyboard();
435
436#if defined _DEBUG
437/* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
438 * be triggered, so rereplace the signals and make the debugger useful. */
439 signal(SIGABRT, nullptr);
440 signal(SIGSEGV, nullptr);
441#endif
442
443 GetVideoModes();
444 if (!CreateMainSurface(_cur_resolution.width, _cur_resolution.height)) {
445 return "Failed to set up Allegro video";
446 }
448 set_close_button_callback(HandleExitGameRequest);
449
450 this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
451
452 return std::nullopt;
453}
454
456{
457 if (--_allegro_instance_count == 0) allegro_exit();
458}
459
461{
462 bool old_ctrl_pressed = _ctrl_pressed;
463
464 _ctrl_pressed = !!(key_shifts & KB_CTRL_FLAG);
465 _shift_pressed = !!(key_shifts & KB_SHIFT_FLAG);
466
467 /* Speedup when pressing tab, except when using ALT+TAB
468 * to switch to another application. */
469 this->fast_forward_key_pressed = key[KEY_TAB] && (key_shifts & KB_ALT_FLAG) == 0;
470
471 /* Determine which directional keys are down. */
472 _dirkeys =
473 (key[KEY_LEFT] ? 1 : 0) |
474 (key[KEY_UP] ? 2 : 0) |
475 (key[KEY_RIGHT] ? 4 : 0) |
476 (key[KEY_DOWN] ? 8 : 0);
477
478 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
479}
480
482{
483 this->StartGameThread();
484
485 for (;;) {
486 if (_exit_game) break;
487
488 this->Tick();
489 this->SleepTillNextTick();
490 }
491
492 this->StopGameThread();
493}
494
496{
497 return CreateMainSurface(w, h);
498}
499
500bool VideoDriver_Allegro::ToggleFullscreen(bool fullscreen)
501{
502 _fullscreen = fullscreen;
503 GetVideoModes(); // get the list of available video modes
504 if (_resolutions.empty() || !this->ChangeResolution(_cur_resolution.width, _cur_resolution.height)) {
505 /* switching resolution failed, put back full_screen to original status */
506 _fullscreen ^= true;
507 return false;
508 }
509 return true;
510}
511
513{
514 return CreateMainSurface(_screen.width, _screen.height);
515}
516
517#endif /* WITH_ALLEGRO */
#define AS(ap_name, size_x, size_y, min_year, max_year, catchment, noise, maint_cost, ttdpatch_type, class_id, name, preview)
AirportSpec definition for airports with at least one depot.
Base of the Allegro video driver.
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr uint8_t FindFirstBit(T x)
Search the first set bit in a value.
constexpr T ClrBit(T &x, const uint8_t y)
Clears a bit in a variable.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
Definition factory.hpp:136
How all blitters should look like.
Definition base.hpp:29
virtual uint8_t GetScreenDepth()=0
Get the screen depth this blitter works for.
virtual Blitter::PaletteAnimation UsePaletteAnimation()=0
Check if the blitter uses palette animation at all.
virtual void PaletteAnimate(const Palette &palette)=0
Called when the 8bpp palette is changed; you should redraw all pixels on the screen that are equal to...
@ None
No palette animation.
@ Blitter
The blitter takes care of the palette animation.
@ VideoBackend
Palette animation should be done by video backend (8bpp only!)
virtual void PostResize()
Post resize event.
Definition base.hpp:205
Factory for the allegro video driver.
Definition allegro_v.h:46
RAII class for measuring simple elements of performance.
void MainLoop() override
Perform the actual drawing.
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
bool PollEvent() override
Process a single system event.
bool ChangeResolution(int w, int h) override
Change the resolution of the window.
void InputLoop() override
Handle input logic, is CTRL pressed, should we fast-forward, etc.
void MakeDirty(int left, int top, int width, int height) override
Mark a particular area dirty.
void Paint() override
Paint the window.
std::optional< std::string_view > Start(const StringList &param) override
Start this driver.
void Stop() override
Stop this driver.
void CheckPaletteAnim() override
Process any pending palette animation.
std::vector< int > GetListOfMonitorRefreshRates() override
Get a list of refresh rates of each available monitor.
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
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.
static std::string GetCaption()
Get the caption to use for the game's title bar.
void StopGameThread()
Stop the loop for the game-tick.
void UpdateAutoResolution()
Apply resolution auto-detection and clamp to sensible defaults.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
bool GetDriverParamBool(const StringList &parm, std::string_view name)
Get a boolean parameter the list of parameters.
Definition driver.cpp:67
std::vector< Dimension > _resolutions
List of resolutions.
Definition driver.cpp:28
Dimension _cur_resolution
The current resolution.
Definition driver.cpp:29
bool _rightclick_emulate
Whether right clicking is emulated.
Definition driver.cpp:30
@ PFE_VIDEO
Speed of painting drawn video buffer.
bool _shift_pressed
Is Shift pressed?
Definition gfx.cpp:39
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:41
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:38
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition gfx.cpp:34
bool _left_button_clicked
Is left mouse button clicked?
Definition gfx.cpp:42
bool _right_button_clicked
Is right mouse button clicked?
Definition gfx.cpp:44
bool _right_button_down
Is right mouse button pressed?
Definition gfx.cpp:43
void HandleCtrlChanged()
State of CONTROL key has changed.
Definition window.cpp:2665
void GameSizeChanged()
Size of the application screen changed.
Definition main_gui.cpp:596
void HandleMouseEvents()
Handle a mouse event from the video driver.
Definition window.cpp:2922
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2609
@ WKC_BACKSLASH
\ Backslash
Definition gfx_type.h:101
@ WKC_MINUS
Definition gfx_type.h:106
@ WKC_COMMA
, Comma
Definition gfx_type.h:104
@ WKC_PERIOD
. Period
Definition gfx_type.h:105
@ WKC_EQUALS
= Equals
Definition gfx_type.h:99
@ WKC_SLASH
/ Forward slash
Definition gfx_type.h:97
@ WKC_SINGLEQUOTE
' Single quote
Definition gfx_type.h:103
@ WKC_R_BRACKET
] Right square bracket
Definition gfx_type.h:102
@ WKC_L_BRACKET
[ Left square bracket
Definition gfx_type.h:100
@ WKC_SEMICOLON
; Semicolon
Definition gfx_type.h:98
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition gfx.cpp:1535
constexpr bool IsInsideBS(const T x, const size_t base, const size_t size)
Checks if a value is between a window started at some base point.
constexpr T Delta(const T a, const T b)
Returns the (absolute) difference between two (scalar) variables.
bool CopyPalette(Palette &local_palette, bool force_copy)
Copy the current palette if the palette was updated.
Definition palette.cpp:225
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
Definition gfx.cpp:1734
Point pos
logical mouse position
Definition gfx_type.h:126
int wheel
mouse wheel movement
Definition gfx_type.h:128
Point delta
relative mouse movement in this tick
Definition gfx_type.h:127
Dimensions (a width and height) of a rectangle in 2D.
Information about the currently used palette.
Definition gfx_type.h:368
int first_dirty
The first dirty element.
Definition gfx_type.h:370
int count_dirty
The number of dirty elements.
Definition gfx_type.h:371
Colour palette[256]
Current palette. Entry 0 has to be always fully transparent!
Definition gfx_type.h:369
Specification of a rectangle with an absolute top-left coordinate and a (relative) width/height.
static Palette _local_palette
Current palette to use for drawing.
Definition win32_v.cpp:54