Merge 'remotes/trunk'
[0ad.git] / source / ps / VideoMode.cpp
blob6ccb3863b11e270e7eb392c3ba9954e625df7a44
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "VideoMode.h"
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 #if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
33 #include "ps/DllLoader.h"
34 #endif
35 #include "ps/Filesystem.h"
36 #include "ps/Game.h"
37 #include "ps/GameSetup/Config.h"
38 #include "ps/Pyrogenesis.h"
39 #include "renderer/backend/dummy/DeviceForward.h"
40 #include "renderer/backend/gl/DeviceForward.h"
41 #include "renderer/backend/IDevice.h"
42 #include "renderer/backend/vulkan/DeviceForward.h"
43 #include "renderer/Renderer.h"
45 #include <string_view>
47 #if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
48 #include <SDL_vulkan.h>
49 #include <stdlib.h>
50 #endif
52 namespace
55 int DEFAULT_WINDOW_W = 1024;
56 int DEFAULT_WINDOW_H = 768;
58 int DEFAULT_FULLSCREEN_W = 1024;
59 int DEFAULT_FULLSCREEN_H = 768;
61 const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow";
63 Renderer::Backend::Backend GetFallbackBackend(const Renderer::Backend::Backend backend)
65 Renderer::Backend::Backend fallback = Renderer::Backend::Backend::DUMMY;
66 // We use a switch instead of a list to have compile-time checks for missed
67 // values and because a linear priority list doesn't work for general case.
68 switch (backend)
70 case Renderer::Backend::Backend::GL:
71 fallback = Renderer::Backend::Backend::GL_ARB;
72 break;
73 case Renderer::Backend::Backend::GL_ARB:
74 fallback = Renderer::Backend::Backend::DUMMY;
75 break;
76 case Renderer::Backend::Backend::DUMMY:
77 break;
78 case Renderer::Backend::Backend::VULKAN:
79 fallback = Renderer::Backend::Backend::GL;
80 break;
82 return fallback;
85 std::string_view GetBackendName(const Renderer::Backend::Backend backend)
87 std::string_view name{"Unknown"};
88 switch (backend)
90 case Renderer::Backend::Backend::GL:
91 name = "GL";
92 break;
93 case Renderer::Backend::Backend::GL_ARB:
94 name = "GL ARB";
95 break;
96 case Renderer::Backend::Backend::DUMMY:
97 name = "Dummy";
98 break;
99 case Renderer::Backend::Backend::VULKAN:
100 name = "Vulkan";
101 break;
103 return name;
106 } // anonymous namespace
108 #if OS_WIN
109 // We can't include wutil directly because GL headers conflict with Windows
110 // until we use a proper GL loader.
111 extern void wutil_SetAppWindow(SDL_Window* window);
113 // After a proper HiDPI integration we should switch to manifest until
114 // SDL has an implemented HiDPI on Windows.
115 extern void wutil_EnableHiDPIOnWindows();
116 #endif
118 CVideoMode g_VideoMode;
120 class CVideoMode::CCursor
122 public:
123 enum class CursorBackend
125 SDL,
126 SYSTEM
129 CCursor();
130 ~CCursor();
132 void SetCursor(const CStrW& name);
133 void ResetCursor();
135 private:
136 CursorBackend m_CursorBackend = CursorBackend::SYSTEM;
137 SDL_Surface* m_CursorSurface = nullptr;
138 SDL_Cursor* m_Cursor = nullptr;
139 CStrW m_CursorName;
142 CVideoMode::CCursor::CCursor()
144 std::string cursorBackend;
145 CFG_GET_VAL("cursorbackend", cursorBackend);
146 if (cursorBackend == "sdl")
147 m_CursorBackend = CursorBackend::SDL;
148 else
149 m_CursorBackend = CursorBackend::SYSTEM;
151 ResetCursor();
154 CVideoMode::CCursor::~CCursor()
156 if (m_Cursor)
157 SDL_FreeCursor(m_Cursor);
158 if (m_CursorSurface)
159 SDL_FreeSurface(m_CursorSurface);
162 void CVideoMode::CCursor::SetCursor(const CStrW& name)
164 if (m_CursorBackend == CursorBackend::SYSTEM || m_CursorName == name)
165 return;
166 m_CursorName = name;
168 if (m_Cursor)
169 SDL_FreeCursor(m_Cursor);
170 if (m_CursorSurface)
171 SDL_FreeSurface(m_CursorSurface);
173 if (name.empty())
175 SDL_ShowCursor(SDL_DISABLE);
176 return;
179 const VfsPath pathBaseName(VfsPath(L"art/textures/cursors") / name);
181 // Read pixel offset of the cursor's hotspot [the bit of it that's
182 // drawn at (g_mouse_x,g_mouse_y)] from file.
183 int hotspotX = 0, hotspotY = 0;
185 const VfsPath pathHotspotName = pathBaseName.ChangeExtension(L".txt");
186 std::shared_ptr<u8> buffer;
187 size_t size;
188 if (g_VFS->LoadFile(pathHotspotName, buffer, size) != INFO::OK)
190 LOGERROR("Can't load hotspot for cursor: %s", pathHotspotName.string8().c_str());
191 return;
193 std::wstringstream s(std::wstring(reinterpret_cast<const wchar_t*>(buffer.get()), size));
194 s >> hotspotX >> hotspotY;
197 const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png");
199 std::shared_ptr<u8> file;
200 size_t fileSize;
201 if (g_VFS->LoadFile(pathImageName, file, fileSize) != INFO::OK)
203 LOGERROR("Can't load image for cursor: %s", pathImageName.string8().c_str());
204 return;
207 Tex t;
208 if (t.decode(file, fileSize) != INFO::OK)
210 LOGERROR("Can't decode image for cursor");
211 return;
214 // Convert to required BGRA format.
215 const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
216 if (t.transform_to(flags) != INFO::OK)
218 LOGERROR("Can't transform image for cursor");
219 return;
221 void* imageBGRA = t.get_data();
222 if (!imageBGRA)
224 LOGERROR("Transformed image is empty for cursor");
225 return;
228 m_CursorSurface = SDL_CreateRGBSurfaceFrom(imageBGRA,
229 static_cast<int>(t.m_Width), static_cast<int>(t.m_Height), 32,
230 static_cast<int>(t.m_Width * 4),
231 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
232 if (!m_CursorSurface)
234 LOGERROR("Can't create surface for cursor: %s", SDL_GetError());
235 return;
237 const float scale = g_VideoMode.GetScale();
238 if (scale != 1.0)
240 SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0,
241 m_CursorSurface->w * scale,
242 m_CursorSurface->h * scale, 32,
243 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
244 if (!scaledSurface)
246 LOGERROR("Can't create scaled surface forcursor: %s", SDL_GetError());
247 return;
249 if (SDL_BlitScaled(m_CursorSurface, nullptr, scaledSurface, nullptr))
250 return;
251 SDL_FreeSurface(m_CursorSurface);
252 m_CursorSurface = scaledSurface;
254 m_Cursor = SDL_CreateColorCursor(m_CursorSurface, hotspotX, hotspotY);
255 if (!m_Cursor)
257 LOGERROR("Can't create cursor: %s", SDL_GetError());
258 return;
261 SDL_SetCursor(m_Cursor);
264 void CVideoMode::CCursor::ResetCursor()
266 SetCursor(DEFAULT_CURSOR_NAME);
269 CVideoMode::CVideoMode() :
270 m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
274 CVideoMode::~CVideoMode() = default;
276 void CVideoMode::ReadConfig()
278 bool windowed = !m_ConfigFullscreen;
279 CFG_GET_VAL("windowed", windowed);
280 m_ConfigFullscreen = !windowed;
282 CFG_GET_VAL("gui.scale", m_Scale);
284 CFG_GET_VAL("xres", m_ConfigW);
285 CFG_GET_VAL("yres", m_ConfigH);
286 CFG_GET_VAL("bpp", m_ConfigBPP);
287 CFG_GET_VAL("display", m_ConfigDisplay);
288 CFG_GET_VAL("hidpi", m_ConfigEnableHiDPI);
289 CFG_GET_VAL("vsync", m_ConfigVSync);
291 CStr rendererBackend;
292 CFG_GET_VAL("rendererbackend", rendererBackend);
293 if (rendererBackend == "glarb")
294 m_Backend = Renderer::Backend::Backend::GL_ARB;
295 else if (rendererBackend == "dummy")
296 m_Backend = Renderer::Backend::Backend::DUMMY;
297 else if (rendererBackend == "vulkan")
298 m_Backend = Renderer::Backend::Backend::VULKAN;
299 else
300 m_Backend = Renderer::Backend::Backend::GL;
302 #if OS_WIN
303 if (m_ConfigEnableHiDPI)
304 wutil_EnableHiDPIOnWindows();
305 #endif
308 bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
310 Uint32 flags = 0;
311 if (fullscreen)
313 bool borderlessFullscreen = true;
314 CFG_GET_VAL("borderless.fullscreen", borderlessFullscreen);
315 flags |= borderlessFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
317 else
319 bool borderlessWindow = false;
320 CFG_GET_VAL("borderless.window", borderlessWindow);
321 if (borderlessWindow)
322 flags |= SDL_WINDOW_BORDERLESS;
325 if (!m_Window)
327 const bool isGLBackend =
328 m_Backend == Renderer::Backend::Backend::GL ||
329 m_Backend == Renderer::Backend::Backend::GL_ARB;
330 if (isGLBackend)
332 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
333 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
334 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
335 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
337 bool debugContext = false;
338 CFG_GET_VAL("renderer.backend.debugcontext", debugContext);
339 if (debugContext)
340 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
342 bool forceGLVersion = false;
343 CFG_GET_VAL("forceglversion", forceGLVersion);
344 if (forceGLVersion)
346 CStr forceGLProfile = "compatibility";
347 int forceGLMajorVersion = 3;
348 int forceGLMinorVersion = 0;
349 CFG_GET_VAL("forceglprofile", forceGLProfile);
350 CFG_GET_VAL("forceglmajorversion", forceGLMajorVersion);
351 CFG_GET_VAL("forceglminorversion", forceGLMinorVersion);
353 int profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
354 if (forceGLProfile == "es")
355 profile = SDL_GL_CONTEXT_PROFILE_ES;
356 else if (forceGLProfile == "core")
357 profile = SDL_GL_CONTEXT_PROFILE_CORE;
358 else if (forceGLProfile != "compatibility")
359 LOGWARNING("Unknown force GL profile '%s', compatibility profile is used", forceGLProfile.c_str());
361 if (forceGLMajorVersion < 1 || forceGLMinorVersion < 0)
363 LOGERROR("Unsupported force GL version: %d.%d", forceGLMajorVersion, forceGLMinorVersion);
365 else
367 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
368 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, forceGLMajorVersion);
369 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, forceGLMinorVersion);
372 else
374 #if CONFIG2_GLES
375 // Require GLES 2.0
376 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
377 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
378 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
379 #else
380 // Some macOS and MESA drivers might not create a context even if they can
381 // with the core profile. So disable it for a while until we can guarantee
382 // its creation.
383 #if OS_WIN
384 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
385 #endif
386 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
387 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
388 #endif
392 // Note: these flags only take affect in SDL_CreateWindow
393 flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
394 if (m_ConfigEnableHiDPI)
395 flags |= SDL_WINDOW_ALLOW_HIGHDPI;
396 if (isGLBackend)
397 flags |= SDL_WINDOW_OPENGL;
398 else if (m_Backend == Renderer::Backend::Backend::VULKAN)
399 flags |= SDL_WINDOW_VULKAN;
400 m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
402 #if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
403 if (m_Backend == Renderer::Backend::Backend::VULKAN)
405 // MoltenVK - enable full component swizzling support.
406 setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
407 CStr fullPathToVulkanLibrary = DllLoader::GenerateFilename("MoltenVK", "", ".dylib");
408 // MoltenVK - only print warnings and errors.
409 setenv("MVK_CONFIG_LOG_LEVEL", "2", 1);
410 if (SDL_Vulkan_LoadLibrary(fullPathToVulkanLibrary.c_str()) != 0)
412 LOGWARNING("Failed to load %s.", fullPathToVulkanLibrary.c_str());
413 DowngradeBackendSettingAfterCreationFailure();
414 return SetVideoMode(w, h, bpp, fullscreen);
416 else
417 LOGMESSAGE("Loaded %s.", fullPathToVulkanLibrary.c_str());
419 #endif
421 m_Window = SDL_CreateWindow(main_window_name, m_WindowedX, m_WindowedY, w, h, flags);
422 if (!m_Window)
424 // SDL might fail to create a window in case of missing a Vulkan driver.
425 if (m_Backend == Renderer::Backend::Backend::VULKAN)
427 LOGWARNING("Failed to create a Vulkan window: %s", SDL_GetError());
428 DowngradeBackendSettingAfterCreationFailure();
429 return SetVideoMode(w, h, bpp, fullscreen);
432 // If fullscreen fails, try windowed mode
433 if (fullscreen)
435 LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
436 "%dx%d:%d (\"%hs\"), falling back to windowed mode",
437 w, h, bpp, SDL_GetError());
438 // Using default size for the window for now, as the attempted setting
439 // could be as large, or larger than the screen size.
440 return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
442 else
444 if (isGLBackend)
446 int depthSize = 24;
447 SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize);
448 if (depthSize > 16)
450 // Fall back to a smaller depth buffer
451 // (The rendering may be ugly but this helps when running in VMware)
452 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
454 return SetVideoMode(w, h, bpp, fullscreen);
458 LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
459 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
460 return false;
464 if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
466 LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
467 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
468 return false;
471 #if OS_WIN
472 // We need to set the window for an error dialog.
473 wutil_SetAppWindow(m_Window);
474 #endif
476 if (!TryCreateBackendDevice(m_Window))
478 DowngradeBackendSettingAfterCreationFailure();
479 SDL_DestroyWindow(m_Window);
480 m_Window = nullptr;
481 return SetVideoMode(w, h, bpp, fullscreen);
484 if (isGLBackend)
485 SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0);
487 else
489 if (m_IsFullscreen != fullscreen)
491 if (!fullscreen)
493 // For some reason, when switching from fullscreen to windowed mode,
494 // we have to set the window size and position before and after switching
495 SDL_SetWindowSize(m_Window, w, h);
496 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
499 if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
501 LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
502 w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
503 return false;
507 if (!fullscreen)
509 SDL_SetWindowSize(m_Window, w, h);
510 SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
514 // Grab the current video settings
515 SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
516 m_CurrentBPP = bpp;
518 // #545: we need to constrain the window in fullscreen mode to avoid mouse
519 // "falling out" of the window in case of multiple displays.
520 bool mouseGrabInFullscreen = true;
521 CFG_GET_VAL("window.mousegrabinfullscreen", mouseGrabInFullscreen);
522 bool mouseGrabInWindowMode = false;
523 CFG_GET_VAL("window.mousegrabinwindowmode", mouseGrabInWindowMode);
525 if (fullscreen ? mouseGrabInFullscreen : mouseGrabInWindowMode)
526 SDL_SetWindowGrab(m_Window, SDL_TRUE);
527 else
528 SDL_SetWindowGrab(m_Window, SDL_FALSE);
530 m_IsFullscreen = fullscreen;
532 g_xres = m_CurrentW;
533 g_yres = m_CurrentH;
535 return true;
538 bool CVideoMode::InitSDL()
540 ENSURE(!m_IsInitialised);
542 ReadConfig();
544 // preferred video mode = current desktop settings
545 // (command line params may override these)
546 // TODO: handle multi-screen and HiDPI properly.
547 SDL_DisplayMode mode;
548 if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
550 m_PreferredW = mode.w;
551 m_PreferredH = mode.h;
552 m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
553 m_PreferredFreq = mode.refresh_rate;
556 int w = m_ConfigW;
557 int h = m_ConfigH;
559 if (m_ConfigFullscreen)
561 // If fullscreen and no explicit size set, default to the desktop resolution
562 if (w == 0 || h == 0)
564 w = m_PreferredW;
565 h = m_PreferredH;
569 // If no size determined, default to something sensible
570 if (w == 0 || h == 0)
572 w = DEFAULT_WINDOW_W;
573 h = DEFAULT_WINDOW_H;
576 if (!m_ConfigFullscreen)
578 // Limit the window to the screen size (if known)
579 if (m_PreferredW)
580 w = std::min(w, m_PreferredW);
581 if (m_PreferredH)
582 h = std::min(h, m_PreferredH);
585 const int bpp = GetBestBPP();
586 if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
587 return false;
589 // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
590 // The driver appears to register its own atexit hook on context creation.
591 // If this atexit hook is called before SDL_Quit destroys the OpenGL context,
592 // some kind of double-free problem causes a crash and lockup in the driver.
593 // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
594 // by destroying the context *before* the driver's atexit hook is called.
595 // (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
596 atexit(SDL_Quit);
597 // End work around.
599 m_IsInitialised = true;
601 if (!m_ConfigFullscreen)
603 m_WindowedW = w;
604 m_WindowedH = h;
607 SetWindowIcon();
609 m_Cursor = std::make_unique<CCursor>();
611 return true;
614 bool CVideoMode::InitNonSDL()
616 ENSURE(!m_IsInitialised);
618 ReadConfig();
620 m_IsInitialised = true;
622 return true;
625 void CVideoMode::Shutdown()
627 ENSURE(m_IsInitialised);
629 m_Cursor.reset();
631 m_IsFullscreen = false;
632 m_IsInitialised = false;
633 m_BackendDevice.reset();
634 if (m_Window)
636 SDL_DestroyWindow(m_Window);
637 m_Window = nullptr;
641 bool CVideoMode::CreateBackendDevice(const bool createSDLContext)
643 if (!createSDLContext && m_Backend == Renderer::Backend::Backend::VULKAN)
644 m_Backend = Renderer::Backend::Backend::GL;
645 SDL_Window* window = createSDLContext ? m_Window : nullptr;
646 while (m_Backend != Renderer::Backend::Backend::DUMMY)
648 if (TryCreateBackendDevice(window))
649 return true;
650 DowngradeBackendSettingAfterCreationFailure();
652 return TryCreateBackendDevice(window);
655 bool CVideoMode::TryCreateBackendDevice(SDL_Window* window)
657 switch (m_Backend)
659 case Renderer::Backend::Backend::GL:
660 m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, false);
661 break;
662 case Renderer::Backend::Backend::GL_ARB:
663 m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, true);
664 break;
665 case Renderer::Backend::Backend::DUMMY:
666 m_BackendDevice = Renderer::Backend::Dummy::CreateDevice(window);
667 ENSURE(m_BackendDevice);
668 break;
669 case Renderer::Backend::Backend::VULKAN:
670 m_BackendDevice = Renderer::Backend::Vulkan::CreateDevice(window);
671 break;
673 return static_cast<bool>(m_BackendDevice);
676 void CVideoMode::DowngradeBackendSettingAfterCreationFailure()
678 const Renderer::Backend::Backend fallback = GetFallbackBackend(m_Backend);
679 LOGERROR("Unable to create device for %s backend, switching to %s.",
680 GetBackendName(m_Backend), GetBackendName(fallback));
681 m_Backend = fallback;
684 bool CVideoMode::ResizeWindow(int w, int h)
686 ENSURE(m_IsInitialised);
688 // Ignore if not windowed
689 if (m_IsFullscreen)
690 return true;
692 // Ignore if the size hasn't changed
693 if (w == m_WindowedW && h == m_WindowedH)
694 return true;
696 int bpp = GetBestBPP();
698 if (!SetVideoMode(w, h, bpp, false))
699 return false;
701 m_WindowedW = w;
702 m_WindowedH = h;
704 UpdateRenderer(w, h);
706 return true;
709 void CVideoMode::Rescale(float scale)
711 ENSURE(m_IsInitialised);
712 m_Scale = scale;
713 UpdateRenderer(m_CurrentW, m_CurrentH);
716 float CVideoMode::GetScale() const
718 return m_Scale;
721 bool CVideoMode::SetFullscreen(bool fullscreen)
723 // This might get called before initialisation by psDisplayError;
724 // if so then silently fail
725 if (!m_IsInitialised)
726 return false;
728 // Check whether this is actually a change
729 if (fullscreen == m_IsFullscreen)
730 return true;
732 if (!m_IsFullscreen)
734 // Windowed -> fullscreen:
736 int w = 0, h = 0;
738 // If a fullscreen size was configured, use that; else use the desktop size; else use a default
739 if (m_ConfigFullscreen)
741 w = m_ConfigW;
742 h = m_ConfigH;
744 if (w == 0 || h == 0)
746 w = m_PreferredW;
747 h = m_PreferredH;
749 if (w == 0 || h == 0)
751 w = DEFAULT_FULLSCREEN_W;
752 h = DEFAULT_FULLSCREEN_H;
755 int bpp = GetBestBPP();
757 if (!SetVideoMode(w, h, bpp, fullscreen))
758 return false;
760 UpdateRenderer(m_CurrentW, m_CurrentH);
762 return true;
764 else
766 // Fullscreen -> windowed:
768 // Go back to whatever the previous window size was
769 int w = m_WindowedW, h = m_WindowedH;
771 int bpp = GetBestBPP();
773 if (!SetVideoMode(w, h, bpp, fullscreen))
774 return false;
776 UpdateRenderer(w, h);
778 return true;
782 bool CVideoMode::ToggleFullscreen()
784 return SetFullscreen(!m_IsFullscreen);
787 bool CVideoMode::IsInFullscreen() const
789 return m_IsFullscreen;
792 void CVideoMode::UpdatePosition(int x, int y)
794 if (!m_IsFullscreen)
796 m_WindowedX = x;
797 m_WindowedY = y;
801 void CVideoMode::UpdateRenderer(int w, int h)
803 if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
804 if (h < 2) h = 2;
806 g_xres = w;
807 g_yres = h;
809 SViewPort vp = { 0, 0, w, h };
811 if (g_VideoMode.GetBackendDevice())
812 g_VideoMode.GetBackendDevice()->OnWindowResize(w, h);
814 if (CRenderer::IsInitialised())
815 g_Renderer.Resize(w, h);
817 if (g_GUI)
818 g_GUI->UpdateResolution();
820 if (g_Console)
821 g_Console->UpdateScreenSize(w, h);
823 if (g_Game)
824 g_Game->GetView()->SetViewport(vp);
827 int CVideoMode::GetBestBPP()
829 if (m_ConfigBPP)
830 return m_ConfigBPP;
831 if (m_PreferredBPP)
832 return m_PreferredBPP;
833 return 32;
836 int CVideoMode::GetXRes() const
838 ENSURE(m_IsInitialised);
839 return m_CurrentW;
842 int CVideoMode::GetYRes() const
844 ENSURE(m_IsInitialised);
845 return m_CurrentH;
848 int CVideoMode::GetBPP() const
850 ENSURE(m_IsInitialised);
851 return m_CurrentBPP;
854 bool CVideoMode::IsVSyncEnabled() const
856 ENSURE(m_IsInitialised);
857 return m_ConfigVSync;
860 int CVideoMode::GetDesktopXRes() const
862 ENSURE(m_IsInitialised);
863 return m_PreferredW;
866 int CVideoMode::GetDesktopYRes() const
868 ENSURE(m_IsInitialised);
869 return m_PreferredH;
872 int CVideoMode::GetDesktopBPP() const
874 ENSURE(m_IsInitialised);
875 return m_PreferredBPP;
878 int CVideoMode::GetDesktopFreq() const
880 ENSURE(m_IsInitialised);
881 return m_PreferredFreq;
884 SDL_Window* CVideoMode::GetWindow()
886 ENSURE(m_IsInitialised);
887 return m_Window;
890 void CVideoMode::SetWindowIcon()
892 // The window icon should be kept outside of art/textures/, or else it will be converted
893 // to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
894 // conversion needlessly complicated.
895 std::shared_ptr<u8> iconFile;
896 size_t iconFileSize;
897 if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
899 LOGWARNING("Window icon not found.");
900 return;
903 Tex iconTexture;
904 if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
905 return;
907 // Convert to required BGRA format.
908 const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
909 if (iconTexture.transform_to(iconFlags) != INFO::OK)
910 return;
912 void* bgra_img = iconTexture.get_data();
913 if (!bgra_img)
914 return;
916 SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
917 iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
918 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
919 if (!iconSurface)
920 return;
922 SDL_SetWindowIcon(m_Window, iconSurface);
923 SDL_FreeSurface(iconSurface);
926 void CVideoMode::SetCursor(const CStrW& name)
928 if (m_Cursor)
929 m_Cursor->SetCursor(name);
932 void CVideoMode::ResetCursor()
934 if (m_Cursor)
935 m_Cursor->ResetCursor();