OpenTTD Source 20260206-master-g4d4e37dbf1
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
14
15#ifdef WITH_ALLEGRO
16
17#include "../stdafx.h"
18#include "../openttd.h"
19#include "../error_func.h"
20#include "../gfx_func.h"
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#ifdef _DEBUG
32/* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
33 * be triggered, so rereplace the signals and make the debugger useful. */
34#include <signal.h>
35#endif
36
37#include "../safeguards.h"
38
39static FVideoDriver_Allegro iFVideoDriver_Allegro;
40
41static BITMAP *_allegro_screen;
42
43static PointDimension _dirty_rects[100];
44static size_t _num_dirty_rects;
45static Palette _local_palette;
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{
60 PerformanceMeasurer framerate(PFE_VIDEO);
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
101 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
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
230{
231 select_mouse_cursor(MOUSE_CURSOR_NONE);
232 show_mouse(nullptr);
233 disable_hardware_cursor();
234}
235
237{
238 std::vector<int> rates = {};
239
240 int refresh_rate = get_refresh_rate();
241 if (refresh_rate != 0) rates.push_back(refresh_rate);
242
243 return rates;
244}
245
246struct AllegroVkMapping {
247 uint16_t vk_from;
248 uint8_t vk_count;
249 uint8_t map_to;
250};
251
252#define AS(x, z) {x, 1, z}
253#define AM(x, y, z, w) {x, y - x + 1, z}
254
255static const AllegroVkMapping _vk_mapping[] = {
256 /* Pageup stuff + up/down */
257 AM(KEY_PGUP, KEY_PGDN, WKC_PAGEUP, WKC_PAGEDOWN),
258 AS(KEY_UP, WKC_UP),
259 AS(KEY_DOWN, WKC_DOWN),
260 AS(KEY_LEFT, WKC_LEFT),
261 AS(KEY_RIGHT, WKC_RIGHT),
262
263 AS(KEY_HOME, WKC_HOME),
264 AS(KEY_END, WKC_END),
265
266 AS(KEY_INSERT, WKC_INSERT),
267 AS(KEY_DEL, WKC_DELETE),
268
269 /* Map letters & digits */
270 AM(KEY_A, KEY_Z, 'A', 'Z'),
271 AM(KEY_0, KEY_9, '0', '9'),
272
273 AS(KEY_ESC, WKC_ESC),
274 AS(KEY_PAUSE, WKC_PAUSE),
275 AS(KEY_BACKSPACE, WKC_BACKSPACE),
276
277 AS(KEY_SPACE, WKC_SPACE),
278 AS(KEY_ENTER, WKC_RETURN),
279 AS(KEY_TAB, WKC_TAB),
280
281 /* Function keys */
282 AM(KEY_F1, KEY_F12, WKC_F1, WKC_F12),
283
284 /* Numeric part. */
285 AM(KEY_0_PAD, KEY_9_PAD, '0', '9'),
286 AS(KEY_SLASH_PAD, WKC_NUM_DIV),
287 AS(KEY_ASTERISK, WKC_NUM_MUL),
288 AS(KEY_MINUS_PAD, WKC_NUM_MINUS),
289 AS(KEY_PLUS_PAD, WKC_NUM_PLUS),
290 AS(KEY_ENTER_PAD, WKC_NUM_ENTER),
291 AS(KEY_DEL_PAD, WKC_DELETE),
292
293 /* Other non-letter keys */
294 AS(KEY_SLASH, WKC_SLASH),
295 AS(KEY_SEMICOLON, WKC_SEMICOLON),
296 AS(KEY_EQUALS, WKC_EQUALS),
297 AS(KEY_OPENBRACE, WKC_L_BRACKET),
298 AS(KEY_BACKSLASH, WKC_BACKSLASH),
299 AS(KEY_CLOSEBRACE, WKC_R_BRACKET),
300
301 AS(KEY_QUOTE, WKC_SINGLEQUOTE),
302 AS(KEY_COMMA, WKC_COMMA),
303 AS(KEY_MINUS, WKC_MINUS),
304 AS(KEY_STOP, WKC_PERIOD),
305 AS(KEY_TILDE, WKC_BACKQUOTE),
306};
307
308static uint32_t ConvertAllegroKeyIntoMy(char32_t *character)
309{
310 int scancode;
311 int unicode = ureadkey(&scancode);
312
313 uint key = 0;
314
315 for (const auto &map : _vk_mapping) {
316 if (IsInsideBS(scancode, map.vk_from, map.vk_count)) {
317 key = scancode - map.vk_from + map.map_to;
318 break;
319 }
320 }
321
322 if (key_shifts & KB_SHIFT_FLAG) key |= WKC_SHIFT;
323 if (key_shifts & KB_CTRL_FLAG) key |= WKC_CTRL;
324 if (key_shifts & KB_ALT_FLAG) key |= WKC_ALT;
325#if 0
326 Debug(driver, 0, "Scancode character pressed {}", scancode);
327 Debug(driver, 0, "Unicode character pressed {}", unicode);
328#endif
329
330 *character = unicode;
331 return key;
332}
333
334static const uint LEFT_BUTTON = 0;
335static const uint RIGHT_BUTTON = 1;
336
338{
339 poll_mouse();
340
341 bool mouse_action = false;
342
343 /* Mouse buttons */
344 static int prev_button_state;
345 if (prev_button_state != mouse_b) {
346 uint diff = prev_button_state ^ mouse_b;
347 while (diff != 0) {
348 uint button = FindFirstBit(diff);
349 ClrBit(diff, button);
350 if (HasBit(mouse_b, button)) {
351 /* Pressed mouse button */
352 if (_rightclick_emulate && (key_shifts & KB_CTRL_FLAG)) {
353 button = RIGHT_BUTTON;
354 ClrBit(diff, RIGHT_BUTTON);
355 }
356 switch (button) {
357 case LEFT_BUTTON:
358 _left_button_down = true;
359 break;
360
361 case RIGHT_BUTTON:
362 _right_button_down = true;
364 break;
365
366 default:
367 /* ignore rest */
368 break;
369 }
370 } else {
371 /* Released mouse button */
373 _right_button_down = false;
374 _left_button_down = false;
375 _left_button_clicked = false;
376 } else if (button == LEFT_BUTTON) {
377 _left_button_down = false;
378 _left_button_clicked = false;
379 } else if (button == RIGHT_BUTTON) {
380 _right_button_down = false;
381 }
382 }
383 }
384 prev_button_state = mouse_b;
385 mouse_action = true;
386 }
387
388 /* Mouse movement */
389 if (_cursor.UpdateCursorPosition(mouse_x, mouse_y)) {
390 position_mouse(_cursor.pos.x, _cursor.pos.y);
391 }
392 if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true;
393
394 static int prev_mouse_z = 0;
395 if (prev_mouse_z != mouse_z) {
396 _cursor.wheel = (prev_mouse_z - mouse_z) < 0 ? -1 : 1;
397 prev_mouse_z = mouse_z;
398 mouse_action = true;
399 }
400
401 if (mouse_action) HandleMouseEvents();
402
403 poll_keyboard();
404 if ((key_shifts & KB_ALT_FLAG) && (key[KEY_ENTER] || key[KEY_F])) {
405 ToggleFullScreen(!_fullscreen);
406 } else if (keypressed()) {
407 char32_t character;
408 uint keycode = ConvertAllegroKeyIntoMy(&character);
409 HandleKeypress(keycode, character);
410 }
411
412 return false;
413}
414
419int _allegro_instance_count = 0;
420
421std::optional<std::string_view> VideoDriver_Allegro::Start(const StringList &param)
422{
423 if (_allegro_instance_count == 0 && install_allegro(SYSTEM_AUTODETECT, &errno, nullptr)) {
424 Debug(driver, 0, "allegro: install_allegro failed '{}'", allegro_error);
425 return "Failed to set up Allegro";
426 }
427 _allegro_instance_count++;
428
429 this->UpdateAutoResolution();
430
431 install_timer();
432 install_mouse();
433 install_keyboard();
434
435#if defined _DEBUG
436/* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
437 * be triggered, so rereplace the signals and make the debugger useful. */
438 signal(SIGABRT, nullptr);
439 signal(SIGSEGV, nullptr);
440#endif
441
442 GetVideoModes();
443 if (!CreateMainSurface(_cur_resolution.width, _cur_resolution.height)) {
444 return "Failed to set up Allegro video";
445 }
447 set_close_button_callback(HandleExitGameRequest);
448
449 this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
450
451 return std::nullopt;
452}
453
455{
456 if (--_allegro_instance_count == 0) allegro_exit();
457}
458
460{
461 bool old_ctrl_pressed = _ctrl_pressed;
462
463 _ctrl_pressed = !!(key_shifts & KB_CTRL_FLAG);
464 _shift_pressed = !!(key_shifts & KB_SHIFT_FLAG);
465
466 /* Speedup when pressing tab, except when using ALT+TAB
467 * to switch to another application. */
468 this->fast_forward_key_pressed = key[KEY_TAB] && (key_shifts & KB_ALT_FLAG) == 0;
469
470 /* Determine which directional keys are down. */
471 _dirkeys =
472 (key[KEY_LEFT] ? 1 : 0) |
473 (key[KEY_UP] ? 2 : 0) |
474 (key[KEY_RIGHT] ? 4 : 0) |
475 (key[KEY_DOWN] ? 8 : 0);
476
477 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
478}
479
481{
482 this->StartGameThread();
483
484 for (;;) {
485 if (_exit_game) break;
486
487 this->Tick();
488 this->SleepTillNextTick();
489 }
490
491 this->StopGameThread();
492}
493
495{
496 return CreateMainSurface(w, h);
497}
498
499bool VideoDriver_Allegro::ToggleFullscreen(bool fullscreen)
500{
501 _fullscreen = fullscreen;
502 GetVideoModes(); // get the list of available video modes
503 if (_resolutions.empty() || !this->ChangeResolution(_cur_resolution.width, _cur_resolution.height)) {
504 /* switching resolution failed, put back full_screen to original status */
505 _fullscreen ^= true;
506 return false;
507 }
508 return true;
509}
510
512{
513 return CreateMainSurface(_screen.width, _screen.height);
514}
515
516#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.
constexpr uint8_t FindFirstBit(T x)
Search the first set bit in a value.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
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
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.
Definition base.hpp:51
@ Blitter
The blitter takes care of the palette animation.
Definition base.hpp:53
@ VideoBackend
Palette animation should be done by video backend (8bpp only!).
Definition base.hpp:52
virtual void PostResize()
Post resize event.
Definition base.hpp:205
Factory for the allegro video driver.
Definition allegro_v.h:46
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.
void ClaimMousePointer() override
Claim the exclusive rights for the mouse pointer.
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.
static Palette _local_palette
Current palette to use for drawing.
Definition cocoa_ogl.mm:42
#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
Error reporting related functions.
Factory to 'query' all available blitters.
Types for recording game performance data.
@ PFE_VIDEO
Speed of painting drawn video buffer.
bool _shift_pressed
Is Shift pressed?
Definition gfx.cpp:40
bool _left_button_down
Is left mouse button pressed?
Definition gfx.cpp:42
bool _ctrl_pressed
Is Ctrl pressed?
Definition gfx.cpp:39
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
Definition gfx.cpp:35
bool _left_button_clicked
Is left mouse button clicked?
Definition gfx.cpp:43
bool _right_button_clicked
Is right mouse button clicked?
Definition gfx.cpp:45
bool _right_button_down
Is right mouse button pressed?
Definition gfx.cpp:44
Functions related to the gfx engine.
void HandleCtrlChanged()
State of CONTROL key has changed.
Definition window.cpp:2712
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:2975
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
Definition window.cpp:2656
@ 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:1554
Integer math functions.
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.
Some generic types.
bool CopyPalette(Palette &local_palette, bool force_copy)
Copy the current palette if the palette was updated.
Definition palette.cpp:225
Functions related to modal progress.
Pseudo random number generator.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
T y
Y coordinate.
T x
X coordinate.
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
Definition gfx.cpp:1767
Point pos
logical mouse position
Definition gfx_type.h:125
int wheel
mouse wheel movement
Definition gfx_type.h:127
Point delta
relative mouse movement in this tick
Definition gfx_type.h:126
Dimensions (a width and height) of a rectangle in 2D.
int first_dirty
The first dirty element.
Definition gfx_type.h:375
int count_dirty
The number of dirty elements.
Definition gfx_type.h:376
Specification of a rectangle with an absolute top-left coordinate and a (relative) width/height.
Base of all threads.
Window functions not directly related to making/drawing windows.