13#include "../openttd.h"
14#include "../error_func.h"
15#include "../gfx_func.h"
16#include "../blitter/factory.hpp"
18#include "../progress.h"
19#include "../core/random_func.hpp"
20#include "../core/math_func.hpp"
21#include "../fileio_func.h"
22#include "../framerate_type.h"
23#include "../window_func.h"
27#include "../safeguards.h"
31static SDL_Surface *_sdl_surface;
32static SDL_Surface *_sdl_realscreen;
33static bool _all_modes;
37#define MAX_DIRTY_RECTS 100
38static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS];
39static int _num_dirty_rects;
40static int _use_hwpalette;
41static int _requested_hwpalette;
45 if (_num_dirty_rects < MAX_DIRTY_RECTS) {
46 _dirty_rects[_num_dirty_rects].x = left;
47 _dirty_rects[_num_dirty_rects].y = top;
48 _dirty_rects[_num_dirty_rects].w = width;
49 _dirty_rects[_num_dirty_rects].h = height;
54static void UpdatePalette(
bool init =
false)
58 for (
int i = 0; i != _local_palette.
count_dirty; i++) {
67 if (_sdl_surface != _sdl_realscreen && init) {
91 if (_sdl_surface != _sdl_realscreen && !init) {
102 SDL_BlitSurface(_sdl_surface,
nullptr, _sdl_realscreen,
nullptr);
103 SDL_UpdateRect(_sdl_realscreen, 0, 0, 0, 0);
107static void InitPalette()
140 int n = _num_dirty_rects;
143 _num_dirty_rects = 0;
145 if (n > MAX_DIRTY_RECTS) {
146 if (_sdl_surface != _sdl_realscreen) {
147 SDL_BlitSurface(_sdl_surface,
nullptr, _sdl_realscreen,
nullptr);
150 SDL_UpdateRect(_sdl_realscreen, 0, 0, 0, 0);
152 if (_sdl_surface != _sdl_realscreen) {
153 for (
int i = 0; i < n; i++) {
154 SDL_BlitSurface(_sdl_surface, &_dirty_rects[i], _sdl_realscreen, &_dirty_rects[i]);
158 SDL_UpdateRects(_sdl_realscreen, n, _dirty_rects);
162static const Dimension _default_resolutions[] = {
176static void GetVideoModes()
178 SDL_Rect **modes = SDL_ListModes(
nullptr, SDL_SWSURFACE | SDL_FULLSCREEN);
179 if (modes ==
nullptr) UserError(
"sdl: no modes available");
183 _all_modes = (SDL_ListModes(
nullptr, SDL_SWSURFACE | (_fullscreen ? SDL_FULLSCREEN : 0)) == (void*)-1);
184 if (modes == (
void*)-1) {
185 for (
const auto &default_resolution : _default_resolutions) {
186 if (SDL_VideoModeOK(default_resolution.width, default_resolution.height, 8, SDL_FULLSCREEN) != 0) {
191 for (
int i = 0; modes[i]; i++) {
192 uint w = modes[i]->w;
193 uint h = modes[i]->h;
194 if (w < 640 || h < 480)
continue;
198 if (
_resolutions.empty()) UserError(
"No usable screen resolutions found!\n");
203static void GetAvailableVideoMode(uint *w, uint *h)
216 if (newdelta < delta) {
225bool VideoDriver_SDL::CreateMainSurface(uint w, uint h)
227 SDL_Surface *newscreen, *icon;
231 GetAvailableVideoMode(&w, &h);
233 Debug(driver, 1,
"SDL: using mode {}x{}x{}", w, h, bpp);
235 if (bpp == 0) UserError(
"Can't use a blitter that blits 0 bpp for normal visuals");
238 if (!icon_path.empty()) {
240 icon = SDL_LoadBMP(icon_path.c_str());
241 if (icon !=
nullptr) {
243 uint32_t rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
245 SDL_SetColorKey(icon, SDL_SRCCOLORKEY, rgbmap);
246 SDL_WM_SetIcon(icon,
nullptr);
247 SDL_FreeSurface(icon);
251 if (_use_hwpalette == 2) {
273 want_hwpalette = bpp == 8 && _fullscreen && _support8bpp ==
S8BPP_HARDWARE;
276 want_hwpalette = _use_hwpalette;
279 if (want_hwpalette)
Debug(driver, 1,
"SDL: requesting hardware palette");
282 if (_sdl_surface !=
nullptr && _sdl_surface != _sdl_realscreen) SDL_FreeSurface(_sdl_surface);
284 if (_sdl_realscreen !=
nullptr) {
285 if (_requested_hwpalette != want_hwpalette) {
294 Debug(driver, 0,
"SDL: Restarting SDL video subsystem, to force hwpalette change");
295 SDL_QuitSubSystem(SDL_INIT_VIDEO);
296 SDL_InitSubSystem(SDL_INIT_VIDEO);
305 _requested_hwpalette = want_hwpalette;
308 newscreen = SDL_SetVideoMode(w, h, bpp, SDL_SWSURFACE | (want_hwpalette ? SDL_HWPALETTE : 0) | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE));
309 if (newscreen ==
nullptr) {
310 Debug(driver, 0,
"SDL: Couldn't allocate a window to draw on");
313 _sdl_realscreen = newscreen;
315 if (bpp == 8 && (_sdl_realscreen->flags & SDL_HWPALETTE) != SDL_HWPALETTE) {
334 Debug(driver, 1,
"SDL: using shadow surface");
335 newscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bpp, 0, 0, 0, 0);
336 if (newscreen ==
nullptr) {
337 Debug(driver, 0,
"SDL: Couldn't allocate a shadow surface to draw on");
343 _num_dirty_rects = 0;
345 _screen.width = newscreen->w;
346 _screen.height = newscreen->h;
347 _screen.pitch = newscreen->pitch / (bpp / 8);
348 _screen.dst_ptr = newscreen->pixels;
349 _sdl_surface = newscreen;
354 if (_fullscreen) _cursor.
in_window =
true;
362 SDL_WM_SetCaption(caption.c_str(), caption.c_str());
369bool VideoDriver_SDL::ClaimMousePointer()
376 const uint16_t vk_from;
377 const uint8_t vk_count;
378 const uint8_t map_to;
380 constexpr SDLVkMapping(SDLKey vk_first, SDLKey vk_last, uint8_t map_first, [[maybe_unused]] uint8_t map_last)
381 : vk_from(vk_first), vk_count(vk_last - vk_first + 1), map_to(map_first)
383 assert((vk_last - vk_first) == (map_last - map_first));
387#define AS(x, z) {x, x, z, z}
388#define AM(x, y, z, w) {x, y, z, w}
392 AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN),
394 AS(SDLK_DOWN, WKC_DOWN),
395 AS(SDLK_LEFT, WKC_LEFT),
396 AS(SDLK_RIGHT, WKC_RIGHT),
398 AS(SDLK_HOME, WKC_HOME),
399 AS(SDLK_END, WKC_END),
401 AS(SDLK_INSERT, WKC_INSERT),
402 AS(SDLK_DELETE, WKC_DELETE),
405 AM(SDLK_a, SDLK_z,
'A',
'Z'),
406 AM(SDLK_0, SDLK_9,
'0',
'9'),
408 AS(SDLK_ESCAPE, WKC_ESC),
409 AS(SDLK_PAUSE, WKC_PAUSE),
410 AS(SDLK_BACKSPACE, WKC_BACKSPACE),
412 AS(SDLK_SPACE, WKC_SPACE),
413 AS(SDLK_RETURN, WKC_RETURN),
414 AS(SDLK_TAB, WKC_TAB),
417 AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
420 AM(SDLK_KP0, SDLK_KP9,
'0',
'9'),
421 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
422 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
423 AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
424 AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
425 AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
426 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
442static uint ConvertSdlKeyIntoMy(SDL_keysym *sym,
char32_t *character)
446 for (
const auto &map : _vk_mapping) {
447 if (
IsInsideBS(sym->sym, map.vk_from, map.vk_count)) {
448 key = sym->sym - map.vk_from + map.map_to;
455 if (sym->scancode == 41) key = WKC_BACKQUOTE;
456#elif defined(__APPLE__)
457 if (sym->scancode == 10) key = WKC_BACKQUOTE;
458#elif defined(__SVR4) && defined(__sun)
459 if (sym->scancode == 60) key = WKC_BACKQUOTE;
460 if (sym->scancode == 49) key = WKC_BACKSPACE;
461#elif defined(__sgi__)
462 if (sym->scancode == 22) key = WKC_BACKQUOTE;
464 if (sym->scancode == 49) key = WKC_BACKQUOTE;
468 if (sym->mod & KMOD_META) key |= WKC_META;
469 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
470 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
471 if (sym->mod & KMOD_ALT) key |= WKC_ALT;
473 *character = sym->unicode;
481 if (!SDL_PollEvent(&ev))
return false;
484 case SDL_MOUSEMOTION: {
485 int32_t x = ev.motion.x;
486 int32_t y = ev.motion.y;
491 while (SDL_PeepEvents(&ev, 1, SDL_GETEVENT, SDL_MOUSEMOTION)) {
498 SDL_WarpMouse(_cursor.
pos.x, _cursor.
pos.y);
504 case SDL_MOUSEBUTTONDOWN:
506 ev.button.button = SDL_BUTTON_RIGHT;
509 switch (ev.button.button) {
510 case SDL_BUTTON_LEFT:
514 case SDL_BUTTON_RIGHT:
519 case SDL_BUTTON_WHEELUP: _cursor.
wheel--;
break;
520 case SDL_BUTTON_WHEELDOWN: _cursor.
wheel++;
break;
527 case SDL_MOUSEBUTTONUP:
532 }
else if (ev.button.button == SDL_BUTTON_LEFT) {
535 }
else if (ev.button.button == SDL_BUTTON_RIGHT) {
541 case SDL_ACTIVEEVENT:
542 if (!(ev.active.state & SDL_APPMOUSEFOCUS))
break;
544 if (ev.active.gain) {
553 HandleExitGameRequest();
557 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_META)) &&
558 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
559 ToggleFullScreen(!_fullscreen);
562 uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
567 case SDL_VIDEORESIZE: {
568 int w = std::max(ev.resize.w, 64);
569 int h = std::max(ev.resize.h, 64);
570 CreateMainSurface(w, h);
573 case SDL_VIDEOEXPOSE: {
577 _num_dirty_rects = MAX_DIRTY_RECTS + 1;
594 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
595 ret_code = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
596 }
else if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
597 ret_code = SDL_InitSubSystem(SDL_INIT_VIDEO);
599 if (ret_code < 0)
return SDL_GetError();
605 return SDL_GetError();
608 SDL_VideoDriverName(buf,
sizeof buf);
609 Debug(driver, 1,
"SDL: using driver '{}'", buf);
619void VideoDriver_SDL::SetupKeyboard()
621 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
622 SDL_EnableUNICODE(1);
627 SDL_QuitSubSystem(SDL_INIT_VIDEO);
628 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
635 uint32_t mod = SDL_GetModState();
637 Uint8 *keys = SDL_GetKeyState(&numkeys);
650 (keys[SDLK_LEFT] ? 1 : 0) |
651 (keys[SDLK_UP] ? 2 : 0) |
652 (keys[SDLK_RIGHT] ? 4 : 0) |
653 (keys[SDLK_DOWN] ? 8 : 0);
663 if (_exit_game)
break;
674 return CreateMainSurface(w, h);
679 _fullscreen = fullscreen;
694 return CreateMainSurface(_screen.width, _screen.height);
#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.
static Blitter * GetCurrentBlitter()
Get the current active blitter (always set by calling SelectBlitter).
How all blitters should look like.
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.
@ PALETTE_ANIMATION_NONE
No palette animation.
@ PALETTE_ANIMATION_VIDEO_BACKEND
Palette animation should be done by video backend (8bpp only!)
@ PALETTE_ANIMATION_BLITTER
The blitter takes care of the palette animation.
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...
virtual void PostResize()
Post resize event.
Factory for the SDL video driver.
void MainLoop() override
Perform the actual drawing.
void CheckPaletteAnim() override
Process any pending palette animation.
void Paint() override
Paint the window.
void Stop() override
Stop this driver.
bool AfterBlitterChange() override
Callback invoked after the blitter was changed.
std::optional< std::string_view > Start(const StringList ¶m) override
Start this driver.
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.
bool PollEvent() override
Process a single system event.
void MakeDirty(int left, int top, int width, int height) override
Mark a particular area dirty.
bool ToggleFullscreen(bool fullscreen) override
Change the full screen setting.
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,...)
Ouptut a line of debugging information.
int GetDriverParamInt(const StringList &parm, const char *name, int def)
Get an integer parameter the list of parameters.
std::vector< Dimension > _resolutions
List of resolutions.
Dimension _cur_resolution
The current resolution.
bool _rightclick_emulate
Whether right clicking is emulated.
bool GetDriverParamBool(const StringList &parm, const char *name)
Get a boolean parameter the list of parameters.
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename)
Find a path to the filename in one of the search directories.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
@ PFE_VIDEO
Speed of painting drawn video buffer.
bool _shift_pressed
Is Shift pressed?
bool _left_button_down
Is left mouse button pressed?
bool _ctrl_pressed
Is Ctrl pressed?
uint8_t _dirkeys
1 = left, 2 = up, 4 = right, 8 = down
bool _left_button_clicked
Is left mouse button clicked?
bool _right_button_clicked
Is right mouse button clicked?
bool _right_button_down
Is right mouse button pressed?
void HandleCtrlChanged()
State of CONTROL key has changed.
void GameSizeChanged()
Size of the application screen changed.
void HandleMouseEvents()
Handle a mouse event from the video driver.
void HandleKeypress(uint keycode, char32_t key)
Handle keyboard input.
@ S8BPP_HARDWARE
Full 8bpp support by OS and hardware.
@ WKC_BACKSLASH
\ Backslash
@ WKC_SLASH
/ Forward slash
@ WKC_SINGLEQUOTE
' Single quote
@ WKC_R_BRACKET
] Right square bracket
@ WKC_L_BRACKET
[ Left square bracket
@ WKC_SEMICOLON
; Semicolon
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
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.
Base of the SDL video driver.
std::vector< std::string > StringList
Type for a list of strings.
bool UpdateCursorPosition(int x, int y)
Update cursor position on mouse movement.
bool fix_at
mouse is moving, but cursor is not (used for scrolling)
Point pos
logical mouse position
bool in_window
mouse inside this window, determines drawing logic
int wheel
mouse wheel movement
Dimensions (a width and height) of a rectangle in 2D.
Information about the currently used palette.
int first_dirty
The first dirty element.
int count_dirty
The number of dirty elements.
Colour palette[256]
Current palette. Entry 0 has to be always fully transparent!
uint8_t b
colour channels in BE order
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
@ WC_GAME_OPTIONS
Game options window; Window numbers: