diff --git a/client/CMT.cpp b/client/CMT.cpp index 43e333c43..7ade1be02 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -24,7 +24,7 @@ #include "gui/NotificationHandler.h" #include "ClientCommandManager.h" #include "windows/CMessage.h" -#include "render/IWindowHandler.h" +#include "render/IScreenHandler.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/CConsoleHandler.h" @@ -348,7 +348,7 @@ int main(int argc, char * argv[]) { if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool()) playIntro(); - GH.windowHandler().clearScreen(); + GH.screenHandler().clearScreen(); } @@ -547,7 +547,7 @@ static void handleEvent(SDL_Event & ev) case EUserEvent::FULLSCREEN_TOGGLED: { boost::unique_lock lock(*CPlayerInterface::pim); - GH.windowHandler().onScreenResize(); + GH.screenHandler().onScreenResize(); break; } default: @@ -564,7 +564,7 @@ static void handleEvent(SDL_Event & ev) #ifndef VCMI_IOS { boost::unique_lock lock(*CPlayerInterface::pim); - GH.windowHandler().onScreenResize(); + GH.screenHandler().onScreenResize(); } #endif break; @@ -651,7 +651,7 @@ static void quitApplication() boost::this_thread::sleep(boost::posix_time::milliseconds(750));//??? if(!settings["session"]["headless"].Bool()) - GH.windowHandler().close(); + GH.screenHandler().close(); if(logConfig != nullptr) { diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2c77548d5..c1b216c0e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -76,7 +76,7 @@ set(client_SRCS renderSDL/SDLImage.cpp renderSDL/SDLImageLoader.cpp renderSDL/SDLRWwrapper.cpp - renderSDL/WindowHandler.cpp + renderSDL/ScreenHandler.cpp renderSDL/SDL_Extensions.cpp widgets/Buttons.cpp @@ -206,7 +206,7 @@ set(client_HEADERS render/IFont.h render/IImage.h render/IImageLoader.h - render/IWindowHandler.h + render/IScreenHandler.h renderSDL/CBitmapFont.h renderSDL/CBitmapHanFont.h @@ -216,7 +216,7 @@ set(client_HEADERS renderSDL/SDLImage.h renderSDL/SDLImageLoader.h renderSDL/SDLRWwrapper.h - renderSDL/WindowHandler.h + renderSDL/ScreenHandler.h renderSDL/SDL_Extensions.h renderSDL/SDL_PixelAccess.h diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index 472807b4f..23db3f839 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -18,7 +18,7 @@ #include "../CGameInfo.h" #include "../render/Colors.h" #include "../renderSDL/SDL_Extensions.h" -#include "../renderSDL/WindowHandler.h" +#include "../renderSDL/ScreenHandler.h" #include "../CMT.h" #include "../CPlayerInterface.h" #include "../battle/BattleInterface.h" @@ -98,7 +98,7 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function(); + screenHandlerInstance = std::make_unique(); shortcutsHandlerInstance = std::make_unique(); mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer()); @@ -806,9 +806,9 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata) SDL_PushEvent(&event); } -IWindowHandler & CGuiHandler::windowHandler() +IScreenHandler & CGuiHandler::screenHandler() { - return *windowHandlerInstance; + return *screenHandlerInstance; } void CGuiHandler::onScreenResize() diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index d764ba25c..8b8033404 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -29,7 +29,7 @@ class CIntObject; class IUpdateable; class IShowActivatable; class IShowable; -class IWindowHandler; +class IScreenHandler; // TODO: event handling need refactoring enum class EUserEvent @@ -96,7 +96,7 @@ private: CIntObjectList doubleClickInterested; CIntObjectList textInterested; - std::unique_ptr windowHandlerInstance; + std::unique_ptr screenHandlerInstance; void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed); void processLists(const ui16 activityFlag, std::function *)> cb); @@ -137,7 +137,7 @@ public: /// moves mouse pointer into specified position inside vcmi window void moveCursorToPosition(const Point & position); - IWindowHandler & windowHandler(); + IScreenHandler & screenHandler(); IUpdateable *curInt; diff --git a/client/render/IWindowHandler.h b/client/render/IScreenHandler.h similarity index 80% rename from client/render/IWindowHandler.h rename to client/render/IScreenHandler.h index 039722fbe..78d540ce4 100644 --- a/client/render/IWindowHandler.h +++ b/client/render/IScreenHandler.h @@ -1,5 +1,5 @@ /* - * IWindowHandler.h, part of VCMI engine + * IScreenHandler.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -14,10 +14,10 @@ VCMI_LIB_NAMESPACE_BEGIN class Point; VCMI_LIB_NAMESPACE_END -class IWindowHandler +class IScreenHandler { public: - virtual ~IWindowHandler() = default; + virtual ~IScreenHandler() = default; /// Updates window state after fullscreen state has been changed in settings virtual void onScreenResize() = 0; @@ -31,6 +31,6 @@ public: /// Returns list of resolutions supported by current screen virtual std::vector getSupportedResolutions() const = 0; - /// Returns range of possible values for screen scaling - virtual std::tuple getSupportedScalingRange() const = 0; + /// Returns range of possible values for screen scaling percentage + virtual std::tuple getSupportedScalingRange() const = 0; }; diff --git a/client/renderSDL/WindowHandler.cpp b/client/renderSDL/ScreenHandler.cpp similarity index 80% rename from client/renderSDL/WindowHandler.cpp rename to client/renderSDL/ScreenHandler.cpp index da7e3322f..1ee0ed5d8 100644 --- a/client/renderSDL/WindowHandler.cpp +++ b/client/renderSDL/ScreenHandler.cpp @@ -1,5 +1,5 @@ /* - * WindowHandler.cpp, part of VCMI engine + * ScreenHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "WindowHandler.h" +#include "ScreenHandler.h" #include "../../lib/CConfigHandler.h" #include "../gui/CGuiHandler.h" @@ -23,6 +23,7 @@ #include +// TODO: should be made into a private members of ScreenHandler SDL_Window * mainWindow = nullptr; SDL_Renderer * mainRenderer = nullptr; SDL_Texture * screenTexture = nullptr; @@ -33,7 +34,7 @@ SDL_Surface * screenBuf = screen; //points to screen (if only advmapint is prese static const std::string NAME_AFFIX = "client"; static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name -std::tuple WindowHandler::getSupportedScalingRange() const +std::tuple ScreenHandler::getSupportedScalingRange() const { // H3 resolution, any resolution smaller than that is not correctly supported static const Point minResolution = {800, 600}; @@ -48,22 +49,22 @@ std::tuple WindowHandler::getSupportedScalingRange() const return { minimalScaling, maximalScaling }; } -Point WindowHandler::getPreferredLogicalResolution() const +Point ScreenHandler::getPreferredLogicalResolution() const { Point renderResolution = getPreferredRenderingResolution(); auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); - double userScaling = settings["video"]["resolution"]["scaling"].Float(); - double scaling = std::clamp(userScaling, minimalScaling, maximalScaling); + int userScaling = settings["video"]["resolution"]["scaling"].Integer(); + int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); Point logicalResolution = renderResolution * 100.0 / scaling; return logicalResolution; } -Point WindowHandler::getPreferredRenderingResolution() const +Point ScreenHandler::getPreferredRenderingResolution() const { - if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_WINDOWED) + if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED) { SDL_Rect bounds; SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds); @@ -79,7 +80,7 @@ Point WindowHandler::getPreferredRenderingResolution() const } } -int WindowHandler::getPreferredDisplayIndex() const +int ScreenHandler::getPreferredDisplayIndex() const { #ifdef VCMI_MOBILE // Assuming no multiple screens on Android / ios? @@ -92,11 +93,11 @@ int WindowHandler::getPreferredDisplayIndex() const #endif } -EWindowMode WindowHandler::getPreferredWindowMode() const +EWindowMode ScreenHandler::getPreferredWindowMode() const { #ifdef VCMI_MOBILE // On Android / ios game will always render to screen size - return EWindowMode::FULLSCREEN_WINDOWED; + return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED; #else const JsonNode & video = settings["video"]; bool fullscreen = video["fullscreen"].Bool(); @@ -106,13 +107,13 @@ EWindowMode WindowHandler::getPreferredWindowMode() const return EWindowMode::WINDOWED; if (realFullscreen) - return EWindowMode::FULLSCREEN_TRUE; + return EWindowMode::FULLSCREEN_EXCLUSIVE; else - return EWindowMode::FULLSCREEN_WINDOWED; + return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED; #endif } -WindowHandler::WindowHandler() +ScreenHandler::ScreenHandler() { #ifdef VCMI_WINDOWS // set VCMI as "per-monitor DPI awareness". This completely disables any DPI-scaling by system. @@ -145,36 +146,34 @@ WindowHandler::WindowHandler() #endif // VCMI_ANDROID validateSettings(); - recreateWindow(); + recreateWindowAndScreenBuffers(); } -bool WindowHandler::recreateWindow() +void ScreenHandler::recreateWindowAndScreenBuffers() { - destroyScreen(); + destroyScreenBuffers(); if(mainWindow == nullptr) - initializeRenderer(); + initializeWindow(); else - updateFullscreenState(); + updateWindowState(); - initializeScreen(); + initializeScreenBuffers(); if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool()) { NotificationHandler::init(mainWindow); } - - return true; } -void WindowHandler::updateFullscreenState() +void ScreenHandler::updateWindowState() { #if !defined(VCMI_MOBILE) int displayIndex = getPreferredDisplayIndex(); switch(getPreferredWindowMode()) { - case EWindowMode::FULLSCREEN_TRUE: + case EWindowMode::FULLSCREEN_EXCLUSIVE: { SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN); @@ -190,7 +189,7 @@ void WindowHandler::updateFullscreenState() return; } - case EWindowMode::FULLSCREEN_WINDOWED: + case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED: { SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex)); @@ -208,7 +207,7 @@ void WindowHandler::updateFullscreenState() #endif } -void WindowHandler::initializeRenderer() +void ScreenHandler::initializeWindow() { mainWindow = createWindow(); @@ -227,7 +226,7 @@ void WindowHandler::initializeRenderer() logGlobal->info("Created renderer %s", info.name); } -void WindowHandler::initializeScreen() +void ScreenHandler::initializeScreenBuffers() { #ifdef VCMI_ENDIAN_BIG int bmask = 0xff000000; @@ -277,7 +276,7 @@ void WindowHandler::initializeScreen() clearScreen(); } -SDL_Window * WindowHandler::createWindowImpl(Point dimensions, int flags, bool center) +SDL_Window * ScreenHandler::createWindowImpl(Point dimensions, int flags, bool center) { int displayIndex = getPreferredDisplayIndex(); int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex); @@ -285,17 +284,17 @@ SDL_Window * WindowHandler::createWindowImpl(Point dimensions, int flags, bool c return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags); } -SDL_Window * WindowHandler::createWindow() +SDL_Window * ScreenHandler::createWindow() { #ifndef VCMI_MOBILE Point dimensions = getPreferredRenderingResolution(); switch(getPreferredWindowMode()) { - case EWindowMode::FULLSCREEN_TRUE: + case EWindowMode::FULLSCREEN_EXCLUSIVE: return createWindowImpl(dimensions, SDL_WINDOW_FULLSCREEN, false); - case EWindowMode::FULLSCREEN_WINDOWED: + case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED: return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN_DESKTOP, false); case EWindowMode::WINDOWED: @@ -325,17 +324,13 @@ SDL_Window * WindowHandler::createWindow() #endif } -void WindowHandler::onScreenResize() +void ScreenHandler::onScreenResize() { - if(!recreateWindow()) - { - //will return false and report error if video mode is not supported - return; - } + recreateWindowAndScreenBuffers(); GH.onScreenResize(); } -void WindowHandler::validateSettings() +void ScreenHandler::validateSettings() { #if !defined(VCMI_MOBILE) { @@ -368,14 +363,29 @@ void WindowHandler::validateSettings() } } - if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_TRUE) + if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE) { - // TODO: check that display supports selected resolution + auto legalOptions = getSupportedResolutions(); + Point selectedResolution = getPreferredRenderingResolution(); + + if(!vstd::contains(legalOptions, selectedResolution)) + { + // resolution selected for fullscreen mode is not supported by display + // try to find current display resolution and use it instead as "reasonable default" + SDL_DisplayMode mode; + + if (SDL_GetDesktopDisplayMode(getPreferredDisplayIndex(), &mode) == 0) + { + Settings writer = settings.write["video"]["resolution"]; + writer["width"].Float() = mode.w; + writer["height"].Float() = mode.h; + } + } } #endif } -int WindowHandler::getPreferredRenderingDriver() const +int ScreenHandler::getPreferredRenderingDriver() const { int result = -1; const JsonNode & video = settings["video"]; @@ -403,9 +413,10 @@ int WindowHandler::getPreferredRenderingDriver() const return result; } -void WindowHandler::destroyScreen() +void ScreenHandler::destroyScreenBuffers() { - screenBuf = nullptr; //it`s a link - just nullify + // screenBuf is not a separate surface, but points to either screen or screen2 - just set to null + screenBuf = nullptr; if(nullptr != screen2) { @@ -426,7 +437,7 @@ void WindowHandler::destroyScreen() } } -void WindowHandler::destroyWindow() +void ScreenHandler::destroyWindow() { if(nullptr != mainRenderer) { @@ -441,32 +452,32 @@ void WindowHandler::destroyWindow() } } -void WindowHandler::close() +void ScreenHandler::close() { if(settings["general"]["notifications"].Bool()) NotificationHandler::destroy(); - destroyScreen(); + destroyScreenBuffers(); destroyWindow(); SDL_Quit(); } -void WindowHandler::clearScreen() +void ScreenHandler::clearScreen() { SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255); SDL_RenderClear(mainRenderer); SDL_RenderPresent(mainRenderer); } -std::vector WindowHandler::getSupportedResolutions() const +std::vector ScreenHandler::getSupportedResolutions() const { int displayID = SDL_GetWindowDisplayIndex(mainWindow); return getSupportedResolutions(displayID); } -std::vector WindowHandler::getSupportedResolutions( int displayIndex) const +std::vector ScreenHandler::getSupportedResolutions( int displayIndex) const { - //TODO: check this method on iOS / Android + //NOTE: this method is never called on Android/iOS, only on desktop systems std::vector result; @@ -488,6 +499,7 @@ std::vector WindowHandler::getSupportedResolutions( int displayIndex) con return left.x * left.y < right.x * right.y; }); + // erase potential duplicates, e.g. resolutions with different framerate / bits per pixel result.erase(boost::unique(result).end(), result.end()); return result; diff --git a/client/renderSDL/WindowHandler.h b/client/renderSDL/ScreenHandler.h similarity index 74% rename from client/renderSDL/WindowHandler.h rename to client/renderSDL/ScreenHandler.h index 1940b50dc..efd28af14 100644 --- a/client/renderSDL/WindowHandler.h +++ b/client/renderSDL/ScreenHandler.h @@ -1,5 +1,5 @@ /* - * WindowHandler.h, part of VCMI engine + * ScreenHandler.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -16,7 +16,7 @@ struct SDL_Renderer; struct SDL_Surface; #include "../../lib/Point.h" -#include "../render/IWindowHandler.h" +#include "../render/IScreenHandler.h" enum class EWindowMode { @@ -24,13 +24,13 @@ enum class EWindowMode WINDOWED, // game runs in a 'window' that always covers entire screen and uses unmodified desktop resolution // The only mode that is available on mobile devices - FULLSCREEN_WINDOWED, + FULLSCREEN_BORDERLESS_WINDOWED, // game runs in a fullscreen mode with resolution selected by player - FULLSCREEN_TRUE + FULLSCREEN_EXCLUSIVE }; /// This class is responsible for management of game window and its main rendering surface -class WindowHandler : public IWindowHandler +class ScreenHandler : public IScreenHandler { /// Dimensions of target surfaces/textures, this value is what game logic views as screen size Point getPreferredLogicalResolution() const; @@ -53,19 +53,26 @@ class WindowHandler : public IWindowHandler /// Creates SDL window using OS-specific settings & user-specific config SDL_Window * createWindow(); - void initializeRenderer(); - void initializeScreen(); - void updateFullscreenState(); - - bool recreateWindow(); - void destroyScreen(); + /// Manages window and SDL renderer + void initializeWindow(); void destroyWindow(); + /// Manages surfaces & textures used for + void initializeScreenBuffers(); + void destroyScreenBuffers(); + + /// Updates state (e.g. position) of game window after resolution/fullscreen change + void updateWindowState(); + + /// Initializes or reiniitalizes all screen state + void recreateWindowAndScreenBuffers(); + + /// Performs validation of settings and updates them to valid values if necessary void validateSettings(); public: /// Creates and initializes screen, window and SDL state - WindowHandler(); + ScreenHandler(); /// Updates and potentially recreates target screen to match selected fullscreen status void onScreenResize() final; @@ -78,5 +85,5 @@ public: std::vector getSupportedResolutions() const final; std::vector getSupportedResolutions(int displayIndex) const; - std::tuple getSupportedScalingRange() const final; + std::tuple getSupportedScalingRange() const final; }; diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index 0362842a6..39ce02729 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -22,7 +22,7 @@ #include "CPlayerInterface.h" #include "windows/GUIClasses.h" #include "CServerHandler.h" -#include "render/IWindowHandler.h" +#include "render/IScreenHandler.h" static void setIntSetting(std::string group, std::string field, int value) @@ -183,7 +183,7 @@ GeneralOptionsTab::GeneralOptionsTab() void GeneralOptionsTab::selectGameResolution() { - supportedResolutions = GH.windowHandler().getSupportedResolutions(); + supportedResolutions = GH.screenHandler().getSupportedResolutions(); std::vector items; size_t currentResolutionIndex = 0; @@ -232,10 +232,10 @@ void GeneralOptionsTab::selectGameScaling() { supportedScaling.clear(); - auto [minimalScaling, maximalScaling] = GH.windowHandler().getSupportedScalingRange(); - for (int i = 0; i <= static_cast(maximalScaling); i += 10) + auto [minimalScaling, maximalScaling] = GH.screenHandler().getSupportedScalingRange(); + for (int i = 0; i <= maximalScaling; i += 10) { - if (i >= static_cast(minimalScaling)) + if (i >= minimalScaling) supportedScaling.push_back(i); }