From 0e599a317677ccf617bee781bb0d81bf9176c504 Mon Sep 17 00:00:00 2001 From: vladislavbelov Date: Tue, 21 Sep 2021 22:44:46 +0000 Subject: [PATCH] Moves cursor to VideoMode to draw it via SDL. It removes the software implementation intentionally. Because it duplicates SDL functionality. But it might be added in future on demand. Tested By: bb, Langbart Differential Revision: https://code.wildfiregames.com/D4278 This was SVN commit r25936. --- binaries/data/config/default.cfg | 5 +- source/gui/GUIManager.cpp | 3 +- .../gui/Scripting/JSInterface_GUIManager.cpp | 9 +- source/lib/res/graphics/cursor.cpp | 356 ------------------ source/lib/res/graphics/cursor.h | 57 --- source/main.cpp | 6 - source/ps/GameSetup/Config.cpp | 5 - source/ps/GameSetup/Config.h | 5 +- source/ps/GameSetup/GameSetup.cpp | 61 --- source/ps/GameSetup/GameSetup.h | 6 - source/ps/Util.cpp | 39 +- source/ps/VideoMode.cpp | 169 +++++++++ source/ps/VideoMode.h | 11 + source/tools/atlas/GameInterface/GameLoop.cpp | 4 - 14 files changed, 204 insertions(+), 532 deletions(-) delete mode 100644 source/lib/res/graphics/cursor.cpp delete mode 100644 source/lib/res/graphics/cursor.h diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 8974b55cb0..c4f9f23af9 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -105,8 +105,9 @@ showsky = true ; for a debugging of a system without GL_KHR_debug. gl.checkerrorafterswap = false -; Disable hardware cursors -nohwcursor = false +; Different ways to draw a cursor, possible values are "sdl" and "system". +; The "system" one doesn't support a visual change of the cursor. +cursorbackend = "sdl" ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp index bcdad45832..edff0fb7e0 100644 --- a/source/gui/GUIManager.cpp +++ b/source/gui/GUIManager.cpp @@ -25,6 +25,7 @@ #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" +#include "ps/VideoMode.h" #include "ps/XML/Xeromyces.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptContext.h" @@ -146,7 +147,7 @@ void CGUIManager::SGUIPage::LoadPage(std::shared_ptr scriptContex hotloadData = Script::WriteStructuredClone(rq, hotloadDataVal); } - g_CursorName = g_DefaultCursor; + g_VideoMode.ResetCursor(); inputs.clear(); gui.reset(new CGUI(scriptContext)); diff --git a/source/gui/Scripting/JSInterface_GUIManager.cpp b/source/gui/Scripting/JSInterface_GUIManager.cpp index c448da9d01..c66b3dfedf 100644 --- a/source/gui/Scripting/JSInterface_GUIManager.cpp +++ b/source/gui/Scripting/JSInterface_GUIManager.cpp @@ -23,6 +23,7 @@ #include "gui/GUIManager.h" #include "gui/ObjectBases/IGUIObject.h" #include "ps/GameSetup/Config.h" +#include "ps/VideoMode.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/StructuredClone.h" @@ -52,16 +53,14 @@ void PopGuiPage(const ScriptRequest& rq, JS::HandleValue args) g_GUI->PopPage(Script::WriteStructuredClone(rq, args)); } -std::wstring SetCursor(const std::wstring& name) +void SetCursor(const std::wstring& name) { - std::wstring old = g_CursorName; - g_CursorName = name; - return old; + g_VideoMode.SetCursor(name); } void ResetCursor() { - g_CursorName = g_DefaultCursor; + g_VideoMode.ResetCursor(); } bool TemplateExists(const std::string& templateName) diff --git a/source/lib/res/graphics/cursor.cpp b/source/lib/res/graphics/cursor.cpp deleted file mode 100644 index 746383231c..0000000000 --- a/source/lib/res/graphics/cursor.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* Copyright (C) 2020 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * mouse cursors (either via OpenGL texture or hardware) - */ - -#include "precompiled.h" -#include "cursor.h" - -#include -#include -#include - -#include "lib/external_libraries/libsdl.h" -#include "lib/ogl.h" -#include "lib/res/h_mgr.h" -#include "ogl_tex.h" - -class SDLCursor -{ - SDL_Surface* surface; - SDL_Cursor* cursor; - -public: - Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) - { - std::shared_ptr file; size_t fileSize; - RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize)); - - Tex t; - RETURN_STATUS_IF_ERR(t.decode(file, fileSize)); - - // convert to required BGRA format. - const size_t flags = (t.m_Flags | TEX_BGR) & ~TEX_DXT; - RETURN_STATUS_IF_ERR(t.transform_to(flags)); - void* bgra_img = t.get_data(); - if(!bgra_img) - WARN_RETURN(ERR::FAIL); - - surface = SDL_CreateRGBSurfaceFrom(bgra_img, (int)t.m_Width, (int)t.m_Height, 32, (int)t.m_Width*4, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - if(!surface) - return ERR::FAIL; - if(scale != 1.0) - { - SDL_Surface* scaled_surface = SDL_CreateRGBSurface(0, surface->w * scale, surface->h * scale, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); - if(!scaled_surface) - return ERR::FAIL; - if(SDL_BlitScaled(surface, NULL, scaled_surface, NULL)) - return ERR::FAIL; - SDL_FreeSurface(surface); - surface = scaled_surface; - } - cursor = SDL_CreateColorCursor(surface, hotspotx_, hotspoty_); - if(!cursor) - return ERR::FAIL; - - return INFO::OK; - } - - void set() - { - SDL_SetCursor(cursor); - } - - void destroy() - { - SDL_FreeCursor(cursor); - SDL_FreeSurface(surface); - } -}; - -// no init is necessary because this is stored in struct Cursor, which -// is 0-initialized by h_mgr. -class GLCursor -{ - Handle ht; - - GLint w, h; - int hotspotx, hotspoty; - -public: - Status create(const PIVFS& vfs, const VfsPath& pathname, int hotspotx_, int hotspoty_, double scale) - { - ht = ogl_tex_load(vfs, pathname); - RETURN_STATUS_IF_ERR(ht); - - size_t width, height; - (void)ogl_tex_get_size(ht, &width, &height, 0); - w = (GLint)(width * scale); - h = (GLint)(height * scale); - - hotspotx = hotspotx_; hotspoty = hotspoty_; - - (void)ogl_tex_set_filter(ht, GL_NEAREST); - (void)ogl_tex_upload(ht); - return INFO::OK; - } - - void destroy() - { - // note: we're stored in a resource => ht is initially 0 => - // this is safe, no need for an is_valid flag - (void)ogl_tex_free(ht); - } - - void draw(int x, int y) const - { -#if CONFIG2_GLES - UNUSED2(x); UNUSED2(y); -#warning TODO: implement cursors for GLES -#else - (void)ogl_tex_bind(ht); - glEnable(GL_TEXTURE_2D); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - glBegin(GL_QUADS); - glTexCoord2i(1, 0); glVertex2i( x-hotspotx+w, y+hotspoty ); - glTexCoord2i(0, 0); glVertex2i( x-hotspotx, y+hotspoty ); - glTexCoord2i(0, 1); glVertex2i( x-hotspotx, y+hotspoty-h ); - glTexCoord2i(1, 1); glVertex2i( x-hotspotx+w, y+hotspoty-h ); - glEnd(); - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); -#endif - } - - Status validate() const - { - const GLint A = 128; // no cursor is expected to get this big - if(w > A || h > A || hotspotx > A || hotspoty > A) - WARN_RETURN(ERR::_1); - if(ht < 0) - WARN_RETURN(ERR::_2); - return INFO::OK; - } -}; - -enum CursorKind -{ - CK_Default, - CK_SDL, - CK_OpenGL -}; - -struct Cursor -{ - double scale; - - // require kind == CK_OpenGL after reload - bool forceGL; - - CursorKind kind; - - // valid iff kind == CK_SDL - SDLCursor sdl_cursor; - - // valid iff kind == CK_OpenGL - GLCursor gl_cursor; -}; - -H_TYPE_DEFINE(Cursor); - -static void Cursor_init(Cursor* c, va_list args) -{ - c->scale = va_arg(args, double); - c->forceGL = (va_arg(args, int) != 0); -} - -static void Cursor_dtor(Cursor* c) -{ - switch(c->kind) - { - case CK_Default: - break; // nothing to do - - case CK_SDL: - c->sdl_cursor.destroy(); - break; - - case CK_OpenGL: - c->gl_cursor.destroy(); - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - break; - } -} - -static Status Cursor_reload(Cursor* c, const PIVFS& vfs, const VfsPath& name, Handle) -{ - const VfsPath pathname(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 pathnameHotspot = pathname.ChangeExtension(L".txt"); - std::shared_ptr buf; size_t size; - RETURN_STATUS_IF_ERR(vfs->LoadFile(pathnameHotspot, buf, size)); - std::wstringstream s(std::wstring((const wchar_t*)buf.get(), size)); - s >> hotspotx >> hotspoty; - } - - const VfsPath pathnameImage = pathname.ChangeExtension(L".png"); - - // try loading as SDL2 cursor - if(!c->forceGL && c->sdl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) - c->kind = CK_SDL; - // fall back to GLCursor (system cursor code is disabled or failed) - else if(c->gl_cursor.create(vfs, pathnameImage, hotspotx, hotspoty, c->scale) == INFO::OK) - c->kind = CK_OpenGL; - // everything failed, leave cursor unchanged - else - c->kind = CK_Default; - - return INFO::OK; -} - -static Status Cursor_validate(const Cursor* c) -{ - switch(c->kind) - { - case CK_Default: - break; // nothing to do - - case CK_SDL: - break; // nothing to do - - case CK_OpenGL: - RETURN_STATUS_IF_ERR(c->gl_cursor.validate()); - break; - - default: - WARN_RETURN(ERR::_2); - break; - } - - return INFO::OK; -} - -static Status Cursor_to_string(const Cursor* c, wchar_t* buf) -{ - const wchar_t* type; - switch(c->kind) - { - case CK_Default: - type = L"default"; - break; - - case CK_SDL: - type = L"sdl"; - break; - - case CK_OpenGL: - type = L"gl"; - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - type = L"?"; - break; - } - - swprintf_s(buf, H_STRING_LEN, L"cursor (%ls)", type); - return INFO::OK; -} - - -// note: these standard resource interface functions are not exposed to the -// caller. all we need here is storage for the SDL_Cursor / GLCursor and -// a name -> data lookup mechanism, both provided by h_mgr. -// in other words, we continually create/free the cursor resource in -// cursor_draw and trust h_mgr's caching to absorb it. - -static Handle cursor_load(const PIVFS& vfs, const VfsPath& name, double scale, bool forceGL) -{ - return h_alloc(H_Cursor, vfs, name, 0, scale, (int)forceGL); -} - -void cursor_shutdown() -{ - h_mgr_free_type(H_Cursor); -} - -static Status cursor_free(Handle& h) -{ - return h_free(h, H_Cursor); -} - - -Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL) -{ - // hide the cursor - if(!name) - { - SDL_ShowCursor(SDL_DISABLE); - return INFO::OK; - } - - Handle hc = cursor_load(vfs, name, scale, forceGL); - // TODO: if forceGL changes at runtime after a cursor is first created, - // we might reuse a cached version of the cursor with the old forceGL flag - - RETURN_STATUS_IF_ERR(hc); // silently ignore failures - - H_DEREF(hc, Cursor, c); - - switch(c->kind) - { - case CK_Default: - break; - - case CK_SDL: - c->sdl_cursor.set(); - SDL_ShowCursor(SDL_ENABLE); - break; - - case CK_OpenGL: - c->gl_cursor.draw(x, y); - SDL_ShowCursor(SDL_DISABLE); - break; - - default: - DEBUG_WARN_ERR(ERR::LOGIC); - break; - } - - (void)cursor_free(hc); - return INFO::OK; -} diff --git a/source/lib/res/graphics/cursor.h b/source/lib/res/graphics/cursor.h deleted file mode 100644 index 5d1d81fd1a..0000000000 --- a/source/lib/res/graphics/cursor.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright (C) 2017 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * mouse cursors (either via OpenGL texture or hardware) - */ - -#ifndef INCLUDED_GRAPHICS_CURSOR -#define INCLUDED_GRAPHICS_CURSOR - -#include "lib/file/vfs/vfs.h" - -/** - * Draw the cursor on-screen. - * - * @param vfs - * @param name Base name of cursor or zero to hide the cursor. - * @param x,y Coordinates [pixels] (origin at lower left) - * (the origin is convenient for drawing via OpenGL, but requires the - * mouse Y coordinate to be subtracted from the client area height. - * Making the caller responsible for this avoids a dependency on - * the g_yres global variable.) - * @param scale Scale factor for drawing size the cursor. - * @param forceGL Require the OpenGL cursor implementation, not hardware cursor - * - * Uses a hardware mouse cursor where available, otherwise a - * portable OpenGL implementation. - **/ -extern Status cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y, double scale, bool forceGL); - -/** - * Forcibly frees all cursor handles. - * - * Currently used just prior to SDL shutdown. - */ -void cursor_shutdown(); - -#endif // #ifndef INCLUDED_GRAPHICS_CURSOR diff --git a/source/main.cpp b/source/main.cpp index c5ecd824c9..780eb37f95 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -159,12 +159,6 @@ static InReaction MainInputHandler(const SDL_Event_* ev) case SDL_WINDOWEVENT: switch(ev->ev.window.event) { - case SDL_WINDOWEVENT_ENTER: - RenderCursor(true); - break; - case SDL_WINDOWEVENT_LEAVE: - RenderCursor(false); - break; case SDL_WINDOWEVENT_RESIZED: g_ResizedW = ev->ev.window.data1; g_ResizedH = ev->ev.window.data2; diff --git a/source/ps/GameSetup/Config.cpp b/source/ps/GameSetup/Config.cpp index f2bc15562a..f207ccccd3 100644 --- a/source/ps/GameSetup/Config.cpp +++ b/source/ps/GameSetup/Config.cpp @@ -26,11 +26,6 @@ #include "ps/GameSetup/CmdLineArgs.h" // (these variables are documented in the header.) - -const wchar_t g_DefaultCursor[] = L"default-arrow"; - -CStrW g_CursorName = g_DefaultCursor; - bool g_PauseOnFocusLoss = false; int g_xres, g_yres; diff --git a/source/ps/GameSetup/Config.h b/source/ps/GameSetup/Config.h index 95fc997e21..d3a758fa0a 100644 --- a/source/ps/GameSetup/Config.h +++ b/source/ps/GameSetup/Config.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,9 +29,6 @@ extern float g_GuiScale; extern bool g_Quickstart; extern bool g_DisableAudio; -extern CStrW g_CursorName; -extern const wchar_t g_DefaultCursor[]; - class CmdLineArgs; extern void CONFIG_Init(const CmdLineArgs& args); diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 48cca3b2d9..3a9b1c3cf6 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -25,7 +25,6 @@ #include "lib/external_libraries/libsdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" -#include "lib/res/graphics/cursor.h" #include "graphics/CinemaManager.h" #include "graphics/Color.h" @@ -113,7 +112,6 @@ ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; -bool g_DoRenderCursor = true; thread_local std::shared_ptr g_ScriptContext; @@ -284,54 +282,6 @@ void Render() g_ProfileViewer.RenderProfile(); ogl_WarnIfError(); - // Draw the cursor (or set the Windows cursor, on Windows) - if (g_DoRenderCursor) - { - PROFILE3_GPU("cursor"); - CStrW cursorName = g_CursorName; - if (cursorName.empty()) - { - cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y, g_GuiScale, false); - } - else - { - bool forceGL = false; - CFG_GET_VAL("nohwcursor", forceGL); - -#if CONFIG2_GLES -#warning TODO: implement cursors for GLES -#else - // set up transform for GL cursor - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - CMatrix3D transform; - transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); - glLoadMatrixf(&transform._11); -#endif - -#if OS_ANDROID -#warning TODO: cursors for Android -#else - if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y, g_GuiScale, forceGL) < 0) - LOGWARNING("Failed to draw cursor '%s'", utf8_from_wstring(cursorName)); -#endif - -#if CONFIG2_GLES -#warning TODO: implement cursors for GLES -#else - // restore transform - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); -#endif - } - } - glEnable(GL_DEPTH_TEST); g_Renderer.EndFrame(); @@ -539,9 +489,6 @@ static void ShutdownPs() SAFE_DELETE(g_GUI); UnloadHotkeys(); - - // disable the special Windows cursor, or free textures for OGL cursors - cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, 1.0, false); } @@ -661,9 +608,6 @@ void Shutdown(int flags) g_Profiler2.ShutdownGPU(); - // Free cursors before shutting down SDL, as they may depend on SDL. - cursor_shutdown(); - TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); @@ -1087,11 +1031,6 @@ void RenderLogger(bool RenderingState) g_DoRenderLogger = RenderingState; } -void RenderCursor(bool RenderingState) -{ - g_DoRenderCursor = RenderingState; -} - /** * Temporarily loads a scenario map and retrieves the "ScriptSettings" JSON * data from it. diff --git a/source/ps/GameSetup/GameSetup.h b/source/ps/GameSetup/GameSetup.h index 87bf6398b8..03d3ae1a24 100644 --- a/source/ps/GameSetup/GameSetup.h +++ b/source/ps/GameSetup/GameSetup.h @@ -74,12 +74,6 @@ enum ShutdownFlags extern void RenderGui(bool RenderingState); extern void RenderLogger(bool RenderingState); -/** - * enable/disable rendering of the cursor - this does not hide cursor, but reverts to OS style - */ -extern void RenderCursor(bool RenderingState); - - class CmdLineArgs; class Paths; extern const std::vector& GetMods(const CmdLineArgs& args, int flags); diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index 8439e395b6..dbf64a4290 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -19,34 +19,32 @@ #include "ps/Util.h" -#include "lib/posix/posix_utsname.h" -#include "lib/ogl.h" -#include "lib/timer.h" -#include "lib/bits.h" // round_up +#include "graphics/GameView.h" +#include "i18n/L10n.h" #include "lib/allocators/shared_ptr.h" -#include "lib/sysdep/sysdep.h" // sys_OpenFile -#include "lib/sysdep/gfx.h" -#include "lib/sysdep/cpu.h" -#include "lib/sysdep/os_cpu.h" +#include "lib/bits.h" // round_up +#include "lib/ogl.h" +#include "lib/posix/posix_utsname.h" #if ARCH_X86_X64 #include "lib/sysdep/arch/x86_x64/topology.h" #endif +#include "lib/sysdep/gfx.h" +#include "lib/sysdep/cpu.h" +#include "lib/sysdep/os_cpu.h" #include "lib/sysdep/smbios.h" +#include "lib/sysdep/sysdep.h" // sys_OpenFile #include "lib/tex/tex.h" - -#include "i18n/L10n.h" +#include "lib/timer.h" #include "lib/utf8.h" - -#include "ps/GameSetup/Config.h" -#include "ps/GameSetup/GameSetup.h" -#include "ps/Game.h" +#include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +#include "ps/Game.h" +#include "ps/GameSetup/Config.h" +#include "ps/GameSetup/GameSetup.h" #include "ps/Pyrogenesis.h" #include "ps/VideoMode.h" #include "renderer/Renderer.h" -#include "maths/MathUtil.h" -#include "graphics/GameView.h" #if CONFIG2_AUDIO #include "soundmanager/SoundManager.h" @@ -55,8 +53,6 @@ #include #include -extern CStrW g_CursorName; - static std::string SplitExts(const char *exts) { std::string str = exts; @@ -376,10 +372,6 @@ void WriteBigScreenshot(const VfsPath& extension, int tiles, int tileWidth, int glReadBuffer(GL_FRONT); #endif - // Hide the cursor - CStrW oldCursor = g_CursorName; - g_CursorName = L""; - // Render each tile CMatrix3D projection; projection.SetIdentity(); @@ -412,9 +404,6 @@ void WriteBigScreenshot(const VfsPath& extension, int tiles, int tileWidth, int } } - // Restore the old cursor - g_CursorName = oldCursor; - #if !CONFIG2_GLES // Restore the buffer settings glDrawBuffer(oldDrawBuffer); diff --git a/source/ps/VideoMode.cpp b/source/ps/VideoMode.cpp index 7642cde319..19b59e726a 100644 --- a/source/ps/VideoMode.cpp +++ b/source/ps/VideoMode.cpp @@ -45,6 +45,8 @@ int DEFAULT_WINDOW_H = 768; int DEFAULT_FULLSCREEN_W = 1024; int DEFAULT_FULLSCREEN_H = 768; +const wchar_t DEFAULT_CURSOR_NAME[] = L"default-arrow"; + } // anonymous namespace #if OS_WIN @@ -59,11 +61,162 @@ extern void wutil_EnableHiDPIOnWindows(); 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 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(buffer.get()), size)); + s >> hotspotX >> hotspotY; + } + + const VfsPath pathImageName = pathBaseName.ChangeExtension(L".png"); + + std::shared_ptr 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(t.m_Width), static_cast(t.m_Height), 32, + static_cast(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_GuiScale; + 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; @@ -307,6 +460,8 @@ bool CVideoMode::InitSDL() SetWindowIcon(); + m_Cursor = std::make_unique(); + return true; } @@ -325,6 +480,8 @@ void CVideoMode::Shutdown() { ENSURE(m_IsInitialised); + m_Cursor.reset(); + m_IsFullscreen = false; m_IsInitialised = false; if (m_Window) @@ -563,3 +720,15 @@ void CVideoMode::SetWindowIcon() 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(); +} diff --git a/source/ps/VideoMode.h b/source/ps/VideoMode.h index cf9e2e6203..f8f1b49167 100644 --- a/source/ps/VideoMode.h +++ b/source/ps/VideoMode.h @@ -18,12 +18,17 @@ #ifndef INCLUDED_VIDEOMODE #define INCLUDED_VIDEOMODE +#include "ps/CStrForward.h" + +#include + typedef struct SDL_Window SDL_Window; class CVideoMode { public: CVideoMode(); + ~CVideoMode(); /** * Initialise the video mode, for use in an SDL-using application. @@ -88,6 +93,9 @@ public: void SetWindowIcon(); + void SetCursor(const CStrW& name); + void ResetCursor(); + private: void ReadConfig(); int GetBestBPP(); @@ -135,6 +143,9 @@ private: int m_CurrentW; int m_CurrentH; int m_CurrentBPP; + + class CCursor; + std::unique_ptr m_Cursor; }; extern CVideoMode g_VideoMode; diff --git a/source/tools/atlas/GameInterface/GameLoop.cpp b/source/tools/atlas/GameInterface/GameLoop.cpp index 45673da778..e4eb3a300f 100644 --- a/source/tools/atlas/GameInterface/GameLoop.cpp +++ b/source/tools/atlas/GameInterface/GameLoop.cpp @@ -126,10 +126,6 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) RegisterHandlers(); - // Disable the game's cursor rendering - extern CStrW g_CursorName; - g_CursorName = L""; - state.args = args; state.running = true; state.view = AtlasView::GetView_None();