From c688411bab62aa1ebe54fde6c88136f3e6b4248e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 30 Apr 2023 01:03:50 +0300 Subject: [PATCH] Extracted window management from CMT to new class --- client/CMT.cpp | 508 +---------------------------- client/CMakeLists.txt | 2 + client/CServerHandler.cpp | 3 +- client/gui/CGuiHandler.cpp | 17 +- client/gui/CGuiHandler.h | 6 +- client/lobby/SelectionTab.cpp | 1 + client/renderSDL/WindowHandler.cpp | 407 +++++++++++++++++++++++ client/renderSDL/WindowHandler.h | 74 +++++ client/windows/GUIClasses.h | 1 - 9 files changed, 522 insertions(+), 497 deletions(-) create mode 100644 client/renderSDL/WindowHandler.cpp create mode 100644 client/renderSDL/WindowHandler.h diff --git a/client/CMT.cpp b/client/CMT.cpp index 76f540451..64aa0f45f 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -7,49 +7,34 @@ * Full text of license available in license.txt file, in main folder * */ + // CMT.cpp : Defines the entry point for the console application. -// #include "StdInc.h" +#include "CMT.h" #include "CGameInfo.h" #include "mainmenu/CMainMenu.h" -#include "lobby/CSelectionBase.h" -#include "windows/CCastleInterface.h" +#include "mainmenu/CPrologEpilogVideo.h" #include "gui/CursorHandler.h" #include "CPlayerInterface.h" #include "CVideoHandler.h" #include "CMusicHandler.h" -#include "Client.h" #include "gui/CGuiHandler.h" #include "CServerHandler.h" #include "gui/NotificationHandler.h" #include "ClientCommandManager.h" #include "windows/CMessage.h" -#include "renderSDL/SDL_Extensions.h" +#include "renderSDL/WindowHandler.h" #include "../lib/filesystem/Filesystem.h" -#include "../lib/filesystem/FileStream.h" #include "../lib/CConsoleHandler.h" -#include "../lib/CGameState.h" -#include "../lib/CBuildingHandler.h" -#include "../CCallback.h" -#include "../lib/CHeroHandler.h" -#include "../lib/spells/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/serializer/BinaryDeserializer.h" -#include "../lib/serializer/BinarySerializer.h" #include "../lib/VCMIDirs.h" -#include "../lib/NetPacks.h" -#include "../lib/CModHandler.h" -#include "../lib/CTownHandler.h" +#include "../lib/mapping/CCampaignHandler.h" + #include "../lib/logging/CBasicLogConfigurator.h" -#include "../lib/CPlayerState.h" -#include "../lib/serializer/Connection.h" -#include #include - -#include "mainmenu/CPrologEpilogVideo.h" #include #include @@ -60,8 +45,6 @@ #include "../lib/CAndroidVMHelper.h" #endif -#include "CMT.h" - #if __MINGW32__ #undef main #endif @@ -70,11 +53,8 @@ namespace po = boost::program_options; namespace po_style = boost::program_options::command_line_style; namespace bfs = boost::filesystem; -std::string NAME_AFFIX = "client"; -std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name CGuiHandler GH; -int preferredDriverIndex = -1; SDL_Window * mainWindow = nullptr; SDL_Renderer * mainRenderer = nullptr; SDL_Texture * screenTexture = nullptr; @@ -95,19 +75,11 @@ static po::variables_map vm; #ifndef VCMI_IOS void processCommand(const std::string &message); #endif -static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true); void playIntro(); static void mainLoop(); static CBasicLogConfigurator *logConfig; -#ifndef VCMI_WINDOWS -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#endif - void init() { CStopWatch tmh; @@ -139,17 +111,6 @@ static void prog_help(const po::options_description &opts) std::cout << opts; } -static void SDLLogCallback(void* userdata, - int category, - SDL_LogPriority priority, - const char* message) -{ - //todo: convert SDL log priority to vcmi log priority - //todo: make separate log domain for SDL - - logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message); -} - #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE) int wmain(int argc, wchar_t* argv[]) #elif defined(VCMI_MOBILE) @@ -255,7 +216,7 @@ int main(int argc, char * argv[]) const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt"; logConfig = new CBasicLogConfigurator(logPath, console); logConfig->configureDefault(); - logGlobal->info(NAME); + logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION); logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff()); logGlobal->info("The log file will be saved to %s", logPath); @@ -340,68 +301,9 @@ int main(int argc, char * argv[]) srand ( (unsigned int)time(nullptr) ); - - const JsonNode& video = settings["video"]; - const JsonNode& res = video["screenRes"]; - - //something is really wrong... - if (res["width"].Float() < 100 || res["height"].Float() < 100) - { - logGlobal->error("Fatal error: failed to load settings!"); - logGlobal->error("Possible reasons:"); - logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath()); - logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath()); - logGlobal->error("VCMI will now exit..."); - exit(EXIT_FAILURE); - } - if(!settings["session"]["headless"].Bool()) - { - if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE)) - { - logGlobal->error("Something was wrong: %s", SDL_GetError()); - exit(-1); - } - - #ifdef VCMI_ANDROID - // manually setting egl pixel format, as a possible solution for sdl2<->android problem - // https://bugzilla.libsdl.org/show_bug.cgi?id=2291 - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); - #endif // VCMI_ANDROID - - //(!)init here AFTER SDL_Init() while using SDL for FPS management GH.init(); - SDL_LogSetOutputFunction(&SDLLogCallback, nullptr); - - int driversCount = SDL_GetNumRenderDrivers(); - std::string preferredDriverName = video["driver"].String(); - - logGlobal->info("Found %d render drivers", driversCount); - - for(int it = 0; it < driversCount; it++) - { - SDL_RendererInfo info; - SDL_GetRenderDriverInfo(it,&info); - - std::string driverName(info.name); - - if(!preferredDriverName.empty() && driverName == preferredDriverName) - { - preferredDriverIndex = it; - logGlobal->info("\t%s (active)", driverName); - } - else - logGlobal->info("\t%s", driverName); - } - - setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float()); - logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff()); - } - CCS = new CClientState(); CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.) CSH = new CServerHandler(); @@ -487,7 +389,6 @@ int main(int argc, char * argv[]) CCS->curh->show(); } - logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff()); session["autoSkip"].Bool() = vm.count("autoSkip"); @@ -577,361 +478,6 @@ void playIntro() } } -#if !defined(VCMI_MOBILE) -static bool checkVideoMode(int monitorIndex, int w, int h) -{ - //we only check that our desired window size fits on screen - SDL_DisplayMode mode; - - if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode)) - { - logGlobal->error("SDL_GetDesktopDisplayMode failed"); - logGlobal->error(SDL_GetError()); - return false; - } - - logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h); - - if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h)) - { - return true; - } - - return false; -} -#endif - -static void cleanupRenderer() -{ - screenBuf = nullptr; //it`s a link - just nullify - - if(nullptr != screen2) - { - SDL_FreeSurface(screen2); - screen2 = nullptr; - } - - if(nullptr != screen) - { - SDL_FreeSurface(screen); - screen = nullptr; - } - - if(nullptr != screenTexture) - { - SDL_DestroyTexture(screenTexture); - screenTexture = nullptr; - } -} - -static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex) -{ - // VCMI will only work with 2 or 4 bytes per pixel - vstd::amax(bpp, 16); - vstd::amin(bpp, 32); - if(bpp>16) - bpp = 32; - - if(displayIndex < 0) - { - if (mainWindow != nullptr) - displayIndex = SDL_GetWindowDisplayIndex(mainWindow); - if (displayIndex < 0) - displayIndex = 0; - } - -#if defined(VCMI_MOBILE) - SDL_GetWindowSize(mainWindow, &w, &h); -#else - if(!checkVideoMode(displayIndex, w, h)) - { - logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h); - } -#endif - - bool bufOnScreen = (screenBuf == screen); - bool realFullscreen = settings["video"]["realFullscreen"].Bool(); - - /* match best rendering resolution */ - int renderWidth = 0, renderHeight = 0; - auto aspectRatio = (float)w / (float)h; - auto minDiff = 10.f; - - // TODO: CONFIGURABLE ADVMAP - static const std::vector supportedResolutions = { - { 1280, 720 } - }; - - - for (const auto& pair : supportedResolutions) - { - int pWidth = pair.x; - int pHeight = pair.y; - - /* filter out resolution which is larger than window */ - if (pWidth > w || pHeight > h) - { - continue; - } - auto ratio = (float)pWidth / (float)pHeight; - auto diff = fabs(aspectRatio - ratio); - /* select closest aspect ratio */ - if (diff < minDiff) - { - renderWidth = pWidth; - renderHeight = pHeight; - minDiff = diff; - } - /* select largest resolution meets prior conditions. - * since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */ - else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth) - { - renderWidth = pWidth; - renderHeight = pHeight; - } - } - if (renderWidth == 0) - { - // no matching resolution for upscaling - complain & fallback to default resolution. - logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h); - Settings newRes = settings.write["video"]["screenRes"]; - - w = supportedResolutions.front().x; - h = supportedResolutions.front().y; - - newRes["width"].Float() = w; - newRes["height"].Float() = h; - - logGlobal->error("Falling back to %dx%d", w, h); - renderWidth = w; - renderHeight = h; - } - else - { - logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight); - } - - cleanupRenderer(); - - if(nullptr == mainWindow) - { -#if defined(VCMI_MOBILE) - auto createWindow = [displayIndex](uint32_t extraFlags) -> bool { - mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags); - return mainWindow != nullptr; - }; - -# ifdef VCMI_IOS - SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1"); - SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1"); - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); - - uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI; - if(!createWindow(windowFlags | SDL_WINDOW_METAL)) - { - logGlobal->warn("Metal unavailable, using OpenGLES"); - createWindow(windowFlags); - } -# else - createWindow(0); -# endif // VCMI_IOS - - // SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate - // That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate - - SDL_Rect screenRect; - - if(SDL_GetDisplayBounds(0, &screenRect) == 0) - { - const auto screenWidth = screenRect.w; - const auto screenHeight = screenRect.h; - - const auto aspect = static_cast(screenWidth) / screenHeight; - - logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect); - - if((double)w / aspect > (double)h) - { - h = (int)round((double)w / aspect); - } - else - { - w = (int)round((double)h * aspect); - } - - logGlobal->info("Changing logical screen size to %dx%d", w, h); - } - else - { - logGlobal->error("Can't fix aspect ratio for screen"); - } -#else - if(fullscreen) - { - if(realFullscreen) - mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN); - else //in windowed full-screen mode use desktop resolution - mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP); - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); - } - else - { - mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0); - } -#endif // defined(VCMI_MOBILE) - - if(nullptr == mainWindow) - { - throw std::runtime_error("Unable to create window\n"); - } - - //create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible - mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0); - - if(nullptr == mainRenderer) - { - throw std::runtime_error("Unable to create renderer\n"); - } - - - SDL_RendererInfo info; - SDL_GetRendererInfo(mainRenderer, &info); - logGlobal->info("Created renderer %s", info.name); - - } - else - { -#if !defined(VCMI_MOBILE) - - if(fullscreen) - { - if(realFullscreen) - { - SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN); - - SDL_DisplayMode mode; - SDL_GetDesktopDisplayMode(displayIndex, &mode); - mode.w = renderWidth; - mode.h = renderHeight; - - SDL_SetWindowDisplayMode(mainWindow, &mode); - } - else - { - SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); - } - - SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex)); - - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); - } - else - { - SDL_SetWindowFullscreen(mainWindow, 0); - SDL_SetWindowSize(mainWindow, w, h); - SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex)); - } -#endif - } - - if(!(fullscreen && realFullscreen)) - { - SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight); - -//following line is bugged not only on android, do not re-enable without checking -//#ifndef VCMI_ANDROID -// // on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events -// SDL_RenderSetViewport(mainRenderer, nullptr); -//#endif - - } - - - #ifdef VCMI_ENDIAN_BIG - int bmask = 0xff000000; - int gmask = 0x00ff0000; - int rmask = 0x0000ff00; - int amask = 0x000000ff; - #else - int bmask = 0x000000ff; - int gmask = 0x0000ff00; - int rmask = 0x00ff0000; - int amask = 0xFF000000; - #endif - - screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask); - if(nullptr == screen) - { - logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError()); - throw std::runtime_error("Unable to create surface"); - } - //No blending for screen itself. Required for proper cursor rendering. - SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE); - - screenTexture = SDL_CreateTexture(mainRenderer, - SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, - renderWidth, renderHeight); - - if(nullptr == screenTexture) - { - logGlobal->error("Unable to create screen texture"); - logGlobal->error(SDL_GetError()); - throw std::runtime_error("Unable to create screen texture"); - } - - screen2 = CSDL_Ext::copySurface(screen); - - - if(nullptr == screen2) - { - throw std::runtime_error("Unable to copy surface\n"); - } - - screenBuf = bufOnScreen ? screen : screen2; - - SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0); - SDL_RenderClear(mainRenderer); - SDL_RenderPresent(mainRenderer); - - if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool()) - { - NotificationHandler::init(mainWindow); - } - - return true; -} - -//used only once during initialization -static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo) -{ - if(!recreateWindow(w, h, bpp, fullscreen, displayIndex)) - { - throw std::runtime_error("Requested screen resolution is not available\n"); - } -} - -static void fullScreenChanged() -{ - boost::unique_lock lock(*CPlayerInterface::pim); - - Settings full = settings.write["video"]["fullscreen"]; - const bool toFullscreen = full->Bool(); - - auto bitsPerPixel = screen->format->BitsPerPixel; - - auto w = screen->w; - auto h = screen->h; - - if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1)) - { - //will return false and report error if video mode is not supported - return; - } - - GH.totalRedraw(); -} - static void handleEvent(SDL_Event & ev) { if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT))) @@ -1009,8 +555,11 @@ static void handleEvent(SDL_Event & ev) CMM->menu->switchToTab("load"); break; case EUserEvent::FULLSCREEN_TOGGLED: - fullScreenChanged(); - break; + { + boost::unique_lock lock(*CPlayerInterface::pim); + GH.windowHandler().onFullscreenChanged(); + break; + } default: logGlobal->error("Unknown user event. Code %d", ev.user.code); break; @@ -1023,7 +572,10 @@ static void handleEvent(SDL_Event & ev) switch (ev.window.event) { case SDL_WINDOWEVENT_RESTORED: #ifndef VCMI_IOS - fullScreenChanged(); + { + boost::unique_lock lock(*CPlayerInterface::pim); + GH.windowHandler().onFullscreenChanged(); + } #endif break; } @@ -1047,10 +599,8 @@ static void handleEvent(SDL_Event & ev) boost::unique_lock lock(eventsM); SDLEventsQueue.push(ev); } - } - static void mainLoop() { SettingsListener resChanged = settings.listen["video"]["fullscreen"]; @@ -1071,7 +621,6 @@ static void mainLoop() CSH->applyPacksOnLobbyScreen(); GH.renderFrame(); - } } @@ -1108,29 +657,9 @@ static void quitApplication() vstd::clear_pointer(console);// should be removed after everything else since used by logging boost::this_thread::sleep(boost::posix_time::milliseconds(750));//??? + if(!settings["session"]["headless"].Bool()) - { - if(settings["general"]["notifications"].Bool()) - { - NotificationHandler::destroy(); - } - - cleanupRenderer(); - - if(nullptr != mainRenderer) - { - SDL_DestroyRenderer(mainRenderer); - mainRenderer = nullptr; - } - - if(nullptr != mainWindow) - { - SDL_DestroyWindow(mainWindow); - mainWindow = nullptr; - } - - SDL_Quit(); - } + GH.windowHandler().close(); if(logConfig != nullptr) { @@ -1145,7 +674,6 @@ static void quitApplication() void handleQuit(bool ask) { - if(CSH->client && LOCPLINT && ask) { CCS->curh->set(Cursor::Map::POINTER); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d2645ef6a..8fb282c74 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -75,6 +75,7 @@ set(client_SRCS renderSDL/SDLImage.cpp renderSDL/SDLImageLoader.cpp renderSDL/SDLRWwrapper.cpp + renderSDL/WindowHandler.cpp renderSDL/SDL_Extensions.cpp widgets/Buttons.cpp @@ -211,6 +212,7 @@ set(client_HEADERS renderSDL/SDLImage.h renderSDL/SDLImageLoader.h renderSDL/SDLRWwrapper.h + renderSDL/WindowHandler.h renderSDL/SDL_Extensions.h renderSDL/SDL_PixelAccess.h diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 6befa3694..530c40551 100644 --- a/client/CServerHandler.cpp +++ b/client/CServerHandler.cpp @@ -123,7 +123,8 @@ public: } }; -extern std::string NAME; +static const std::string NAME_AFFIX = "client"; +static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name CServerHandler::CServerHandler() : state(EClientState::NONE), mx(std::make_shared()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index f242f7ee8..33378b550 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -18,6 +18,7 @@ #include "../CGameInfo.h" #include "../render/Colors.h" #include "../renderSDL/SDL_Extensions.h" +#include "../renderSDL/WindowHandler.h" #include "../CMT.h" #include "../CPlayerInterface.h" #include "../battle/BattleInterface.h" @@ -95,9 +96,10 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function(); shortcutsHandlerInstance = std::make_unique(); - mainFPSmng = new CFramerateManager(); - mainFPSmng->init(settings["video"]["targetfps"].Integer()); + mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer()); + isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool(); pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float(); } @@ -803,7 +805,12 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata) SDL_PushEvent(&event); } -CFramerateManager::CFramerateManager() +WindowHandler & CGuiHandler::windowHandler() +{ + return *windowHandlerInstance; +} + +CFramerateManager::CFramerateManager(int newRate) : rate(0) , rateticks(0) , fps(0) @@ -811,7 +818,9 @@ CFramerateManager::CFramerateManager() , accumulatedTime(0) , lastticks(0) , timeElapsed(0) -{} +{ + init(newRate); +} void CFramerateManager::init(int newRate) { diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index b0aea577b..84a159c3b 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -29,6 +29,7 @@ class CIntObject; class IUpdateable; class IShowActivatable; class IShowable; +class WindowHandler; // TODO: event handling need refactoring enum class EUserEvent @@ -56,7 +57,7 @@ private: ui32 accumulatedFrames; public: - CFramerateManager(); // initializes the manager with a given fps rate + CFramerateManager(int newRate); // initializes the manager with a given fps rate void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer void framerateDelay(); // needs to be called every game update cycle ui32 getElapsedMilliseconds() const {return this->timeElapsed;} @@ -95,6 +96,7 @@ private: CIntObjectList doubleClickInterested; CIntObjectList textInterested; + std::unique_ptr windowHandlerInstance; void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed); void processLists(const ui16 activityFlag, std::function *)> cb); @@ -135,6 +137,8 @@ public: /// moves mouse pointer into specified position inside vcmi window void moveCursorToPosition(const Point & position); + WindowHandler & windowHandler(); + IUpdateable *curInt; Point lastClick; diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index 75e42d1b6..82619da49 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -31,6 +31,7 @@ #include "../../lib/NetPacksLobby.h" #include "../../lib/CGeneralTextHandler.h" +#include "../../lib/CConfigHandler.h" #include "../../lib/CModHandler.h" #include "../../lib/GameSettings.h" #include "../../lib/filesystem/Filesystem.h" diff --git a/client/renderSDL/WindowHandler.cpp b/client/renderSDL/WindowHandler.cpp new file mode 100644 index 000000000..e53f94a93 --- /dev/null +++ b/client/renderSDL/WindowHandler.cpp @@ -0,0 +1,407 @@ +/* + * WindowHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "WindowHandler.h" + +#include "../../lib/CConfigHandler.h" +#include "../gui/CGuiHandler.h" +#include "../gui/NotificationHandler.h" +#include "CMT.h" +#include "SDL_Extensions.h" + +#include + +static const std::string NAME_AFFIX = "client"; +static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name + +Point WindowHandler::getPreferredLogicalResolution() const +{ + // TODO: CONFIGURABLE ADVMAP - IMPLEMENT UI SCALE SETTING + return {1280, 720}; +} + +Point WindowHandler::getPreferredRenderingResolution() const +{ + if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_WINDOWED) + { + SDL_Rect bounds; + SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds); + return Point(bounds.w, bounds.h); + } + else + { + const JsonNode & video = settings["video"]; + int width = video["screenRes"]["width"].Integer(); + int height = video["screenRes"]["height"].Integer(); + + return Point(width, height); + } +} + +int WindowHandler::getPreferredDisplayIndex() const +{ +#ifdef VCMI_MOBILE + // Assuming no multiple screens on Android / ios? + return 0; +#else + if (mainWindow != nullptr) + return SDL_GetWindowDisplayIndex(mainWindow); + else + return settings["video"]["displayIndex"].Integer(); +#endif +} + +EWindowMode WindowHandler::getPreferredWindowMode() const +{ +#ifdef VCMI_MOBILE + // On Android / ios game will always render to screen size + return EWindowMode::FULLSCREEN_WINDOWED; +#else + const JsonNode & video = settings["video"]; + bool fullscreen = video["fullscreen"].Bool(); + bool realFullscreen = settings["video"]["realFullscreen"].Bool(); + + if (!fullscreen) + return EWindowMode::WINDOWED; + + if (realFullscreen) + return EWindowMode::FULLSCREEN_TRUE; + else + return EWindowMode::FULLSCREEN_WINDOWED; +#endif +} + +WindowHandler::WindowHandler() +{ + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE)) + { + logGlobal->error("Something was wrong: %s", SDL_GetError()); + exit(-1); + } + + const auto & logCallback = [](void * userdata, int category, SDL_LogPriority priority, const char * message) + { + logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message); + }; + + SDL_LogSetOutputFunction(logCallback, nullptr); + +#ifdef VCMI_ANDROID + // manually setting egl pixel format, as a possible solution for sdl2<->android problem + // https://bugzilla.libsdl.org/show_bug.cgi?id=2291 + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); +#endif // VCMI_ANDROID + + validateSettings(); + recreateWindow(); +} + +bool WindowHandler::recreateWindow() +{ + destroyScreen(); + + if(mainWindow == nullptr) + initializeRenderer(); + else + updateFullscreenState(); + + initializeScreen(); + + if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool()) + { + NotificationHandler::init(mainWindow); + } + + return true; +} + +void WindowHandler::updateFullscreenState() +{ +#if !defined(VCMI_MOBILE) + int displayIndex = getPreferredDisplayIndex(); + + switch(getPreferredWindowMode()) + { + case EWindowMode::FULLSCREEN_TRUE: + { + SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN); + + SDL_DisplayMode mode; + SDL_GetDesktopDisplayMode(displayIndex, &mode); + Point resolution = getPreferredRenderingResolution(); + + mode.w = resolution.x; + mode.h = resolution.y; + + SDL_SetWindowDisplayMode(mainWindow, &mode); + SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex)); + + return; + } + case EWindowMode::FULLSCREEN_WINDOWED: + { + SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex)); + return; + } + case EWindowMode::WINDOWED: + { + Point resolution = getPreferredLogicalResolution(); + SDL_SetWindowFullscreen(mainWindow, 0); + SDL_SetWindowSize(mainWindow, resolution.x, resolution.y); + SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex)); + return; + } + } +#endif +} + +void WindowHandler::initializeRenderer() +{ + mainWindow = createWindow(); + + if(mainWindow == nullptr) + throw std::runtime_error("Unable to create window\n"); + + //create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible + mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0); + + if(mainRenderer == nullptr) + throw std::runtime_error("Unable to create renderer\n"); + + SDL_RendererInfo info; + SDL_GetRendererInfo(mainRenderer, &info); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); + logGlobal->info("Created renderer %s", info.name); +} + +void WindowHandler::initializeScreen() +{ +#ifdef VCMI_ENDIAN_BIG + int bmask = 0xff000000; + int gmask = 0x00ff0000; + int rmask = 0x0000ff00; + int amask = 0x000000ff; +#else + int bmask = 0x000000ff; + int gmask = 0x0000ff00; + int rmask = 0x00ff0000; + int amask = 0xFF000000; +#endif + + auto logicalSize = getPreferredLogicalResolution(); + SDL_RenderSetLogicalSize(mainRenderer, logicalSize.x, logicalSize.y); + + screen = SDL_CreateRGBSurface(0, logicalSize.x, logicalSize.y, 32, rmask, gmask, bmask, amask); + if(nullptr == screen) + { + logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", logicalSize.x, logicalSize.y, 32, SDL_GetError()); + throw std::runtime_error("Unable to create surface"); + } + //No blending for screen itself. Required for proper cursor rendering. + SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE); + + screenTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, logicalSize.x, logicalSize.y); + + if(nullptr == screenTexture) + { + logGlobal->error("Unable to create screen texture"); + logGlobal->error(SDL_GetError()); + throw std::runtime_error("Unable to create screen texture"); + } + + screen2 = CSDL_Ext::copySurface(screen); + + if(nullptr == screen2) + { + throw std::runtime_error("Unable to copy surface\n"); + } + + screenBuf = screen; + + SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0); + SDL_RenderClear(mainRenderer); + SDL_RenderPresent(mainRenderer); +} + +SDL_Window * WindowHandler::createWindowImpl(Point dimensions, int displayIndex, int flags, bool center) +{ + int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex); + + return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags); +} + +SDL_Window * WindowHandler::createWindow() +{ +#ifndef VCMI_MOBILE + const JsonNode & video = settings["video"]; + int displayIndex = video["displayIndex"].Float(); + bool fullscreen = video["fullscreen"].Bool(); + bool realFullscreen = video["realFullscreen"].Bool(); + Point dimensions = getPreferredRenderingResolution(); + + if(!fullscreen) + return createWindowImpl(dimensions, displayIndex, 0, true); + + if(realFullscreen) + return createWindowImpl(dimensions, displayIndex, SDL_WINDOW_FULLSCREEN, false); + else + return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN_DESKTOP, false); +#endif + +#ifdef VCMI_IOS + SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1"); + SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1"); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); + + uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI; + SDL_Window * result = createWindowImpl(Point(), displayIndex, windowFlags | SDL_WINDOW_METAL, false); + + if(result != nullptr) + return result; + + logGlobal->warn("Metal unavailable, using OpenGLES"); + return createWindowImpl(Point(), displayIndex, windowFlags, false); +#endif + +#ifdef VCMI_ANDROID + return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN, false); +#endif +} + +void WindowHandler::onFullscreenChanged() +{ + if(!recreateWindow()) + { + //will return false and report error if video mode is not supported + return; + } + GH.totalRedraw(); +} + +void WindowHandler::validateSettings() +{ +#if !defined(VCMI_MOBILE) + { + int displayIndex = settings["video"]["displayIndex"].Integer(); + int displaysCount = SDL_GetNumVideoDisplays(); + + if (displayIndex >= displaysCount) + { + Settings writer = settings.write["video"]["displayIndex"]; + writer->Float() = 0; + } + } + + if (getPreferredWindowMode() == EWindowMode::WINDOWED) + { + //we only check that our desired window size fits on screen + int displayIndex = getPreferredDisplayIndex(); + Point resolution = getPreferredRenderingResolution(); + + SDL_DisplayMode mode; + + if (SDL_GetDesktopDisplayMode(displayIndex, &mode) == 0) + { + if(resolution.x > mode.w || resolution.y > mode.h) + { + Settings writer = settings.write["video"]["screenRes"]; + writer["width"].Float() = mode.w; + writer["height"].Float() = mode.h; + } + } + } + + if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_TRUE) + { + // TODO: check that display supports selected resolution + } +#endif +} + +int WindowHandler::getPreferredRenderingDriver() const +{ + int result = -1; + const JsonNode & video = settings["video"]; + + int driversCount = SDL_GetNumRenderDrivers(); + std::string preferredDriverName = video["driver"].String(); + + logGlobal->info("Found %d render drivers", driversCount); + + for(int it = 0; it < driversCount; it++) + { + SDL_RendererInfo info; + SDL_GetRenderDriverInfo(it, &info); + + std::string driverName(info.name); + + if(!preferredDriverName.empty() && driverName == preferredDriverName) + { + result = it; + logGlobal->info("\t%s (active)", driverName); + } + else + logGlobal->info("\t%s", driverName); + } + return result; +} + +void WindowHandler::destroyScreen() +{ + screenBuf = nullptr; //it`s a link - just nullify + + if(nullptr != screen2) + { + SDL_FreeSurface(screen2); + screen2 = nullptr; + } + + if(nullptr != screen) + { + SDL_FreeSurface(screen); + screen = nullptr; + } + + if(nullptr != screenTexture) + { + SDL_DestroyTexture(screenTexture); + screenTexture = nullptr; + } +} + +void WindowHandler::destroyWindow() +{ + if(nullptr != mainRenderer) + { + SDL_DestroyRenderer(mainRenderer); + mainRenderer = nullptr; + } + + if(nullptr != mainWindow) + { + SDL_DestroyWindow(mainWindow); + mainWindow = nullptr; + } +} + +void WindowHandler::close() +{ + if(settings["general"]["notifications"].Bool()) + NotificationHandler::destroy(); + + destroyScreen(); + destroyWindow(); + SDL_Quit(); +} diff --git a/client/renderSDL/WindowHandler.h b/client/renderSDL/WindowHandler.h new file mode 100644 index 000000000..0d99311af --- /dev/null +++ b/client/renderSDL/WindowHandler.h @@ -0,0 +1,74 @@ +/* + * WindowHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +struct SDL_Texture; +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Surface; + +#include "../../lib/Point.h" + +enum class EWindowMode +{ + // game runs in a window that covers part of the screen + 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, + // game runs in a fullscreen mode with resolution selected by player + FULLSCREEN_TRUE +}; + +/// This class is responsible for management of game window and its main rendering surface +class WindowHandler +{ + /// Dimensions of target surfaces/textures, this value is what game logic views as screen size + Point getPreferredLogicalResolution() const; + + /// Dimensions of output window, if different from logicalResolution SDL will perform scaling + /// This value is what player views as window size + Point getPreferredRenderingResolution() const; + + EWindowMode getPreferredWindowMode() const; + + /// Returns index of display on which window should be created + int getPreferredDisplayIndex() const; + + /// Returns index of rendering driver preferred by player or -1 if no preference + int getPreferredRenderingDriver() const; + + /// Creates SDL window with specified parameters + SDL_Window * createWindowImpl(Point dimensions, int displayIndex, int flags, bool center); + + /// Creates SDL window using OS-specific settings & user-specific config + SDL_Window * createWindow(); + + void initializeRenderer(); + void initializeScreen(); + void updateFullscreenState(); + + bool recreateWindow(); + void destroyScreen(); + void destroyWindow(); + + void validateSettings(); +public: + + /// Creates and initializes screen, window and SDL state + WindowHandler(); + + /// Updates and potentially recreates target screen to match selected fullscreen status + void onFullscreenChanged(); + + /// De-initializes and destroys screen, window and SDL state + void close(); +}; diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 8688ace10..35e4c489f 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -12,7 +12,6 @@ #include "CWindowObject.h" #include "../lib/GameConstants.h" #include "../lib/ResourceSet.h" -#include "../lib/CConfigHandler.h" #include "../lib/int3.h" #include "../widgets/CWindowWithArtifacts.h" #include "../widgets/CGarrisonInt.h"