1
1
forked from 0ad/0ad
0ad/source/ps/VideoMode.cpp
bb 157c6af18e Make the space in 0 A.D. non-breaking throughout the codebase.
Avoid cases of filenames
Update years in terms and other legal(ish) documents
Don't update years in license headers, since change is not meaningful

Will add linter rule in seperate commit

Happy recompiling everyone!

Original Patch By: Nescio
Comment By: Gallaecio
Differential Revision: D2620
This was SVN commit r27786.
2023-07-27 20:54:46 +00:00

937 lines
23 KiB
C++

/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "VideoMode.h"
#include "graphics/GameView.h"
#include "gui/GUIManager.h"
#include "lib/config2.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/sysdep/os.h"
#include "lib/tex/tex.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
#if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
#include "ps/DllLoader.h"
#endif
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Pyrogenesis.h"
#include "renderer/backend/dummy/DeviceForward.h"
#include "renderer/backend/gl/DeviceForward.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/vulkan/DeviceForward.h"
#include "renderer/Renderer.h"
#include <string_view>
#if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
#include <SDL_vulkan.h>
#include <stdlib.h>
#endif
namespace
{
int DEFAULT_WINDOW_W = 1024;
int DEFAULT_WINDOW_H = 768;
int DEFAULT_FULLSCREEN_W = 1024;
int DEFAULT_FULLSCREEN_H = 768;
const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow";
Renderer::Backend::Backend GetFallbackBackend(const Renderer::Backend::Backend backend)
{
Renderer::Backend::Backend fallback = Renderer::Backend::Backend::DUMMY;
// We use a switch instead of a list to have compile-time checks for missed
// values and because a linear priority list doesn't work for general case.
switch (backend)
{
case Renderer::Backend::Backend::GL:
fallback = Renderer::Backend::Backend::GL_ARB;
break;
case Renderer::Backend::Backend::GL_ARB:
fallback = Renderer::Backend::Backend::DUMMY;
break;
case Renderer::Backend::Backend::DUMMY:
break;
case Renderer::Backend::Backend::VULKAN:
fallback = Renderer::Backend::Backend::GL;
break;
}
return fallback;
}
std::string_view GetBackendName(const Renderer::Backend::Backend backend)
{
std::string_view name{"Unknown"};
switch (backend)
{
case Renderer::Backend::Backend::GL:
name = "GL";
break;
case Renderer::Backend::Backend::GL_ARB:
name = "GL ARB";
break;
case Renderer::Backend::Backend::DUMMY:
name = "Dummy";
break;
case Renderer::Backend::Backend::VULKAN:
name = "Vulkan";
break;
}
return name;
}
} // anonymous namespace
#if OS_WIN
// We can't include wutil directly because GL headers conflict with Windows
// until we use a proper GL loader.
extern void wutil_SetAppWindow(SDL_Window* window);
// After a proper HiDPI integration we should switch to manifest until
// SDL has an implemented HiDPI on Windows.
extern void wutil_EnableHiDPIOnWindows();
#endif
CVideoMode g_VideoMode;
class CVideoMode::CCursor
{
public:
enum class CursorBackend
{
SDL,
SYSTEM
};
CCursor();
~CCursor();
void SetCursor(const CStrW& name);
void ResetCursor();
private:
CursorBackend m_CursorBackend = CursorBackend::SYSTEM;
SDL_Surface* m_CursorSurface = nullptr;
SDL_Cursor* m_Cursor = nullptr;
CStrW m_CursorName;
};
CVideoMode::CCursor::CCursor()
{
std::string cursorBackend;
CFG_GET_VAL("cursorbackend", cursorBackend);
if (cursorBackend == "sdl")
m_CursorBackend = CursorBackend::SDL;
else
m_CursorBackend = CursorBackend::SYSTEM;
ResetCursor();
}
CVideoMode::CCursor::~CCursor()
{
if (m_Cursor)
SDL_FreeCursor(m_Cursor);
if (m_CursorSurface)
SDL_FreeSurface(m_CursorSurface);
}
void CVideoMode::CCursor::SetCursor(const CStrW& name)
{
if (m_CursorBackend == CursorBackend::SYSTEM || m_CursorName == name)
return;
m_CursorName = name;
if (m_Cursor)
SDL_FreeCursor(m_Cursor);
if (m_CursorSurface)
SDL_FreeSurface(m_CursorSurface);
if (name.empty())
{
SDL_ShowCursor(SDL_DISABLE);
return;
}
const VfsPath pathBaseName(VfsPath(L"art/textures/cursors") / name);
// Read pixel offset of the cursor's hotspot [the bit of it that's
// drawn at (g_mouse_x,g_mouse_y)] from file.
int hotspotX = 0, hotspotY = 0;
{
const VfsPath pathHotspotName = pathBaseName.ChangeExtension(L".txt");
std::shared_ptr<u8> buffer;
size_t size;
if (g_VFS->LoadFile(pathHotspotName, buffer, size) != INFO::OK)
{
LOGERROR("Can't load hotspot for cursor: %s", pathHotspotName.string8().c_str());
return;
}
std::wstringstream s(std::wstring(reinterpret_cast<const wchar_t*>(buffer.get()), size));
s >> hotspotX >> hotspotY;
}
const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png");
std::shared_ptr<u8> file;
size_t fileSize;
if (g_VFS->LoadFile(pathImageName, file, fileSize) != INFO::OK)
{
LOGERROR("Can't load image for cursor: %s", pathImageName.string8().c_str());
return;
}
Tex t;
if (t.decode(file, fileSize) != INFO::OK)
{
LOGERROR("Can't decode image for cursor");
return;
}
// Convert to required BGRA format.
const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT;
if (t.transform_to(flags) != INFO::OK)
{
LOGERROR("Can't transform image for cursor");
return;
}
void* imageBGRA = t.get_data();
if (!imageBGRA)
{
LOGERROR("Transformed image is empty for cursor");
return;
}
m_CursorSurface = SDL_CreateRGBSurfaceFrom(imageBGRA,
static_cast<int>(t.m_Width), static_cast<int>(t.m_Height), 32,
static_cast<int>(t.m_Width * 4),
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!m_CursorSurface)
{
LOGERROR("Can't create surface for cursor: %s", SDL_GetError());
return;
}
const float scale = g_VideoMode.GetScale();
if (scale != 1.0)
{
SDL_Surface* scaledSurface = SDL_CreateRGBSurface(0,
m_CursorSurface->w * scale,
m_CursorSurface->h * scale, 32,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!scaledSurface)
{
LOGERROR("Can't create scaled surface forcursor: %s", SDL_GetError());
return;
}
if (SDL_BlitScaled(m_CursorSurface, nullptr, scaledSurface, nullptr))
return;
SDL_FreeSurface(m_CursorSurface);
m_CursorSurface = scaledSurface;
}
m_Cursor = SDL_CreateColorCursor(m_CursorSurface, hotspotX, hotspotY);
if (!m_Cursor)
{
LOGERROR("Can't create cursor: %s", SDL_GetError());
return;
}
SDL_SetCursor(m_Cursor);
}
void CVideoMode::CCursor::ResetCursor()
{
SetCursor(DEFAULT_CURSOR_NAME);
}
CVideoMode::CVideoMode() :
m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
{
}
CVideoMode::~CVideoMode() = default;
void CVideoMode::ReadConfig()
{
bool windowed = !m_ConfigFullscreen;
CFG_GET_VAL("windowed", windowed);
m_ConfigFullscreen = !windowed;
CFG_GET_VAL("gui.scale", m_Scale);
CFG_GET_VAL("xres", m_ConfigW);
CFG_GET_VAL("yres", m_ConfigH);
CFG_GET_VAL("bpp", m_ConfigBPP);
CFG_GET_VAL("display", m_ConfigDisplay);
CFG_GET_VAL("hidpi", m_ConfigEnableHiDPI);
CFG_GET_VAL("vsync", m_ConfigVSync);
CStr rendererBackend;
CFG_GET_VAL("rendererbackend", rendererBackend);
if (rendererBackend == "glarb")
m_Backend = Renderer::Backend::Backend::GL_ARB;
else if (rendererBackend == "dummy")
m_Backend = Renderer::Backend::Backend::DUMMY;
else if (rendererBackend == "vulkan")
m_Backend = Renderer::Backend::Backend::VULKAN;
else
m_Backend = Renderer::Backend::Backend::GL;
#if OS_WIN
if (m_ConfigEnableHiDPI)
wutil_EnableHiDPIOnWindows();
#endif
}
bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
{
Uint32 flags = 0;
if (fullscreen)
{
bool borderlessFullscreen = true;
CFG_GET_VAL("borderless.fullscreen", borderlessFullscreen);
flags |= borderlessFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
}
else
{
bool borderlessWindow = false;
CFG_GET_VAL("borderless.window", borderlessWindow);
if (borderlessWindow)
flags |= SDL_WINDOW_BORDERLESS;
}
if (!m_Window)
{
const bool isGLBackend =
m_Backend == Renderer::Backend::Backend::GL ||
m_Backend == Renderer::Backend::Backend::GL_ARB;
if (isGLBackend)
{
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
bool debugContext = false;
CFG_GET_VAL("renderer.backend.debugcontext", debugContext);
if (debugContext)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
bool forceGLVersion = false;
CFG_GET_VAL("forceglversion", forceGLVersion);
if (forceGLVersion)
{
CStr forceGLProfile = "compatibility";
int forceGLMajorVersion = 3;
int forceGLMinorVersion = 0;
CFG_GET_VAL("forceglprofile", forceGLProfile);
CFG_GET_VAL("forceglmajorversion", forceGLMajorVersion);
CFG_GET_VAL("forceglminorversion", forceGLMinorVersion);
int profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
if (forceGLProfile == "es")
profile = SDL_GL_CONTEXT_PROFILE_ES;
else if (forceGLProfile == "core")
profile = SDL_GL_CONTEXT_PROFILE_CORE;
else if (forceGLProfile != "compatibility")
LOGWARNING("Unknown force GL profile '%s', compatibility profile is used", forceGLProfile.c_str());
if (forceGLMajorVersion < 1 || forceGLMinorVersion < 0)
{
LOGERROR("Unsupported force GL version: %d.%d", forceGLMajorVersion, forceGLMinorVersion);
}
else
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, forceGLMajorVersion);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, forceGLMinorVersion);
}
}
else
{
#if CONFIG2_GLES
// Require GLES 2.0
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
// Some macOS and MESA drivers might not create a context even if they can
// with the core profile. So disable it for a while until we can guarantee
// its creation.
#if OS_WIN
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
#endif
}
}
// Note: these flags only take affect in SDL_CreateWindow
flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
if (m_ConfigEnableHiDPI)
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
if (isGLBackend)
flags |= SDL_WINDOW_OPENGL;
else if (m_Backend == Renderer::Backend::Backend::VULKAN)
flags |= SDL_WINDOW_VULKAN;
m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
#if OS_MACOSX && SDL_VERSION_ATLEAST(2, 0, 6)
if (m_Backend == Renderer::Backend::Backend::VULKAN)
{
// MoltenVK - enable full component swizzling support.
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
CStr fullPathToVulkanLibrary = DllLoader::GenerateFilename("MoltenVK", "", ".dylib");
// MoltenVK - only print warnings and errors.
setenv("MVK_CONFIG_LOG_LEVEL", "2", 1);
if (SDL_Vulkan_LoadLibrary(fullPathToVulkanLibrary.c_str()) != 0)
{
LOGWARNING("Failed to load %s.", fullPathToVulkanLibrary.c_str());
DowngradeBackendSettingAfterCreationFailure();
return SetVideoMode(w, h, bpp, fullscreen);
}
else
LOGMESSAGE("Loaded %s.", fullPathToVulkanLibrary.c_str());
}
#endif
m_Window = SDL_CreateWindow(main_window_name, m_WindowedX, m_WindowedY, w, h, flags);
if (!m_Window)
{
// SDL might fail to create a window in case of missing a Vulkan driver.
if (m_Backend == Renderer::Backend::Backend::VULKAN)
{
LOGWARNING("Failed to create a Vulkan window: %s", SDL_GetError());
DowngradeBackendSettingAfterCreationFailure();
return SetVideoMode(w, h, bpp, fullscreen);
}
// If fullscreen fails, try windowed mode
if (fullscreen)
{
LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
"%dx%d:%d (\"%hs\"), falling back to windowed mode",
w, h, bpp, SDL_GetError());
// Using default size for the window for now, as the attempted setting
// could be as large, or larger than the screen size.
return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
}
else
{
if (isGLBackend)
{
int depthSize = 24;
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize);
if (depthSize > 16)
{
// Fall back to a smaller depth buffer
// (The rendering may be ugly but this helps when running in VMware)
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
return SetVideoMode(w, h, bpp, fullscreen);
}
}
LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
}
if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
{
LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
#if OS_WIN
// We need to set the window for an error dialog.
wutil_SetAppWindow(m_Window);
#endif
if (!TryCreateBackendDevice(m_Window))
{
DowngradeBackendSettingAfterCreationFailure();
SDL_DestroyWindow(m_Window);
m_Window = nullptr;
return SetVideoMode(w, h, bpp, fullscreen);
}
if (isGLBackend)
SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0);
}
else
{
if (m_IsFullscreen != fullscreen)
{
if (!fullscreen)
{
// For some reason, when switching from fullscreen to windowed mode,
// we have to set the window size and position before and after switching
SDL_SetWindowSize(m_Window, w, h);
SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
}
if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
{
LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
return false;
}
}
if (!fullscreen)
{
SDL_SetWindowSize(m_Window, w, h);
SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
}
}
// Grab the current video settings
SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
m_CurrentBPP = bpp;
// #545: we need to constrain the window in fullscreen mode to avoid mouse
// "falling out" of the window in case of multiple displays.
bool mouseGrabInFullscreen = true;
CFG_GET_VAL("window.mousegrabinfullscreen", mouseGrabInFullscreen);
bool mouseGrabInWindowMode = false;
CFG_GET_VAL("window.mousegrabinwindowmode", mouseGrabInWindowMode);
if (fullscreen ? mouseGrabInFullscreen : mouseGrabInWindowMode)
SDL_SetWindowGrab(m_Window, SDL_TRUE);
else
SDL_SetWindowGrab(m_Window, SDL_FALSE);
m_IsFullscreen = fullscreen;
g_xres = m_CurrentW;
g_yres = m_CurrentH;
return true;
}
bool CVideoMode::InitSDL()
{
ENSURE(!m_IsInitialised);
ReadConfig();
// preferred video mode = current desktop settings
// (command line params may override these)
// TODO: handle multi-screen and HiDPI properly.
SDL_DisplayMode mode;
if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
{
m_PreferredW = mode.w;
m_PreferredH = mode.h;
m_PreferredBPP = SDL_BITSPERPIXEL(mode.format);
m_PreferredFreq = mode.refresh_rate;
}
int w = m_ConfigW;
int h = m_ConfigH;
if (m_ConfigFullscreen)
{
// If fullscreen and no explicit size set, default to the desktop resolution
if (w == 0 || h == 0)
{
w = m_PreferredW;
h = m_PreferredH;
}
}
// If no size determined, default to something sensible
if (w == 0 || h == 0)
{
w = DEFAULT_WINDOW_W;
h = DEFAULT_WINDOW_H;
}
if (!m_ConfigFullscreen)
{
// Limit the window to the screen size (if known)
if (m_PreferredW)
w = std::min(w, m_PreferredW);
if (m_PreferredH)
h = std::min(h, m_PreferredH);
}
const int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
return false;
// Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
// The driver appears to register its own atexit hook on context creation.
// If this atexit hook is called before SDL_Quit destroys the OpenGL context,
// some kind of double-free problem causes a crash and lockup in the driver.
// Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
// by destroying the context *before* the driver's atexit hook is called.
// (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
atexit(SDL_Quit);
// End work around.
m_IsInitialised = true;
if (!m_ConfigFullscreen)
{
m_WindowedW = w;
m_WindowedH = h;
}
SetWindowIcon();
m_Cursor = std::make_unique<CCursor>();
return true;
}
bool CVideoMode::InitNonSDL()
{
ENSURE(!m_IsInitialised);
ReadConfig();
m_IsInitialised = true;
return true;
}
void CVideoMode::Shutdown()
{
ENSURE(m_IsInitialised);
m_Cursor.reset();
m_IsFullscreen = false;
m_IsInitialised = false;
m_BackendDevice.reset();
if (m_Window)
{
SDL_DestroyWindow(m_Window);
m_Window = nullptr;
}
}
bool CVideoMode::CreateBackendDevice(const bool createSDLContext)
{
if (!createSDLContext && m_Backend == Renderer::Backend::Backend::VULKAN)
m_Backend = Renderer::Backend::Backend::GL;
SDL_Window* window = createSDLContext ? m_Window : nullptr;
while (m_Backend != Renderer::Backend::Backend::DUMMY)
{
if (TryCreateBackendDevice(window))
return true;
DowngradeBackendSettingAfterCreationFailure();
}
return TryCreateBackendDevice(window);
}
bool CVideoMode::TryCreateBackendDevice(SDL_Window* window)
{
switch (m_Backend)
{
case Renderer::Backend::Backend::GL:
m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, false);
break;
case Renderer::Backend::Backend::GL_ARB:
m_BackendDevice = Renderer::Backend::GL::CreateDevice(window, true);
break;
case Renderer::Backend::Backend::DUMMY:
m_BackendDevice = Renderer::Backend::Dummy::CreateDevice(window);
ENSURE(m_BackendDevice);
break;
case Renderer::Backend::Backend::VULKAN:
m_BackendDevice = Renderer::Backend::Vulkan::CreateDevice(window);
break;
}
return static_cast<bool>(m_BackendDevice);
}
void CVideoMode::DowngradeBackendSettingAfterCreationFailure()
{
const Renderer::Backend::Backend fallback = GetFallbackBackend(m_Backend);
LOGERROR("Unable to create device for %s backend, switching to %s.",
GetBackendName(m_Backend), GetBackendName(fallback));
m_Backend = fallback;
}
bool CVideoMode::ResizeWindow(int w, int h)
{
ENSURE(m_IsInitialised);
// Ignore if not windowed
if (m_IsFullscreen)
return true;
// Ignore if the size hasn't changed
if (w == m_WindowedW && h == m_WindowedH)
return true;
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, false))
return false;
m_WindowedW = w;
m_WindowedH = h;
UpdateRenderer(w, h);
return true;
}
void CVideoMode::Rescale(float scale)
{
ENSURE(m_IsInitialised);
m_Scale = scale;
UpdateRenderer(m_CurrentW, m_CurrentH);
}
float CVideoMode::GetScale() const
{
return m_Scale;
}
bool CVideoMode::SetFullscreen(bool fullscreen)
{
// This might get called before initialisation by psDisplayError;
// if so then silently fail
if (!m_IsInitialised)
return false;
// Check whether this is actually a change
if (fullscreen == m_IsFullscreen)
return true;
if (!m_IsFullscreen)
{
// Windowed -> fullscreen:
int w = 0, h = 0;
// If a fullscreen size was configured, use that; else use the desktop size; else use a default
if (m_ConfigFullscreen)
{
w = m_ConfigW;
h = m_ConfigH;
}
if (w == 0 || h == 0)
{
w = m_PreferredW;
h = m_PreferredH;
}
if (w == 0 || h == 0)
{
w = DEFAULT_FULLSCREEN_W;
h = DEFAULT_FULLSCREEN_H;
}
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, fullscreen))
return false;
UpdateRenderer(m_CurrentW, m_CurrentH);
return true;
}
else
{
// Fullscreen -> windowed:
// Go back to whatever the previous window size was
int w = m_WindowedW, h = m_WindowedH;
int bpp = GetBestBPP();
if (!SetVideoMode(w, h, bpp, fullscreen))
return false;
UpdateRenderer(w, h);
return true;
}
}
bool CVideoMode::ToggleFullscreen()
{
return SetFullscreen(!m_IsFullscreen);
}
bool CVideoMode::IsInFullscreen() const
{
return m_IsFullscreen;
}
void CVideoMode::UpdatePosition(int x, int y)
{
if (!m_IsFullscreen)
{
m_WindowedX = x;
m_WindowedY = y;
}
}
void CVideoMode::UpdateRenderer(int w, int h)
{
if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
if (h < 2) h = 2;
g_xres = w;
g_yres = h;
SViewPort vp = { 0, 0, w, h };
if (g_VideoMode.GetBackendDevice())
g_VideoMode.GetBackendDevice()->OnWindowResize(w, h);
if (CRenderer::IsInitialised())
g_Renderer.Resize(w, h);
if (g_GUI)
g_GUI->UpdateResolution();
if (g_Console)
g_Console->UpdateScreenSize(w, h);
if (g_Game)
g_Game->GetView()->SetViewport(vp);
}
int CVideoMode::GetBestBPP()
{
if (m_ConfigBPP)
return m_ConfigBPP;
if (m_PreferredBPP)
return m_PreferredBPP;
return 32;
}
int CVideoMode::GetXRes() const
{
ENSURE(m_IsInitialised);
return m_CurrentW;
}
int CVideoMode::GetYRes() const
{
ENSURE(m_IsInitialised);
return m_CurrentH;
}
int CVideoMode::GetBPP() const
{
ENSURE(m_IsInitialised);
return m_CurrentBPP;
}
bool CVideoMode::IsVSyncEnabled() const
{
ENSURE(m_IsInitialised);
return m_ConfigVSync;
}
int CVideoMode::GetDesktopXRes() const
{
ENSURE(m_IsInitialised);
return m_PreferredW;
}
int CVideoMode::GetDesktopYRes() const
{
ENSURE(m_IsInitialised);
return m_PreferredH;
}
int CVideoMode::GetDesktopBPP() const
{
ENSURE(m_IsInitialised);
return m_PreferredBPP;
}
int CVideoMode::GetDesktopFreq() const
{
ENSURE(m_IsInitialised);
return m_PreferredFreq;
}
SDL_Window* CVideoMode::GetWindow()
{
ENSURE(m_IsInitialised);
return m_Window;
}
void CVideoMode::SetWindowIcon()
{
// The window icon should be kept outside of art/textures/, or else it will be converted
// to DDS by the archive builder and will become unusable here. Using DDS makes BGRA
// conversion needlessly complicated.
std::shared_ptr<u8> iconFile;
size_t iconFileSize;
if (g_VFS->LoadFile("art/icons/window.png", iconFile, iconFileSize) != INFO::OK)
{
LOGWARNING("Window icon not found.");
return;
}
Tex iconTexture;
if (iconTexture.decode(iconFile, iconFileSize) != INFO::OK)
return;
// Convert to required BGRA format.
const size_t iconFlags = (iconTexture.m_Flags | TEX_BGR) & ~TEX_DXT;
if (iconTexture.transform_to(iconFlags) != INFO::OK)
return;
void* bgra_img = iconTexture.get_data();
if (!bgra_img)
return;
SDL_Surface *iconSurface = SDL_CreateRGBSurfaceFrom(bgra_img,
iconTexture.m_Width, iconTexture.m_Height, 32, iconTexture.m_Width * 4,
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (!iconSurface)
return;
SDL_SetWindowIcon(m_Window, iconSurface);
SDL_FreeSurface(iconSurface);
}
void CVideoMode::SetCursor(const CStrW& name)
{
if (m_Cursor)
m_Cursor->SetCursor(name);
}
void CVideoMode::ResetCursor()
{
if (m_Cursor)
m_Cursor->ResetCursor();
}