1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 2011-2020 Warzone 2100 Project
4
5 Warzone 2100 is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 Warzone 2100 is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Warzone 2100; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 /**
20 * @file main_sdl.cpp
21 *
22 * SDL backend code
23 */
24
25 // Get platform defines before checking for them!
26 #include "lib/framework/wzapp.h"
27
28 #include "lib/framework/input.h"
29 #include "lib/framework/utf.h"
30 #include "lib/ivis_opengl/pieclip.h"
31 #include "lib/ivis_opengl/piemode.h"
32 #include "lib/ivis_opengl/screen.h"
33 #include "lib/exceptionhandler/dumpinfo.h"
34 #include "lib/gamelib/gtime.h"
35 #include "src/warzoneconfig.h"
36 #include "src/game.h"
37 #include "gfx_api_sdl.h"
38 #include "gfx_api_gl_sdl.h"
39 #include <SDL.h>
40 #include <SDL_thread.h>
41 #include <SDL_clipboard.h>
42 #if defined(HAVE_SDL_VULKAN_H)
43 #include <SDL_vulkan.h>
44 #endif
45 #include <SDL_version.h>
46 #include "wz2100icon.h"
47 #include "cursors_sdl.h"
48 #include <algorithm>
49 #include <map>
50 #include <locale.h>
51 #include <atomic>
52 #include <chrono>
53
54 #if defined(WZ_OS_MAC)
55 #include "cocoa_sdl_helpers.h"
56 #include "cocoa_wz_menus.h"
57 #endif
58
59 void mainLoop();
60 // used in crash reports & version info
61 const char *BACKEND = "SDL";
62
63 std::map<KEY_CODE, SDL_Keycode> KEY_CODE_to_SDLKey;
64 std::map<SDL_Keycode, KEY_CODE > SDLKey_to_KEY_CODE;
65
66 int realmain(int argc, char *argv[]);
67
68 // the main stub which calls realmain() aka, WZ's main startup routines
main(int argc,char * argv[])69 int main(int argc, char *argv[])
70 {
71 return realmain(argc, argv);
72 }
73
74 // At this time, we only have 1 window.
75 static SDL_Window *WZwindow = nullptr;
76 static optional<video_backend> WZbackend = video_backend::opengl;
77
78 #if defined(WZ_OS_MAC)
79 // on macOS, SDL_WINDOW_FULLSCREEN_DESKTOP *must* be used (or high-DPI fullscreen toggling breaks)
80 const WINDOW_MODE WZ_SDL_DEFAULT_FULLSCREEN_MODE = WINDOW_MODE::desktop_fullscreen;
81 #else
82 const WINDOW_MODE WZ_SDL_DEFAULT_FULLSCREEN_MODE = WINDOW_MODE::fullscreen;
83 #endif
84 static WINDOW_MODE lastFullscreenMode = WZ_SDL_DEFAULT_FULLSCREEN_MODE;
85
86 // The screen that the game window is on.
87 int screenIndex = 0;
88 // The logical resolution of the game in the game's coordinate system (points).
89 unsigned int screenWidth = 0;
90 unsigned int screenHeight = 0;
91 // The logical resolution of the SDL window in the window's coordinate system (points) - i.e. not accounting for the Game Display Scale setting.
92 unsigned int windowWidth = 0;
93 unsigned int windowHeight = 0;
94 // The current display scale factor.
95 unsigned int current_displayScale = 100;
96 float current_displayScaleFactor = 1.f;
97
98 static std::vector<screeninfo> displaylist; // holds all our possible display lists
99
100 std::atomic<Uint32> wzSDLAppEvent((Uint32)-1);
101 enum wzSDLAppEventCodes
102 {
103 MAINTHREADEXEC
104 };
105
106 /* The possible states for keys */
107 enum KEY_STATE
108 {
109 KEY_UP,
110 KEY_PRESSED,
111 KEY_DOWN,
112 KEY_RELEASED,
113 KEY_PRESSRELEASE, // When a key goes up and down in a frame
114 KEY_DOUBLECLICK, // Only used by mouse keys
115 KEY_DRAG // Only used by mouse keys
116 };
117
118 struct INPUT_STATE
119 {
120 KEY_STATE state; /// Last key/mouse state
121 UDWORD lastdown; /// last key/mouse button down timestamp
122 Vector2i pressPos; ///< Location of last mouse press event.
123 Vector2i releasePos; ///< Location of last mouse release event.
124 };
125
126 // Clipboard routines
127 bool has_scrap(void);
128 bool get_scrap(char **dst);
129
130 /// constant for the interval between 2 singleclicks for doubleclick event in ms
131 #define DOUBLE_CLICK_INTERVAL 250
132
133 /* The current state of the keyboard */
134 static INPUT_STATE aKeyState[KEY_MAXSCAN]; // NOTE: SDL_NUM_SCANCODES is the max, but KEY_MAXSCAN is our limit
135
136 /* The current location of the mouse */
137 static Uint16 mouseXPos = 0;
138 static Uint16 mouseYPos = 0;
139 static Vector2i mouseWheelSpeed;
140 static bool mouseInWindow = true;
141
142 /* How far the mouse has to move to start a drag */
143 #define DRAG_THRESHOLD 5
144
145 /* Which button is being used for a drag */
146 static MOUSE_KEY_CODE dragKey;
147
148 /* The start of a possible drag by the mouse */
149 static int dragX = 0;
150 static int dragY = 0;
151
152 /* The current mouse button state */
153 static INPUT_STATE aMouseState[MOUSE_END];
154 static MousePresses mousePresses;
155
156 /* The current screen resizing state for this iteration through the game loop, in the game coordinate system */
157 struct ScreenSizeChange
158 {
ScreenSizeChangeScreenSizeChange159 ScreenSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
160 : oldWidth(oldWidth)
161 , oldHeight(oldHeight)
162 , newWidth(newWidth)
163 , newHeight(newHeight)
164 { }
165 unsigned int oldWidth;
166 unsigned int oldHeight;
167 unsigned int newWidth;
168 unsigned int newHeight;
169 };
170 static ScreenSizeChange* currentScreenResizingStatus = nullptr;
171
172 /* The size of the input buffer */
173 #define INPUT_MAXSTR 256
174
175 /* The input string buffer */
176 struct InputKey
177 {
178 UDWORD key;
179 utf_32_char unicode;
180 };
181
182 static InputKey pInputBuffer[INPUT_MAXSTR];
183 static InputKey *pStartBuffer, *pEndBuffer;
184 static utf_32_char *utf8Buf; // is like the old 'unicode' from SDL 1.x
185 void* GetTextEventsOwner = nullptr;
186
187 /**************************/
188 /*** Misc support ***/
189 /**************************/
190
wzGetPlatform()191 WzString wzGetPlatform()
192 {
193 return WzString::fromUtf8(SDL_GetPlatform());
194 }
195
196 // See if we have TEXT in the clipboard
has_scrap(void)197 bool has_scrap(void)
198 {
199 return SDL_HasClipboardText();
200 }
201
202 // Set the clipboard text
wzSetClipboardText(const char * src)203 bool wzSetClipboardText(const char *src)
204 {
205 if (SDL_SetClipboardText(src))
206 {
207 debug(LOG_ERROR, "Could not put clipboard text because : %s", SDL_GetError());
208 return false;
209 }
210 return true;
211 }
212
213 // Get text from the clipboard
get_scrap(char ** dst)214 bool get_scrap(char **dst)
215 {
216 if (has_scrap())
217 {
218 char *cliptext = SDL_GetClipboardText();
219 if (!cliptext)
220 {
221 debug(LOG_ERROR, "Could not get clipboard text because : %s", SDL_GetError());
222 return false;
223 }
224 *dst = cliptext;
225 return true;
226 }
227 else
228 {
229 // wasn't text or no text in the clipboard
230 return false;
231 }
232 }
233
StartTextInput(void * pTextInputRequester)234 void StartTextInput(void* pTextInputRequester)
235 {
236 if (!GetTextEventsOwner)
237 {
238 SDL_StartTextInput(); // enable text events
239 debug(LOG_INPUT, "SDL text events started");
240 }
241 else if (pTextInputRequester != GetTextEventsOwner)
242 {
243 debug(LOG_INPUT, "StartTextInput called by new input requester before old requester called StopTextInput");
244 }
245 GetTextEventsOwner = pTextInputRequester;
246 }
247
StopTextInput(void * pTextInputResigner)248 void StopTextInput(void* pTextInputResigner)
249 {
250 if (!GetTextEventsOwner)
251 {
252 debug(LOG_INPUT, "Ignoring StopTextInput call when text input is already disabled");
253 return;
254 }
255 if (pTextInputResigner != GetTextEventsOwner)
256 {
257 // Rejecting StopTextInput from regsigner who is not the last requester
258 debug(LOG_INPUT, "Ignoring StopTextInput call from resigner that is not the last requester (i.e. caller of StartTextInput)");
259 return;
260 }
261 SDL_StopTextInput(); // disable text events
262 GetTextEventsOwner = nullptr;
263 debug(LOG_INPUT, "SDL text events stopped");
264 }
265
isInTextInputMode()266 bool isInTextInputMode()
267 {
268 bool result = (GetTextEventsOwner != nullptr);
269 ASSERT((SDL_IsTextInputActive() != SDL_FALSE) == result, "How did GetTextEvents state and SDL_IsTextInputActive get out of sync?");
270 return result;
271 }
272
273 /* Put a character into a text buffer overwriting any text under the cursor */
wzGetSelection()274 WzString wzGetSelection()
275 {
276 WzString retval;
277 static char *scrap = nullptr;
278
279 if (get_scrap(&scrap))
280 {
281 retval = WzString::fromUtf8(scrap);
282 }
283 return retval;
284 }
285
wzAvailableResolutions()286 std::vector<screeninfo> wzAvailableResolutions()
287 {
288 return displaylist;
289 }
290
wzAvailableDisplayScales()291 std::vector<unsigned int> wzAvailableDisplayScales()
292 {
293 static const unsigned int wzDisplayScales[] = { 100, 125, 150, 200, 250, 300, 400, 500 };
294 return std::vector<unsigned int>(wzDisplayScales, wzDisplayScales + (sizeof(wzDisplayScales) / sizeof(wzDisplayScales[0])));
295 }
296
sortGfxBackendsForCurrentSystem(std::vector<video_backend> & backends)297 static std::vector<video_backend>& sortGfxBackendsForCurrentSystem(std::vector<video_backend>& backends)
298 {
299 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX) && (defined(_M_ARM64) || defined(_M_ARM))
300 // On ARM-based Windows, DirectX should be first (for compatibility)
301 std::stable_sort(backends.begin(), backends.end(), [](video_backend a, video_backend b) -> bool {
302 if (a == b) { return false; }
303 if (a == video_backend::directx) { return true; }
304 return false;
305 });
306 #else
307 // currently, no-op
308 #endif
309 return backends;
310 }
311
wzAvailableGfxBackends()312 std::vector<video_backend> wzAvailableGfxBackends()
313 {
314 std::vector<video_backend> availableBackends;
315 availableBackends.push_back(video_backend::opengl);
316 #if !defined(WZ_OS_MAC) // OpenGL ES is not supported on macOS, and WZ doesn't currently ship with an OpenGL ES library on macOS
317 availableBackends.push_back(video_backend::opengles);
318 #endif
319 #if defined(WZ_VULKAN_ENABLED) && defined(HAVE_SDL_VULKAN_H)
320 availableBackends.push_back(video_backend::vulkan);
321 #endif
322 #if defined(WZ_BACKEND_DIRECTX)
323 availableBackends.push_back(video_backend::directx);
324 #endif
325 sortGfxBackendsForCurrentSystem(availableBackends);
326 return availableBackends;
327 }
328
wzGetDefaultGfxBackendForCurrentSystem()329 video_backend wzGetDefaultGfxBackendForCurrentSystem()
330 {
331 // SDL backend supports: OpenGL, OpenGLES, Vulkan (if compiled with support), DirectX (on Windows, via LibANGLE)
332
333 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX) && (defined(_M_ARM64) || defined(_M_ARM))
334 // On ARM-based Windows, DirectX should be the default (for compatibility)
335 return video_backend::directx;
336 #else
337 // Future TODO examples:
338 // - Default to Vulkan backend on macOS versions > 10.??, to use Metal via MoltenVK (needs testing - and may require exclusions depending on hardware?)
339 // - Default to DirectX (via LibANGLE) backend on Windows, depending on Windows version (and possibly hardware? / DirectX-level support?)
340 // - Check if Vulkan appears to be properly supported on a Windows / Linux system, and default to Vulkan backend?
341
342 // For now, default to OpenGL (which automatically falls back to OpenGL ES if needed)
343 return video_backend::opengl;
344 #endif
345 }
346
wzGetNextFallbackGfxBackendForCurrentSystem(const video_backend & current_failed_backend)347 static video_backend wzGetNextFallbackGfxBackendForCurrentSystem(const video_backend& current_failed_backend)
348 {
349 video_backend next_backend;
350 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX)
351 switch (current_failed_backend)
352 {
353 case video_backend::opengl:
354 // offer DirectX as a fallback option if OpenGL failed
355 next_backend = video_backend::directx;
356 break;
357 #if (defined(_M_ARM64) || defined(_M_ARM))
358 case video_backend::directx:
359 // since DirectX is the default on ARM-based Windows, offer OpenGL as an alternative
360 next_backend = video_backend::opengl;
361 break;
362 #endif
363 default:
364 // offer usual default
365 next_backend = wzGetDefaultGfxBackendForCurrentSystem();
366 break;
367 }
368 #elif defined(WZ_OS_MAC)
369 switch (current_failed_backend)
370 {
371 case video_backend::opengl:
372 // offer Vulkan (which uses Vulkan -> Metal) as a fallback option if OpenGL failed
373 next_backend = video_backend::vulkan;
374 break;
375 default:
376 // offer usual default
377 next_backend = wzGetDefaultGfxBackendForCurrentSystem();
378 break;
379 }
380 #else
381 next_backend = wzGetDefaultGfxBackendForCurrentSystem();
382 #endif
383
384 // sanity-check: verify that next_backend is in available backends
385 const auto available = wzAvailableGfxBackends();
386 if (std::find(available.begin(), available.end(), next_backend) == available.end())
387 {
388 // next_backend does not exist in the list of available backends, so default to wzGetDefaultGfxBackendForCurrentSystem()
389 next_backend = wzGetDefaultGfxBackendForCurrentSystem();
390 }
391
392 return next_backend;
393 }
394
SDL_WZBackend_GetDrawableSize(SDL_Window * window,int * w,int * h)395 void SDL_WZBackend_GetDrawableSize(SDL_Window* window,
396 int* w,
397 int* h)
398 {
399 if (!WZbackend.has_value())
400 {
401 return;
402 }
403 switch (WZbackend.value())
404 {
405 #if defined(WZ_BACKEND_DIRECTX)
406 case video_backend::directx: // because DirectX is supported via OpenGLES (LibANGLE)
407 #endif
408 case video_backend::opengl:
409 case video_backend::opengles:
410 return SDL_GL_GetDrawableSize(window, w, h);
411 case video_backend::vulkan:
412 #if defined(HAVE_SDL_VULKAN_H)
413 return SDL_Vulkan_GetDrawableSize(window, w, h);
414 #else
415 SDL_version compiled_version;
416 SDL_VERSION(&compiled_version);
417 debug(LOG_FATAL, "The version of SDL used for compilation (%u.%u.%u) did not have the SDL_vulkan.h header", (unsigned int)compiled_version.major, (unsigned int)compiled_version.minor, (unsigned int)compiled_version.patch);
418 if (w) { w = 0; }
419 if (h) { h = 0; }
420 return;
421 #endif
422 case video_backend::num_backends:
423 debug(LOG_FATAL, "Should never happen");
424 return;
425 }
426 }
427
setDisplayScale(unsigned int displayScale)428 void setDisplayScale(unsigned int displayScale)
429 {
430 ASSERT(displayScale >= 100, "Invalid display scale: %u", displayScale);
431 current_displayScale = displayScale;
432 current_displayScaleFactor = (float)displayScale / 100.f;
433 }
434
wzGetCurrentDisplayScale()435 unsigned int wzGetCurrentDisplayScale()
436 {
437 return current_displayScale;
438 }
439
wzShowMouse(bool visible)440 void wzShowMouse(bool visible)
441 {
442 SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
443 }
444
wzGetTicks()445 int wzGetTicks()
446 {
447 return SDL_GetTicks();
448 }
449
wzDisplayDialog(DialogType type,const char * title,const char * message)450 void wzDisplayDialog(DialogType type, const char *title, const char *message)
451 {
452 Uint32 sdl_messagebox_flags = 0;
453 switch (type)
454 {
455 case Dialog_Error:
456 sdl_messagebox_flags = SDL_MESSAGEBOX_ERROR;
457 break;
458 case Dialog_Warning:
459 sdl_messagebox_flags = SDL_MESSAGEBOX_WARNING;
460 break;
461 case Dialog_Information:
462 sdl_messagebox_flags = SDL_MESSAGEBOX_INFORMATION;
463 break;
464 }
465 SDL_ShowSimpleMessageBox(sdl_messagebox_flags, title, message, WZwindow);
466 }
467
wzGetCurrentWindowMode()468 WINDOW_MODE wzGetCurrentWindowMode()
469 {
470 if (!WZbackend.has_value())
471 {
472 // return a dummy value
473 return WINDOW_MODE::windowed;
474 }
475 if (WZwindow == nullptr)
476 {
477 debug(LOG_WARNING, "wzGetCurrentWindowMode called when window is not available");
478 return WINDOW_MODE::windowed;
479 }
480
481 Uint32 flags = SDL_GetWindowFlags(WZwindow);
482 if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
483 {
484 return WINDOW_MODE::desktop_fullscreen;
485 }
486 else if (flags & SDL_WINDOW_FULLSCREEN)
487 {
488 return WINDOW_MODE::fullscreen;
489 }
490 else
491 {
492 return WINDOW_MODE::windowed;
493 }
494 }
495
wzSupportedWindowModes()496 std::vector<WINDOW_MODE> wzSupportedWindowModes()
497 {
498 #if defined(WZ_OS_MAC)
499 // on macOS, SDL_WINDOW_FULLSCREEN_DESKTOP *must* be used (or high-DPI fullscreen toggling breaks)
500 // thus "classic" fullscreen is not supported
501 return {WINDOW_MODE::desktop_fullscreen, WINDOW_MODE::windowed};
502 #else
503 return {WINDOW_MODE::desktop_fullscreen, WINDOW_MODE::windowed, WINDOW_MODE::fullscreen};
504 #endif
505 }
506
wzGetNextWindowMode(WINDOW_MODE currentMode)507 WINDOW_MODE wzGetNextWindowMode(WINDOW_MODE currentMode)
508 {
509 auto supportedModes = wzSupportedWindowModes();
510 ASSERT_OR_RETURN(WINDOW_MODE::windowed, !supportedModes.empty(), "No supported fullscreen / windowed modes available?");
511 auto it = std::find(supportedModes.begin(), supportedModes.end(), currentMode);
512 if (it == supportedModes.end())
513 {
514 // we appear to be in an unsupported mode - so default to the first
515 return *supportedModes.begin();
516 }
517 ++it;
518 if (it == supportedModes.end()) { it = supportedModes.begin(); }
519 return *it;
520 }
521
wzAltEnterToggleFullscreen()522 WINDOW_MODE wzAltEnterToggleFullscreen()
523 {
524 // toggles out of and into the "last" fullscreen mode
525 auto mode = wzGetCurrentWindowMode();
526 switch (mode)
527 {
528 case WINDOW_MODE::desktop_fullscreen:
529 // fall-through
530 case WINDOW_MODE::fullscreen:
531 lastFullscreenMode = mode;
532 if (wzChangeWindowMode(WINDOW_MODE::windowed))
533 {
534 mode = WINDOW_MODE::windowed;
535 }
536 break;
537 case WINDOW_MODE::windowed:
538 // use lastFullscreenMode
539 if (wzChangeWindowMode(lastFullscreenMode))
540 {
541 mode = lastFullscreenMode;
542 }
543 break;
544 }
545 return mode;
546 }
547
wzChangeWindowMode(WINDOW_MODE mode)548 bool wzChangeWindowMode(WINDOW_MODE mode)
549 {
550 if (wzGetCurrentWindowMode() == mode)
551 {
552 // already in this mode
553 return true;
554 }
555
556 int sdl_result = -1;
557 switch (mode)
558 {
559 case WINDOW_MODE::desktop_fullscreen:
560 sdl_result = SDL_SetWindowFullscreen(WZwindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
561 if (sdl_result != 0) { return false; }
562 wzSetWindowIsResizable(false);
563 break;
564 case WINDOW_MODE::windowed:
565 sdl_result = SDL_SetWindowFullscreen(WZwindow, 0);
566 if (sdl_result != 0) { return false; }
567 wzSetWindowIsResizable(true);
568 break;
569 case WINDOW_MODE::fullscreen:
570 sdl_result = SDL_SetWindowFullscreen(WZwindow, SDL_WINDOW_FULLSCREEN);
571 if (sdl_result != 0) { return false; }
572 wzSetWindowIsResizable(false);
573 break;
574 }
575
576 return sdl_result == 0;
577 }
578
wzIsFullscreen()579 bool wzIsFullscreen()
580 {
581 if (WZwindow == nullptr)
582 {
583 // NOTE: Can't use debug(...) to log here or it will risk overwriting fatal error messages,
584 // as `_debug(...)` calls this function
585 return false;
586 }
587 Uint32 flags = SDL_GetWindowFlags(WZwindow);
588 if ((flags & SDL_WINDOW_FULLSCREEN) || (flags & SDL_WINDOW_FULLSCREEN_DESKTOP))
589 {
590 return true;
591 }
592 return false;
593 }
594
wzIsMaximized()595 bool wzIsMaximized()
596 {
597 if (WZwindow == nullptr)
598 {
599 debug(LOG_WARNING, "wzIsMaximized called when window is not available");
600 return false;
601 }
602 Uint32 flags = SDL_GetWindowFlags(WZwindow);
603 if (flags & SDL_WINDOW_MAXIMIZED)
604 {
605 return true;
606 }
607 return false;
608 }
609
wzQuit()610 void wzQuit()
611 {
612 // Create a quit event to halt game loop.
613 SDL_Event quitEvent;
614 quitEvent.type = SDL_QUIT;
615 SDL_PushEvent(&quitEvent);
616 }
617
wzGrabMouse()618 void wzGrabMouse()
619 {
620 if (!WZbackend.has_value())
621 {
622 return;
623 }
624 if (WZwindow == nullptr)
625 {
626 debug(LOG_WARNING, "wzGrabMouse called when window is not available - ignoring");
627 return;
628 }
629 SDL_SetWindowGrab(WZwindow, SDL_TRUE);
630 }
631
wzReleaseMouse()632 void wzReleaseMouse()
633 {
634 if (!WZbackend.has_value())
635 {
636 return;
637 }
638 if (WZwindow == nullptr)
639 {
640 debug(LOG_WARNING, "wzReleaseMouse called when window is not available - ignoring");
641 return;
642 }
643 SDL_SetWindowGrab(WZwindow, SDL_FALSE);
644 }
645
wzDelay(unsigned int delay)646 void wzDelay(unsigned int delay)
647 {
648 SDL_Delay(delay);
649 }
650
651 /**************************/
652 /*** Thread support ***/
653 /**************************/
wzThreadCreate(int (* threadFunc)(void *),void * data)654 WZ_THREAD *wzThreadCreate(int (*threadFunc)(void *), void *data)
655 {
656 return (WZ_THREAD *)SDL_CreateThread(threadFunc, "wzThread", data);
657 }
658
wzThreadID(WZ_THREAD * thread)659 unsigned long wzThreadID(WZ_THREAD *thread)
660 {
661 SDL_threadID threadID = SDL_GetThreadID((SDL_Thread *)thread);
662 return threadID;
663 }
664
wzThreadJoin(WZ_THREAD * thread)665 int wzThreadJoin(WZ_THREAD *thread)
666 {
667 int result;
668 SDL_WaitThread((SDL_Thread *)thread, &result);
669 return result;
670 }
671
wzThreadDetach(WZ_THREAD * thread)672 void wzThreadDetach(WZ_THREAD *thread)
673 {
674 SDL_DetachThread((SDL_Thread *)thread);
675 }
676
wzThreadStart(WZ_THREAD * thread)677 void wzThreadStart(WZ_THREAD *thread)
678 {
679 (void)thread; // no-op
680 }
681
wzYieldCurrentThread()682 void wzYieldCurrentThread()
683 {
684 SDL_Delay(40);
685 }
686
wzMutexCreate()687 WZ_MUTEX *wzMutexCreate()
688 {
689 return (WZ_MUTEX *)SDL_CreateMutex();
690 }
691
wzMutexDestroy(WZ_MUTEX * mutex)692 void wzMutexDestroy(WZ_MUTEX *mutex)
693 {
694 SDL_DestroyMutex((SDL_mutex *)mutex);
695 }
696
wzMutexLock(WZ_MUTEX * mutex)697 void wzMutexLock(WZ_MUTEX *mutex)
698 {
699 SDL_LockMutex((SDL_mutex *)mutex);
700 }
701
wzMutexUnlock(WZ_MUTEX * mutex)702 void wzMutexUnlock(WZ_MUTEX *mutex)
703 {
704 SDL_UnlockMutex((SDL_mutex *)mutex);
705 }
706
wzSemaphoreCreate(int startValue)707 WZ_SEMAPHORE *wzSemaphoreCreate(int startValue)
708 {
709 return (WZ_SEMAPHORE *)SDL_CreateSemaphore(startValue);
710 }
711
wzSemaphoreDestroy(WZ_SEMAPHORE * semaphore)712 void wzSemaphoreDestroy(WZ_SEMAPHORE *semaphore)
713 {
714 SDL_DestroySemaphore((SDL_sem *)semaphore);
715 }
716
wzSemaphoreWait(WZ_SEMAPHORE * semaphore)717 void wzSemaphoreWait(WZ_SEMAPHORE *semaphore)
718 {
719 SDL_SemWait((SDL_sem *)semaphore);
720 }
721
wzSemaphorePost(WZ_SEMAPHORE * semaphore)722 void wzSemaphorePost(WZ_SEMAPHORE *semaphore)
723 {
724 SDL_SemPost((SDL_sem *)semaphore);
725 }
726
727 // Asynchronously executes exec->doExecOnMainThread() on the main thread
728 // `exec` should be a subclass of `WZ_MAINTHREADEXEC`
729 //
730 // `exec` must be allocated on the heap since the main event loop takes ownership of it
731 // and will handle deleting it once it has been processed.
732 // It is not safe to access `exec` after calling wzAsyncExecOnMainThread.
733 //
734 // No guarantees are made about when execFunc() will be called relative to the
735 // calling of this function - this function may return before, during, or after
736 // execFunc()'s execution on the main thread.
wzAsyncExecOnMainThread(WZ_MAINTHREADEXEC * exec)737 void wzAsyncExecOnMainThread(WZ_MAINTHREADEXEC *exec)
738 {
739 Uint32 _wzSDLAppEvent = wzSDLAppEvent.load();
740 assert(_wzSDLAppEvent != ((Uint32)-1));
741 if (_wzSDLAppEvent == ((Uint32)-1)) {
742 // The app-defined event has not yet been registered with SDL
743 return;
744 }
745 SDL_Event event;
746 SDL_memset(&event, 0, sizeof(event));
747 event.type = _wzSDLAppEvent;
748 event.user.code = wzSDLAppEventCodes::MAINTHREADEXEC;
749 event.user.data1 = exec;
750 assert(event.user.data1 != nullptr);
751 event.user.data2 = 0;
752 SDL_PushEvent(&event);
753 // receiver handles deleting `exec` on the main thread after doExecOnMainThread() has been called
754 }
755
756 /*!
757 ** The keycodes we care about
758 **/
initKeycodes()759 static inline void initKeycodes()
760 {
761 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_ESC, SDLK_ESCAPE));
762 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_1, SDLK_1));
763 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_2, SDLK_2));
764 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_3, SDLK_3));
765 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_4, SDLK_4));
766 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_5, SDLK_5));
767 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_6, SDLK_6));
768 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_7, SDLK_7));
769 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_8, SDLK_8));
770 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_9, SDLK_9));
771 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_0, SDLK_0));
772 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_MINUS, SDLK_MINUS));
773 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_EQUALS, SDLK_EQUALS));
774 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKSPACE, SDLK_BACKSPACE));
775 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_TAB, SDLK_TAB));
776 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Q, SDLK_q));
777 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_W, SDLK_w));
778 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_E, SDLK_e));
779 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_R, SDLK_r));
780 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_T, SDLK_t));
781 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Y, SDLK_y));
782 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_U, SDLK_u));
783 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_I, SDLK_i));
784 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_O, SDLK_o));
785 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_P, SDLK_p));
786 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LBRACE, SDLK_LEFTBRACKET));
787 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RBRACE, SDLK_RIGHTBRACKET));
788 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RETURN, SDLK_RETURN));
789 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LCTRL, SDLK_LCTRL));
790 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_A, SDLK_a));
791 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_S, SDLK_s));
792 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_D, SDLK_d));
793 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F, SDLK_f));
794 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_G, SDLK_g));
795 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_H, SDLK_h));
796 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_J, SDLK_j));
797 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_K, SDLK_k));
798 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_L, SDLK_l));
799 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SEMICOLON, SDLK_SEMICOLON));
800 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_QUOTE, SDLK_QUOTE));
801 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKQUOTE, SDLK_BACKQUOTE));
802 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LSHIFT, SDLK_LSHIFT));
803 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LMETA, SDLK_LGUI));
804 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LSUPER, SDLK_LGUI));
805 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKSLASH, SDLK_BACKSLASH));
806 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Z, SDLK_z));
807 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_X, SDLK_x));
808 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_C, SDLK_c));
809 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_V, SDLK_v));
810 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_B, SDLK_b));
811 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_N, SDLK_n));
812 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_M, SDLK_m));
813 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_COMMA, SDLK_COMMA));
814 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_FULLSTOP, SDLK_PERIOD));
815 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_FORWARDSLASH, SDLK_SLASH));
816 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RSHIFT, SDLK_RSHIFT));
817 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RMETA, SDLK_RGUI));
818 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RSUPER, SDLK_RGUI));
819 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_STAR, SDLK_KP_MULTIPLY));
820 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LALT, SDLK_LALT));
821 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SPACE, SDLK_SPACE));
822 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_CAPSLOCK, SDLK_CAPSLOCK));
823 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F1, SDLK_F1));
824 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F2, SDLK_F2));
825 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F3, SDLK_F3));
826 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F4, SDLK_F4));
827 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F5, SDLK_F5));
828 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F6, SDLK_F6));
829 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F7, SDLK_F7));
830 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F8, SDLK_F8));
831 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F9, SDLK_F9));
832 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F10, SDLK_F10));
833 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_NUMLOCK, SDLK_NUMLOCKCLEAR));
834 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SCROLLLOCK, SDLK_SCROLLLOCK));
835 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_7, SDLK_KP_7));
836 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_8, SDLK_KP_8));
837 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_9, SDLK_KP_9));
838 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_MINUS, SDLK_KP_MINUS));
839 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_4, SDLK_KP_4));
840 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_5, SDLK_KP_5));
841 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_6, SDLK_KP_6));
842 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_PLUS, SDLK_KP_PLUS));
843 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_1, SDLK_KP_1));
844 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_2, SDLK_KP_2));
845 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_3, SDLK_KP_3));
846 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_0, SDLK_KP_0));
847 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_FULLSTOP, SDLK_KP_PERIOD));
848 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F11, SDLK_F11));
849 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F12, SDLK_F12));
850 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RCTRL, SDLK_RCTRL));
851 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_BACKSLASH, SDLK_KP_DIVIDE));
852 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RALT, SDLK_RALT));
853 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_HOME, SDLK_HOME));
854 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_UPARROW, SDLK_UP));
855 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_PAGEUP, SDLK_PAGEUP));
856 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LEFTARROW, SDLK_LEFT));
857 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RIGHTARROW, SDLK_RIGHT));
858 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_END, SDLK_END));
859 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_DOWNARROW, SDLK_DOWN));
860 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_PAGEDOWN, SDLK_PAGEDOWN));
861 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_INSERT, SDLK_INSERT));
862 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_DELETE, SDLK_DELETE));
863 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KPENTER, SDLK_KP_ENTER));
864 KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_IGNORE, SDL_Keycode(5190)));
865
866 std::map<KEY_CODE, SDL_Keycode>::iterator it;
867 for (it = KEY_CODE_to_SDLKey.begin(); it != KEY_CODE_to_SDLKey.end(); it++)
868 {
869 SDLKey_to_KEY_CODE.insert(std::pair<SDL_Keycode, KEY_CODE>(it->second, it->first));
870 }
871 }
872
sdlKeyToKeyCode(SDL_Keycode key)873 static inline KEY_CODE sdlKeyToKeyCode(SDL_Keycode key)
874 {
875 std::map<SDL_Keycode, KEY_CODE >::iterator it;
876
877 it = SDLKey_to_KEY_CODE.find(key);
878 if (it != SDLKey_to_KEY_CODE.end())
879 {
880 return it->second;
881 }
882 return (KEY_CODE)key;
883 }
884
keyCodeToSDLKey(KEY_CODE code)885 static inline SDL_Keycode keyCodeToSDLKey(KEY_CODE code)
886 {
887 std::map<KEY_CODE, SDL_Keycode>::iterator it;
888
889 it = KEY_CODE_to_SDLKey.find(code);
890 if (it != KEY_CODE_to_SDLKey.end())
891 {
892 return it->second;
893 }
894 return (SDL_Keycode)code;
895 }
896
897 // Cyclic increment.
inputPointerNext(InputKey * p)898 static InputKey *inputPointerNext(InputKey *p)
899 {
900 return p + 1 == pInputBuffer + INPUT_MAXSTR ? pInputBuffer : p + 1;
901 }
902
903 /* add count copies of the characater code to the input buffer */
inputAddBuffer(UDWORD key,utf_32_char unicode)904 static void inputAddBuffer(UDWORD key, utf_32_char unicode)
905 {
906 /* Calculate what pEndBuffer will be set to next */
907 InputKey *pNext = inputPointerNext(pEndBuffer);
908
909 if (pNext == pStartBuffer)
910 {
911 return; // Buffer full.
912 }
913
914 // Add key to buffer.
915 pEndBuffer->key = key;
916 pEndBuffer->unicode = unicode;
917 pEndBuffer = pNext;
918 }
919
keyScanToString(KEY_CODE code,char * ascii,UDWORD maxStringSize)920 void keyScanToString(KEY_CODE code, char *ascii, UDWORD maxStringSize)
921 {
922 if (code == KEY_LCTRL)
923 {
924 // shortcuts with modifier keys work with either key.
925 strcpy(ascii, "Ctrl");
926 return;
927 }
928 else if (code == KEY_LSHIFT)
929 {
930 // shortcuts with modifier keys work with either key.
931 strcpy(ascii, "Shift");
932 return;
933 }
934 else if (code == KEY_LALT)
935 {
936 // shortcuts with modifier keys work with either key.
937 strcpy(ascii, "Alt");
938 return;
939 }
940 else if (code == KEY_LMETA)
941 {
942 // shortcuts with modifier keys work with either key.
943 #ifdef WZ_OS_MAC
944 strcpy(ascii, "Cmd");
945 #else
946 strcpy(ascii, "Meta");
947 #endif
948 return;
949 }
950
951 if (code < KEY_MAXSCAN)
952 {
953 snprintf(ascii, maxStringSize, "%s", SDL_GetKeyName(keyCodeToSDLKey(code)));
954 if (ascii[0] >= 'a' && ascii[0] <= 'z' && ascii[1] != 0)
955 {
956 // capitalize
957 ascii[0] += 'A' - 'a';
958 return;
959 }
960 }
961 else
962 {
963 strcpy(ascii, "???");
964 }
965 }
966
967 /* Initialise the input module */
inputInitialise(void)968 void inputInitialise(void)
969 {
970 for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
971 {
972 aKeyState[i].state = KEY_UP;
973 }
974
975 for (unsigned int i = 0; i < MOUSE_END; i++)
976 {
977 aMouseState[i].state = KEY_UP;
978 }
979
980 pStartBuffer = pInputBuffer;
981 pEndBuffer = pInputBuffer;
982
983 dragX = mouseXPos = screenWidth / 2;
984 dragY = mouseYPos = screenHeight / 2;
985 dragKey = MOUSE_LMB;
986
987 }
988
989 /* Clear the input buffer */
inputClearBuffer(void)990 void inputClearBuffer(void)
991 {
992 pStartBuffer = pInputBuffer;
993 pEndBuffer = pInputBuffer;
994 }
995
996 /* Return the next key press or 0 if no key in the buffer.
997 * The key returned will have been remapped to the correct ascii code for the
998 * windows key map.
999 * All key presses are buffered up (including windows auto repeat).
1000 */
inputGetKey(utf_32_char * unicode)1001 UDWORD inputGetKey(utf_32_char *unicode)
1002 {
1003 UDWORD retVal;
1004
1005 if (pStartBuffer == pEndBuffer)
1006 {
1007 return 0; // Buffer empty.
1008 }
1009
1010 retVal = pStartBuffer->key;
1011 if (unicode)
1012 {
1013 *unicode = pStartBuffer->unicode;
1014 }
1015 if (!retVal)
1016 {
1017 retVal = ' '; // Don't return 0 if we got a virtual key, since that's interpreted as no input.
1018 }
1019 pStartBuffer = inputPointerNext(pStartBuffer);
1020
1021 return retVal;
1022 }
1023
inputGetClicks()1024 MousePresses const &inputGetClicks()
1025 {
1026 return mousePresses;
1027 }
1028
1029 /*!
1030 * This is called once a frame so that the system can tell
1031 * whether a key was pressed this turn or held down from the last frame.
1032 */
inputNewFrame(void)1033 void inputNewFrame(void)
1034 {
1035 // handle the keyboard
1036 for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
1037 {
1038 if (aKeyState[i].state == KEY_PRESSED)
1039 {
1040 aKeyState[i].state = KEY_DOWN;
1041 debug(LOG_NEVER, "This key is DOWN! %x, %d [%s]", i, i, SDL_GetKeyName(keyCodeToSDLKey((KEY_CODE)i)));
1042 }
1043 else if (aKeyState[i].state == KEY_RELEASED ||
1044 aKeyState[i].state == KEY_PRESSRELEASE)
1045 {
1046 aKeyState[i].state = KEY_UP;
1047 debug(LOG_NEVER, "This key is UP! %x, %d [%s]", i, i, SDL_GetKeyName(keyCodeToSDLKey((KEY_CODE)i)));
1048 }
1049 }
1050
1051 // handle the mouse
1052 for (unsigned int i = 0; i < MOUSE_END; i++)
1053 {
1054 if (aMouseState[i].state == KEY_PRESSED)
1055 {
1056 aMouseState[i].state = KEY_DOWN;
1057 }
1058 else if (aMouseState[i].state == KEY_RELEASED
1059 || aMouseState[i].state == KEY_DOUBLECLICK
1060 || aMouseState[i].state == KEY_PRESSRELEASE)
1061 {
1062 aMouseState[i].state = KEY_UP;
1063 }
1064 }
1065 mousePresses.clear();
1066 mouseWheelSpeed = Vector2i(0, 0);
1067 }
1068
1069 /*!
1070 * Release all keys (and buttons) when we lose focus
1071 */
inputLoseFocus(void)1072 void inputLoseFocus(void)
1073 {
1074 /* Lost the window focus, have to take this as a global key up */
1075 for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
1076 {
1077 aKeyState[i].state = KEY_UP;
1078 }
1079 for (unsigned int i = 0; i < MOUSE_END; i++)
1080 {
1081 aMouseState[i].state = KEY_UP;
1082 }
1083 }
1084
1085 /* This returns true if the key is currently depressed */
keyDown(KEY_CODE code)1086 bool keyDown(KEY_CODE code)
1087 {
1088 ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1089 return (aKeyState[code].state != KEY_UP);
1090 }
1091
1092 /* This returns true if the key went from being up to being down this frame */
keyPressed(KEY_CODE code)1093 bool keyPressed(KEY_CODE code)
1094 {
1095 ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1096 return ((aKeyState[code].state == KEY_PRESSED) || (aKeyState[code].state == KEY_PRESSRELEASE));
1097 }
1098
1099 /* This returns true if the key went from being down to being up this frame */
keyReleased(KEY_CODE code)1100 bool keyReleased(KEY_CODE code)
1101 {
1102 ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1103 return ((aKeyState[code].state == KEY_RELEASED) || (aKeyState[code].state == KEY_PRESSRELEASE));
1104 }
1105
1106 /* Return the X coordinate of the mouse */
mouseX(void)1107 Uint16 mouseX(void)
1108 {
1109 return mouseXPos;
1110 }
1111
1112 /* Return the Y coordinate of the mouse */
mouseY(void)1113 Uint16 mouseY(void)
1114 {
1115 return mouseYPos;
1116 }
1117
wzMouseInWindow()1118 bool wzMouseInWindow()
1119 {
1120 return mouseInWindow;
1121 }
1122
getMouseWheelSpeed()1123 Vector2i const& getMouseWheelSpeed()
1124 {
1125 return mouseWheelSpeed;
1126 }
1127
mousePressPos_DEPRECATED(MOUSE_KEY_CODE code)1128 Vector2i mousePressPos_DEPRECATED(MOUSE_KEY_CODE code)
1129 {
1130 return aMouseState[code].pressPos;
1131 }
1132
mouseReleasePos_DEPRECATED(MOUSE_KEY_CODE code)1133 Vector2i mouseReleasePos_DEPRECATED(MOUSE_KEY_CODE code)
1134 {
1135 return aMouseState[code].releasePos;
1136 }
1137
1138 /* This returns true if the mouse key is currently depressed */
mouseDown(MOUSE_KEY_CODE code)1139 bool mouseDown(MOUSE_KEY_CODE code)
1140 {
1141 return (aMouseState[code].state != KEY_UP) ||
1142
1143 // holding down LMB and RMB counts as holding down MMB
1144 (code == MOUSE_MMB && aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state != KEY_UP);
1145 }
1146
1147 /* This returns true if the mouse key was double clicked */
mouseDClicked(MOUSE_KEY_CODE code)1148 bool mouseDClicked(MOUSE_KEY_CODE code)
1149 {
1150 return (aMouseState[code].state == KEY_DOUBLECLICK);
1151 }
1152
1153 /* This returns true if the mouse key went from being up to being down this frame */
mousePressed(MOUSE_KEY_CODE code)1154 bool mousePressed(MOUSE_KEY_CODE code)
1155 {
1156 return ((aMouseState[code].state == KEY_PRESSED) ||
1157 (aMouseState[code].state == KEY_DOUBLECLICK) ||
1158 (aMouseState[code].state == KEY_PRESSRELEASE));
1159 }
1160
1161 /* This returns true if the mouse key went from being down to being up this frame */
mouseReleased(MOUSE_KEY_CODE code)1162 bool mouseReleased(MOUSE_KEY_CODE code)
1163 {
1164 return ((aMouseState[code].state == KEY_RELEASED) ||
1165 (aMouseState[code].state == KEY_DOUBLECLICK) ||
1166 (aMouseState[code].state == KEY_PRESSRELEASE));
1167 }
1168
1169 /* Check for a mouse drag, return the drag start coords if dragging */
mouseDrag(MOUSE_KEY_CODE code,UDWORD * px,UDWORD * py)1170 bool mouseDrag(MOUSE_KEY_CODE code, UDWORD *px, UDWORD *py)
1171 {
1172 if ((aMouseState[code].state == KEY_DRAG) ||
1173 // dragging LMB and RMB counts as dragging MMB
1174 (code == MOUSE_MMB && ((aMouseState[MOUSE_LMB].state == KEY_DRAG && aMouseState[MOUSE_RMB].state != KEY_UP) ||
1175 (aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state == KEY_DRAG))))
1176 {
1177 *px = dragX;
1178 *py = dragY;
1179 return true;
1180 }
1181
1182 return false;
1183 }
1184
1185 /*!
1186 * Handle keyboard events
1187 */
inputHandleKeyEvent(SDL_KeyboardEvent * keyEvent)1188 static void inputHandleKeyEvent(SDL_KeyboardEvent *keyEvent)
1189 {
1190 switch (keyEvent->type)
1191 {
1192 case SDL_KEYDOWN:
1193 {
1194 unsigned vk = 0;
1195 switch (keyEvent->keysym.sym)
1196 {
1197 // our "editing" keys for text
1198 case SDLK_LEFT:
1199 vk = INPBUF_LEFT;
1200 break;
1201 case SDLK_RIGHT:
1202 vk = INPBUF_RIGHT;
1203 break;
1204 case SDLK_UP:
1205 vk = INPBUF_UP;
1206 break;
1207 case SDLK_DOWN:
1208 vk = INPBUF_DOWN;
1209 break;
1210 case SDLK_HOME:
1211 vk = INPBUF_HOME;
1212 break;
1213 case SDLK_END:
1214 vk = INPBUF_END;
1215 break;
1216 case SDLK_INSERT:
1217 vk = INPBUF_INS;
1218 break;
1219 case SDLK_DELETE:
1220 vk = INPBUF_DEL;
1221 break;
1222 case SDLK_PAGEUP:
1223 vk = INPBUF_PGUP;
1224 break;
1225 case SDLK_PAGEDOWN:
1226 vk = INPBUF_PGDN;
1227 break;
1228 case KEY_BACKSPACE:
1229 vk = INPBUF_BKSPACE;
1230 break;
1231 case KEY_TAB:
1232 vk = INPBUF_TAB;
1233 break;
1234 case KEY_RETURN:
1235 vk = INPBUF_CR;
1236 break;
1237 case KEY_ESC:
1238 vk = INPBUF_ESC;
1239 break;
1240 default:
1241 break;
1242 }
1243 // Keycodes without character representations are determined by their scancode bitwise OR-ed with 1<<30 (0x40000000).
1244 unsigned currentKey = keyEvent->keysym.sym;
1245 if (vk)
1246 {
1247 // Take care of 'editing' keys that were pressed
1248 inputAddBuffer(vk, 0);
1249 debug(LOG_INPUT, "Editing key: 0x%x, %d SDLkey=[%s] pressed", vk, vk, SDL_GetKeyName(currentKey));
1250 }
1251 else
1252 {
1253 // add everything else
1254 inputAddBuffer(currentKey, 0);
1255 }
1256
1257 debug(LOG_INPUT, "Key Code (pressed): 0x%x, %d, [%c] SDLkey=[%s]", currentKey, currentKey, currentKey < 128 && currentKey > 31 ? (char)currentKey : '?', SDL_GetKeyName(currentKey));
1258
1259 KEY_CODE code = sdlKeyToKeyCode(currentKey);
1260 if (code >= KEY_MAXSCAN)
1261 {
1262 break;
1263 }
1264 if (aKeyState[code].state == KEY_UP ||
1265 aKeyState[code].state == KEY_RELEASED ||
1266 aKeyState[code].state == KEY_PRESSRELEASE)
1267 {
1268 // whether double key press or not
1269 aKeyState[code].state = KEY_PRESSED;
1270 aKeyState[code].lastdown = 0;
1271 }
1272 break;
1273 }
1274
1275 case SDL_KEYUP:
1276 {
1277 unsigned currentKey = keyEvent->keysym.sym;
1278 debug(LOG_INPUT, "Key Code (*Depressed*): 0x%x, %d, [%c] SDLkey=[%s]", currentKey, currentKey, currentKey < 128 && currentKey > 31 ? (char)currentKey : '?', SDL_GetKeyName(currentKey));
1279 KEY_CODE code = sdlKeyToKeyCode(keyEvent->keysym.sym);
1280 if (code >= KEY_MAXSCAN)
1281 {
1282 break;
1283 }
1284 if (aKeyState[code].state == KEY_PRESSED)
1285 {
1286 aKeyState[code].state = KEY_PRESSRELEASE;
1287 }
1288 else if (aKeyState[code].state == KEY_DOWN)
1289 {
1290 aKeyState[code].state = KEY_RELEASED;
1291 }
1292 break;
1293 }
1294 default:
1295 break;
1296 }
1297 }
1298
1299 /*!
1300 * Handle text events (if we were to use SDL2)
1301 */
inputhandleText(SDL_TextInputEvent * Tevent)1302 void inputhandleText(SDL_TextInputEvent *Tevent)
1303 {
1304 size_t size = SDL_strlen(Tevent->text);
1305 if (size)
1306 {
1307 if (utf8Buf)
1308 {
1309 // clean up memory from last use.
1310 free(utf8Buf);
1311 utf8Buf = nullptr;
1312 }
1313 size_t newtextsize = 0;
1314 utf8Buf = UTF8toUTF32(Tevent->text, &newtextsize);
1315 debug(LOG_INPUT, "Keyboard: text input \"%s\"", Tevent->text);
1316 for (unsigned i = 0; i < newtextsize / sizeof(utf_32_char); ++i)
1317 {
1318 inputAddBuffer(0, utf8Buf[i]);
1319 }
1320 }
1321 }
1322
1323 /*!
1324 * Handle mouse wheel events
1325 */
inputHandleMouseWheelEvent(SDL_MouseWheelEvent * wheel)1326 static void inputHandleMouseWheelEvent(SDL_MouseWheelEvent *wheel)
1327 {
1328 mouseWheelSpeed += Vector2i(wheel->x, wheel->y);
1329
1330 if (wheel->x > 0 || wheel->y > 0)
1331 {
1332 aMouseState[MOUSE_WUP].state = KEY_PRESSED;
1333 aMouseState[MOUSE_WUP].lastdown = 0;
1334 }
1335 else if (wheel->x < 0 || wheel->y < 0)
1336 {
1337 aMouseState[MOUSE_WDN].state = KEY_PRESSED;
1338 aMouseState[MOUSE_WDN].lastdown = 0;
1339 }
1340 }
1341
1342 /*!
1343 * Handle mouse button events (We can handle up to 5)
1344 */
inputHandleMouseButtonEvent(SDL_MouseButtonEvent * buttonEvent)1345 static void inputHandleMouseButtonEvent(SDL_MouseButtonEvent *buttonEvent)
1346 {
1347 mouseXPos = (int)((float)buttonEvent->x / current_displayScaleFactor);
1348 mouseYPos = (int)((float)buttonEvent->y / current_displayScaleFactor);
1349
1350 MOUSE_KEY_CODE mouseKeyCode;
1351 switch (buttonEvent->button)
1352 {
1353 case SDL_BUTTON_LEFT: mouseKeyCode = MOUSE_LMB; break;
1354 case SDL_BUTTON_MIDDLE: mouseKeyCode = MOUSE_MMB; break;
1355 case SDL_BUTTON_RIGHT: mouseKeyCode = MOUSE_RMB; break;
1356 case SDL_BUTTON_X1: mouseKeyCode = MOUSE_X1; break;
1357 case SDL_BUTTON_X2: mouseKeyCode = MOUSE_X2; break;
1358 default: return; // Unknown button.
1359 }
1360
1361 MousePress mousePress;
1362 mousePress.key = mouseKeyCode;
1363 mousePress.pos = Vector2i(mouseXPos, mouseYPos);
1364
1365 switch (buttonEvent->type)
1366 {
1367 case SDL_MOUSEBUTTONDOWN:
1368 mousePress.action = MousePress::Press;
1369 mousePresses.push_back(mousePress);
1370
1371 aMouseState[mouseKeyCode].pressPos.x = mouseXPos;
1372 aMouseState[mouseKeyCode].pressPos.y = mouseYPos;
1373 if (aMouseState[mouseKeyCode].state == KEY_UP
1374 || aMouseState[mouseKeyCode].state == KEY_RELEASED
1375 || aMouseState[mouseKeyCode].state == KEY_PRESSRELEASE)
1376 {
1377 // whether double click or not
1378 if (realTime - aMouseState[mouseKeyCode].lastdown < DOUBLE_CLICK_INTERVAL)
1379 {
1380 aMouseState[mouseKeyCode].state = KEY_DOUBLECLICK;
1381 aMouseState[mouseKeyCode].lastdown = 0;
1382 }
1383 else
1384 {
1385 aMouseState[mouseKeyCode].state = KEY_PRESSED;
1386 aMouseState[mouseKeyCode].lastdown = realTime;
1387 }
1388
1389 if (mouseKeyCode < MOUSE_X1) // Assume they are draggin' with either LMB|RMB|MMB
1390 {
1391 dragKey = mouseKeyCode;
1392 dragX = mouseXPos;
1393 dragY = mouseYPos;
1394 }
1395 }
1396 break;
1397 case SDL_MOUSEBUTTONUP:
1398 mousePress.action = MousePress::Release;
1399 mousePresses.push_back(mousePress);
1400
1401 aMouseState[mouseKeyCode].releasePos.x = mouseXPos;
1402 aMouseState[mouseKeyCode].releasePos.y = mouseYPos;
1403 if (aMouseState[mouseKeyCode].state == KEY_PRESSED)
1404 {
1405 aMouseState[mouseKeyCode].state = KEY_PRESSRELEASE;
1406 }
1407 else if (aMouseState[mouseKeyCode].state == KEY_DOWN
1408 || aMouseState[mouseKeyCode].state == KEY_DRAG
1409 || aMouseState[mouseKeyCode].state == KEY_DOUBLECLICK)
1410 {
1411 aMouseState[mouseKeyCode].state = KEY_RELEASED;
1412 }
1413 break;
1414 default:
1415 break;
1416 }
1417 }
1418
1419 /*!
1420 * Handle mousemotion events
1421 */
inputHandleMouseMotionEvent(SDL_MouseMotionEvent * motionEvent)1422 static void inputHandleMouseMotionEvent(SDL_MouseMotionEvent *motionEvent)
1423 {
1424 switch (motionEvent->type)
1425 {
1426 case SDL_MOUSEMOTION:
1427 /* store the current mouse position */
1428 mouseXPos = (int)((float)motionEvent->x / current_displayScaleFactor);
1429 mouseYPos = (int)((float)motionEvent->y / current_displayScaleFactor);
1430
1431 /* now see if a drag has started */
1432 if ((aMouseState[dragKey].state == KEY_PRESSED ||
1433 aMouseState[dragKey].state == KEY_DOWN) &&
1434 (ABSDIF(dragX, mouseXPos) > DRAG_THRESHOLD ||
1435 ABSDIF(dragY, mouseYPos) > DRAG_THRESHOLD))
1436 {
1437 aMouseState[dragKey].state = KEY_DRAG;
1438 }
1439 break;
1440 default:
1441 break;
1442 }
1443 }
1444
1445 static int copied_argc = 0;
1446 static char** copied_argv = nullptr;
1447
1448 // This stage, we only setup keycodes, and copy argc & argv for later use initializing stuff.
wzMain(int & argc,char ** argv)1449 void wzMain(int &argc, char **argv)
1450 {
1451 initKeycodes();
1452
1453 // Create copies of argc and arv (for later use initializing QApplication for the script engine)
1454 copied_argv = new char*[argc+1];
1455 for(int i=0; i < argc; i++) {
1456 size_t len = strlen(argv[i]) + 1;
1457 copied_argv[i] = new char[len];
1458 memcpy(copied_argv[i], argv[i], len);
1459 }
1460 copied_argv[argc] = NULL;
1461 copied_argc = argc;
1462 }
1463
1464 #define MIN_WZ_GAMESCREEN_WIDTH 640
1465 #define MIN_WZ_GAMESCREEN_HEIGHT 480
1466
handleGameScreenSizeChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)1467 void handleGameScreenSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
1468 {
1469 screenWidth = newWidth;
1470 screenHeight = newHeight;
1471
1472 pie_SetVideoBufferWidth(screenWidth);
1473 pie_SetVideoBufferHeight(screenHeight);
1474 pie_UpdateSurfaceGeometry();
1475
1476 if (currentScreenResizingStatus == nullptr)
1477 {
1478 // The screen size change details are stored in scaled, logical units (points)
1479 // i.e. the values expect by the game engine.
1480 currentScreenResizingStatus = new ScreenSizeChange(oldWidth, oldHeight, screenWidth, screenHeight);
1481 }
1482 else
1483 {
1484 // update the new screen width / height, in case more than one resize message is processed this event loop
1485 currentScreenResizingStatus->newWidth = screenWidth;
1486 currentScreenResizingStatus->newHeight = screenHeight;
1487 }
1488 }
1489
handleWindowSizeChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)1490 void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
1491 {
1492 windowWidth = newWidth;
1493 windowHeight = newHeight;
1494
1495 // NOTE: This function receives the window size in the window's logical units, but not accounting for the interface scale factor.
1496 // Therefore, the provided old/newWidth/Height must be divided by the interface scale factor to calculate the new
1497 // *game* screen logical width / height.
1498 unsigned int oldScreenWidth = oldWidth / current_displayScaleFactor;
1499 unsigned int oldScreenHeight = oldHeight / current_displayScaleFactor;
1500 unsigned int newScreenWidth = newWidth / current_displayScaleFactor;
1501 unsigned int newScreenHeight = newHeight / current_displayScaleFactor;
1502
1503 handleGameScreenSizeChange(oldScreenWidth, oldScreenHeight, newScreenWidth, newScreenHeight);
1504
1505 gfx_api::context::get().handleWindowSizeChange(oldWidth, oldHeight, newWidth, newHeight);
1506 }
1507
1508
wzGetMinimumWindowSizeForDisplayScaleFactor(unsigned int * minWindowWidth,unsigned int * minWindowHeight,float displayScaleFactor=current_displayScaleFactor)1509 void wzGetMinimumWindowSizeForDisplayScaleFactor(unsigned int *minWindowWidth, unsigned int *minWindowHeight, float displayScaleFactor = current_displayScaleFactor)
1510 {
1511 if (minWindowWidth != nullptr)
1512 {
1513 *minWindowWidth = (int)ceil(MIN_WZ_GAMESCREEN_WIDTH * displayScaleFactor);
1514 }
1515 if (minWindowHeight != nullptr)
1516 {
1517 *minWindowHeight = (int)ceil(MIN_WZ_GAMESCREEN_HEIGHT * displayScaleFactor);
1518 }
1519 }
1520
wzGetMaximumDisplayScaleFactorsForWindowSize(unsigned int width,unsigned int height,float * horizScaleFactor,float * vertScaleFactor)1521 void wzGetMaximumDisplayScaleFactorsForWindowSize(unsigned int width, unsigned int height, float *horizScaleFactor, float *vertScaleFactor)
1522 {
1523 if (horizScaleFactor != nullptr)
1524 {
1525 *horizScaleFactor = (float)width / (float)MIN_WZ_GAMESCREEN_WIDTH;
1526 }
1527 if (vertScaleFactor != nullptr)
1528 {
1529 *vertScaleFactor = (float)height / (float)MIN_WZ_GAMESCREEN_HEIGHT;
1530 }
1531 }
1532
wzGetMaximumDisplayScaleFactorForWindowSize(unsigned int width,unsigned int height)1533 float wzGetMaximumDisplayScaleFactorForWindowSize(unsigned int width, unsigned int height)
1534 {
1535 float maxHorizScaleFactor = 0.f, maxVertScaleFactor = 0.f;
1536 wzGetMaximumDisplayScaleFactorsForWindowSize(width, height, &maxHorizScaleFactor, &maxVertScaleFactor);
1537 return std::min(maxHorizScaleFactor, maxVertScaleFactor);
1538 }
1539
1540 // returns: the maximum display scale percentage (sourced from wzAvailableDisplayScales), or 0 if window is below the minimum required size for the minimum supported display scale
wzGetMaximumDisplayScaleForWindowSize(unsigned int width,unsigned int height)1541 unsigned int wzGetMaximumDisplayScaleForWindowSize(unsigned int width, unsigned int height)
1542 {
1543 float maxDisplayScaleFactor = wzGetMaximumDisplayScaleFactorForWindowSize(width, height);
1544 unsigned int maxDisplayScalePercentage = floor(maxDisplayScaleFactor * 100.f);
1545
1546 auto availableDisplayScales = wzAvailableDisplayScales();
1547 std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1548
1549 auto maxDisplayScale = std::lower_bound(availableDisplayScales.begin(), availableDisplayScales.end(), maxDisplayScalePercentage);
1550 if (maxDisplayScale == availableDisplayScales.end())
1551 {
1552 // return the largest available display scale
1553 return availableDisplayScales.back();
1554 }
1555 if (*maxDisplayScale != maxDisplayScalePercentage)
1556 {
1557 if (maxDisplayScale == availableDisplayScales.begin())
1558 {
1559 // no lower display scale to return
1560 return 0;
1561 }
1562 --maxDisplayScale;
1563 }
1564 return *maxDisplayScale;
1565 }
1566
wzGetMaximumDisplayScaleForCurrentWindowSize()1567 unsigned int wzGetMaximumDisplayScaleForCurrentWindowSize()
1568 {
1569 return wzGetMaximumDisplayScaleForWindowSize(windowWidth, windowHeight);
1570 }
1571
wzGetSuggestedDisplayScaleForCurrentWindowSize(unsigned int desiredMaxScreenDimension)1572 unsigned int wzGetSuggestedDisplayScaleForCurrentWindowSize(unsigned int desiredMaxScreenDimension)
1573 {
1574 unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForCurrentWindowSize();
1575
1576 auto availableDisplayScales = wzAvailableDisplayScales();
1577 std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1578
1579 for (auto it = availableDisplayScales.begin(); it != availableDisplayScales.end() && (*it <= maxDisplayScale); it++)
1580 {
1581 auto resultingLogicalScreenWidth = (windowWidth * 100) / *it;
1582 auto resultingLogicalScreenHeight = (windowHeight * 100) / *it;
1583 if (resultingLogicalScreenWidth <= desiredMaxScreenDimension && resultingLogicalScreenHeight <= desiredMaxScreenDimension)
1584 {
1585 return *it;
1586 }
1587 }
1588
1589 return maxDisplayScale;
1590 }
1591
wzWindowSizeIsSmallerThanMinimumRequired(unsigned int width,unsigned int height,float displayScaleFactor=current_displayScaleFactor)1592 bool wzWindowSizeIsSmallerThanMinimumRequired(unsigned int width, unsigned int height, float displayScaleFactor = current_displayScaleFactor)
1593 {
1594 unsigned int minWindowWidth = 0, minWindowHeight = 0;
1595 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, displayScaleFactor);
1596 return ((width < minWindowWidth) || (height < minWindowHeight));
1597 }
1598
processScreenSizeChangeNotificationIfNeeded()1599 void processScreenSizeChangeNotificationIfNeeded()
1600 {
1601 if (currentScreenResizingStatus != nullptr)
1602 {
1603 // WZ must process the screen size change
1604 screen_updateGeometry(); // must come after gfx_api::context::handleWindowSizeChange
1605 gameScreenSizeDidChange(currentScreenResizingStatus->oldWidth, currentScreenResizingStatus->oldHeight, currentScreenResizingStatus->newWidth, currentScreenResizingStatus->newHeight);
1606 delete currentScreenResizingStatus;
1607 currentScreenResizingStatus = nullptr;
1608 }
1609 }
1610
1611 #if defined(WZ_OS_WIN)
1612
1613 # if defined(__has_include)
1614 # if __has_include(<shellscalingapi.h>)
1615 # include <shellscalingapi.h>
1616 # endif
1617 # endif
1618
1619 # if !defined(DPI_ENUMS_DECLARED)
1620 typedef enum PROCESS_DPI_AWARENESS
1621 {
1622 PROCESS_DPI_UNAWARE = 0,
1623 PROCESS_SYSTEM_DPI_AWARE = 1,
1624 PROCESS_PER_MONITOR_DPI_AWARE = 2
1625 } PROCESS_DPI_AWARENESS;
1626 # endif
1627 typedef HRESULT (WINAPI *GetProcessDpiAwarenessFunction)(
1628 HANDLE hprocess,
1629 PROCESS_DPI_AWARENESS *value
1630 );
1631 typedef BOOL (WINAPI *IsProcessDPIAwareFunction)();
1632
win_IsProcessDPIAware()1633 static bool win_IsProcessDPIAware()
1634 {
1635 HMODULE hShcore = LoadLibraryW(L"Shcore.dll");
1636 if (hShcore != NULL)
1637 {
1638 const auto* func_getProcessDpiAwareness = reinterpret_cast<GetProcessDpiAwarenessFunction>(reinterpret_cast<void*>(GetProcAddress(hShcore, "GetProcessDpiAwareness")));
1639 if (func_getProcessDpiAwareness)
1640 {
1641 PROCESS_DPI_AWARENESS result = PROCESS_DPI_UNAWARE;
1642 if (func_getProcessDpiAwareness(nullptr, &result) == S_OK)
1643 {
1644 FreeLibrary(hShcore);
1645 return result != PROCESS_DPI_UNAWARE;
1646 }
1647 }
1648 FreeLibrary(hShcore);
1649 }
1650 HMODULE hUser32 = LoadLibraryW(L"User32.dll");
1651 ASSERT_OR_RETURN(false, hUser32 != NULL, "Unable to get handle to User32?");
1652 const auto* func_isProcessDPIAware = reinterpret_cast<IsProcessDPIAwareFunction>(reinterpret_cast<void*>(GetProcAddress(hUser32, "IsProcessDPIAware")));
1653 bool bIsProcessDPIAware = false;
1654 if (func_isProcessDPIAware)
1655 {
1656 bIsProcessDPIAware = (func_isProcessDPIAware() == TRUE);
1657 }
1658 FreeLibrary(hUser32);
1659 return bIsProcessDPIAware;
1660 }
1661 #endif
1662
1663 #if defined(WZ_OS_WIN) // currently only used on Windows
wzGetDisplayDPI(int displayIndex,float * dpi,float * baseDpi)1664 static bool wzGetDisplayDPI(int displayIndex, float* dpi, float* baseDpi)
1665 {
1666 const float systemBaseDpi =
1667 #if defined(WZ_OS_WIN) || defined(_WIN32)
1668 96.f;
1669 #elif defined(WZ_OS_MAC) || defined(__APPLE__)
1670 72.f;
1671 #elif defined(__ANDROID__)
1672 160.f;
1673 #else
1674 // default to 96, but possibly extend if needed
1675 96.f;
1676 #endif
1677 if (baseDpi)
1678 {
1679 *baseDpi = systemBaseDpi;
1680 }
1681
1682 float hdpi, vdpi;
1683 if (SDL_GetDisplayDPI(displayIndex, nullptr, &hdpi, &vdpi) != 0)
1684 {
1685 debug(LOG_WARNING, "Failed to get the display (%d) DPI because : %s", displayIndex, SDL_GetError());
1686 return false;
1687 }
1688 if (dpi)
1689 {
1690 *dpi = std::min(hdpi, vdpi);
1691 }
1692 return true;
1693 }
1694 #endif
1695
wzGetDefaultBaseDisplayScale(int displayIndex)1696 unsigned int wzGetDefaultBaseDisplayScale(int displayIndex)
1697 {
1698 #if defined(WZ_OS_WIN)
1699 // SDL does not yet have built-in "high-DPI display" support on Windows (as of SDL 2.0.14)
1700 // Thus, all adjustments must be made using the Display Scale feature in WZ itself
1701 // Calculate a good base game Display Scale based on the actual screen display scale
1702 if (!win_IsProcessDPIAware())
1703 {
1704 return 100;
1705 }
1706 float dpi, baseDpi;
1707 if (!wzGetDisplayDPI(displayIndex, &dpi, &baseDpi))
1708 {
1709 // Failed to get the display DPI
1710 return 100;
1711 }
1712 unsigned int approxActualDisplayScale = static_cast<unsigned int>(ceil((dpi / baseDpi) * 100.f));
1713 auto availableDisplayScales = wzAvailableDisplayScales();
1714 std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1715 auto displayScale = std::lower_bound(availableDisplayScales.begin(), availableDisplayScales.end(), approxActualDisplayScale);
1716 if (displayScale == availableDisplayScales.end())
1717 {
1718 // return the largest available display scale
1719 return availableDisplayScales.back();
1720 }
1721 if (*displayScale != approxActualDisplayScale)
1722 {
1723 if (displayScale == availableDisplayScales.begin())
1724 {
1725 // no lower display scale to return
1726 return 100;
1727 }
1728 --displayScale;
1729 }
1730 return *displayScale;
1731 #elif defined(WZ_OS_MAC)
1732 // SDL has built-in "high-DPI display" support on Apple platforms
1733 // Just return 100%
1734 return 100;
1735 #else
1736 // SDL has built-in "high-DPI display" support on many other platforms (Wayland [SDL 2.0.10+])
1737 // For now, rely on that, and just return 100%
1738 return 100;
1739 #endif
1740 }
1741
wzChangeDisplayScale(unsigned int displayScale)1742 bool wzChangeDisplayScale(unsigned int displayScale)
1743 {
1744 if (WZwindow == nullptr)
1745 {
1746 debug(LOG_WARNING, "wzChangeDisplayScale called when window is not available");
1747 return false;
1748 }
1749
1750 float newDisplayScaleFactor = (float)displayScale / 100.f;
1751
1752 if (wzWindowSizeIsSmallerThanMinimumRequired(windowWidth, windowHeight, newDisplayScaleFactor))
1753 {
1754 // The current window width and/or height are below the required minimum window size
1755 // for this display scale factor.
1756 return false;
1757 }
1758
1759 // Store the new display scale factor
1760 setDisplayScale(displayScale);
1761
1762 // Set the new minimum window size
1763 unsigned int minWindowWidth = 0, minWindowHeight = 0;
1764 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, newDisplayScaleFactor);
1765 SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
1766
1767 // Update the game's logical screen size
1768 unsigned int oldScreenWidth = screenWidth, oldScreenHeight = screenHeight;
1769 unsigned int newScreenWidth = windowWidth, newScreenHeight = windowHeight;
1770 if (newDisplayScaleFactor > 1.0)
1771 {
1772 newScreenWidth = windowWidth / newDisplayScaleFactor;
1773 newScreenHeight = windowHeight / newDisplayScaleFactor;
1774 }
1775 handleGameScreenSizeChange(oldScreenWidth, oldScreenHeight, newScreenWidth, newScreenHeight);
1776 gameDisplayScaleFactorDidChange(newDisplayScaleFactor);
1777
1778 // Update the current mouse coordinates
1779 // (The prior stored mouseXPos / mouseYPos apply to the old coordinate system, and must be translated to the
1780 // new game coordinate system. Since the mouse hasn't moved - or it would generate events that override this -
1781 // the current position with respect to the window (which hasn't changed size) can be queried and used to
1782 // calculate the new game coordinate system mouse position.)
1783 //
1784 int windowMouseXPos = 0, windowMouseYPos = 0;
1785 SDL_GetMouseState(&windowMouseXPos, &windowMouseYPos);
1786 debug(LOG_WZ, "Old mouse position: %d, %d", mouseXPos, mouseYPos);
1787 mouseXPos = (int)((float)windowMouseXPos / current_displayScaleFactor);
1788 mouseYPos = (int)((float)windowMouseYPos / current_displayScaleFactor);
1789 debug(LOG_WZ, "New mouse position: %d, %d", mouseXPos, mouseYPos);
1790
1791
1792 processScreenSizeChangeNotificationIfNeeded();
1793
1794 return true;
1795 }
1796
wzChangeWindowResolution(int screen,unsigned int width,unsigned int height)1797 bool wzChangeWindowResolution(int screen, unsigned int width, unsigned int height)
1798 {
1799 if (WZwindow == nullptr)
1800 {
1801 debug(LOG_WARNING, "wzChangeWindowResolution called when window is not available");
1802 return false;
1803 }
1804 debug(LOG_WZ, "Attempt to change resolution to [%d] %dx%d", screen, width, height);
1805
1806 #if defined(WZ_OS_MAC)
1807 // Workaround for SDL (2.0.5) quirk on macOS:
1808 // When the green titlebar button is used to fullscreen the app in a new space:
1809 // - SDL does not return SDL_WINDOW_MAXIMIZED nor SDL_WINDOW_FULLSCREEN.
1810 // - Attempting to change the window resolution "succeeds" (in that the new window size is "set" and returned
1811 // by the SDL GetWindowSize functions).
1812 // - But other things break (ex. mouse coordinate translation) if the resolution is changed while the window
1813 // is maximized in this way.
1814 // - And the GL drawable size remains unchanged.
1815 // - So if it's been fullscreened by the user like this, but doesn't show as SDL_WINDOW_FULLSCREEN,
1816 // prevent window resolution changes.
1817 if (cocoaIsSDLWindowFullscreened(WZwindow) && !wzIsFullscreen())
1818 {
1819 debug(LOG_WZ, "The main window is fullscreened, but SDL doesn't think it is. Changing window resolution is not possible in this state. (SDL Bug).");
1820 return false;
1821 }
1822 #endif
1823
1824 // Get current window size + position + bounds
1825 int prev_x = 0, prev_y = 0, prev_width = 0, prev_height = 0;
1826 SDL_GetWindowPosition(WZwindow, &prev_x, &prev_y);
1827 SDL_GetWindowSize(WZwindow, &prev_width, &prev_height);
1828
1829 // Get the usable bounds for the current screen
1830 SDL_Rect bounds;
1831 if (wzIsFullscreen())
1832 {
1833 // When in fullscreen mode, obtain the screen's overall bounds
1834 if (SDL_GetDisplayBounds(screen, &bounds) != 0) {
1835 debug(LOG_ERROR, "Failed to get display bounds for screen: %d", screen);
1836 return false;
1837 }
1838 debug(LOG_WZ, "SDL_GetDisplayBounds for screen [%d]: pos %d x %d : res %d x %d", screen, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
1839 }
1840 else
1841 {
1842 // When in windowed mode, obtain the screen's *usable* display bounds
1843 if (SDL_GetDisplayUsableBounds(screen, &bounds) != 0) {
1844 debug(LOG_ERROR, "Failed to get usable display bounds for screen: %d", screen);
1845 return false;
1846 }
1847 debug(LOG_WZ, "SDL_GetDisplayUsableBounds for screen [%d]: pos %d x %d : WxH %d x %d", screen, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
1848
1849 // Verify that the desired window size does not exceed the usable bounds of the specified display.
1850 if ((width > bounds.w) || (height > bounds.h))
1851 {
1852 debug(LOG_WZ, "Unable to change window size to (%d x %d) because it is larger than the screen's usable bounds", width, height);
1853 return false;
1854 }
1855 }
1856
1857 // Check whether the desired window size is smaller than the minimum required for the current Display Scale
1858 unsigned int priorDisplayScale = current_displayScale;
1859 if (wzWindowSizeIsSmallerThanMinimumRequired(width, height))
1860 {
1861 // The new window size is smaller than the minimum required size for the current display scale level.
1862
1863 unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(width, height);
1864 if (maxDisplayScale < 100)
1865 {
1866 // Cannot adjust display scale factor below 1. Desired window size is below the minimum supported.
1867 debug(LOG_WZ, "Unable to change window size to (%d x %d) because it is smaller than the minimum supported at a 100%% display scale", width, height);
1868 return false;
1869 }
1870
1871 // Adjust the current display scale level to the nearest supported level.
1872 debug(LOG_WZ, "The current Display Scale (%d%%) is too high for the desired window size. Reducing the current Display Scale to the maximum possible for the desired window size: %d%%.", current_displayScale, maxDisplayScale);
1873 wzChangeDisplayScale(maxDisplayScale);
1874
1875 // Store the new display scale
1876 war_SetDisplayScale(maxDisplayScale);
1877 }
1878
1879 // Position the window (centered) on the screen (for its upcoming new size)
1880 SDL_SetWindowPosition(WZwindow, SDL_WINDOWPOS_CENTERED_DISPLAY(screen), SDL_WINDOWPOS_CENTERED_DISPLAY(screen));
1881
1882 // Change the window size
1883 // NOTE: Changing the window size will trigger an SDL window size changed event which will handle recalculating layout.
1884 SDL_SetWindowSize(WZwindow, width, height);
1885
1886 // Check that the new size is the desired size
1887 int resultingWidth, resultingHeight = 0;
1888 SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
1889 if (resultingWidth != width || resultingHeight != height) {
1890 // Attempting to set the resolution failed
1891 debug(LOG_WZ, "Attempting to change the resolution to %dx%d seems to have failed (result: %dx%d).", width, height, resultingWidth, resultingHeight);
1892
1893 // Revert to the prior position + resolution + display scale, and return false
1894 SDL_SetWindowSize(WZwindow, prev_width, prev_height);
1895 SDL_SetWindowPosition(WZwindow, prev_x, prev_y);
1896 if (current_displayScale != priorDisplayScale)
1897 {
1898 // Reverse the correction applied to the Display Scale to support the desired resolution.
1899 wzChangeDisplayScale(priorDisplayScale);
1900 war_SetDisplayScale(priorDisplayScale);
1901 }
1902 return false;
1903 }
1904
1905 // Store the updated screenIndex
1906 screenIndex = screen;
1907
1908 return true;
1909 }
1910
1911 // Returns the current window screen, width, and height
wzGetWindowResolution(int * screen,unsigned int * width,unsigned int * height)1912 void wzGetWindowResolution(int *screen, unsigned int *width, unsigned int *height)
1913 {
1914 ASSERT_OR_RETURN(, WZwindow != nullptr, "wzGetWindowResolution called when window is not available");
1915
1916 if (screen != nullptr)
1917 {
1918 *screen = screenIndex;
1919 }
1920
1921 int currentWidth = 0, currentHeight = 0;
1922 SDL_GetWindowSize(WZwindow, ¤tWidth, ¤tHeight);
1923 assert(currentWidth >= 0);
1924 assert(currentHeight >= 0);
1925 if (width != nullptr)
1926 {
1927 *width = currentWidth;
1928 }
1929 if (height != nullptr)
1930 {
1931 *height = currentHeight;
1932 }
1933 }
1934
SDL_backend(const video_backend & backend)1935 static SDL_WindowFlags SDL_backend(const video_backend& backend)
1936 {
1937 switch (backend)
1938 {
1939 #if defined(WZ_BACKEND_DIRECTX)
1940 case video_backend::directx: // because DirectX is supported via OpenGLES (LibANGLE)
1941 #endif
1942 case video_backend::opengl:
1943 case video_backend::opengles:
1944 return SDL_WINDOW_OPENGL;
1945 case video_backend::vulkan:
1946 #if SDL_VERSION_ATLEAST(2, 0, 6)
1947 return SDL_WINDOW_VULKAN;
1948 #else
1949 debug(LOG_FATAL, "The version of SDL used for compilation does not support SDL_WINDOW_VULKAN");
1950 break;
1951 #endif
1952 case video_backend::num_backends:
1953 debug(LOG_FATAL, "Should never happen");
1954 break;
1955 }
1956 return SDL_WindowFlags{};
1957 }
1958
shouldResetGfxBackendPrompt(video_backend currentBackend,video_backend newBackend,std::string failedToInitializeObject="graphics",std::string additionalErrorDetails="")1959 bool shouldResetGfxBackendPrompt(video_backend currentBackend, video_backend newBackend, std::string failedToInitializeObject = "graphics", std::string additionalErrorDetails = "")
1960 {
1961 // Offer to reset to the specified gfx backend
1962 std::string resetString = std::string("Reset to ") + to_display_string(newBackend) + "";
1963 const SDL_MessageBoxButtonData buttons[] = {
1964 { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, resetString.c_str() },
1965 { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 2, "Not Now" },
1966 };
1967 std::string titleString = std::string("Warzone: Failed to initialize ") + failedToInitializeObject;
1968 std::string messageString = std::string("Failed to initialize ") + failedToInitializeObject + " for backend: " + to_display_string(currentBackend) + ".\n\n";
1969 if (!additionalErrorDetails.empty())
1970 {
1971 messageString += "Error Details: \n\"" + additionalErrorDetails + "\"\n\n";
1972 }
1973 messageString += "Do you want to reset the graphics backend to: " + to_display_string(newBackend) + "?";
1974 const SDL_MessageBoxData messageboxdata = {
1975 SDL_MESSAGEBOX_ERROR, /* .flags */
1976 WZwindow, /* .window */
1977 titleString.c_str(), /* .title */
1978 messageString.c_str(), /* .message */
1979 SDL_arraysize(buttons), /* .numbuttons */
1980 buttons, /* .buttons */
1981 nullptr /* .colorScheme */
1982 };
1983 int buttonid;
1984 if (SDL_ShowMessageBox(&messageboxdata, &buttonid) < 0) {
1985 // error displaying message box
1986 debug(LOG_FATAL, "Failed to display message box");
1987 return false;
1988 }
1989 if (buttonid == 1)
1990 {
1991 return true;
1992 }
1993 return false;
1994 }
1995
resetGfxBackend(video_backend newBackend,bool displayRestartMessage=true)1996 void resetGfxBackend(video_backend newBackend, bool displayRestartMessage = true)
1997 {
1998 war_setGfxBackend(newBackend);
1999 if (displayRestartMessage)
2000 {
2001 std::string title = std::string("Backend reset to: ") + to_display_string(newBackend);
2002 wzDisplayDialog(Dialog_Information, title.c_str(), "(Note: Do not specify a --gfxbackend option, or it will override this new setting.)\n\nPlease restart Warzone 2100 to use the new graphics setting.");
2003 }
2004 }
2005
wzSDLOneTimeInit()2006 bool wzSDLOneTimeInit()
2007 {
2008 const Uint32 sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_TIMER;
2009 if (!(SDL_WasInit(sdl_init_flags) == sdl_init_flags))
2010 {
2011 if (SDL_Init(sdl_init_flags) != 0)
2012 {
2013 debug(LOG_ERROR, "Error: Could not initialise SDL (%s).", SDL_GetError());
2014 return false;
2015 }
2016
2017 if ((SDL_IsTextInputActive() != SDL_FALSE) && (GetTextEventsOwner == nullptr))
2018 {
2019 // start text input disabled
2020 SDL_StopTextInput();
2021 }
2022 }
2023
2024 if (wzSDLAppEvent == ((Uint32)-1))
2025 {
2026 wzSDLAppEvent = SDL_RegisterEvents(1);
2027 if (wzSDLAppEvent == ((Uint32)-1))
2028 {
2029 // Failed to register app-defined event with SDL
2030 debug(LOG_ERROR, "Error: Failed to register app-defined SDL event (%s).", SDL_GetError());
2031 return false;
2032 }
2033 }
2034
2035 return true;
2036 }
2037
wzSDLOneTimeInitSubsystem(uint32_t subsystem_flag)2038 static bool wzSDLOneTimeInitSubsystem(uint32_t subsystem_flag)
2039 {
2040 if (SDL_WasInit(subsystem_flag) == subsystem_flag)
2041 {
2042 // already initialized
2043 return true;
2044 }
2045 if (SDL_InitSubSystem(subsystem_flag) != 0)
2046 {
2047 debug(LOG_WZ, "SDL_InitSubSystem(%" PRIu32 ") failed", subsystem_flag);
2048 return false;
2049 }
2050 if ((SDL_IsTextInputActive() != SDL_FALSE) && (GetTextEventsOwner == nullptr))
2051 {
2052 // start text input disabled
2053 SDL_StopTextInput();
2054 }
2055 return true;
2056 }
2057
wzSDLPreWindowCreate_InitOpenGLAttributes(bool antialiasing,bool useOpenGLES,bool useOpenGLESLibrary)2058 void wzSDLPreWindowCreate_InitOpenGLAttributes(bool antialiasing, bool useOpenGLES, bool useOpenGLESLibrary)
2059 {
2060 // Set OpenGL attributes before creating the SDL Window
2061
2062 // Set minimum number of bits for the RGB channels of the color buffer
2063 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
2064 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
2065 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
2066 #if defined(WZ_OS_WIN)
2067 if (useOpenGLES)
2068 {
2069 // Always force minimum 8-bit color channels when using OpenGL ES on Windows
2070 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
2071 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
2072 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
2073 }
2074 #endif
2075
2076 // Set the double buffer OpenGL attribute.
2077 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
2078
2079 // Request a 24-bit depth buffer.
2080 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
2081
2082 // Enable stencil buffer, needed for shadows to work.
2083 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
2084
2085 if (antialiasing)
2086 {
2087 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
2088 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing);
2089 }
2090
2091 if (!sdl_OpenGL_Impl::configureOpenGLContextRequest(sdl_OpenGL_Impl::getInitialContextRequest(useOpenGLES), useOpenGLESLibrary))
2092 {
2093 // Failed to configure OpenGL context request
2094 debug(LOG_FATAL, "Failed to configure OpenGL context request");
2095 SDL_Quit();
2096 exit(EXIT_FAILURE);
2097 }
2098 }
2099
wzSDLPreWindowCreate_InitVulkanLibrary()2100 void wzSDLPreWindowCreate_InitVulkanLibrary()
2101 {
2102 #if defined(WZ_OS_MAC)
2103 // Attempt to explicitly load libvulkan.dylib with a full path
2104 // to support situations where run-time search paths using @executable_path are prohibited
2105 std::string fullPathToVulkanLibrary = cocoaGetFrameworksPath("libvulkan.dylib");
2106 if (SDL_Vulkan_LoadLibrary(fullPathToVulkanLibrary.c_str()) != 0)
2107 {
2108 debug(LOG_ERROR, "Failed to explicitly load Vulkan library");
2109 }
2110 #else
2111 // rely on SDL's built-in loading paths / behavior
2112 #endif
2113 }
2114
2115 // This stage, we handle display mode setting
wzMainScreenSetup_CreateVideoWindow(const video_backend & backend,int antialiasing,WINDOW_MODE fullscreen,int vsync,bool highDPI)2116 optional<SDL_gfx_api_Impl_Factory::Configuration> wzMainScreenSetup_CreateVideoWindow(const video_backend& backend, int antialiasing, WINDOW_MODE fullscreen, int vsync, bool highDPI)
2117 {
2118 const bool useOpenGLES = (backend == video_backend::opengles)
2119 #if defined(WZ_BACKEND_DIRECTX)
2120 || (backend == video_backend::directx)
2121 #endif
2122 ;
2123 const bool useOpenGLESLibrary = false
2124 #if defined(WZ_BACKEND_DIRECTX)
2125 || (backend == video_backend::directx)
2126 #endif
2127 ;
2128 const bool usesSDLBackend_OpenGL = useOpenGLES || (backend == video_backend::opengl);
2129
2130 // populate with the saved configuration values (if we had any)
2131 int width = war_GetWidth();
2132 int height = war_GetHeight();
2133 // NOTE: Prior to wzMainScreenSetup being run, the display system is populated with the window width + height
2134 // (i.e. not taking into account the game display scale). This function later sets the display system
2135 // to the *game screen* width and height (taking into account the display scale).
2136
2137 // Initialize video subsystem (if not yet initialized)
2138 if (!wzSDLOneTimeInitSubsystem(SDL_INIT_VIDEO))
2139 {
2140 debug(LOG_FATAL, "Error: Could not initialise SDL video subsystem (%s).", SDL_GetError());
2141 SDL_Quit();
2142 exit(EXIT_FAILURE);
2143 }
2144
2145 if (usesSDLBackend_OpenGL)
2146 {
2147 wzSDLPreWindowCreate_InitOpenGLAttributes(antialiasing, useOpenGLES, useOpenGLESLibrary);
2148 }
2149 else if (backend == video_backend::vulkan)
2150 {
2151 wzSDLPreWindowCreate_InitVulkanLibrary();
2152 }
2153
2154 // Populated our resolution list (does all displays now)
2155 SDL_DisplayMode displaymode;
2156 struct screeninfo screenlist;
2157 for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) // How many monitors we got
2158 {
2159 int numdisplaymodes = SDL_GetNumDisplayModes(i); // Get the number of display modes on this monitor
2160 for (int j = 0; j < numdisplaymodes; j++)
2161 {
2162 displaymode.format = displaymode.w = displaymode.h = displaymode.refresh_rate = 0;
2163 displaymode.driverdata = 0;
2164 if (SDL_GetDisplayMode(i, j, &displaymode) < 0)
2165 {
2166 debug(LOG_FATAL, "SDL_LOG_CATEGORY_APPLICATION error:%s", SDL_GetError());
2167 SDL_Quit();
2168 exit(EXIT_FAILURE);
2169 }
2170
2171 debug(LOG_WZ, "Monitor [%d] %dx%d %d %s", i, displaymode.w, displaymode.h, displaymode.refresh_rate, SDL_GetPixelFormatName(displaymode.format));
2172 if ((displaymode.w < MIN_WZ_GAMESCREEN_WIDTH) || (displaymode.h < MIN_WZ_GAMESCREEN_HEIGHT))
2173 {
2174 debug(LOG_WZ, "Monitor mode resolution < %d x %d -- discarding entry", MIN_WZ_GAMESCREEN_WIDTH, MIN_WZ_GAMESCREEN_HEIGHT);
2175 }
2176 else if (displaymode.refresh_rate < 59)
2177 {
2178 debug(LOG_WZ, "Monitor mode refresh rate < 59 -- discarding entry");
2179 // only store 60Hz & higher modes, some display report 59 on Linux
2180 }
2181 else
2182 {
2183 screenlist.width = displaymode.w;
2184 screenlist.height = displaymode.h;
2185 screenlist.refresh_rate = displaymode.refresh_rate;
2186 screenlist.screen = i; // which monitor this belongs to
2187 displaylist.push_back(screenlist);
2188 }
2189 }
2190 }
2191
2192 SDL_DisplayMode current = { 0, 0, 0, 0, 0 };
2193 for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i)
2194 {
2195 int display = SDL_GetCurrentDisplayMode(i, ¤t);
2196 if (display != 0)
2197 {
2198 debug(LOG_FATAL, "Can't get the current display mode, because: %s", SDL_GetError());
2199 SDL_Quit();
2200 exit(EXIT_FAILURE);
2201 }
2202 debug(LOG_WZ, "Monitor [%d] %dx%d %d", i, current.w, current.h, current.refresh_rate);
2203 }
2204
2205 if (width == 0 || height == 0)
2206 {
2207 width = windowWidth = current.w;
2208 height = windowHeight = current.h;
2209 }
2210 else
2211 {
2212 windowWidth = width;
2213 windowHeight = height;
2214 }
2215
2216 setDisplayScale(war_GetDisplayScale());
2217
2218 SDL_Rect bounds;
2219 for (int i = 0; i < SDL_GetNumVideoDisplays(); i++)
2220 {
2221 SDL_GetDisplayBounds(i, &bounds);
2222 debug(LOG_WZ, "Monitor %d: pos %d x %d : res %d x %d", i, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
2223 }
2224 screenIndex = war_GetScreen();
2225 const int currentNumDisplays = SDL_GetNumVideoDisplays();
2226 if (currentNumDisplays < 1)
2227 {
2228 debug(LOG_FATAL, "SDL_GetNumVideoDisplays returned: %d, with error: %s", currentNumDisplays, SDL_GetError());
2229 SDL_Quit();
2230 exit(EXIT_FAILURE);
2231 }
2232 if (screenIndex > currentNumDisplays)
2233 {
2234 debug(LOG_WARNING, "Invalid screen [%d] defined in configuration; there are only %d displays; falling back to display 0", screenIndex, currentNumDisplays);
2235 screenIndex = 0;
2236 war_SetScreen(0);
2237 }
2238
2239 if (war_getAutoAdjustDisplayScale())
2240 {
2241 // Since SDL (at least <= 2.0.14) does not provide built-in support for high-DPI displays on *some* platforms
2242 // (example: Windows), do our best to bump up the game's Display Scale setting to be a better match if the screen
2243 // on which the game starts has a higher effective Display Scale than the game's starting Display Scale setting.
2244 unsigned int screenBaseDisplayScale = wzGetDefaultBaseDisplayScale(screenIndex);
2245 if (screenBaseDisplayScale > wzGetCurrentDisplayScale())
2246 {
2247 // When bumping up the display scale, also increase the target window size proportionally
2248 debug(LOG_WZ, "Increasing game Display Scale to better match calculated default base display scale: %u%% -> %u%%", wzGetCurrentDisplayScale(), screenBaseDisplayScale);
2249 unsigned int priorDisplayScale = wzGetCurrentDisplayScale();
2250 setDisplayScale(screenBaseDisplayScale);
2251 float displayScaleDiff = (float)screenBaseDisplayScale / (float)priorDisplayScale;
2252 windowWidth = static_cast<unsigned int>(ceil((float)windowWidth * displayScaleDiff));
2253 windowHeight = static_cast<unsigned int>(ceil((float)windowHeight * displayScaleDiff));
2254 war_SetDisplayScale(screenBaseDisplayScale); // save the new display scale configuration
2255 }
2256 }
2257
2258 // Calculate the minimum window size given the current display scale
2259 unsigned int minWindowWidth = 0, minWindowHeight = 0;
2260 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2261
2262 if ((windowWidth < minWindowWidth) || (windowHeight < minWindowHeight))
2263 {
2264 // The desired window width and/or height is lower than the required minimum for the current display scale.
2265 // Reduce the display scale to the maximum supported (for the desired window size), and recalculate the required minimum window size.
2266 unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(windowWidth, windowHeight);
2267 maxDisplayScale = std::max(100u, maxDisplayScale); // if wzGetMaximumDisplayScaleForWindowSize fails, it returns < 100
2268 setDisplayScale(maxDisplayScale);
2269 war_SetDisplayScale(maxDisplayScale); // save the new display scale configuration
2270 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2271 }
2272
2273 windowWidth = std::max(windowWidth, minWindowWidth);
2274 windowHeight = std::max(windowHeight, minWindowHeight);
2275
2276 auto supportedFullscreenModes = wzSupportedWindowModes();
2277 if (std::find(supportedFullscreenModes.begin(), supportedFullscreenModes.end(), fullscreen) == supportedFullscreenModes.end())
2278 {
2279 debug(LOG_ERROR, "Unsupported fullscreen mode specified: %d; using default", static_cast<int>(fullscreen));
2280 fullscreen = WINDOW_MODE::windowed;
2281 war_setWindowMode(fullscreen); // persist the change
2282 }
2283
2284 if (fullscreen == WINDOW_MODE::windowed)
2285 {
2286 // Determine the maximum usable windowed size for this display/screen
2287 SDL_Rect displayUsableBounds = { 0, 0, 0, 0 };
2288 if (SDL_GetDisplayUsableBounds(screenIndex, &displayUsableBounds) == 0)
2289 {
2290 if (displayUsableBounds.w > 0 && displayUsableBounds.h > 0)
2291 {
2292 windowWidth = std::min((unsigned int)displayUsableBounds.w, windowWidth);
2293 windowHeight = std::min((unsigned int)displayUsableBounds.h, windowHeight);
2294 }
2295 }
2296 }
2297
2298 //// The flags to pass to SDL_CreateWindow
2299 int video_flags = SDL_backend(backend) | SDL_WINDOW_SHOWN;
2300
2301 switch (fullscreen)
2302 {
2303 case WINDOW_MODE::desktop_fullscreen:
2304 video_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
2305 break;
2306 case WINDOW_MODE::fullscreen:
2307 video_flags |= SDL_WINDOW_FULLSCREEN;
2308 break;
2309 case WINDOW_MODE::windowed:
2310 // Allow the window to be manually resized, if not fullscreen
2311 video_flags |= SDL_WINDOW_RESIZABLE;
2312 break;
2313 }
2314
2315 if (highDPI)
2316 {
2317 // Allow SDL to enable its built-in High-DPI display support.
2318 // This flag is ignored on some platforms (ex. Windows is not supported as of SDL 2.0.10),
2319 // but does support: macOS, Wayland [SDL 2.0.10+].
2320 video_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
2321 }
2322
2323 WZwindow = SDL_CreateWindow(PACKAGE_NAME, SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), windowWidth, windowHeight, video_flags);
2324
2325 if (!WZwindow)
2326 {
2327 std::string createWindowErrorStr = SDL_GetError();
2328 video_backend defaultBackend = wzGetNextFallbackGfxBackendForCurrentSystem(backend);
2329 if ((backend != defaultBackend) && shouldResetGfxBackendPrompt(backend, defaultBackend, "window", createWindowErrorStr))
2330 {
2331 resetGfxBackend(defaultBackend);
2332 return nullopt; // must return so new configuration will be saved
2333 }
2334 else
2335 {
2336 debug(LOG_FATAL, "Can't create a window, because: %s", createWindowErrorStr.c_str());
2337 }
2338 SDL_Quit();
2339 exit(EXIT_FAILURE);
2340 }
2341
2342 // Check that the actual window size matches the desired window size
2343 int resultingWidth, resultingHeight = 0;
2344 SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
2345 if (resultingWidth < minWindowWidth || resultingHeight < minWindowHeight)
2346 {
2347 // The created window size (that the system returned) is less than the minimum required window size (for this display scale)
2348 debug(LOG_WARNING, "Failed to create window at desired resolution: [%d] %d x %d; instead, received window of resolution: [%d] %d x %d; which is below the required minimum size of %d x %d for the current display scale level", war_GetScreen(), windowWidth, windowHeight, war_GetScreen(), resultingWidth, resultingHeight, minWindowWidth, minWindowHeight);
2349
2350 // Adjust the display scale (if possible)
2351 unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(resultingWidth, resultingHeight);
2352 if (maxDisplayScale >= 100)
2353 {
2354 // Reduce the display scale
2355 debug(LOG_WARNING, "Reducing the display scale level to the maximum supported for this window size: %u", maxDisplayScale);
2356 setDisplayScale(maxDisplayScale);
2357 war_SetDisplayScale(maxDisplayScale); // save the new display scale configuration
2358 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2359 }
2360 else
2361 {
2362 // Problem: The resulting window size that the system provided is below the minimum required
2363 // for the lowest possible display scale - i.e. the window is just too small
2364 debug(LOG_ERROR, "The window size created by the system is too small!");
2365
2366 // TODO: Should probably exit with a fatal error here?
2367 // For now...
2368
2369 // Attempt to default to base resolution at 100%
2370 setDisplayScale(100);
2371 war_SetDisplayScale(100); // save the new display scale configuration
2372 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2373
2374 SDL_SetWindowSize(WZwindow, minWindowWidth, minWindowHeight);
2375 windowWidth = minWindowWidth;
2376 windowHeight = minWindowHeight;
2377
2378 // Center window on screen
2379 SDL_SetWindowPosition(WZwindow, SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex));
2380
2381 // Re-request resulting window size
2382 SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
2383 }
2384 }
2385 ASSERT(resultingWidth > 0 && resultingHeight > 0, "Invalid - SDL returned window width: %d, window height: %d", resultingWidth, resultingHeight);
2386 windowWidth = (unsigned int)resultingWidth;
2387 windowHeight = (unsigned int)resultingHeight;
2388
2389 // Calculate the game screen's logical dimensions
2390 screenWidth = windowWidth;
2391 screenHeight = windowHeight;
2392 if (current_displayScaleFactor > 1.0f)
2393 {
2394 screenWidth = windowWidth / current_displayScaleFactor;
2395 screenHeight = windowHeight / current_displayScaleFactor;
2396 }
2397 pie_SetVideoBufferWidth(screenWidth);
2398 pie_SetVideoBufferHeight(screenHeight);
2399
2400 // Set the minimum window size
2401 SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
2402
2403 #if !defined(WZ_OS_MAC) // Do not use this method to set the window icon on macOS.
2404
2405 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
2406 uint32_t rmask = 0xff000000;
2407 uint32_t gmask = 0x00ff0000;
2408 uint32_t bmask = 0x0000ff00;
2409 uint32_t amask = 0x000000ff;
2410 #else
2411 uint32_t rmask = 0x000000ff;
2412 uint32_t gmask = 0x0000ff00;
2413 uint32_t bmask = 0x00ff0000;
2414 uint32_t amask = 0xff000000;
2415 #endif
2416
2417 #if defined(__GNUC__)
2418 #pragma GCC diagnostic push
2419 // ignore warning: cast from type 'const unsigned char*' to type 'void*' casts away qualifiers [-Wcast-qual]
2420 // FIXME?
2421 #pragma GCC diagnostic ignored "-Wcast-qual"
2422 #endif
2423 SDL_Surface *surface_icon = SDL_CreateRGBSurfaceFrom((void *)wz2100icon.pixel_data, wz2100icon.width, wz2100icon.height, wz2100icon.bytes_per_pixel * 8,
2424 wz2100icon.width * wz2100icon.bytes_per_pixel, rmask, gmask, bmask, amask);
2425 #if defined(__GNUC__)
2426 #pragma GCC diagnostic pop
2427 #endif
2428
2429 if (surface_icon)
2430 {
2431 SDL_SetWindowIcon(WZwindow, surface_icon);
2432 SDL_FreeSurface(surface_icon);
2433 }
2434 else
2435 {
2436 debug(LOG_ERROR, "Could not set window icon because %s", SDL_GetError());
2437 }
2438 #endif
2439
2440 SDL_SetWindowTitle(WZwindow, PACKAGE_NAME);
2441
2442 SDL_gfx_api_Impl_Factory::Configuration sdl_impl_config;
2443 sdl_impl_config.useOpenGLES = useOpenGLES;
2444 sdl_impl_config.useOpenGLESLibrary = useOpenGLESLibrary;
2445 return sdl_impl_config;
2446 }
2447
wzMainScreenSetup_VerifyWindow()2448 bool wzMainScreenSetup_VerifyWindow()
2449 {
2450 int bitDepth = war_GetVideoBufferDepth();
2451
2452 int bpp = SDL_BITSPERPIXEL(SDL_GetWindowPixelFormat(WZwindow));
2453 debug(LOG_WZ, "Bpp = %d format %s" , bpp, SDL_GetPixelFormatName(SDL_GetWindowPixelFormat(WZwindow)));
2454 if (!bpp)
2455 {
2456 debug(LOG_ERROR, "Video mode %ux%u@%dbpp is not supported!", windowWidth, windowHeight, bitDepth);
2457 return false;
2458 }
2459 switch (bpp)
2460 {
2461 case 32:
2462 case 24: // all is good...
2463 break;
2464 case 16:
2465 wz_info("Using colour depth of %i instead of a 32/24 bit depth (True color).", bpp);
2466 wz_info("You will experience graphics glitches!");
2467 break;
2468 case 8:
2469 debug(LOG_FATAL, "You don't want to play Warzone with a bit depth of %i, do you?", bpp);
2470 SDL_Quit();
2471 exit(1);
2472 break;
2473 default:
2474 debug(LOG_FATAL, "Unsupported bit depth: %i", bpp);
2475 exit(1);
2476 break;
2477 }
2478
2479 return true;
2480 }
2481
wzMainScreenSetup(optional<video_backend> backend,int antialiasing,WINDOW_MODE fullscreen,int vsync,bool highDPI)2482 bool wzMainScreenSetup(optional<video_backend> backend, int antialiasing, WINDOW_MODE fullscreen, int vsync, bool highDPI)
2483 {
2484 // Output linked SDL version
2485 char buf[512];
2486 SDL_version linked_sdl_version;
2487 SDL_GetVersion(&linked_sdl_version);
2488 ssprintf(buf, "Linked SDL version: %u.%u.%u\n", (unsigned int)linked_sdl_version.major, (unsigned int)linked_sdl_version.minor, (unsigned int)linked_sdl_version.patch);
2489 addDumpInfo(buf);
2490 debug(LOG_WZ, "%s", buf);
2491
2492 if (!wzSDLOneTimeInit())
2493 {
2494 // wzSDLOneTimeInit already logged an error on failure
2495 return false;
2496 }
2497
2498 #if defined(WZ_OS_MAC)
2499 // on macOS, support maximizing to a fullscreen space (modern behavior)
2500 if (SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "1") == SDL_FALSE)
2501 {
2502 debug(LOG_WARNING, "Failed to set hint: SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES");
2503 }
2504 #endif
2505
2506 WZbackend = backend;
2507
2508 SDL_gfx_api_Impl_Factory::Configuration sdl_impl_config;
2509
2510 if (backend.has_value())
2511 {
2512 auto result = wzMainScreenSetup_CreateVideoWindow(backend.value(), antialiasing, fullscreen, vsync, highDPI);
2513 if (!result.has_value())
2514 {
2515 return false; // must return so new configuration will be saved
2516 }
2517 sdl_impl_config = result.value();
2518
2519 /* initialise all cursors */
2520 if (war_GetColouredCursor())
2521 {
2522 sdlInitColoredCursors();
2523 }
2524 else
2525 {
2526 sdlInitCursors();
2527 }
2528 }
2529
2530 setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
2531
2532 #if defined(WZ_OS_MAC)
2533 if (backend.has_value())
2534 {
2535 cocoaSetupWZMenus();
2536 }
2537 #endif
2538
2539 const auto vsyncMode = to_swap_mode(vsync);
2540
2541 gfx_api::backend_type gfxapi_backend = gfx_api::backend_type::null_backend;
2542 if (backend.has_value())
2543 {
2544 if (backend.value() == video_backend::vulkan)
2545 {
2546 gfxapi_backend = gfx_api::backend_type::vulkan_backend;
2547 }
2548 else
2549 {
2550 gfxapi_backend = gfx_api::backend_type::opengl_backend;
2551 }
2552 }
2553
2554 if (!gfx_api::context::initialize(SDL_gfx_api_Impl_Factory(WZwindow, sdl_impl_config), antialiasing, vsyncMode, gfxapi_backend))
2555 {
2556 // Failed to initialize desired backend / renderer settings
2557 if (backend.has_value())
2558 {
2559 video_backend defaultBackend = wzGetNextFallbackGfxBackendForCurrentSystem(backend.value());
2560 if ((backend.value() != defaultBackend) && shouldResetGfxBackendPrompt(backend.value(), defaultBackend))
2561 {
2562 resetGfxBackend(defaultBackend);
2563 return false; // must return so new configuration will be saved
2564 }
2565 else
2566 {
2567 debug(LOG_FATAL, "gfx_api::context::get().initialize failed for backend: %s", to_string(backend.value()).c_str());
2568 }
2569 }
2570 else
2571 {
2572 // headless mode failed in gfx_api::context::initialize??
2573 debug(LOG_FATAL, "gfx_api::context::get().initialize failed for headless-mode backend?");
2574 }
2575 SDL_Quit();
2576 exit(EXIT_FAILURE);
2577 }
2578
2579 if (backend.has_value())
2580 {
2581 wzMainScreenSetup_VerifyWindow();
2582 }
2583
2584 return true;
2585 }
2586
2587
2588 // Calculates and returns the scale factor from the SDL window's coordinate system (in points) to the raw
2589 // underlying pixels of the viewport / renderer.
2590 //
2591 // IMPORTANT: This value is *non-inclusive* of any user-configured Game Display Scale.
2592 //
2593 // This exposes what is effectively the SDL window's "High-DPI Scale Factor", if SDL's high-DPI support is enabled and functioning.
2594 //
2595 // In the normal, non-high-DPI-supported case, (in which the context's drawable size in pixels and the window's logical
2596 // size in points are equal) this will return 1.0 for both values.
2597 //
wzGetWindowToRendererScaleFactor(float * horizScaleFactor,float * vertScaleFactor)2598 void wzGetWindowToRendererScaleFactor(float *horizScaleFactor, float *vertScaleFactor)
2599 {
2600 if (!WZbackend.has_value())
2601 {
2602 if (horizScaleFactor != nullptr)
2603 {
2604 *horizScaleFactor = 1.0f;
2605 }
2606 if (vertScaleFactor != nullptr)
2607 {
2608 *vertScaleFactor = 1.0f;
2609 }
2610 return;
2611 }
2612 ASSERT_OR_RETURN(, WZwindow != nullptr, "wzGetWindowToRendererScaleFactor called when window is not available");
2613
2614 // Obtain the window context's drawable size in pixels
2615 int drawableWidth, drawableHeight = 0;
2616 SDL_WZBackend_GetDrawableSize(WZwindow, &drawableWidth, &drawableHeight);
2617
2618 // Obtain the logical window size (in points)
2619 int logicalWindowWidth, logicalWindowHeight = 0;
2620 SDL_GetWindowSize(WZwindow, &logicalWindowWidth, &logicalWindowHeight);
2621
2622 debug(LOG_WZ, "Window Logical Size (%d, %d) vs Drawable Size in Pixels (%d, %d)", logicalWindowWidth, logicalWindowHeight, drawableWidth, drawableHeight);
2623
2624 if (horizScaleFactor != nullptr)
2625 {
2626 *horizScaleFactor = ((float)drawableWidth / (float)logicalWindowWidth) * current_displayScaleFactor;
2627 }
2628 if (vertScaleFactor != nullptr)
2629 {
2630 *vertScaleFactor = ((float)drawableHeight / (float)logicalWindowHeight) * current_displayScaleFactor;
2631 }
2632
2633 int displayIndex = SDL_GetWindowDisplayIndex(WZwindow);
2634 if (displayIndex >= 0)
2635 {
2636 float hdpi, vdpi;
2637 if (SDL_GetDisplayDPI(displayIndex, nullptr, &hdpi, &vdpi) < 0)
2638 {
2639 debug(LOG_WARNING, "Failed to get the display (%d) DPI because : %s", displayIndex, SDL_GetError());
2640 }
2641 else
2642 {
2643 debug(LOG_WZ, "Display (%d) DPI: %f, %f", displayIndex, hdpi, vdpi);
2644 }
2645 }
2646 else
2647 {
2648 debug(LOG_WARNING, "Failed to get the display index for the window because : %s", SDL_GetError());
2649 }
2650 }
2651
2652 // Calculates and returns the total scale factor from the game's coordinate system (in points)
2653 // to the raw underlying pixels of the viewport / renderer.
2654 //
2655 // IMPORTANT: This value is *inclusive* of both the user-configured "Display Scale" *AND* any underlying
2656 // high-DPI / "Retina" display support provided by SDL.
2657 //
2658 // It is equivalent to: (SDL Window's High-DPI Scale Factor) x (WZ Game Display Scale Factor)
2659 //
2660 // Therefore, if SDL is providing a supported high-DPI window / context, this value will be greater
2661 // than the WZ (user-configured) Display Scale Factor.
2662 //
2663 // It should be used only for internal (non-user-displayed) cases in which the full scaling factor from
2664 // the game system's coordinate system (in points) to the underlying display pixels is required.
2665 // (For example, when rasterizing text for best display.)
2666 //
wzGetGameToRendererScaleFactor(float * horizScaleFactor,float * vertScaleFactor)2667 void wzGetGameToRendererScaleFactor(float *horizScaleFactor, float *vertScaleFactor)
2668 {
2669 float horizWindowScaleFactor = 0.f, vertWindowScaleFactor = 0.f;
2670 wzGetWindowToRendererScaleFactor(&horizWindowScaleFactor, &vertWindowScaleFactor);
2671 assert(horizWindowScaleFactor != 0.f);
2672 assert(vertWindowScaleFactor != 0.f);
2673
2674 if (horizScaleFactor != nullptr)
2675 {
2676 *horizScaleFactor = horizWindowScaleFactor * current_displayScaleFactor;
2677 }
2678 if (vertScaleFactor != nullptr)
2679 {
2680 *vertScaleFactor = vertWindowScaleFactor * current_displayScaleFactor;
2681 }
2682 }
2683
wzSetWindowIsResizable(bool resizable)2684 void wzSetWindowIsResizable(bool resizable)
2685 {
2686 if (WZwindow == nullptr)
2687 {
2688 debug(LOG_WARNING, "wzSetWindowIsResizable called when window is not available");
2689 return;
2690 }
2691 SDL_bool sdl_resizable = (resizable) ? SDL_TRUE : SDL_FALSE;
2692 SDL_SetWindowResizable(WZwindow, sdl_resizable);
2693
2694 if (resizable)
2695 {
2696 // Set the minimum window size
2697 unsigned int minWindowWidth = 0, minWindowHeight = 0;
2698 wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, current_displayScaleFactor);
2699 SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
2700 }
2701 }
2702
wzIsWindowResizable()2703 bool wzIsWindowResizable()
2704 {
2705 if (WZwindow == nullptr)
2706 {
2707 debug(LOG_WARNING, "wzIsWindowResizable called when window is not available");
2708 return false;
2709 }
2710 Uint32 flags = SDL_GetWindowFlags(WZwindow);
2711 if (flags & SDL_WINDOW_RESIZABLE)
2712 {
2713 return true;
2714 }
2715 return false;
2716 }
2717
2718 /*!
2719 * Activation (focus change ... and) eventhandler. Mainly for debugging.
2720 */
handleActiveEvent(SDL_Event * event)2721 static void handleActiveEvent(SDL_Event *event)
2722 {
2723 if (event->type == SDL_WINDOWEVENT)
2724 {
2725 switch (event->window.event)
2726 {
2727 case SDL_WINDOWEVENT_SHOWN:
2728 debug(LOG_WZ, "Window %d shown", event->window.windowID);
2729 break;
2730 case SDL_WINDOWEVENT_HIDDEN:
2731 debug(LOG_WZ, "Window %d hidden", event->window.windowID);
2732 break;
2733 case SDL_WINDOWEVENT_EXPOSED:
2734 debug(LOG_WZ, "Window %d exposed", event->window.windowID);
2735 break;
2736 case SDL_WINDOWEVENT_MOVED:
2737 debug(LOG_WZ, "Window %d moved to %d,%d", event->window.windowID, event->window.data1, event->window.data2);
2738 // FIXME: Handle detecting which screen the window was moved to, and update saved war_SetScreen?
2739 break;
2740 case SDL_WINDOWEVENT_RESIZED:
2741 case SDL_WINDOWEVENT_SIZE_CHANGED:
2742 debug(LOG_WZ, "Window %d resized to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
2743 {
2744 unsigned int oldWindowWidth = windowWidth;
2745 unsigned int oldWindowHeight = windowHeight;
2746
2747 Uint32 windowFlags = SDL_GetWindowFlags(WZwindow);
2748 debug(LOG_WZ, "Window resized to window flags: %u", windowFlags);
2749
2750 int newWindowWidth = 0, newWindowHeight = 0;
2751 SDL_GetWindowSize(WZwindow, &newWindowWidth, &newWindowHeight);
2752
2753 if ((event->window.data1 != newWindowWidth) || (event->window.data2 != newWindowHeight))
2754 {
2755 // This can happen - so we use the values retrieved from SDL_GetWindowSize in any case - but
2756 // log it for tracking down the SDL-related causes later.
2757 debug(LOG_WARNING, "Received width and height (%d x %d) do not match those from GetWindowSize (%d x %d)", event->window.data1, event->window.data2, newWindowWidth, newWindowHeight);
2758 }
2759
2760 handleWindowSizeChange(oldWindowWidth, oldWindowHeight, newWindowWidth, newWindowHeight);
2761
2762 // Store the new values (in case the user manually resized the window bounds)
2763 war_SetWidth(newWindowWidth);
2764 war_SetHeight(newWindowHeight);
2765 }
2766 break;
2767 case SDL_WINDOWEVENT_MINIMIZED:
2768 debug(LOG_WZ, "Window %d minimized", event->window.windowID);
2769 break;
2770 case SDL_WINDOWEVENT_MAXIMIZED:
2771 debug(LOG_WZ, "Window %d maximized", event->window.windowID);
2772 break;
2773 case SDL_WINDOWEVENT_RESTORED:
2774 debug(LOG_WZ, "Window %d restored", event->window.windowID);
2775 break;
2776 case SDL_WINDOWEVENT_ENTER:
2777 debug(LOG_WZ, "Mouse entered window %d", event->window.windowID);
2778 break;
2779 case SDL_WINDOWEVENT_LEAVE:
2780 debug(LOG_WZ, "Mouse left window %d", event->window.windowID);
2781 break;
2782 case SDL_WINDOWEVENT_FOCUS_GAINED:
2783 mouseInWindow = SDL_TRUE;
2784 debug(LOG_WZ, "Window %d gained keyboard focus", event->window.windowID);
2785 break;
2786 case SDL_WINDOWEVENT_FOCUS_LOST:
2787 mouseInWindow = SDL_FALSE;
2788 debug(LOG_WZ, "Window %d lost keyboard focus", event->window.windowID);
2789 break;
2790 case SDL_WINDOWEVENT_CLOSE:
2791 debug(LOG_WZ, "Window %d closed", event->window.windowID);
2792 WZwindow = nullptr;
2793 break;
2794 case SDL_WINDOWEVENT_TAKE_FOCUS:
2795 debug(LOG_WZ, "Window %d is being offered focus", event->window.windowID);
2796 break;
2797 default:
2798 debug(LOG_WZ, "Window %d got unknown event %d", event->window.windowID, event->window.event);
2799 break;
2800 }
2801 }
2802 }
2803
2804 // Actual mainloop
wzMainEventLoop(void)2805 void wzMainEventLoop(void)
2806 {
2807 SDL_Event event;
2808
2809 while (true)
2810 {
2811 /* Deal with any windows messages */
2812 while (SDL_PollEvent(&event))
2813 {
2814 switch (event.type)
2815 {
2816 case SDL_KEYUP:
2817 case SDL_KEYDOWN:
2818 inputHandleKeyEvent(&event.key);
2819 break;
2820 case SDL_MOUSEBUTTONUP:
2821 case SDL_MOUSEBUTTONDOWN:
2822 inputHandleMouseButtonEvent(&event.button);
2823 break;
2824 case SDL_MOUSEMOTION:
2825 inputHandleMouseMotionEvent(&event.motion);
2826 break;
2827 case SDL_MOUSEWHEEL:
2828 inputHandleMouseWheelEvent(&event.wheel);
2829 break;
2830 case SDL_WINDOWEVENT:
2831 handleActiveEvent(&event);
2832 break;
2833 case SDL_TEXTINPUT: // SDL now handles text input differently
2834 inputhandleText(&event.text);
2835 break;
2836 case SDL_QUIT:
2837 return;
2838 default:
2839 break;
2840 }
2841
2842 if (wzSDLAppEvent == event.type)
2843 {
2844 // Custom WZ App Event
2845 switch (event.user.code)
2846 {
2847 case wzSDLAppEventCodes::MAINTHREADEXEC:
2848 if (event.user.data1 != nullptr)
2849 {
2850 WZ_MAINTHREADEXEC * pExec = static_cast<WZ_MAINTHREADEXEC *>(event.user.data1);
2851 pExec->doExecOnMainThread();
2852 delete pExec;
2853 }
2854 break;
2855 default:
2856 break;
2857 }
2858 }
2859 }
2860
2861 processScreenSizeChangeNotificationIfNeeded();
2862 mainLoop(); // WZ does its thing
2863 inputNewFrame(); // reset input states
2864 }
2865 }
2866
wzPumpEventsWhileLoading()2867 void wzPumpEventsWhileLoading()
2868 {
2869 SDL_PumpEvents();
2870 }
2871
wzShutdown()2872 void wzShutdown()
2873 {
2874 // order is important!
2875 sdlFreeCursors();
2876 if (WZwindow != nullptr)
2877 {
2878 SDL_DestroyWindow(WZwindow);
2879 WZwindow = nullptr;
2880 }
2881 SDL_Quit();
2882
2883 // delete copies of argc, argv
2884 if (copied_argv != nullptr)
2885 {
2886 for(int i=0; i < copied_argc; i++) {
2887 delete [] copied_argv[i];
2888 }
2889 delete [] copied_argv;
2890 copied_argv = nullptr;
2891 copied_argc = 0;
2892 }
2893 }
2894