12 #include "../stdafx.h"
13 #include "../openttd.h"
14 #include "../gfx_func.h"
16 #include "../blitter/factory.hpp"
17 #include "../network/network.h"
18 #include "../thread.h"
19 #include "../progress.h"
20 #include "../core/random_func.hpp"
21 #include "../core/math_func.hpp"
22 #include "../fileio_func.h"
23 #include "../framerate_type.h"
24 #include "../window_func.h"
28 #include <condition_variable>
30 # include <emscripten.h>
31 # include <emscripten/html5.h>
34 #include "../safeguards.h"
38 static SDL_Window *_sdl_window;
39 static SDL_Surface *_sdl_surface;
40 static SDL_Surface *_sdl_realscreen;
47 static std::condition_variable_any *
_draw_signal =
nullptr;
51 static SDL_Palette *_sdl_palette;
55 static bool _cursor_new_in_window =
false;
58 #define MAX_DIRTY_RECTS 100
59 static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS];
60 static int _num_dirty_rects;
63 static int _window_size_w;
64 static int _window_size_h;
68 if (_num_dirty_rects < MAX_DIRTY_RECTS) {
69 _dirty_rects[_num_dirty_rects].x = left;
70 _dirty_rects[_num_dirty_rects].y = top;
71 _dirty_rects[_num_dirty_rects].w =
width;
72 _dirty_rects[_num_dirty_rects].h =
height;
77 static void UpdatePalette(
bool init =
false)
89 SDL_SetSurfacePalette(_sdl_surface, _sdl_palette);
91 if (_sdl_surface != _sdl_realscreen && init) {
112 SDL_SetSurfacePalette(_sdl_realscreen, _sdl_palette);
115 if (_sdl_surface != _sdl_realscreen && !init) {
126 SDL_BlitSurface(_sdl_surface,
nullptr, _sdl_realscreen,
nullptr);
127 SDL_UpdateWindowSurface(_sdl_window);
131 static void InitPalette()
139 static void CheckPaletteAnim()
163 static void DrawSurfaceToScreen()
167 int n = _num_dirty_rects;
170 _num_dirty_rects = 0;
172 if (n > MAX_DIRTY_RECTS) {
173 if (_sdl_surface != _sdl_realscreen) {
174 SDL_BlitSurface(_sdl_surface,
nullptr, _sdl_realscreen,
nullptr);
177 SDL_UpdateWindowSurface(_sdl_window);
179 if (_sdl_surface != _sdl_realscreen) {
180 for (
int i = 0; i < n; i++) {
181 SDL_BlitSurface(_sdl_surface, &_dirty_rects[i], _sdl_realscreen, &_dirty_rects[i]);
185 SDL_UpdateWindowSurfaceRects(_sdl_window, _dirty_rects, n);
189 static void DrawSurfaceToScreenThread()
201 DrawSurfaceToScreen();
206 static void GetVideoModes()
208 int modes = SDL_GetNumDisplayModes(0);
209 if (modes == 0)
usererror(
"sdl: no modes available");
213 SDL_DisplayMode mode;
214 for (
int i = 0; i < modes; i++) {
215 SDL_GetDisplayMode(0, i, &mode);
220 if (w < 640 || h < 480)
continue;
229 static void GetAvailableVideoMode(uint *w, uint *h)
242 if (newdelta < delta) {
251 bool VideoDriver_SDL::CreateMainSurface(uint w, uint h,
bool resize)
253 SDL_Surface *newscreen;
257 GetAvailableVideoMode(&w, &h);
259 DEBUG(driver, 1,
"SDL2: using mode %ux%ux%d", w, h, bpp);
262 if (_sdl_surface !=
nullptr && _sdl_surface != _sdl_realscreen) SDL_FreeSurface(_sdl_surface);
264 seprintf(caption,
lastof(caption),
"OpenTTD %s", _openttd_revision);
266 if (_sdl_window ==
nullptr) {
267 Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
270 flags |= SDL_WINDOW_FULLSCREEN;
273 int x = SDL_WINDOWPOS_UNDEFINED, y = SDL_WINDOWPOS_UNDEFINED;
275 if (SDL_GetDisplayBounds(this->startup_display, &r) == 0) {
276 x = r.x + std::max(0, r.w -
static_cast<int>(w)) / 2;
277 y = r.y + std::max(0, r.h -
static_cast<int>(h)) / 4;
279 _sdl_window = SDL_CreateWindow(
285 if (_sdl_window ==
nullptr) {
286 DEBUG(driver, 0,
"SDL2: Couldn't allocate a window to draw on");
291 if (!icon_path.empty()) {
293 SDL_Surface *icon = SDL_LoadBMP(icon_path.c_str());
294 if (icon !=
nullptr) {
296 uint32 rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
298 SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
299 SDL_SetWindowIcon(_sdl_window, icon);
300 SDL_FreeSurface(icon);
305 if (resize) SDL_SetWindowSize(_sdl_window, w, h);
307 newscreen = SDL_GetWindowSurface(_sdl_window);
308 if (newscreen == NULL) {
309 DEBUG(driver, 0,
"SDL2: Couldn't get window surface: %s", SDL_GetError());
313 _sdl_realscreen = newscreen;
316 newscreen = SDL_CreateRGBSurface(0, w, h, 8, 0, 0, 0, 0);
318 if (newscreen ==
nullptr) {
319 DEBUG(driver, 0,
"SDL2: Couldn't allocate shadow surface: %s", SDL_GetError());
324 if (_sdl_palette ==
nullptr) {
325 _sdl_palette = SDL_AllocPalette(256);
328 if (_sdl_palette ==
nullptr) {
329 DEBUG(driver, 0,
"SDL_AllocPalette() failed: %s", SDL_GetError());
334 _num_dirty_rects = 0;
336 _screen.width = newscreen->w;
337 _screen.height = newscreen->h;
338 _screen.pitch = newscreen->pitch / (bpp / 8);
339 _screen.dst_ptr = newscreen->pixels;
340 _sdl_surface = newscreen;
345 if (_fullscreen) _cursor.
in_window =
true;
357 bool VideoDriver_SDL::ClaimMousePointer()
360 #ifdef __EMSCRIPTEN__
361 SDL_SetRelativeMouseMode(SDL_TRUE);
372 SDL_StartTextInput();
396 #define AS(x, z) {x, 0, z, false}
397 #define AM(x, y, z, w) {x, (byte)(y - x), z, false}
398 #define AS_UP(x, z) {x, 0, z, true}
399 #define AM_UP(x, y, z, w) {x, (byte)(y - x), z, true}
403 AS_UP(SDLK_PAGEUP, WKC_PAGEUP),
404 AS_UP(SDLK_PAGEDOWN, WKC_PAGEDOWN),
405 AS_UP(SDLK_UP, WKC_UP),
406 AS_UP(SDLK_DOWN, WKC_DOWN),
407 AS_UP(SDLK_LEFT, WKC_LEFT),
408 AS_UP(SDLK_RIGHT, WKC_RIGHT),
410 AS_UP(SDLK_HOME, WKC_HOME),
411 AS_UP(SDLK_END, WKC_END),
413 AS_UP(SDLK_INSERT, WKC_INSERT),
414 AS_UP(SDLK_DELETE, WKC_DELETE),
417 AM(SDLK_a, SDLK_z,
'A',
'Z'),
418 AM(SDLK_0, SDLK_9,
'0',
'9'),
420 AS_UP(SDLK_ESCAPE, WKC_ESC),
421 AS_UP(SDLK_PAUSE, WKC_PAUSE),
422 AS_UP(SDLK_BACKSPACE, WKC_BACKSPACE),
424 AS(SDLK_SPACE, WKC_SPACE),
425 AS(SDLK_RETURN, WKC_RETURN),
426 AS(SDLK_TAB, WKC_TAB),
429 AM_UP(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
432 AM(SDLK_KP_0, SDLK_KP_9,
'0',
'9'),
433 AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
434 AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
435 AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
436 AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
437 AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
438 AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
454 static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym,
WChar *character)
458 bool unprintable =
false;
460 for (map = _vk_mapping; map !=
endof(_vk_mapping); ++map) {
461 if ((uint)(sym->sym - map->vk_from) <= map->vk_count) {
462 key = sym->sym - map->vk_from + map->map_to;
463 unprintable = map->unprintable;
469 if (sym->scancode == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
472 if (sym->mod & KMOD_GUI) key |= WKC_META;
473 if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
474 if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
475 if (sym->mod & KMOD_ALT) key |= WKC_ALT;
478 if (sym->mod & KMOD_GUI ||
479 sym->mod & KMOD_CTRL ||
480 sym->mod & KMOD_ALT ||
482 *character = WKC_NONE;
484 *character = sym->sym;
494 static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
499 for (map = _vk_mapping; map !=
endof(_vk_mapping); ++map) {
500 if ((uint)(kc - map->vk_from) <= map->vk_count) {
501 key = kc - map->vk_from + map->map_to;
508 SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
509 if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
514 int VideoDriver_SDL::PollEvent()
518 if (!SDL_PollEvent(&ev))
return -2;
521 case SDL_MOUSEMOTION:
522 #ifdef __EMSCRIPTEN__
523 if (_cursor_new_in_window) {
529 _cursor.
pos.x = ev.motion.x;
530 _cursor.
pos.y = ev.motion.y;
531 _cursor.
dirty =
true;
533 _cursor_new_in_window =
false;
534 SDL_SetRelativeMouseMode(SDL_TRUE);
540 SDL_WarpMouseInWindow(_sdl_window, _cursor.
pos.x, _cursor.
pos.y);
547 if (ev.wheel.y > 0) {
549 }
else if (ev.wheel.y < 0) {
554 case SDL_MOUSEBUTTONDOWN:
556 ev.button.button = SDL_BUTTON_RIGHT;
559 switch (ev.button.button) {
560 case SDL_BUTTON_LEFT:
564 case SDL_BUTTON_RIGHT:
574 case SDL_MOUSEBUTTONUP:
579 }
else if (ev.button.button == SDL_BUTTON_LEFT) {
582 }
else if (ev.button.button == SDL_BUTTON_RIGHT) {
589 HandleExitGameRequest();
593 if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) &&
594 (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
595 if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen);
599 uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
603 keycode == WKC_DELETE ||
604 keycode == WKC_NUM_ENTER ||
605 keycode == WKC_LEFT ||
606 keycode == WKC_RIGHT ||
608 keycode == WKC_DOWN ||
609 keycode == WKC_HOME ||
610 keycode == WKC_END ||
611 keycode & WKC_META ||
612 keycode & WKC_CTRL ||
614 (keycode >= WKC_F1 && keycode <= WKC_F12) ||
621 case SDL_TEXTINPUT: {
623 SDL_Keycode kc = SDL_GetKeyFromName(ev.text.text);
624 uint keycode = ConvertSdlKeycodeIntoMy(kc);
635 case SDL_WINDOWEVENT: {
636 if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
638 _num_dirty_rects = MAX_DIRTY_RECTS + 1;
639 }
else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
640 int w = std::max(ev.window.data1, 64);
641 int h = std::max(ev.window.data2, 64);
642 CreateMainSurface(w, h, w != ev.window.data1 || h != ev.window.data2);
643 }
else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
646 #ifdef __EMSCRIPTEN__
649 _cursor_new_in_window =
true;
650 SDL_SetRelativeMouseMode(SDL_FALSE);
652 }
else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
670 SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION,
"0");
671 SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
"1");
677 if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
678 ret_code = SDL_InitSubSystem(SDL_INIT_VIDEO);
680 if (ret_code < 0)
return SDL_GetError();
683 int num_displays = SDL_GetNumVideoDisplays();
684 if (!
IsInsideBS(this->startup_display, 0, num_displays)) {
687 SDL_GetGlobalMouseState(&mx, &my);
688 this->startup_display = 0;
689 for (
int display = 0; display < num_displays; ++display) {
691 if (SDL_GetDisplayBounds(display, &r) == 0 &&
IsInsideBS(mx, r.x, r.w) &&
IsInsideBS(my, r.y, r.h)) {
692 DEBUG(driver, 1,
"SDL2: Mouse is at (%d, %d), use display %d (%d, %d, %d, %d)", mx, my, display, r.x, r.y, r.w, r.h);
693 this->startup_display = display;
703 return SDL_GetError();
706 const char *dname = SDL_GetCurrentVideoDriver();
707 DEBUG(driver, 1,
"SDL2: using driver '%s'", dname);
721 SDL_QuitSubSystem(SDL_INIT_VIDEO);
722 if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
727 void VideoDriver_SDL::LoopOnce()
732 uint32 prev_cur_ticks = cur_ticks;
735 while (PollEvent() == -1) {}
737 #ifdef __EMSCRIPTEN__
744 emscripten_cancel_main_loop();
750 mod = SDL_GetModState();
751 keys = SDL_GetKeyboardState(&numkeys);
758 if (keys[SDL_SCANCODE_TAB] && (mod & KMOD_ALT) == 0)
761 if (!
_networking && _game_mode != GM_MENU) _fast_forward |= 2;
762 }
else if (_fast_forward & 2) {
766 cur_ticks = SDL_GetTicks();
767 if (SDL_TICKS_PASSED(cur_ticks, next_tick) || (_fast_forward && !
_pause_mode) || cur_ticks < prev_cur_ticks) {
769 last_cur_ticks = cur_ticks;
779 (keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
780 (keys[SDL_SCANCODE_UP] ? 2 : 0) |
781 (keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
782 (keys[SDL_SCANCODE_DOWN] ? 8 : 0);
804 #ifndef __EMSCRIPTEN__
819 DrawSurfaceToScreen();
825 cur_ticks = SDL_GetTicks();
826 last_cur_ticks = cur_ticks;
838 draw_lock = std::unique_lock<std::recursive_mutex>(*
_draw_mutex);
861 #ifdef __EMSCRIPTEN__
863 emscripten_set_main_loop_arg(&this->EmscriptenLoop,
this, 0, 1);
865 while (!_exit_game) {
873 void VideoDriver_SDL::MainLoopCleanup()
880 if (draw_lock.owns_lock()) draw_lock.unlock();
891 #ifdef __EMSCRIPTEN__
892 emscripten_exit_pointerlock();
896 EM_ASM(
if (window[
"openttd_syncfs"]) openttd_syncfs());
897 EM_ASM(
if (window[
"openttd_exit"]) openttd_exit());
903 std::unique_lock<std::recursive_mutex>
lock;
906 return CreateMainSurface(w, h,
true);
911 std::unique_lock<std::recursive_mutex>
lock;
916 SDL_GetWindowSize(_sdl_window, &_window_size_w, &_window_size_h);
920 if (SDL_GetCurrentDisplayMode(0, &dm) < 0) {
921 DEBUG(driver, 0,
"SDL_GetCurrentDisplayMode() failed: %s", SDL_GetError());
923 SDL_SetWindowSize(_sdl_window, dm.w, dm.h);
927 DEBUG(driver, 1,
"SDL2: Setting %s",
fullscreen ?
"fullscreen" :
"windowed");
928 int ret = SDL_SetWindowFullscreen(_sdl_window,
fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
932 if (!
fullscreen) SDL_SetWindowSize(_sdl_window, _window_size_w, _window_size_h);
934 DEBUG(driver, 0,
"SDL_SetWindowFullscreen() failed: %s", SDL_GetError());
944 SDL_GetWindowSize(_sdl_window, &w, &h);
945 return CreateMainSurface(w, h,
false);
958 Dimension VideoDriver_SDL::GetScreenSize()
const
960 SDL_DisplayMode mode;
961 if (SDL_GetCurrentDisplayMode(this->startup_display, &mode) != 0)
return VideoDriver::GetScreenSize();
963 return {
static_cast<uint
>(mode.w),
static_cast<uint
>(mode.h) };