Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "VideoMode.h"
21 :
22 : #include "graphics/GameView.h"
23 : #include "gui/GUIManager.h"
24 : #include "lib/config2.h"
25 : #include "lib/external_libraries/libsdl.h"
26 : #include "lib/sysdep/os.h"
27 : #include "lib/tex/tex.h"
28 : #include "ps/CConsole.h"
29 : #include "ps/CLogger.h"
30 : #include "ps/ConfigDB.h"
31 : #include "ps/CStr.h"
32 : #include "ps/Filesystem.h"
33 : #include "ps/Game.h"
34 : #include "ps/GameSetup/Config.h"
35 : #include "ps/Pyrogenesis.h"
36 : #include "renderer/backend/dummy/DeviceForward.h"
37 : #include "renderer/backend/gl/DeviceForward.h"
38 : #include "renderer/backend/IDevice.h"
39 : #include "renderer/backend/vulkan/DeviceForward.h"
40 : #include "renderer/Renderer.h"
41 :
42 : #include <string_view>
43 :
44 : namespace
45 : {
46 :
47 : int DEFAULT_WINDOW_W = 1024;
48 : int DEFAULT_WINDOW_H = 768;
49 :
50 : int DEFAULT_FULLSCREEN_W = 1024;
51 : int DEFAULT_FULLSCREEN_H = 768;
52 :
53 : const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow";
54 :
55 0 : Renderer::Backend::Backend GetFallbackBackend(const Renderer::Backend::Backend backend)
56 : {
57 0 : Renderer::Backend::Backend fallback = Renderer::Backend::Backend::DUMMY;
58 : // We use a switch instead of a list to have compile-time checks for missed
59 : // values and because a linear priority list doesn't work for general case.
60 0 : switch (backend)
61 : {
62 0 : case Renderer::Backend::Backend::GL:
63 0 : fallback = Renderer::Backend::Backend::GL_ARB;
64 0 : break;
65 0 : case Renderer::Backend::Backend::GL_ARB:
66 0 : fallback = Renderer::Backend::Backend::DUMMY;
67 0 : break;
68 0 : case Renderer::Backend::Backend::DUMMY:
69 0 : break;
70 0 : case Renderer::Backend::Backend::VULKAN:
71 0 : fallback = Renderer::Backend::Backend::GL;
72 0 : break;
73 : }
74 0 : return fallback;
75 : }
76 :
77 0 : std::string_view GetBackendName(const Renderer::Backend::Backend backend)
78 : {
79 0 : std::string_view name{"Unknown"};
80 0 : switch (backend)
81 : {
82 0 : case Renderer::Backend::Backend::GL:
83 0 : name = "GL";
84 0 : break;
85 0 : case Renderer::Backend::Backend::GL_ARB:
86 0 : name = "GL ARB";
87 0 : break;
88 0 : case Renderer::Backend::Backend::DUMMY:
89 0 : name = "Dummy";
90 0 : break;
91 0 : case Renderer::Backend::Backend::VULKAN:
92 0 : name = "Vulkan";
93 0 : break;
94 : }
95 0 : return name;
96 : }
97 :
98 : } // anonymous namespace
99 :
100 : #if OS_WIN
101 : // We can't include wutil directly because GL headers conflict with Windows
102 : // until we use a proper GL loader.
103 : extern void wutil_SetAppWindow(SDL_Window* window);
104 :
105 : // After a proper HiDPI integration we should switch to manifest until
106 : // SDL has an implemented HiDPI on Windows.
107 : extern void wutil_EnableHiDPIOnWindows();
108 : #endif
109 :
110 1 : CVideoMode g_VideoMode;
111 :
112 : class CVideoMode::CCursor
113 : {
114 : public:
115 : enum class CursorBackend
116 : {
117 : SDL,
118 : SYSTEM
119 : };
120 :
121 : CCursor();
122 : ~CCursor();
123 :
124 : void SetCursor(const CStrW& name);
125 : void ResetCursor();
126 :
127 : private:
128 : CursorBackend m_CursorBackend = CursorBackend::SYSTEM;
129 : SDL_Surface* m_CursorSurface = nullptr;
130 : SDL_Cursor* m_Cursor = nullptr;
131 : CStrW m_CursorName;
132 : };
133 :
134 0 : CVideoMode::CCursor::CCursor()
135 : {
136 0 : std::string cursorBackend;
137 0 : CFG_GET_VAL("cursorbackend", cursorBackend);
138 0 : if (cursorBackend == "sdl")
139 0 : m_CursorBackend = CursorBackend::SDL;
140 : else
141 0 : m_CursorBackend = CursorBackend::SYSTEM;
142 :
143 0 : ResetCursor();
144 0 : }
145 :
146 0 : CVideoMode::CCursor::~CCursor()
147 : {
148 0 : if (m_Cursor)
149 0 : SDL_FreeCursor(m_Cursor);
150 0 : if (m_CursorSurface)
151 0 : SDL_FreeSurface(m_CursorSurface);
152 0 : }
153 :
154 0 : void CVideoMode::CCursor::SetCursor(const CStrW& name)
155 : {
156 0 : if (m_CursorBackend == CursorBackend::SYSTEM || m_CursorName == name)
157 0 : return;
158 0 : m_CursorName = name;
159 :
160 0 : if (m_Cursor)
161 0 : SDL_FreeCursor(m_Cursor);
162 0 : if (m_CursorSurface)
163 0 : SDL_FreeSurface(m_CursorSurface);
164 :
165 0 : if (name.empty())
166 : {
167 0 : SDL_ShowCursor(SDL_DISABLE);
168 0 : return;
169 : }
170 :
171 0 : const VfsPath pathBaseName(VfsPath(L"art/textures/cursors") / name);
172 :
173 : // Read pixel offset of the cursor's hotspot [the bit of it that's
174 : // drawn at (g_mouse_x,g_mouse_y)] from file.
175 0 : int hotspotX = 0, hotspotY = 0;
176 : {
177 0 : const VfsPath pathHotspotName = pathBaseName.ChangeExtension(L".txt");
178 0 : std::shared_ptr<u8> buffer;
179 : size_t size;
180 0 : if (g_VFS->LoadFile(pathHotspotName, buffer, size) != INFO::OK)
181 : {
182 0 : LOGERROR("Can't load hotspot for cursor: %s", pathHotspotName.string8().c_str());
183 0 : return;
184 : }
185 0 : std::wstringstream s(std::wstring(reinterpret_cast<const wchar_t*>(buffer.get()), size));
186 0 : s >> hotspotX >> hotspotY;
187 : }
188 :
189 0 : const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png");
190 :
191 0 : std::shared_ptr<u8> file;
192 : size_t fileSize;
193 0 : if (g_VFS->LoadFile(pathImageName, file, fileSize) != INFO::OK)
194 : {
195 0 : LOGERROR("Can't load image for cursor: %s", pathImageName.string8().c_str());
196 0 : return;
197 : }
198 :
199 0 : Tex t;
200 0 : if (t.decode(file, fileSize) != INFO::OK)
201 : {
202 0 : LOGERROR("Can't decode image for cursor");
203 0 : return;
204 : }
205 :
206 : // Convert to required BGRA format.
207 0 : const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
208 0 : if (t.transform_to(flags) != INFO::OK)
209 : {
210 0 : LOGERROR("Can't transform image for cursor");
211 0 : return;
212 : }
213 0 : void* imageBGRA = t.get_data();
214 0 : if (!imageBGRA)
215 : {
216 0 : LOGERROR("Transformed image is empty for cursor");
217 0 : return;
218 : }
219 :
220 0 : m_CursorSurface = SDL_CreateRGBSurfaceFrom(imageBGRA,
221 0 : static_cast<int>(t.m_Width), static_cast<int>(t.m_Height), 32,
222 0 : static_cast<int>(t.m_Width * 4),
223 : 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
224 0 : if (!m_CursorSurface)
225 : {
226 0 : LOGERROR("Can't create surface for cursor: %s", SDL_GetError());
227 0 : return;
228 : }
229 0 : const float scale = g_VideoMode.GetScale();
230 0 : if (scale != 1.0)
231 : {
232 0 : SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0,
233 0 : m_CursorSurface->w * scale,
234 0 : m_CursorSurface->h * scale, 32,
235 0 : 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
236 0 : if (!scaledSurface)
237 : {
238 0 : LOGERROR("Can't create scaled surface forcursor: %s", SDL_GetError());
239 0 : return;
240 : }
241 0 : if (SDL_BlitScaled(m_CursorSurface, nullptr, scaledSurface, nullptr))
242 0 : return;
243 0 : SDL_FreeSurface(m_CursorSurface);
244 0 : m_CursorSurface = scaledSurface;
245 : }
246 0 : m_Cursor = SDL_CreateColorCursor(m_CursorSurface, hotspotX, hotspotY);
247 0 : if (!m_Cursor)
248 : {
249 0 : LOGERROR("Can't create cursor: %s", SDL_GetError());
250 0 : return;
251 : }
252 :
253 0 : SDL_SetCursor(m_Cursor);
254 : }
255 :
256 0 : void CVideoMode::CCursor::ResetCursor()
257 : {
258 0 : SetCursor(DEFAULT_CURSOR_NAME);
259 0 : }
260 :
261 1 : CVideoMode::CVideoMode() :
262 1 : m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
263 : {
264 1 : }
265 :
266 : CVideoMode::~CVideoMode() = default;
267 :
268 6 : void CVideoMode::ReadConfig()
269 : {
270 6 : bool windowed = !m_ConfigFullscreen;
271 6 : CFG_GET_VAL("windowed", windowed);
272 6 : m_ConfigFullscreen = !windowed;
273 :
274 6 : CFG_GET_VAL("gui.scale", m_Scale);
275 :
276 6 : CFG_GET_VAL("xres", m_ConfigW);
277 6 : CFG_GET_VAL("yres", m_ConfigH);
278 6 : CFG_GET_VAL("bpp", m_ConfigBPP);
279 6 : CFG_GET_VAL("display", m_ConfigDisplay);
280 6 : CFG_GET_VAL("hidpi", m_ConfigEnableHiDPI);
281 6 : CFG_GET_VAL("vsync", m_ConfigVSync);
282 :
283 12 : CStr rendererBackend;
284 6 : CFG_GET_VAL("rendererbackend", rendererBackend);
285 6 : if (rendererBackend == "glarb")
286 0 : m_Backend = Renderer::Backend::Backend::GL_ARB;
287 6 : else if (rendererBackend == "dummy")
288 6 : m_Backend = Renderer::Backend::Backend::DUMMY;
289 0 : else if (rendererBackend == "vulkan")
290 0 : m_Backend = Renderer::Backend::Backend::VULKAN;
291 : else
292 0 : m_Backend = Renderer::Backend::Backend::GL;
293 :
294 : #if OS_WIN
295 : if (m_ConfigEnableHiDPI)
296 : wutil_EnableHiDPIOnWindows();
297 : #endif
298 6 : }
299 :
300 0 : bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
301 : {
302 0 : Uint32 flags = 0;
303 0 : if (fullscreen)
304 : {
305 0 : bool borderlessFullscreen = true;
306 0 : CFG_GET_VAL("borderless.fullscreen", borderlessFullscreen);
307 0 : flags |= borderlessFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
308 : }
309 : else
310 : {
311 0 : bool borderlessWindow = false;
312 0 : CFG_GET_VAL("borderless.window", borderlessWindow);
313 0 : if (borderlessWindow)
314 0 : flags |= SDL_WINDOW_BORDERLESS;
315 : }
316 :
317 0 : if (!m_Window)
318 : {
319 0 : const bool isGLBackend =
320 0 : m_Backend == Renderer::Backend::Backend::GL ||
321 0 : m_Backend == Renderer::Backend::Backend::GL_ARB;
322 0 : if (isGLBackend)
323 : {
324 0 : SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
325 0 : SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
326 0 : SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
327 0 : SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
328 :
329 0 : bool debugContext = false;
330 0 : CFG_GET_VAL("renderer.backend.debugcontext", debugContext);
331 0 : if (debugContext)
332 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
333 :
334 0 : bool forceGLVersion = false;
335 0 : CFG_GET_VAL("forceglversion", forceGLVersion);
336 0 : if (forceGLVersion)
337 : {
338 0 : CStr forceGLProfile = "compatibility";
339 0 : int forceGLMajorVersion = 3;
340 0 : int forceGLMinorVersion = 0;
341 0 : CFG_GET_VAL("forceglprofile", forceGLProfile);
342 0 : CFG_GET_VAL("forceglmajorversion", forceGLMajorVersion);
343 0 : CFG_GET_VAL("forceglminorversion", forceGLMinorVersion);
344 :
345 0 : int profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
346 0 : if (forceGLProfile == "es")
347 0 : profile = SDL_GL_CONTEXT_PROFILE_ES;
348 0 : else if (forceGLProfile == "core")
349 0 : profile = SDL_GL_CONTEXT_PROFILE_CORE;
350 0 : else if (forceGLProfile != "compatibility")
351 0 : LOGWARNING("Unknown force GL profile '%s', compatibility profile is used", forceGLProfile.c_str());
352 :
353 0 : if (forceGLMajorVersion < 1 || forceGLMinorVersion < 0)
354 : {
355 0 : LOGERROR("Unsupported force GL version: %d.%d", forceGLMajorVersion, forceGLMinorVersion);
356 : }
357 : else
358 : {
359 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
360 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, forceGLMajorVersion);
361 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, forceGLMinorVersion);
362 : }
363 : }
364 : else
365 : {
366 : #if CONFIG2_GLES
367 : // Require GLES 2.0
368 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
369 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
370 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
371 : #else
372 : // Some macOS and MESA drivers might not create a context even if they can
373 : // with the core profile. So disable it for a while until we can guarantee
374 : // its creation.
375 : #if OS_WIN
376 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
377 : #endif
378 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
379 0 : SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
380 : #endif
381 : }
382 : }
383 :
384 : // Note: these flags only take affect in SDL_CreateWindow
385 0 : flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
386 0 : if (m_ConfigEnableHiDPI)
387 0 : flags |= SDL_WINDOW_ALLOW_HIGHDPI;
388 0 : if (isGLBackend)
389 0 : flags |= SDL_WINDOW_OPENGL;
390 0 : else if (m_Backend == Renderer::Backend::Backend::VULKAN)
391 0 : flags |= SDL_WINDOW_VULKAN;
392 0 : m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
393 :
394 0 : m_Window = SDL_CreateWindow(main_window_name, m_WindowedX, m_WindowedY, w, h, flags);
395 0 : if (!m_Window)
396 : {
397 : // SDL might fail to create a window in case of missing a Vulkan driver.
398 0 : if (m_Backend == Renderer::Backend::Backend::VULKAN)
399 : {
400 0 : DowngradeBackendSettingAfterCreationFailure();
401 0 : return SetVideoMode(w, h, bpp, fullscreen);
402 : }
403 :
404 : // If fullscreen fails, try windowed mode
405 0 : if (fullscreen)
406 : {
407 0 : LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
408 : "%dx%d:%d (\"%hs\"), falling back to windowed mode",
409 : w, h, bpp, SDL_GetError());
410 : // Using default size for the window for now, as the attempted setting
411 : // could be as large, or larger than the screen size.
412 0 : return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
413 : }
414 : else
415 : {
416 0 : if (isGLBackend)
417 : {
418 0 : int depthSize = 24;
419 0 : SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize);
420 0 : if (depthSize > 16)
421 : {
422 : // Fall back to a smaller depth buffer
423 : // (The rendering may be ugly but this helps when running in VMware)
424 0 : SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
425 :
426 0 : return SetVideoMode(w, h, bpp, fullscreen);
427 : }
428 : }
429 :
430 0 : LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
431 : w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
432 0 : return false;
433 : }
434 : }
435 :
436 0 : if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
437 : {
438 0 : LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
439 : w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
440 0 : return false;
441 : }
442 :
443 : #if OS_WIN
444 : // We need to set the window for an error dialog.
445 : wutil_SetAppWindow(m_Window);
446 : #endif
447 :
448 0 : if (!TryCreateBackendDevice(m_Window))
449 : {
450 0 : DowngradeBackendSettingAfterCreationFailure();
451 0 : SDL_DestroyWindow(m_Window);
452 0 : m_Window = nullptr;
453 0 : return SetVideoMode(w, h, bpp, fullscreen);
454 : }
455 :
456 0 : if (isGLBackend)
457 0 : SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0);
458 : }
459 : else
460 : {
461 0 : if (m_IsFullscreen != fullscreen)
462 : {
463 0 : if (!fullscreen)
464 : {
465 : // For some reason, when switching from fullscreen to windowed mode,
466 : // we have to set the window size and position before and after switching
467 0 : SDL_SetWindowSize(m_Window, w, h);
468 0 : SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
469 : }
470 :
471 0 : if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
472 : {
473 0 : LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
474 : w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
475 0 : return false;
476 : }
477 : }
478 :
479 0 : if (!fullscreen)
480 : {
481 0 : SDL_SetWindowSize(m_Window, w, h);
482 0 : SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
483 : }
484 : }
485 :
486 : // Grab the current video settings
487 0 : SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
488 0 : m_CurrentBPP = bpp;
489 :
490 0 : if (fullscreen)
491 0 : SDL_SetWindowGrab(m_Window, SDL_TRUE);
492 : else
493 0 : SDL_SetWindowGrab(m_Window, SDL_FALSE);
494 :
495 0 : m_IsFullscreen = fullscreen;
496 :
497 0 : g_xres = m_CurrentW;
498 0 : g_yres = m_CurrentH;
499 :
500 0 : return true;
501 : }
502 :
503 0 : bool CVideoMode::InitSDL()
504 : {
505 0 : ENSURE(!m_IsInitialised);
506 :
507 0 : ReadConfig();
508 :
509 : // preferred video mode = current desktop settings
510 : // (command line params may override these)
511 : // TODO: handle multi-screen and HiDPI properly.
512 : SDL_DisplayMode mode;
513 0 : if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
514 : {
515 0 : m_PreferredW = mode.w;
516 0 : m_PreferredH = mode.h;
517 0 : m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
518 0 : m_PreferredFreq = mode.refresh_rate;
519 : }
520 :
521 0 : int w = m_ConfigW;
522 0 : int h = m_ConfigH;
523 :
524 0 : if (m_ConfigFullscreen)
525 : {
526 : // If fullscreen and no explicit size set, default to the desktop resolution
527 0 : if (w == 0 || h == 0)
528 : {
529 0 : w = m_PreferredW;
530 0 : h = m_PreferredH;
531 : }
532 : }
533 :
534 : // If no size determined, default to something sensible
535 0 : if (w == 0 || h == 0)
536 : {
537 0 : w = DEFAULT_WINDOW_W;
538 0 : h = DEFAULT_WINDOW_H;
539 : }
540 :
541 0 : if (!m_ConfigFullscreen)
542 : {
543 : // Limit the window to the screen size (if known)
544 0 : if (m_PreferredW)
545 0 : w = std::min(w, m_PreferredW);
546 0 : if (m_PreferredH)
547 0 : h = std::min(h, m_PreferredH);
548 : }
549 :
550 0 : const int bpp = GetBestBPP();
551 0 : if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
552 0 : return false;
553 :
554 : // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
555 : // The driver appears to register its own atexit hook on context creation.
556 : // If this atexit hook is called before SDL_Quit destroys the OpenGL context,
557 : // some kind of double-free problem causes a crash and lockup in the driver.
558 : // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
559 : // by destroying the context *before* the driver's atexit hook is called.
560 : // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
561 0 : atexit(SDL_Quit);
562 : // End work around.
563 :
564 0 : m_IsInitialised = true;
565 :
566 0 : if (!m_ConfigFullscreen)
567 : {
568 0 : m_WindowedW = w;
569 0 : m_WindowedH = h;
570 : }
571 :
572 0 : SetWindowIcon();
573 :
574 0 : m_Cursor = std::make_unique<CCursor>();
575 :
576 0 : return true;
577 : }
578 :
579 6 : bool CVideoMode::InitNonSDL()
580 : {
581 6 : ENSURE(!m_IsInitialised);
582 :
583 6 : ReadConfig();
584 :
585 6 : m_IsInitialised = true;
586 :
587 6 : return true;
588 : }
589 :
590 6 : void CVideoMode::Shutdown()
591 : {
592 6 : ENSURE(m_IsInitialised);
593 :
594 6 : m_Cursor.reset();
595 :
596 6 : m_IsFullscreen = false;
597 6 : m_IsInitialised = false;
598 6 : m_BackendDevice.reset();
599 6 : if (m_Window)
600 : {
601 0 : SDL_DestroyWindow(m_Window);
602 0 : m_Window = nullptr;
603 : }
604 6 : }
605 :
606 6 : bool CVideoMode::CreateBackendDevice(const bool createSDLContext)
607 : {
608 6 : if (!createSDLContext && m_Backend == Renderer::Backend::Backend::VULKAN)
609 0 : m_Backend = Renderer::Backend::Backend::GL;
610 6 : SDL_Window* window = createSDLContext ? m_Window : nullptr;
611 6 : while (m_Backend != Renderer::Backend::Backend::DUMMY)
612 : {
613 0 : if (TryCreateBackendDevice(window))
614 0 : return true;
615 0 : DowngradeBackendSettingAfterCreationFailure();
616 : }
617 6 : return TryCreateBackendDevice(window);
618 : }
619 :
620 6 : bool CVideoMode::TryCreateBackendDevice(SDL_Window* window)
621 : {
622 6 : switch (m_Backend)
623 : {
624 0 : case Renderer::Backend::Backend::GL:
625 0 : m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, false);
626 0 : break;
627 0 : case Renderer::Backend::Backend::GL_ARB:
628 0 : m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, true);
629 0 : break;
630 6 : case Renderer::Backend::Backend::DUMMY:
631 6 : m_BackendDevice = Renderer::Backend::Dummy::CreateDevice(window);
632 6 : ENSURE(m_BackendDevice);
633 6 : break;
634 0 : case Renderer::Backend::Backend::VULKAN:
635 0 : m_BackendDevice = Renderer::Backend::Vulkan::CreateDevice(window);
636 0 : break;
637 : }
638 6 : return static_cast<bool>(m_BackendDevice);
639 : }
640 :
641 0 : void CVideoMode::DowngradeBackendSettingAfterCreationFailure()
642 : {
643 0 : const Renderer::Backend::Backend fallback = GetFallbackBackend(m_Backend);
644 0 : LOGERROR("Unable to create device for %s backend, switching to %s.",
645 : GetBackendName(m_Backend), GetBackendName(fallback));
646 0 : m_Backend = fallback;
647 0 : }
648 :
649 0 : bool CVideoMode::ResizeWindow(int w, int h)
650 : {
651 0 : ENSURE(m_IsInitialised);
652 :
653 : // Ignore if not windowed
654 0 : if (m_IsFullscreen)
655 0 : return true;
656 :
657 : // Ignore if the size hasn't changed
658 0 : if (w == m_WindowedW && h == m_WindowedH)
659 0 : return true;
660 :
661 0 : int bpp = GetBestBPP();
662 :
663 0 : if (!SetVideoMode(w, h, bpp, false))
664 0 : return false;
665 :
666 0 : m_WindowedW = w;
667 0 : m_WindowedH = h;
668 :
669 0 : UpdateRenderer(w, h);
670 :
671 0 : return true;
672 : }
673 :
674 0 : void CVideoMode::Rescale(float scale)
675 : {
676 0 : ENSURE(m_IsInitialised);
677 0 : m_Scale = scale;
678 0 : UpdateRenderer(m_CurrentW, m_CurrentH);
679 0 : }
680 :
681 8 : float CVideoMode::GetScale() const
682 : {
683 8 : return m_Scale;
684 : }
685 :
686 0 : bool CVideoMode::SetFullscreen(bool fullscreen)
687 : {
688 : // This might get called before initialisation by psDisplayError;
689 : // if so then silently fail
690 0 : if (!m_IsInitialised)
691 0 : return false;
692 :
693 : // Check whether this is actually a change
694 0 : if (fullscreen == m_IsFullscreen)
695 0 : return true;
696 :
697 0 : if (!m_IsFullscreen)
698 : {
699 : // Windowed -> fullscreen:
700 :
701 0 : int w = 0, h = 0;
702 :
703 : // If a fullscreen size was configured, use that; else use the desktop size; else use a default
704 0 : if (m_ConfigFullscreen)
705 : {
706 0 : w = m_ConfigW;
707 0 : h = m_ConfigH;
708 : }
709 0 : if (w == 0 || h == 0)
710 : {
711 0 : w = m_PreferredW;
712 0 : h = m_PreferredH;
713 : }
714 0 : if (w == 0 || h == 0)
715 : {
716 0 : w = DEFAULT_FULLSCREEN_W;
717 0 : h = DEFAULT_FULLSCREEN_H;
718 : }
719 :
720 0 : int bpp = GetBestBPP();
721 :
722 0 : if (!SetVideoMode(w, h, bpp, fullscreen))
723 0 : return false;
724 :
725 0 : UpdateRenderer(m_CurrentW, m_CurrentH);
726 :
727 0 : return true;
728 : }
729 : else
730 : {
731 : // Fullscreen -> windowed:
732 :
733 : // Go back to whatever the previous window size was
734 0 : int w = m_WindowedW, h = m_WindowedH;
735 :
736 0 : int bpp = GetBestBPP();
737 :
738 0 : if (!SetVideoMode(w, h, bpp, fullscreen))
739 0 : return false;
740 :
741 0 : UpdateRenderer(w, h);
742 :
743 0 : return true;
744 : }
745 : }
746 :
747 0 : bool CVideoMode::ToggleFullscreen()
748 : {
749 0 : return SetFullscreen(!m_IsFullscreen);
750 : }
751 :
752 0 : bool CVideoMode::IsInFullscreen() const
753 : {
754 0 : return m_IsFullscreen;
755 : }
756 :
757 0 : void CVideoMode::UpdatePosition(int x, int y)
758 : {
759 0 : if (!m_IsFullscreen)
760 : {
761 0 : m_WindowedX = x;
762 0 : m_WindowedY = y;
763 : }
764 0 : }
765 :
766 0 : void CVideoMode::UpdateRenderer(int w, int h)
767 : {
768 0 : if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
769 0 : if (h < 2) h = 2;
770 :
771 0 : g_xres = w;
772 0 : g_yres = h;
773 :
774 0 : SViewPort vp = { 0, 0, w, h };
775 :
776 0 : if (g_VideoMode.GetBackendDevice())
777 0 : g_VideoMode.GetBackendDevice()->OnWindowResize(w, h);
778 :
779 0 : if (CRenderer::IsInitialised())
780 0 : g_Renderer.Resize(w, h);
781 :
782 0 : if (g_GUI)
783 0 : g_GUI->UpdateResolution();
784 :
785 0 : if (g_Console)
786 0 : g_Console->UpdateScreenSize(w, h);
787 :
788 0 : if (g_Game)
789 0 : g_Game->GetView()->SetViewport(vp);
790 0 : }
791 :
792 0 : int CVideoMode::GetBestBPP()
793 : {
794 0 : if (m_ConfigBPP)
795 0 : return m_ConfigBPP;
796 0 : if (m_PreferredBPP)
797 0 : return m_PreferredBPP;
798 0 : return 32;
799 : }
800 :
801 0 : int CVideoMode::GetXRes() const
802 : {
803 0 : ENSURE(m_IsInitialised);
804 0 : return m_CurrentW;
805 : }
806 :
807 0 : int CVideoMode::GetYRes() const
808 : {
809 0 : ENSURE(m_IsInitialised);
810 0 : return m_CurrentH;
811 : }
812 :
813 0 : int CVideoMode::GetBPP() const
814 : {
815 0 : ENSURE(m_IsInitialised);
816 0 : return m_CurrentBPP;
817 : }
818 :
819 0 : bool CVideoMode::IsVSyncEnabled() const
820 : {
821 0 : ENSURE(m_IsInitialised);
822 0 : return m_ConfigVSync;
823 : }
824 :
825 0 : int CVideoMode::GetDesktopXRes() const
826 : {
827 0 : ENSURE(m_IsInitialised);
828 0 : return m_PreferredW;
829 : }
830 :
831 0 : int CVideoMode::GetDesktopYRes() const
832 : {
833 0 : ENSURE(m_IsInitialised);
834 0 : return m_PreferredH;
835 : }
836 :
837 0 : int CVideoMode::GetDesktopBPP() const
838 : {
839 0 : ENSURE(m_IsInitialised);
840 0 : return m_PreferredBPP;
841 : }
842 :
843 0 : int CVideoMode::GetDesktopFreq() const
844 : {
845 0 : ENSURE(m_IsInitialised);
846 0 : return m_PreferredFreq;
847 : }
848 :
849 0 : SDL_Window* CVideoMode::GetWindow()
850 : {
851 0 : ENSURE(m_IsInitialised);
852 0 : return m_Window;
853 : }
854 :
855 0 : void CVideoMode::SetWindowIcon()
856 : {
857 : // The window icon should be kept outside of art/textures/, or else it will be converted
858 : // to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
859 : // conversion needlessly complicated.
860 0 : std::shared_ptr<u8> iconFile;
861 : size_t iconFileSize;
862 0 : if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
863 : {
864 0 : LOGWARNING("Window icon not found.");
865 0 : return;
866 : }
867 :
868 0 : Tex iconTexture;
869 0 : if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
870 0 : return;
871 :
872 : // Convert to required BGRA format.
873 0 : const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
874 0 : if (iconTexture.transform_to(iconFlags) != INFO::OK)
875 0 : return;
876 :
877 0 : void* bgra_img = iconTexture.get_data();
878 0 : if (!bgra_img)
879 0 : return;
880 :
881 0 : SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
882 0 : iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
883 0 : 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
884 0 : if (!iconSurface)
885 0 : return;
886 :
887 0 : SDL_SetWindowIcon(m_Window, iconSurface);
888 0 : SDL_FreeSurface(iconSurface);
889 : }
890 :
891 0 : void CVideoMode::SetCursor(const CStrW& name)
892 : {
893 0 : if (m_Cursor)
894 0 : m_Cursor->SetCursor(name);
895 0 : }
896 :
897 6 : void CVideoMode::ResetCursor()
898 : {
899 6 : if (m_Cursor)
900 0 : m_Cursor->ResetCursor();
901 9 : }
|