LCOV - code coverage report
Current view: top level - source/ps - VideoMode.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 51 446 11.4 %
Date: 2023-01-19 00:18:29 Functions: 10 38 26.3 %

          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 : }

Generated by: LCOV version 1.13