diff --git a/AI/Interfaces/C/CMakeLists.txt b/AI/Interfaces/C/CMakeLists.txt index 5b2d4b531a0..eaa7d305fe5 100644 --- a/AI/Interfaces/C/CMakeLists.txt +++ b/AI/Interfaces/C/CMakeLists.txt @@ -43,6 +43,13 @@ macro (configure_native_skirmish_ai mySourceDirRel_var additionalSources_var # Create a list of all the AIs own source files get_native_sources_recursive(mySources "${mySourceDir}" "${myDir}") + if (APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "arm64|aarch64") + set(appleArm64AngelScriptCallfunc "${mySourceDir}/lib/angelscript/source/as_callfunc_arm64_xcode.S") + if (EXISTS "${appleArm64AngelScriptCallfunc}") + enable_language(ASM) + list(APPEND mySources "${appleArm64AngelScriptCallfunc}") + endif (EXISTS "${appleArm64AngelScriptCallfunc}") + endif (APPLE AND CMAKE_OSX_ARCHITECTURES MATCHES "arm64|aarch64") # Compile the library add_library(${myTarget} MODULE ${mySources} ${additionalSources} ${myVersionDepFile}) diff --git a/AI/Wrappers/CUtils/Util.c b/AI/Wrappers/CUtils/Util.c index 4764a619900..00e24f6fa56 100644 --- a/AI/Wrappers/CUtils/Util.c +++ b/AI/Wrappers/CUtils/Util.c @@ -911,4 +911,3 @@ const char* util_map_getValueByKey( return value; } - diff --git a/CMakeLists.txt b/CMakeLists.txt index d016d27287c..d35d402e908 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,8 @@ if (USE_ASAN) endif (MSVC) endif (USE_ASAN) +option(RECOIL_MACOS_SDL3_EGL "Use project-local SDL3 Cocoa windowing and Mesa Cocoa EGL/Zink on macOS" ${APPLE}) + ### Tracy related stuff option(RECOIL_DETAILED_TRACY_ZONING "Enable additional detailed tracy zones (only enable this for testing/debugging)" FALSE) if (RECOIL_DETAILED_TRACY_ZONING) @@ -315,6 +317,9 @@ endif (RECOIL_DETAILED_TRACY_ZONING) # Note the missing REQUIRED, as headless & dedi may not depend on those. # So req. checks are done in the build target's CMakeLists.txt. find_package(SDL2 MODULE) +if (APPLE AND RECOIL_MACOS_SDL3_EGL) + find_package(MesaEGL MODULE) +endif() find_package_static(DevIL 1.8.0 REQUIRED) @@ -442,6 +447,9 @@ option(ENABLE_STREFLOP "build Spring with streflop support (REQUIRED FOR MULTIPL if (ENABLE_STREFLOP) set(STREFLOP_AUTO ON CACHE BOOL "Enable STREFLOP autodetection" FORCE) + if (IS_ARM64_BUILD) + set(STREFLOP_NEON ON CACHE BOOL "Use ARM NEON for deterministic ARM64 streflop" FORCE) + endif () else () add_definitions(-DNOT_USING_STREFLOP) endif () diff --git a/rts/CMakeLists.txt b/rts/CMakeLists.txt index 001ea7cb6ba..bc9f8d68c67 100644 --- a/rts/CMakeLists.txt +++ b/rts/CMakeLists.txt @@ -34,6 +34,10 @@ if(DEBUG_GLSTATE) add_definitions(-DDEBUG_GLSTATE) endif() +if(ENABLE_STREFLOP AND STREFLOP_NEON) + add_definitions(-DSTREFLOP_NEON) +endif() + ### give error when not found find_package_static(DevIL REQUIRED) diff --git a/rts/Game/Camera.cpp b/rts/Game/Camera.cpp index 8c5ea462a4b..0ffaa4ed3a1 100644 --- a/rts/Game/Camera.cpp +++ b/rts/Game/Camera.cpp @@ -299,7 +299,7 @@ void CCamera::LoadMatrices() const void CCamera::LoadViewport() const { RECOIL_DETAILED_TRACY_ZONE; - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + globalRendering->LoadDefaultFramebufferViewport(viewport[0], viewport[1], viewport[2], viewport[3]); } diff --git a/rts/Game/UI/MiniMap.cpp b/rts/Game/UI/MiniMap.cpp index 2f202bffc18..c894ce44b03 100644 --- a/rts/Game/UI/MiniMap.cpp +++ b/rts/Game/UI/MiniMap.cpp @@ -322,12 +322,12 @@ void CMiniMap::SetAspectRatioGeometry(const float& viewSizeX, const float& viewS void CMiniMap::LoadDualViewport() const { glEnable(GL_SCISSOR_TEST); - glScissor(globalRendering->dualViewPosX, globalRendering->dualViewPosY, globalRendering->dualViewSizeX, globalRendering->dualViewSizeY); + globalRendering->LoadDefaultFramebufferScissor(globalRendering->dualViewPosX, globalRendering->dualViewPosY, globalRendering->dualViewSizeX, globalRendering->dualViewSizeY); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); - glViewport(curPos.x, curPos.y, curDim.x, curDim.y); + globalRendering->LoadDefaultFramebufferViewport(curPos.x, curPos.y, curDim.x, curDim.y); } @@ -2065,4 +2065,3 @@ void CMiniMap::SetClipPlanes(const bool lua) const /******************************************************************************/ - diff --git a/rts/Game/UI/MouseHandler.cpp b/rts/Game/UI/MouseHandler.cpp index 60640294423..8125bc4ae9c 100644 --- a/rts/Game/UI/MouseHandler.cpp +++ b/rts/Game/UI/MouseHandler.cpp @@ -778,7 +778,11 @@ void CMouseHandler::ShowMouse() hideCursor = false; +#if defined(RECOIL_MACOS_SDL3_EGL) + SDL_SetWindowRelativeMouseMode(globalRendering->GetWindow(), false); +#else SDL_SetRelativeMouseMode(SDL_FALSE); +#endif // don't use SDL_ShowCursor here since it would cause flickering with hwCursor // (by switching between default cursor and later the real one, e.g. `attack`) @@ -802,7 +806,11 @@ void CMouseHandler::HideMouse() // this way the mouse position will never change so it is also unnecessary to call // SDL_WarpMouseInWindow and handle the associated wart of filtering motion events // technically supersedes SDL_ShowCursor as well +#if defined(RECOIL_MACOS_SDL3_EGL) + SDL_SetWindowRelativeMouseMode(globalRendering->GetWindow(), true); +#else SDL_SetRelativeMouseMode(SDL_TRUE); +#endif const int2 viewMouseCenter = GetViewMouseCenter(); diff --git a/rts/Lua/LuaOpenGL.cpp b/rts/Lua/LuaOpenGL.cpp index b588056ead2..a507f5b90cd 100644 --- a/rts/Lua/LuaOpenGL.cpp +++ b/rts/Lua/LuaOpenGL.cpp @@ -43,6 +43,7 @@ #include "Map/BaseGroundDrawer.h" #include "Map/MapInfo.h" #include "Map/ReadMap.h" +#include "Rendering/Fonts/CFontTexture.h" #include "Rendering/Fonts/glFont.h" #include "Rendering/GlobalRendering.h" #include "Rendering/LineDrawer.h" @@ -829,6 +830,8 @@ void LuaOpenGL::EnableDrawScreenCommon() EnableCommon(DRAW_SCREEN); resetMatrixFunc = ResetScreenMatrices; + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); SetupScreenMatrices(); SetupScreenLighting(); ResetGLState(); @@ -2856,8 +2859,9 @@ int LuaOpenGL::TexRect(lua_State* L) const float x2 = luaL_checkfloat(L, 3); const float y2 = luaL_checkfloat(L, 4); - // Spring's textures get loaded with a vertical flip - // We change that for the default settings. + // Legacy Lua textured-quad helper, not GL_TEXTURE_RECTANGLE semantics: + // 4/6-arg calls use normalized coords with Spring's default vertical flip, + // while 8-arg calls pass explicit caller-provided coords through unchanged. if (args <= 6) { float s1 = 0.0f; @@ -3184,7 +3188,7 @@ int LuaOpenGL::Scissor(lua_State* L) const GLsizei h = (GLsizei)luaL_checkint(L, 4); if (w < 0) luaL_argerror(L, 3, " must be greater than or equal zero!"); if (h < 0) luaL_argerror(L, 4, " must be greater than or equal zero!"); - glScissor(x + globalRendering->viewPosX, y + globalRendering->viewPosY, w, h); + globalRendering->LoadDefaultFramebufferScissor(x + globalRendering->viewPosX, y + globalRendering->viewPosY, w, h); } else { luaL_error(L, "Incorrect arguments to gl.Scissor()"); @@ -3212,7 +3216,7 @@ int LuaOpenGL::Viewport(lua_State* L) if (w < 0) luaL_argerror(L, 3, " must be greater than or equal zero!"); if (h < 0) luaL_argerror(L, 4, " must be greater than or equal zero!"); - glViewport(x, y, w, h); + globalRendering->LoadDefaultFramebufferViewport(x, y, w, h); return 0; } @@ -4355,9 +4359,6 @@ int LuaOpenGL::CopyToTexture(lua_State* L) if (tex == nullptr) return 0; - glBindTexture(tex->target, tex->id); - glEnable(tex->target); // leave it bound and enabled - const auto xoff = (GLint)luaL_checknumber(L, 2); const auto yoff = (GLint)luaL_checknumber(L, 3); const auto x = (GLint)luaL_checknumber(L, 4); @@ -4367,10 +4368,9 @@ int LuaOpenGL::CopyToTexture(lua_State* L) const auto target = (GLenum)luaL_optnumber(L, 8, tex->target); const auto level = (GLenum)luaL_optnumber(L, 9, 0); + const auto texBind = GL::TexBind(tex->target, tex->id); glCopyTexSubImage2D(target, level, xoff, yoff, x, y, w, h); - if (tex->target != GL_TEXTURE_2D) {glDisable(tex->target);} - return 0; } @@ -4401,9 +4401,7 @@ int LuaOpenGL::RenderToTexture(lua_State* L) return 0; GLint currentFBO = 0; - if (drawMode == DRAW_WORLD_SHADOW) { - glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFBO); - } + glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, ¤tFBO); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, tex->fbo); @@ -6147,6 +6145,7 @@ int LuaOpenGL::CreateList(lua_State* L) SMatrixStateData matData = GetLuaContextData(L)->glMatrixTracker.GetMatrixState(); GetLuaContextData(L)->glMatrixTracker.PopMatrixState(prevMSD, false); glEndList(); + CFontTexture::UploadPendingGlyphAtlasTextures(); if (error != 0) { glDeleteLists(list, 1); diff --git a/rts/Menu/SelectMenu.cpp b/rts/Menu/SelectMenu.cpp index 36f15602c23..f18b3369e2e 100644 --- a/rts/Menu/SelectMenu.cpp +++ b/rts/Menu/SelectMenu.cpp @@ -364,10 +364,18 @@ bool SelectMenu::HandleEventSelf(const SDL_Event& ev) { switch (ev.type) { case SDL_KEYDOWN: { +#if defined(RECOIL_MACOS_SDL3_EGL) + if (ev.key.key == SDLK_ESCAPE) { +#else if (ev.key.keysym.sym == SDLK_ESCAPE) { +#endif LOG("[SelectMenu] user exited"); Quit(); +#if defined(RECOIL_MACOS_SDL3_EGL) + } else if (ev.key.key == SDLK_RETURN) { +#else } else if (ev.key.keysym.sym == SDLK_RETURN) { +#endif Single(); return true; } diff --git a/rts/Rendering/Fonts/CFontTexture.cpp b/rts/Rendering/Fonts/CFontTexture.cpp index 3c6ac3660e0..b0fe252e993 100644 --- a/rts/Rendering/Fonts/CFontTexture.cpp +++ b/rts/Rendering/Fonts/CFontTexture.cpp @@ -1000,6 +1000,20 @@ void CFontTexture::Update() { eventHandler.FontsChanged(); } +void CFontTexture::UploadPendingGlyphAtlasTextures() { + assert(CFontTexture::sync.GetThreadSafety() || Threading::IsMainThread()); + auto lock = CFontTexture::sync.GetScopedLock(); + + for (const auto& font : allFonts) { + auto lf = font.lock(); + if (!lf) + continue; + + if (lf->GlyphAtlasTextureNeedsUpload()) + lf->UploadGlyphAtlasTexture(); + } +} + const GlyphInfo& CFontTexture::GetGlyph(char32_t ch) { RECOIL_DETAILED_TRACY_ZONE; diff --git a/rts/Rendering/Fonts/CFontTexture.h b/rts/Rendering/Fonts/CFontTexture.h index bca41927e13..6a5f8f50f1a 100644 --- a/rts/Rendering/Fonts/CFontTexture.h +++ b/rts/Rendering/Fonts/CFontTexture.h @@ -113,6 +113,7 @@ class CFontTexture static void InitFonts(); static void KillFonts(); static void Update(); + static void UploadPendingGlyphAtlasTextures(); static bool AddFallbackFont(const std::string& fontfile); static void ClearFallbackFonts(); static bool ClearAllGlyphs(); diff --git a/rts/Rendering/GL/myGL.cpp b/rts/Rendering/GL/myGL.cpp index 6e4396554b7..572636c1633 100644 --- a/rts/Rendering/GL/myGL.cpp +++ b/rts/Rendering/GL/myGL.cpp @@ -52,8 +52,8 @@ bool CheckAvailableVideoModes() // Get available fullscreen/hardware modes const int numDisplays = SDL_GetNumVideoDisplays(); - SDL_DisplayMode ddm = {0, 0, 0, 0, nullptr}; - SDL_DisplayMode cdm = {0, 0, 0, 0, nullptr}; + SDL_DisplayMode ddm = {}; + SDL_DisplayMode cdm = {}; // ddm is virtual, contains all displays in multi-monitor setups // for fullscreen windows with non-native resolutions, ddm holds @@ -78,8 +78,8 @@ bool CheckAvailableVideoModes() continue; } - SDL_DisplayMode cm = {0, 0, 0, 0, nullptr}; - SDL_DisplayMode pm = {0, 0, 0, 0, nullptr}; + SDL_DisplayMode cm = {}; + SDL_DisplayMode pm = {}; SDL_Rect db; SDL_GetDisplayBounds(k, &db); const std::string dn = SDL_GetDisplayName(k); @@ -97,16 +97,16 @@ bool CheckAvailableVideoModes() if (cm.w == pm.w && cm.h == pm.h && (SDL_BPP(cm.format) < SDL_BPP(pm.format) || cm.refresh_rate < pm.refresh_rate)) continue; - globalRenderingInfo.availableVideoModes.emplace_back(GlobalRenderingInfo::AvailableVideoMode{ - dn, - k + 1, - cm.w, - cm.h, - static_cast(SDL_BPP(cm.format)), - cm.refresh_rate - }); + globalRenderingInfo.availableVideoModes.emplace_back(GlobalRenderingInfo::AvailableVideoMode{ + dn, + k + 1, + cm.w, + cm.h, + static_cast(SDL_BPP(cm.format)), + static_cast(cm.refresh_rate) + }); - LOG("\t\t[%2i] %ix%ix%ibpp@%iHz", int(i + 1), cm.w, cm.h, SDL_BPP(cm.format), cm.refresh_rate); + LOG("\t\t[%2i] %ix%ix%ibpp@%iHz", int(i + 1), cm.w, cm.h, SDL_BPP(cm.format), static_cast(cm.refresh_rate)); pm = cm; } } @@ -740,4 +740,4 @@ void glClearErrors(const char* cls, const char* fnc, bool verbose) } else { for (int count = 0; (glGetError() != GL_NO_ERROR) && (count < 10000); count++); } -} \ No newline at end of file +} diff --git a/rts/Rendering/GlobalRendering.cpp b/rts/Rendering/GlobalRendering.cpp index be3c80e3a93..b137b3772ab 100644 --- a/rts/Rendering/GlobalRendering.cpp +++ b/rts/Rendering/GlobalRendering.cpp @@ -1,8 +1,11 @@ /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#include +#include #include #include #include +#include #include @@ -33,6 +36,9 @@ #include "System/Platform/SharedLib.h" #include "System/Platform/WindowManagerHelper.h" #include "System/Platform/errorhandler.h" +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) +#include "System/Platform/Mac/MacSDL3EGLBridge.h" +#endif #include "System/ScopedResource.h" #include "System/QueueToMain.h" #include "System/creg/creg_cond.h" @@ -57,6 +63,9 @@ CONFIG(int, ForceDisableExplicitAttribLocs).defaultValue(0).minimumValue(0).maxi CONFIG(int, ForceDisableClipCtrl).defaultValue(0).minimumValue(0).maximumValue(1); //CONFIG(int, ForceDisableShaders).defaultValue(0).minimumValue(0).maximumValue(1); CONFIG(int, ForceDisableGL4).defaultValue(0).safemodeValue(1).minimumValue(0).maximumValue(1); +CONFIG(int, ZinkMoltenVKCustomBorderColorUnsafe).defaultValue(-1).minimumValue(-1).maximumValue(1).description("Central macOS/Zink/MoltenVK custom border color risk hook. -1 runtime detect, 0 force safe, 1 force unsafe."); +CONFIG(int, ZinkMoltenVKLogicOpUnsafe).defaultValue(-1).minimumValue(-1).maximumValue(1).description("Central macOS/Zink/MoltenVK logic-op risk hook. -1 runtime detect, 0 force safe, 1 force unsafe."); +CONFIG(int, GeometryShadersUnsupported).defaultValue(-1).minimumValue(-1).maximumValue(1).description("Central GS-free backend hook. -1 runtime detect, 0 force allowed, 1 force unsupported."); CONFIG(int, ForceCoreContext).defaultValue(0).minimumValue(0).maximumValue(1); CONFIG(int, ForceSwapBuffers).defaultValue(1).minimumValue(0).maximumValue(1); @@ -70,16 +79,16 @@ CONFIG(bool, TeamNanoSpray).defaultValue(true).headlessValue(false); CONFIG(int, MinimizeOnFocusLoss).defaultValue(0).minimumValue(0).maximumValue(1).description("When set to 1 minimize Window if it loses key focus when in fullscreen mode."); -CONFIG(bool, Fullscreen).defaultValue(true).headlessValue(false).description("Sets whether the game will run in fullscreen, as opposed to a window. For Windowed Fullscreen of Borderless Window, set this to 0, WindowBorderless to 1, and WindowPosX and WindowPosY to 0."); -CONFIG(bool, WindowBorderless).defaultValue(false).description("When set and Fullscreen is 0, will put the game in Borderless Window mode, also known as Windowed Fullscreen. When using this, it is generally best to also set WindowPosX and WindowPosY to 0"); +CONFIG(bool, Fullscreen).defaultValue(true).headlessValue(false).description("Logical-resolution-first fullscreen/windowed policy. Uses a logical-safe area by default; physical mode is kept as an advanced/explicit option."); +CONFIG(bool, WindowBorderless).defaultValue(false).description("Windowed fullscreen on supported backends (logical-safe full-screen style). Pair with Fullscreen=0, WindowPosX=0, WindowPosY=0 for default borderless behavior."); CONFIG(bool, BlockCompositing).defaultValue(false).safemodeValue(true).description("Disables kwin compositing to fix tearing, possible fixes low FPS in windowed mode, too."); // setting this as default 0 for now is because if the frame were to be dropped for being late, DWMFlush will force the compositor to use the framebuffer. This can result in blocking until the framebuffer can be composited (up to 1 frame) and may not be desirable for all use cases (specifically with vsync set to off). However, only more widespread testing and investigation across various hardware/os configs would tell us what advantage DWMFlush would bring. CONFIG(int, DWMFlush).defaultValue(0).description("Force Windows Desktop Compositors DWMFlush before each SDL_GL_SwapWindow, preventing dropped frames (use nVidias FrameView to validate dropped frames, or BARs Jitter Timer widget). Value of 1 does DWMFlush before SwapBuffers, value of 2 does DWMFlush after swapbuffers."); -CONFIG(int, XResolution).defaultValue(0).headlessValue(8).minimumValue(0).description("Sets the width of the game screen. If set to 0 Spring will autodetect the current resolution of your desktop."); -CONFIG(int, YResolution).defaultValue(0).headlessValue(8).minimumValue(0).description("Sets the height of the game screen. If set to 0 Spring will autodetect the current resolution of your desktop."); -CONFIG(int, XResolutionWindowed).defaultValue(0).headlessValue(8).minimumValue(0).description("See XResolution, just for windowed."); -CONFIG(int, YResolutionWindowed).defaultValue(0).headlessValue(8).minimumValue(0).description("See YResolution, just for windowed."); +CONFIG(int, XResolution).defaultValue(0).headlessValue(8).minimumValue(0).description("Sets logical fullscreen/windowed width. If 0, Spring auto-detects the current logical-safe display width."); +CONFIG(int, YResolution).defaultValue(0).headlessValue(8).minimumValue(0).description("Sets logical fullscreen/windowed height. If 0, Spring auto-detects the current logical-safe display height."); +CONFIG(int, XResolutionWindowed).defaultValue(0).headlessValue(8).minimumValue(0).description("See XResolution. Used when Fullscreen=0."); +CONFIG(int, YResolutionWindowed).defaultValue(0).headlessValue(8).minimumValue(0).description("See YResolution. Used when Fullscreen=0."); CONFIG(int, WindowPosX).defaultValue(0 ).description("Sets the horizontal position of the game window, if Fullscreen is 0. When WindowBorderless is set, this should usually be 0."); CONFIG(int, WindowPosY).defaultValue(32).description("Sets the vertical position of the game window, if Fullscreen is 0. When WindowBorderless is set, this should usually be 0."); @@ -194,6 +203,9 @@ CR_REG_METADATA(CGlobalRendering, ( CR_IGNORED(supportClipSpaceControl), CR_IGNORED(supportSeamlessCubeMaps), CR_IGNORED(supportFragDepthLayout), + CR_IGNORED(zinkMoltenVKCustomBorderColorUnsafe), + CR_IGNORED(zinkMoltenVKLogicOpUnsafe), + CR_IGNORED(geometryShadersUnsupported), CR_IGNORED(haveGL4), CR_IGNORED(glslMaxVaryings), CR_IGNORED(glslMaxAttributes), @@ -324,6 +336,9 @@ CGlobalRendering::CGlobalRendering() , supportClipSpaceControl(false) , supportSeamlessCubeMaps(false) , supportFragDepthLayout(false) + , zinkMoltenVKCustomBorderColorUnsafe(false) + , zinkMoltenVKLogicOpUnsafe(false) + , geometryShadersUnsupported(false) , haveGL4(false) , glslMaxVaryings(0) @@ -425,10 +440,34 @@ SDL_Window* CGlobalRendering::CreateSDLWindow(const char* title) const // SDL_WINDOW_FULLSCREEN_DESKTOP for "fake" fullscreen that takes the size of the desktop; // and 0 for windowed mode. - uint32_t sdlFlags = (SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - sdlFlags |= (borderless_ ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) * fullScreen_; - sdlFlags |= (SDL_WINDOW_BORDERLESS * borderless_); + uint32_t sdlFlags = SDL_WINDOW_RESIZABLE; +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + sdlFlags |= SDL_WINDOW_HIGH_PIXEL_DENSITY; + sdlFlags |= (SDL_WINDOW_BORDERLESS * (borderless_ || fullScreen_)); +#else + sdlFlags |= SDL_WINDOW_OPENGL; + sdlFlags |= (borderless_ ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) * fullScreen_; + sdlFlags |= (SDL_WINDOW_BORDERLESS * borderless_); +#endif + +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + SDL_Rect safeBounds = {winPosX_, winPosY_, newRes.x, newRes.y}; + if (fullScreen_ || borderless_) { + MacSDL3EGL::DisplayGeometry initialGeometry = MacSDL3EGL::QueryDisplayGeometry(nullptr); + safeBounds = initialGeometry.safeBounds; + winPosX_ = safeBounds.x; + winPosY_ = safeBounds.y; + newRes = {safeBounds.w, safeBounds.h}; + } + if ((newWindow = SDL_CreateWindow(title, newRes.x, newRes.y, sdlFlags)) == nullptr) { + LOG_L(L_WARNING, frmts[0], __func__, SDL_GetError(), 0, 24); + } else { + SDL_SetWindowPosition(newWindow, winPosX_, winPosY_); + LOG(frmts[1], __func__, 0, 24, wpfName = SDL_GetPixelFormatName(SDL_GetWindowPixelFormat(newWindow))); + MacSDL3EGL::LogDisplayGeometry(newWindow, "after-sdl3-window-create"); + } +#else for (size_t i = 0; i < (aaLvls.size()) && (newWindow == nullptr); i++) { if (i > 0 && aaLvls[i] == aaLvls[i - 1]) break; @@ -447,6 +486,7 @@ SDL_Window* CGlobalRendering::CreateSDLWindow(const char* title) const LOG(frmts[1], __func__, aaLvls[i], zbBits[j], wpfName = SDL_GetPixelFormatName(SDL_GetWindowPixelFormat(newWindow))); } } +#endif if (newWindow == nullptr) { auto buf = fmt::sprintf("[GR::%s] could not create SDL-window\n", __func__); @@ -461,6 +501,15 @@ SDL_Window* CGlobalRendering::CreateSDLWindow(const char* title) const SDL_GLContext CGlobalRendering::CreateGLContext(const int2& minCtx) { +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (macSDL3EGLBridge == nullptr) + macSDL3EGLBridge = std::make_unique(); + + if (!macSDL3EGLBridge->Initialize(sdlWindow, minCtx.x, minCtx.y, forceCoreContext != 0)) + return nullptr; + + return reinterpret_cast(macSDL3EGLBridge->GetContextOpaque()); +#else SDL_GLContext newContext = nullptr; constexpr int2 glCtxs[] = {{2, 0}, {2, 1}, {3, 0}, {3, 1}, {3, 2}, {3, 3}, {4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 5}, {4, 6}}; @@ -513,11 +562,17 @@ SDL_GLContext CGlobalRendering::CreateGLContext(const int2& minCtx) // should never fail at this point return (newContext = SDL_GL_CreateContext(sdlWindow)); +#endif } bool CGlobalRendering::CreateWindowAndContext(const char* title) { +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "cocoa"); + if (!SDL_Init(SDL_INIT_VIDEO)) { +#else if (SDL_Init(SDL_INIT_VIDEO) == -1) { +#endif LOG_L(L_FATAL, "[GR::%s] error \"%s\" initializing SDL", __func__, SDL_GetError()); return false; } @@ -587,12 +642,28 @@ bool CGlobalRendering::CreateWindowAndContext(const char* title) if ((glContext = CreateGLContext(minCtx)) == nullptr) return false; +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + const int gladStatus = gladLoadGLLoader(MacSDL3EGL::GetGLProcAddress); + LOG("[GR::%s] gladLoadGLLoader via Mesa EGL returned %d", __func__, gladStatus); + if (gladStatus == 0) { + LOG_L(L_FATAL, "[GR::%s] gladLoadGLLoader via Mesa EGL failed", __func__); + return false; + } +#else gladLoadGL(); +#endif +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + macSDL3EGLBridge->LogContextDiagnostics("after-glad-load"); +#endif GLX::Load(sdlWindow); if (!CheckGLContextVersion(minCtx)) { int ctxProfile = 0; +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + ctxProfile = globalRenderingInfo.glContextIsCore ? SDL_GL_CONTEXT_PROFILE_CORE : SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; +#else SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &ctxProfile); +#endif const std::string errStr = fmt::format("current OpenGL version {}.{}(core={}) is less than required {}.{}(core={}), aborting", globalRenderingInfo.glContextVersion.x, globalRenderingInfo.glContextVersion.y, globalRenderingInfo.glContextIsCore, @@ -610,7 +681,12 @@ bool CGlobalRendering::CreateWindowAndContext(const char* title) void CGlobalRendering::MakeCurrentContext(bool clear) const { +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (macSDL3EGLBridge != nullptr) + macSDL3EGLBridge->MakeCurrent(clear); +#else SDL_GL_MakeCurrent(sdlWindow, clear ? nullptr : glContext); +#endif } @@ -621,12 +697,19 @@ void CGlobalRendering::DestroyWindowAndContext() { WindowManagerHelper::SetIconSurface(sdlWindow, nullptr); SetWindowInputGrabbing(false); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (macSDL3EGLBridge != nullptr) + macSDL3EGLBridge->Destroy(); +#else SDL_GL_MakeCurrent(sdlWindow, nullptr); +#endif SDL_DestroyWindow(sdlWindow); #if !defined(HEADLESS) +#if !defined(__APPLE__) || !defined(RECOIL_MACOS_SDL3_EGL) if (glContext) SDL_GL_DeleteContext(glContext); +#endif #endif sdlWindow = nullptr; @@ -700,7 +783,11 @@ void CGlobalRendering::SwapBuffers(bool allowSwapBuffers, bool clearErrors) } #endif +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + macSDL3EGLBridge->SwapBuffers(); +#else SDL_GL_SwapWindow(sdlWindow); +#endif #ifdef _WIN32 if (forceDWMFlush == 2){ @@ -828,6 +915,7 @@ void CGlobalRendering::SetGLSupportFlags() haveIntel = ( glVendor.find( "intel") != std::string::npos); haveNvidia = ( glVendor.find("nvidia ") != std::string::npos); haveMesa = (glRenderer.find("mesa ") != std::string::npos) || (glRenderer.find("gallium ") != std::string::npos) || (glVersion.find(" mesa ") != std::string::npos); + const bool haveZinkMoltenVK = haveMesa && glRenderer.find("zink") != std::string::npos && glRenderer.find("moltenvk") != std::string::npos; if (haveAMD) { globalRenderingInfo.gpuName = globalRenderingInfo.glRenderer; @@ -908,6 +996,13 @@ void CGlobalRendering::SetGLSupportFlags() //supportFragDepthLayout = ((globalRenderingInfo.glContextVersion.x * 10 + globalRenderingInfo.glContextVersion.y) >= 42); supportFragDepthLayout = GLAD_GL_ARB_conservative_depth; //stick to the theory that reported = exist + const int customBorderCfg = configHandler->GetInt("ZinkMoltenVKCustomBorderColorUnsafe"); + const int logicOpCfg = configHandler->GetInt("ZinkMoltenVKLogicOpUnsafe"); + const int geometryShaderCfg = configHandler->GetInt("GeometryShadersUnsupported"); + zinkMoltenVKCustomBorderColorUnsafe = customBorderCfg > 0 || (customBorderCfg < 0 && haveZinkMoltenVK); + zinkMoltenVKLogicOpUnsafe = logicOpCfg > 0 || (logicOpCfg < 0 && haveZinkMoltenVK); + geometryShadersUnsupported = geometryShaderCfg > 0 || (geometryShaderCfg < 0 && haveZinkMoltenVK); + //stick to the theory that reported = exist //supportMSAAFrameBuffer &= ((globalRenderingInfo.glContextVersion.x * 10 + globalRenderingInfo.glContextVersion.y) >= 32); @@ -1002,7 +1097,26 @@ void CGlobalRendering::QueryVersionInfo(char (&sdlVersionStr)[64], char (&glVidM sdlVC.major, sdlVC.minor, sdlVC.patch ); - if (!GetAvailableVideoRAM(&grInfo.gpuMemorySize.x, grInfo.glVendor)) + bool haveVideoRAM = GetAvailableVideoRAM(&grInfo.gpuMemorySize.x, grInfo.glVendor); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (!haveVideoRAM && macSDL3EGLBridge != nullptr) { + const uint64_t metalWorkingSetBytes = macSDL3EGLBridge->GetRecommendedMaxWorkingSetSizeBytes(); + const uint64_t metalWorkingSetKiB = metalWorkingSetBytes / 1024; + + if (metalWorkingSetKiB > 0) { + const uint64_t clampedWorkingSetKiB = std::min( + metalWorkingSetKiB, + static_cast(std::numeric_limits::max()) + ); + + grInfo.gpuMemorySize.x = static_cast(clampedWorkingSetKiB); + grInfo.gpuMemorySize.y = grInfo.gpuMemorySize.x; + haveVideoRAM = true; + } + } +#endif + + if (!haveVideoRAM) return; const GLint totalMemMB = grInfo.gpuMemorySize.x / 1024; @@ -1023,10 +1137,19 @@ void CGlobalRendering::LogVersionInfo(const char* sdlVersionStr, const char* glV LOG("\tGPU memory : %s", glVidMemStr); LOG("\tSDL swap-int: %d", SDL_GL_GetSwapInterval()); LOG("\tSDL driver : %s", globalRenderingInfo.sdlDriverName); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (macSDL3EGLBridge != nullptr) { + macSDL3EGLBridge->LogRuntimeEnvironment(); + macSDL3EGLBridge->LogGeometry("LogVersionInfo"); + } +#endif LOG("\t"); LOG("\tInitialized OpenGL Context: %i.%i (%s)", globalRenderingInfo.glContextVersion.x, globalRenderingInfo.glContextVersion.y, globalRenderingInfo.glContextIsCore ? "Core" : "Compat"); LOG("\tGLSL shader support : %i", true); LOG("\tGL4 support : %i", haveGL4); + LOG("\tZink/MoltenVK custom border unsafe: %i", zinkMoltenVKCustomBorderColorUnsafe); + LOG("\tZink/MoltenVK logic-op unsafe : %i", zinkMoltenVKLogicOpUnsafe); + LOG("\tgeometry shaders unsupported : %i", geometryShadersUnsupported); LOG("\tFBO extension support : %i", FBO::IsSupported()); LOG("\tNVX GPU mem-info support : %i", IsExtensionSupported("GL_NVX_gpu_memory_info")); LOG("\tATI GPU mem-info support : %i", IsExtensionSupported("GL_ATI_meminfo")); @@ -1232,6 +1355,15 @@ void CGlobalRendering::SetWindowAttributes(SDL_Window* window) const int2 maxRes = GetMaxWinRes(); int2 newRes = GetCfgWinRes(); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + SDL_Rect safeBounds = MacSDL3EGL::GetSafeDisplayBounds(window); + if (fullScreen || borderless) { + winPosX = safeBounds.x; + winPosY = safeBounds.y; + newRes = {safeBounds.w, safeBounds.h}; + } +#endif + LOG("[GR::%s][1] cfgFullScreen=%d numDisplays=%d winPos=<%d,%d> newRes=<%d,%d>", __func__, fullScreen, numDisplays, winPosX, winPosY, newRes.x, newRes.y); GetWindowPosSizeBounded(winPosX, winPosY, newRes.x, newRes.y); LOG("[GR::%s][2] cfgFullScreen=%d numDisplays=%d winPos=<%d,%d> newRes=<%d,%d>", __func__, fullScreen, numDisplays, winPosX, winPosY, newRes.x, newRes.y); @@ -1245,12 +1377,23 @@ void CGlobalRendering::SetWindowAttributes(SDL_Window* window) SDL_SetWindowPosition(window, winPosX, winPosY); SDL_SetWindowSize(window, newRes.x, newRes.y); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (SDL_SetWindowFullscreen(window, 0) != 0) + LOG("[GR::%s][4][SDL_SetWindowFullscreen] err=\"%s\"", __func__, SDL_GetError()); + SDL_SetWindowBordered(window, (borderless || fullScreen) ? SDL_FALSE : SDL_TRUE); + MacSDL3EGL::LogDisplayGeometry(window, "SetWindowAttributes"); +#else if (SDL_SetWindowFullscreen(window, (borderless ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) * fullScreen) != 0) LOG("[GR::%s][4][SDL_SetWindowFullscreen] err=\"%s\"", __func__, SDL_GetError()); SDL_SetWindowBordered(window, borderless ? SDL_FALSE : SDL_TRUE); +#endif - if (newRes == maxRes) + if (newRes == maxRes +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + && !fullScreen && !borderless +#endif + ) SDL_MaximizeWindow(window); WindowManagerHelper::SetWindowResizable(window, !borderless && !fullScreen); @@ -1366,9 +1509,14 @@ bool CGlobalRendering::SetWindowPosHelper(int displayIdx, int winRPosX, int winR } int2 CGlobalRendering::GetMaxWinRes() const { +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + SDL_Rect safeBounds = MacSDL3EGL::GetSafeDisplayBounds(sdlWindow); + return {safeBounds.w, safeBounds.h}; +#else SDL_DisplayMode dmode; SDL_GetDesktopDisplayMode(GetCurrentDisplayIndex(), &dmode); return {dmode.w, dmode.h}; +#endif } int2 CGlobalRendering::GetCfgWinRes() const @@ -1399,6 +1547,12 @@ void CGlobalRendering::GetDisplayBounds(SDL_Rect& r, const int* di) const void CGlobalRendering::GetUsableDisplayBounds(SDL_Rect& r, const int* di) const { +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (di == nullptr) { + r = MacSDL3EGL::GetSafeDisplayBounds(sdlWindow); + return; + } +#endif const int displayIndex = di ? *di : GetCurrentDisplayIndex(); SDL_GetDisplayUsableBounds(displayIndex, &r); } @@ -1570,6 +1724,11 @@ void CGlobalRendering::ReadWindowPosAndSize() SDL_GetWindowSize(sdlWindow, &winSizeX, &winSizeY); SDL_GetWindowPosition(sdlWindow, &winPosX, &winPosY); +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (macSDL3EGLBridge != nullptr) + macSDL3EGLBridge->UpdateDrawableSize("ReadWindowPosAndSize"); + MacSDL3EGL::LogDisplayGeometry(sdlWindow, "ReadWindowPosAndSize"); +#endif //enforce >=0 https://github.com/beyond-all-reason/spring/issues/23 //winPosX = std::max(winPosX, 0); @@ -2015,12 +2174,84 @@ bool CGlobalRendering::ToggleGLDebugOutput(unsigned int msgSrceIdx, unsigned int return true; } +static GLint ScaleDefaultFramebufferCoord(int value, float scale) +{ + return static_cast((static_cast(value) * scale) + (value >= 0 ? 0.5f : -0.5f)); +} + +static GLsizei ScaleDefaultFramebufferSize(int value, float scale) +{ + return static_cast((static_cast(value) * scale) + 0.5f); +} + +void CGlobalRendering::LoadDefaultFramebufferViewport(int px, int py, int sx, int sy) const +{ +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (sdlWindow != nullptr) { + GLint drawFramebuffer = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFramebuffer); + if (drawFramebuffer != 0) { + glViewport(px, py, sx, sy); + return; + } + + const MacSDL3EGL::DisplayGeometry displayGeometry = MacSDL3EGL::QueryDisplayGeometry(sdlWindow); + const float scaleX = + (displayGeometry.logicalWindowWidth > 0 && displayGeometry.drawableWidth > 0)? + (static_cast(displayGeometry.drawableWidth) / static_cast(displayGeometry.logicalWindowWidth)): 1.0f; + const float scaleY = + (displayGeometry.logicalWindowHeight > 0 && displayGeometry.drawableHeight > 0)? + (static_cast(displayGeometry.drawableHeight) / static_cast(displayGeometry.logicalWindowHeight)): 1.0f; + + glViewport( + ScaleDefaultFramebufferCoord(px, scaleX), + ScaleDefaultFramebufferCoord(py, scaleY), + ScaleDefaultFramebufferSize(sx, scaleX), + ScaleDefaultFramebufferSize(sy, scaleY) + ); + return; + } +#endif + glViewport(px, py, sx, sy); +} + +void CGlobalRendering::LoadDefaultFramebufferScissor(int px, int py, int sx, int sy) const +{ +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + if (sdlWindow != nullptr) { + GLint drawFramebuffer = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFramebuffer); + if (drawFramebuffer != 0) { + glScissor(px, py, sx, sy); + return; + } + + const MacSDL3EGL::DisplayGeometry displayGeometry = MacSDL3EGL::QueryDisplayGeometry(sdlWindow); + const float scaleX = + (displayGeometry.logicalWindowWidth > 0 && displayGeometry.drawableWidth > 0)? + (static_cast(displayGeometry.drawableWidth) / static_cast(displayGeometry.logicalWindowWidth)): 1.0f; + const float scaleY = + (displayGeometry.logicalWindowHeight > 0 && displayGeometry.drawableHeight > 0)? + (static_cast(displayGeometry.drawableHeight) / static_cast(displayGeometry.logicalWindowHeight)): 1.0f; + + glScissor( + ScaleDefaultFramebufferCoord(px, scaleX), + ScaleDefaultFramebufferCoord(py, scaleY), + ScaleDefaultFramebufferSize(sx, scaleX), + ScaleDefaultFramebufferSize(sy, scaleY) + ); + return; + } +#endif + glScissor(px, py, sx, sy); +} + void CGlobalRendering::LoadViewport() { - glViewport(viewPosX, viewPosY, viewSizeX, viewSizeY); + LoadDefaultFramebufferViewport(viewPosX, viewPosY, viewSizeX, viewSizeY); } void CGlobalRendering::LoadDualViewport() { - glViewport(dualViewPosX, dualViewPosY, dualViewSizeX, dualViewSizeY); + LoadDefaultFramebufferViewport(dualViewPosX, dualViewPosY, dualViewSizeX, dualViewSizeY); } diff --git a/rts/Rendering/GlobalRendering.h b/rts/Rendering/GlobalRendering.h index 0406b8bfed7..acc32123fed 100644 --- a/rts/Rendering/GlobalRendering.h +++ b/rts/Rendering/GlobalRendering.h @@ -14,10 +14,22 @@ #include "System/type2.h" class SharedLib; + struct SDL_version; struct SDL_Rect; struct SDL_Window; +#if !defined(SDL_video_h_) +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) +struct SDL_GLContextState; +typedef SDL_GLContextState* SDL_GLContext; +#else typedef void* SDL_GLContext; +#endif +#endif + +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) +namespace MacSDL3EGL { class Bridge; } +#endif /** * @brief Globally accessible unsynced, rendering related data @@ -98,6 +110,8 @@ class CGlobalRendering { void LoadViewport(); void LoadDualViewport(); + void LoadDefaultFramebufferViewport(int px, int py, int sx, int sy) const; + void LoadDefaultFramebufferScissor(int px, int py, int sx, int sy) const; void UpdateWindowBorders(SDL_Window* window) const; @@ -329,6 +343,9 @@ class CGlobalRendering { bool supportClipSpaceControl; bool supportSeamlessCubeMaps; bool supportFragDepthLayout; + bool zinkMoltenVKCustomBorderColorUnsafe; + bool zinkMoltenVKLogicOpUnsafe; + bool geometryShadersUnsupported; /** * Shader capabilities @@ -383,6 +400,9 @@ class CGlobalRendering { public: SDL_Window* sdlWindow; SDL_GLContext glContext; +#if defined(__APPLE__) && defined(RECOIL_MACOS_SDL3_EGL) + std::unique_ptr macSDL3EGLBridge; +#endif public: /** * @brief maximum texture unit number @@ -425,4 +445,3 @@ class CGlobalRendering { extern CGlobalRendering* globalRendering; #endif /* _GLOBAL_RENDERING_H */ - diff --git a/rts/Rendering/Models/AssIO.h b/rts/Rendering/Models/AssIO.h index 0f29cf04e83..3720bdb4522 100644 --- a/rts/Rendering/Models/AssIO.h +++ b/rts/Rendering/Models/AssIO.h @@ -3,6 +3,8 @@ #ifndef ASS_IO_H #define ASS_IO_H +#include + // assimp public headers modify #pragma pack across includes (clang -Wpragma-pack) #ifdef __clang__ #pragma clang diagnostic push diff --git a/rts/Rml/Backends/RmlUi_Backend.cpp b/rts/Rml/Backends/RmlUi_Backend.cpp index 70383aa17fb..9e43f7d6769 100644 --- a/rts/Rml/Backends/RmlUi_Backend.cpp +++ b/rts/Rml/Backends/RmlUi_Backend.cpp @@ -551,6 +551,16 @@ bool processContextEvent(Rml::Context* context, const SDL_Event& event) case SDL_TEXTINPUT: return true; // handled elsewhere +#if defined(RECOIL_MACOS_SDL3_EGL) + case SDL_WINDOWEVENT_SIZE_CHANGED: { + auto x = event.window.data1; + auto y = event.window.data2; + + state->render_interface.SetViewport(x, y); + state->winX = x; + state->winY = y; + } break; +#else case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { auto x = event.window.data1; @@ -561,6 +571,7 @@ bool processContextEvent(Rml::Context* context, const SDL_Event& event) state->winY = y; } } break; +#endif default: break; diff --git a/rts/Rml/Backends/RmlUi_SystemInterface.cpp b/rts/Rml/Backends/RmlUi_SystemInterface.cpp index adac2bf99ce..be944c111f5 100644 --- a/rts/Rml/Backends/RmlUi_SystemInterface.cpp +++ b/rts/Rml/Backends/RmlUi_SystemInterface.cpp @@ -193,16 +193,34 @@ bool RmlSDLRecoil::InputEventHandler(Rml::Context* context, const SDL_Event& ev) result = context->ProcessMouseWheel(float(-ev.wheel.y), GetKeyModifierState()); break; case SDL_KEYDOWN: +#if defined(RECOIL_MACOS_SDL3_EGL) + result = context->ProcessKeyDown(ConvertKey(ev.key.key), GetKeyModifierState()); + if (ev.key.key == SDLK_RETURN || ev.key.key == SDLK_KP_ENTER) +#else result = context->ProcessKeyDown(ConvertKey(ev.key.keysym.sym), GetKeyModifierState()); if (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_KP_ENTER) +#endif result &= context->ProcessTextInput('\n'); break; case SDL_KEYUP: +#if defined(RECOIL_MACOS_SDL3_EGL) + result = context->ProcessKeyUp(ConvertKey(ev.key.key), GetKeyModifierState()); +#else result = context->ProcessKeyUp(ConvertKey(ev.key.keysym.sym), GetKeyModifierState()); +#endif break; case SDL_TEXTINPUT: result = context->ProcessTextInput(Rml::String(&ev.text.text[0])); break; +#if defined(RECOIL_MACOS_SDL3_EGL) + case SDL_WINDOWEVENT_SIZE_CHANGED: { + Rml::Vector2i dimensions(ev.window.data1, ev.window.data2); + context->SetDimensions(dimensions); + } break; + case SDL_WINDOWEVENT_LEAVE: + context->ProcessMouseLeave(); + break; +#else case SDL_WINDOWEVENT: { switch (ev.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: { @@ -214,6 +232,7 @@ bool RmlSDLRecoil::InputEventHandler(Rml::Context* context, const SDL_Event& ev) break; } } break; +#endif default: break; } @@ -383,4 +402,3 @@ int RmlSDLRecoil::GetKeyModifierState() return retval; } - diff --git a/rts/Sim/Units/Unit.cpp b/rts/Sim/Units/Unit.cpp index 9d2f32b304d..86030800356 100644 --- a/rts/Sim/Units/Unit.cpp +++ b/rts/Sim/Units/Unit.cpp @@ -965,7 +965,8 @@ static auto SplitResourcePackIntoPositiveNegative (const SResourcePack &pack) { SResourcePack positive {0.0f}, negative {0.0f}; - for (auto [resourceID, value] : std::views::enumerate (pack)) { + for (int resourceID = 0; resourceID < SResourcePack::MAX_RESOURCES; ++resourceID) { + const auto value = pack[resourceID]; if (value < 0.0f) negative[resourceID] = -value; else diff --git a/rts/System/CMakeLists.txt b/rts/System/CMakeLists.txt index c8fae324574..daa6487adc6 100644 --- a/rts/System/CMakeLists.txt +++ b/rts/System/CMakeLists.txt @@ -1,4 +1,8 @@ +if(ENABLE_STREFLOP AND STREFLOP_NEON) + add_definitions(-DSTREFLOP_NEON) +endif() + # When USE_MIMALLOC is enabled, MemoryOverride.cpp handles operator new/delete with Tracy tracking. # Only compile TraceMemory.cpp when Tracy memory profiling is enabled but mimalloc is not used. if (TRACY_PROFILE_MEMORY AND NOT USE_MIMALLOC) @@ -144,8 +148,12 @@ set(sources_engine_System_Platform_Mac "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/MessageBox.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/CrashHandler.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/WindowManagerHelper.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Linux/ThreadSupport.cpp" ) +if (RECOIL_MACOS_SDL3_EGL) + list(APPEND sources_engine_System_Platform_Mac + "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/MacSDL3EGLBridge.mm" + ) +endif() set(sources_engine_System_Platform_Windows "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Win/CrashHandler.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Win/Hardware.cpp" @@ -157,6 +165,8 @@ set(sources_engine_System_Platform_Windows set(sources_engine_System_Threading_Mac "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/Signal.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/CpuTopology.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/ThreadSupport.cpp" ) set(sources_engine_System_Threading_Linux "${CMAKE_CURRENT_SOURCE_DIR}/Platform/Linux/CpuTopology.cpp" diff --git a/rts/System/FileSystem/ArchiveScanner.cpp b/rts/System/FileSystem/ArchiveScanner.cpp index ef176e51405..383e1c671a0 100644 --- a/rts/System/FileSystem/ArchiveScanner.cpp +++ b/rts/System/FileSystem/ArchiveScanner.cpp @@ -1842,4 +1842,3 @@ int CArchiveScanner::GetMetaFileClass(const std::string& filePath) return 0; } - diff --git a/rts/System/FileSystem/FileSystem.cpp b/rts/System/FileSystem/FileSystem.cpp index f319d2199ca..2652090b5ea 100644 --- a/rts/System/FileSystem/FileSystem.cpp +++ b/rts/System/FileSystem/FileSystem.cpp @@ -943,4 +943,4 @@ const std::string& FileSystem::GetCacheDir() { static const std::string cacheBaseDir = "cache"; return cacheBaseDir; -} \ No newline at end of file +} diff --git a/rts/System/FileSystem/FileSystem.h b/rts/System/FileSystem/FileSystem.h index f9bcf0097f6..63a3bfe1008 100644 --- a/rts/System/FileSystem/FileSystem.h +++ b/rts/System/FileSystem/FileSystem.h @@ -215,4 +215,4 @@ class FileSystem // static bool CheckDir(const std::string& dir) const; static const std::string& GetCacheDir(); -}; \ No newline at end of file +}; diff --git a/rts/System/Input/KeyInput.cpp b/rts/System/Input/KeyInput.cpp index ea46846863e..9bd2ebd7526 100644 --- a/rts/System/Input/KeyInput.cpp +++ b/rts/System/Input/KeyInput.cpp @@ -79,7 +79,7 @@ namespace KeyInput { void Update(int fakeMetaKey) { int numKeys = 0; - const uint8_t* kbState = SDL_GetKeyboardState(&numKeys); + const auto* kbState = SDL_GetKeyboardState(&numKeys); keyMods = SDL_GetModState(); @@ -134,10 +134,17 @@ namespace KeyInput { SDL_Event event; event.type = event.key.type = SDL_KEYUP; +#if defined(RECOIL_MACOS_SDL3_EGL) + event.key.down = false; + event.key.key = keycode; + event.key.mod = SDL_KMOD_NONE; + event.key.scancode = scancode; +#else event.key.state = SDL_RELEASED; event.key.keysym.sym = keycode; event.key.keysym.mod = 0; event.key.keysym.scancode = scancode; +#endif SDL_PushEvent(&event); } } diff --git a/rts/System/Input/MouseInput.cpp b/rts/System/Input/MouseInput.cpp index c7fbed6708c..905c270d606 100644 --- a/rts/System/Input/MouseInput.cpp +++ b/rts/System/Input/MouseInput.cpp @@ -91,6 +91,21 @@ bool IMouseInput::HandleSDLMouseEvent(const SDL_Event& event) mouse->MouseWheel(event.wheel.y); } break; +#if defined(RECOIL_MACOS_SDL3_EGL) + case SDL_WINDOWEVENT_ENTER: { + if (mouse != nullptr) + mouse->WindowEnter(); + } break; + case SDL_WINDOWEVENT_LEAVE: { + mousepos = { + globalRendering->viewPosX + (globalRendering->viewSizeX >> 1), + globalRendering->viewWindowOffsetY + (globalRendering->viewSizeY >> 1) + }; + + if (mouse != nullptr) + mouse->WindowLeave(); + } break; +#else case SDL_WINDOWEVENT: { switch (event.window.event) { case SDL_WINDOWEVENT_ENTER: { @@ -109,6 +124,7 @@ bool IMouseInput::HandleSDLMouseEvent(const SDL_Event& event) } break; } } break; +#endif } return false; @@ -261,4 +277,3 @@ void IMouseInput::FreeInstance(IMouseInput* mouseInp) { memset(mouseInputMem, 0, sizeof(mouseInputMem)); mouseInput = nullptr; } - diff --git a/rts/System/Matrix44f.h b/rts/System/Matrix44f.h index 3dd6767dd33..89a2cec4491 100644 --- a/rts/System/Matrix44f.h +++ b/rts/System/Matrix44f.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include "System/float3.h" @@ -176,9 +178,13 @@ class CMatrix44f }; std::string str() const { - return std::format( - "m44(\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f}\n{:.3f} {:.3f} {:.3f} {:.3f})", + char buf[256]; + std::snprintf( + buf, + sizeof(buf), + "m44(\n%.3f %.3f %.3f %.3f\n%.3f %.3f %.3f %.3f\n%.3f %.3f %.3f %.3f\n%.3f %.3f %.3f %.3f)", m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]); + return buf; } }; @@ -216,4 +222,4 @@ void delmat3(T*** mat) { delete [] **mat; delete [] *mat; delete [] mat; -} \ No newline at end of file +} diff --git a/rts/System/MemPoolTypes.h b/rts/System/MemPoolTypes.h index dcd5730e040..16fdf488e47 100644 --- a/rts/System/MemPoolTypes.h +++ b/rts/System/MemPoolTypes.h @@ -5,6 +5,7 @@ #include #include +#include #include // memset #include #include @@ -431,7 +432,12 @@ inline size_t StablePosAllocator::Allocate(size_t numElems) if (positionToSize.empty()) { size_t returnPos = data.size(); data.resize(data.size() + numElems); - myLog("StablePosAllocator::Allocate(%u) = %u [thread_id = %u]", uint32_t(numElems), uint32_t(returnPos), static_cast(Threading::GetCurrentThreadId())); + #ifdef __APPLE__ + const uint32_t threadId = static_cast(reinterpret_cast(Threading::GetCurrentThreadId())); + #else + const uint32_t threadId = static_cast(Threading::GetCurrentThreadId()); + #endif + myLog("StablePosAllocator::Allocate(%u) = %u [thread_id = %u]", uint32_t(numElems), uint32_t(returnPos), threadId); return returnPos; } @@ -556,4 +562,3 @@ inline void StablePosAllocator::Free(size_t firstElem, size_t numElems, const } #endif - diff --git a/rts/System/Platform/Mac/CpuTopology.cpp b/rts/System/Platform/Mac/CpuTopology.cpp new file mode 100644 index 00000000000..1c53d3f28f5 --- /dev/null +++ b/rts/System/Platform/Mac/CpuTopology.cpp @@ -0,0 +1,190 @@ +/* This file is part of the Recoil engine (GPL v2 or later), see LICENSE.html */ + +/** + * macOS-specific CPU topology detection using sysctl APIs. + * + * Apple Silicon (M1/M2/M3/M4) uses a big.LITTLE architecture with + * Performance (P) cores and Efficiency (E) cores. This implementation + * queries the hw.perflevel* sysctl keys to detect core types and cache + * topology. + * + * LIMITATION: macOS does not support pthread_setaffinity_np(). Thread + * placement on P-cores vs E-cores is controlled indirectly via QoS + * classes (e.g. QOS_CLASS_USER_INTERACTIVE for P-cores, + * QOS_CLASS_BACKGROUND for E-cores). The masks returned here are + * informational -- they follow the convention that P-cores occupy the + * low CPU indices and E-cores occupy the high indices, matching the + * logical numbering exposed by macOS on Apple Silicon. + */ + +#if defined(__APPLE__) + +#include "System/Platform/CpuTopology.h" +#include "System/Log/ILog.h" + +#include +#include +#include +#include + +namespace cpu_topology { + +#define MAX_CPUS 32 // Maximum logical CPUs (matches Linux/Windows limit) + +// Query an integer value via sysctlbyname. Returns true on success. +static bool SysctlInt(const char* name, int* out) { + size_t size = sizeof(*out); + if (sysctlbyname(name, out, &size, nullptr, 0) != 0) + return false; + return true; +} + +// Query a 64-bit value via sysctlbyname. Returns true on success. +static bool SysctlInt64(const char* name, int64_t* out) { + size_t size = sizeof(*out); + if (sysctlbyname(name, out, &size, nullptr, 0) != 0) + return false; + return true; +} + +ProcessorMasks GetProcessorMasks() { + ProcessorMasks masks; + + int nperflevels = 0; + if (!SysctlInt("hw.nperflevels", &nperflevels) || nperflevels < 1) { + // Fallback: no performance level info (Intel Mac or very old macOS). + // Treat every logical CPU as a performance core. + int ncpu = 0; + if (!SysctlInt("hw.ncpu", &ncpu)) + ncpu = static_cast(sysconf(_SC_NPROCESSORS_CONF)); + + if (ncpu > MAX_CPUS) + ncpu = MAX_CPUS; + + for (int i = 0; i < ncpu; ++i) + masks.performanceCoreMask |= (1u << i); + + LOG("macOS CpuTopology: no perflevel info, treating all %d CPUs as performance cores.", ncpu); + return masks; + } + + // Apple Silicon with big.LITTLE: + // perflevel0 = Performance cores (highest perf level) + // perflevel1 = Efficiency cores + int pcores = 0; + int ecores = 0; + SysctlInt("hw.perflevel0.physicalcpu", &pcores); + if (nperflevels >= 2) + SysctlInt("hw.perflevel1.physicalcpu", &ecores); + + // Clamp to MAX_CPUS. + const int totalCores = pcores + ecores; + if (totalCores > MAX_CPUS) { + LOG_L(L_WARNING, "macOS CpuTopology: total cores (%d) exceeds MAX_CPUS (%d), clamping.", totalCores, MAX_CPUS); + if (pcores > MAX_CPUS) pcores = MAX_CPUS; + ecores = std::min(ecores, MAX_CPUS - pcores); + } + + // Convention: P-cores occupy logical CPU indices [0, pcores), + // E-cores occupy [pcores, pcores + ecores). + for (int i = 0; i < pcores; ++i) + masks.performanceCoreMask |= (1u << i); + + for (int i = pcores; i < pcores + ecores; ++i) + masks.efficiencyCoreMask |= (1u << i); + + // Apple Silicon has no SMT / hyperthreading. + masks.hyperThreadLowMask = 0; + masks.hyperThreadHighMask = 0; + + LOG("macOS CpuTopology: %d P-cores, %d E-cores (perflevels=%d).", pcores, ecores, nperflevels); + return masks; +} + +ProcessorCaches GetProcessorCache() { + ProcessorCaches caches; + + int nperflevels = 0; + SysctlInt("hw.nperflevels", &nperflevels); + + int pcores = 0; + int ecores = 0; + + if (nperflevels >= 1) + SysctlInt("hw.perflevel0.physicalcpu", &pcores); + if (nperflevels >= 2) + SysctlInt("hw.perflevel1.physicalcpu", &ecores); + + // Clamp. + if (pcores + ecores > MAX_CPUS) { + if (pcores > MAX_CPUS) pcores = MAX_CPUS; + ecores = std::min(ecores, MAX_CPUS - pcores); + } + + // macOS does not expose per-cluster cache sharing maps, so treat all + // P-cores as one group and all E-cores as another. + auto addGroup = [&](int startCpu, int count, const char* l2Key) { + if (count <= 0) + return; + + ProcessorGroupCaches group; + for (int i = startCpu; i < startCpu + count && i < MAX_CPUS; ++i) + group.groupMask |= (1u << i); + + int64_t l2Size = 0; + if (l2Key && SysctlInt64(l2Key, &l2Size)) + group.cacheSizes[1] = static_cast(l2Size); + + int64_t l3Size = 0; + if (startCpu == 0 && SysctlInt64("hw.l3cachesize", &l3Size)) + group.cacheSizes[2] = static_cast(l3Size); + + caches.groupCaches.push_back(group); + }; + + if (nperflevels >= 1) + addGroup(0, pcores, "hw.perflevel0.l2cachesize"); + if (nperflevels >= 2) + addGroup(pcores, ecores, "hw.perflevel1.l2cachesize"); + + if (caches.groupCaches.empty()) { + // Fallback for Intel Macs or missing perflevel data. + int ncpu = 0; + if (!SysctlInt("hw.ncpu", &ncpu)) + ncpu = static_cast(sysconf(_SC_NPROCESSORS_CONF)); + if (ncpu > MAX_CPUS) ncpu = MAX_CPUS; + + ProcessorGroupCaches group; + for (int i = 0; i < ncpu; ++i) + group.groupMask |= (1u << i); + + int64_t l2 = 0; + if (SysctlInt64("hw.l2cachesize", &l2)) + group.cacheSizes[1] = static_cast(l2); + + int64_t l3 = 0; + if (SysctlInt64("hw.l3cachesize", &l3)) + group.cacheSizes[2] = static_cast(l3); + + caches.groupCaches.push_back(group); + } + + std::ranges::stable_sort( + caches.groupCaches, + [](const auto& lh, const auto& rh) { + return lh.cacheSizes[1] > rh.cacheSizes[1]; + }); + + return caches; +} + +ThreadPinPolicy GetThreadPinPolicy() { + // macOS cannot pin threads to specific cores (no pthread_setaffinity_np). + // ANY_PERF_CORE lets the scheduler choose among the performance cores, + // which aligns with using QoS_CLASS_USER_INTERACTIVE at the caller level. + return THREAD_PIN_POLICY_ANY_PERF_CORE; +} + +} // namespace cpu_topology + +#endif // __APPLE__ diff --git a/rts/System/Platform/Mac/CrashHandler.cpp b/rts/System/Platform/Mac/CrashHandler.cpp index 5716add1092..46bf61b97bb 100644 --- a/rts/System/Platform/Mac/CrashHandler.cpp +++ b/rts/System/Platform/Mac/CrashHandler.cpp @@ -73,7 +73,7 @@ static void TranslateStackTrace(StackTrace& stacktrace, const int logLevel) stackFrame.path = ""; } - LOG_L(L_DEBUG, "\tsymbol = \"%s\", path = \"%s\", addr = 0x%lx", stackFrame.symbol.c_str(), path, stackFrame.ip); + LOG_L(L_DEBUG, "\tsymbol = \"%s\", path = \"%s\", addr = %p", stackFrame.symbol.c_str(), path, stackFrame.ip); } LOG_L(L_DEBUG, "[%s][2]", __func__); @@ -116,7 +116,11 @@ static void TranslateStackTrace(StackTrace& stacktrace, const int logLevel) execCommandString.clear(); stackFrameIndices.clear(); + #if defined(__aarch64__) || defined(__arm64__) + execCommandBuffer << ADDR2LINE << " -o " << modulePath << " -arch arm64 -l " << std::hex << addrPathPair.first; + #else execCommandBuffer << ADDR2LINE << " -o " << modulePath << " -arch x86_64 -l " << std::hex << addrPathPair.first; + #endif // insert requested addresses that should be translated by atos int i = 0; diff --git a/rts/System/Platform/Mac/MacSDL3EGLBridge.h b/rts/System/Platform/Mac/MacSDL3EGLBridge.h new file mode 100644 index 00000000000..66c850cea31 --- /dev/null +++ b/rts/System/Platform/Mac/MacSDL3EGLBridge.h @@ -0,0 +1,69 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ + +#ifndef MAC_SDL3_EGL_BRIDGE_H +#define MAC_SDL3_EGL_BRIDGE_H + +#include + +#include + +struct SDL_Window; + +namespace MacSDL3EGL { + +struct DisplayGeometry { + int displayIndex = 0; + const char* displayName = "unknown"; + + SDL_Rect displayBounds = {0, 0, 0, 0}; + SDL_Rect usableBounds = {0, 0, 0, 0}; + SDL_Rect safeBounds = {0, 0, 0, 0}; + + int logicalWindowWidth = 0; + int logicalWindowHeight = 0; + int drawableWidth = 0; + int drawableHeight = 0; + float sdlScale = 1.0f; + float cocoaScale = 1.0f; + + bool m4Pro14NotchPolicy = false; + bool m4Pro14NotchPolicyForced = false; +}; + +DisplayGeometry QueryDisplayGeometry(SDL_Window* window); +SDL_Rect GetSafeDisplayBounds(SDL_Window* window); +void LogDisplayGeometry(SDL_Window* window, const char* phase); +void* GetGLProcAddress(const char* name); + +class Bridge { +public: + Bridge(); + ~Bridge(); + + Bridge(const Bridge&) = delete; + Bridge& operator=(const Bridge&) = delete; + + bool Initialize(SDL_Window* window, int requestMajor, int requestMinor, bool requestCoreProfile); + void Destroy(); + + bool MakeCurrent(bool clear) const; + bool SwapBuffers(); + void UpdateDrawableSize(const char* reason); + + void* GetContextOpaque() const; + bool IsInitialized() const; + int GetSwapInterval() const; + uint64_t GetRecommendedMaxWorkingSetSizeBytes() const; + + void LogRuntimeEnvironment() const; + void LogContextDiagnostics(const char* phase) const; + void LogGeometry(const char* phase) const; + +private: + struct Impl; + Impl* impl; +}; + +} // namespace MacSDL3EGL + +#endif // MAC_SDL3_EGL_BRIDGE_H diff --git a/rts/System/Platform/Mac/MacSDL3EGLBridge.mm b/rts/System/Platform/Mac/MacSDL3EGLBridge.mm new file mode 100644 index 00000000000..13558318469 --- /dev/null +++ b/rts/System/Platform/Mac/MacSDL3EGLBridge.mm @@ -0,0 +1,627 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ + +#include "System/Platform/Mac/MacSDL3EGLBridge.h" + +#import +#import +#import + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "System/Log/ILog.h" + +namespace { + +static constexpr uint32_t BAR_ON_MVK_COCOA_EGL_SURFACE_MAGIC = 0x434f4341u; +static constexpr uint32_t BAR_ON_MVK_COCOA_EGL_SURFACE_VERSION = 1u; + +struct BarOnMVKPrivateCocoaSurface { + uint32_t magic = BAR_ON_MVK_COCOA_EGL_SURFACE_MAGIC; + uint32_t version = BAR_ON_MVK_COCOA_EGL_SURFACE_VERSION; + void* metalLayer = nullptr; + uint32_t width = 0; + uint32_t height = 0; +}; + +using PFNGLGETSTRINGPROC_LOCAL = const unsigned char* (*)(unsigned int name); + +static constexpr unsigned int GL_VENDOR_LOCAL = 0x1F00; +static constexpr unsigned int GL_RENDERER_LOCAL = 0x1F01; +static constexpr unsigned int GL_VERSION_LOCAL = 0x1F02; +static constexpr unsigned int GL_SHADING_LANGUAGE_VERSION_LOCAL = 0x8B8C; + +static const char* EnvValue(const char* name) +{ + const char* value = std::getenv(name); + return value != nullptr ? value : ""; +} + +static bool EnvEnabled(const char* name) +{ + const char* value = std::getenv(name); + return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0; +} + +static float CocoaBackingScale(NSWindow* window) +{ + if (window != nil && window.backingScaleFactor > 0.0) + return static_cast(window.backingScaleFactor); + + if (NSScreen.mainScreen != nil && NSScreen.mainScreen.backingScaleFactor > 0.0) + return static_cast(NSScreen.mainScreen.backingScaleFactor); + + return 1.0f; +} + +static NSWindow* GetNSWindow(SDL_Window* window) +{ + if (window == nullptr) + return nil; + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + return (__bridge NSWindow*)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); +} + +static CGFloat NativeCocoaDrawableScale(NSWindow* nsWindow) +{ + const char* scaleEnv = std::getenv("BAR_NATIVE_COCOA_DRAWABLE_SCALE"); + if (scaleEnv != nullptr && scaleEnv[0] != '\0') { + if (std::strcmp(scaleEnv, "native") == 0 || std::strcmp(scaleEnv, "retina") == 0) + return CocoaBackingScale(nsWindow); + + char* endPtr = nullptr; + const float requestedScale = std::strtof(scaleEnv, &endPtr); + if (endPtr != scaleEnv && std::isfinite(requestedScale) && requestedScale > 0.0f) + return static_cast(requestedScale); + } + + return 1.0; +} + +static CGSize CurrentLayerDrawableSize(NSWindow* nsWindow) +{ + if (nsWindow == nil || nsWindow.contentView == nil || nsWindow.contentView.layer == nil) + return CGSizeMake(0.0, 0.0); + + CALayer* layer = nsWindow.contentView.layer; + if (![layer isKindOfClass:[CAMetalLayer class]]) + return CGSizeMake(0.0, 0.0); + + return ((CAMetalLayer*)layer).drawableSize; +} + +static CGSize UpdateLayerDrawableSize(NSWindow* nsWindow, NSView* view, CAMetalLayer* layer, const char* reason) +{ + const CGFloat cocoaScale = CocoaBackingScale(nsWindow); + const CGFloat drawableScale = NativeCocoaDrawableScale(nsWindow); + const NSRect bounds = view != nil ? view.bounds : NSMakeRect(0, 0, 0, 0); + const CGSize drawableSize = CGSizeMake(bounds.size.width * drawableScale, bounds.size.height * drawableScale); + const CGSize previousDrawableSize = layer.drawableSize; + const CGFloat previousScale = layer.contentsScale; + const bool sizeChanged = + std::abs(static_cast(previousDrawableSize.width - drawableSize.width)) > 0.5 || + std::abs(static_cast(previousDrawableSize.height - drawableSize.height)) > 0.5 || + std::abs(static_cast(previousScale - drawableScale)) > 0.001; + const bool quietPreSwap = (reason != nullptr && std::strcmp(reason, "pre-swap") == 0); + + layer.contentsScale = drawableScale; + if (sizeChanged) + layer.drawableSize = drawableSize; + + if (sizeChanged || !quietPreSwap) { + LOG("[MacSDL3EGL::%s] reason=%s logicalView=%.1fx%.1f drawable=%.1fx%.1f cocoaScale=%.3f drawableScale=%.3f changed=%d", + __func__, reason, + static_cast(bounds.size.width), + static_cast(bounds.size.height), + static_cast(drawableSize.width), + static_cast(drawableSize.height), + static_cast(cocoaScale), + static_cast(drawableScale), + static_cast(sizeChanged) + ); + } + + return drawableSize; +} + +static const char* PixelFormatName(MTLPixelFormat pixelFormat) +{ + switch (pixelFormat) { + case MTLPixelFormatBGRA8Unorm: return "MTLPixelFormatBGRA8Unorm"; + case MTLPixelFormatBGRA8Unorm_sRGB: return "MTLPixelFormatBGRA8Unorm_sRGB"; + default: return "unknown"; + } +} + +static EGLContext CreateContext(EGLDisplay display, EGLConfig config, int major, int minor, bool coreProfile) +{ + const EGLint contextAttribs[] = { + EGL_CONTEXT_MAJOR_VERSION, major, + EGL_CONTEXT_MINOR_VERSION, minor, + EGL_NONE + }; + + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context != EGL_NO_CONTEXT) { + LOG("[MacSDL3EGL::%s] eglCreateContext requested=%d.%d core=%d result=success", + __func__, major, minor, int(coreProfile)); + return context; + } + + LOG_L(L_WARNING, "[MacSDL3EGL::%s] eglCreateContext requested=%d.%d core=%d failed eglGetError=0x%04x; trying default context", + __func__, major, minor, int(coreProfile), eglGetError()); + + const EGLint fallbackAttribs[] = {EGL_NONE}; + context = eglCreateContext(display, config, EGL_NO_CONTEXT, fallbackAttribs); + if (context != EGL_NO_CONTEXT) { + LOG("[MacSDL3EGL::%s] eglCreateContext requested=fallback result=success", __func__); + } else { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglCreateContext requested=fallback failed eglGetError=0x%04x", __func__, eglGetError()); + } + + return context; +} + +static bool ChooseConfig(EGLDisplay display, EGLConfig* outConfig) +{ + const EGLint preferredAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE + }; + + EGLint count = 0; + if (eglChooseConfig(display, preferredAttribs, outConfig, 1, &count) && count > 0) { + LOG("[MacSDL3EGL::%s] eglChooseConfig depth=24 stencil=8 count=%d", __func__, count); + return true; + } + + LOG_L(L_WARNING, "[MacSDL3EGL::%s] preferred eglChooseConfig failed count=%d eglGetError=0x%04x; trying color-only config", + __func__, count, eglGetError()); + + const EGLint fallbackAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_NONE + }; + + count = 0; + if (eglChooseConfig(display, fallbackAttribs, outConfig, 1, &count) && count > 0) { + LOG("[MacSDL3EGL::%s] eglChooseConfig depth=0 stencil=0 count=%d", __func__, count); + return true; + } + + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglChooseConfig failed count=%d eglGetError=0x%04x", __func__, count, eglGetError()); + return false; +} + +static bool LooksLikeM4Pro14Panel(const MacSDL3EGL::DisplayGeometry& geometry) +{ + const bool logical1512 = geometry.displayBounds.w == 1512 || geometry.usableBounds.w == 1512; + const bool targetHeight = geometry.displayBounds.h == 982 || geometry.displayBounds.h == 950 || geometry.usableBounds.h == 950; + const bool retinaScale = geometry.sdlScale >= 1.9f || geometry.cocoaScale >= 1.9f; + const char* name = geometry.displayName != nullptr ? geometry.displayName : ""; + const bool builtinName = + std::strstr(name, "Built-in") != nullptr || + std::strstr(name, "Liquid Retina") != nullptr || + std::strstr(name, "Color LCD") != nullptr; + + return logical1512 && targetHeight && retinaScale && builtinName; +} + +static int DetectDisplayIndexFromMouse() +{ + const int numDisplays = SDL_GetNumVideoDisplays(); + if (numDisplays <= 1) + return 0; + + float mouseX = 0.0f; + float mouseY = 0.0f; + SDL_GetGlobalMouseState(&mouseX, &mouseY); + + const SDL_Point mousePoint{static_cast(mouseX), static_cast(mouseY)}; + SDL_Rect bounds; + + for (int displayIndex = 0; displayIndex < numDisplays; ++displayIndex) { + if (SDL_GetDisplayBounds(displayIndex, &bounds) != 0) + continue; + if (SDL_PointInRect(&mousePoint, &bounds)) + return displayIndex; + } + + return 0; +} + +static void ApplyNotchPolicy(MacSDL3EGL::DisplayGeometry* geometry) +{ + geometry->safeBounds.x = geometry->displayBounds.x; + geometry->safeBounds.w = 1512; + geometry->safeBounds.h = 950; + geometry->safeBounds.y = geometry->displayBounds.y + std::max(32, geometry->displayBounds.h - geometry->safeBounds.h); + geometry->m4Pro14NotchPolicy = true; +} + +} // namespace + +namespace MacSDL3EGL { + +struct Bridge::Impl { + SDL_Window* window = nullptr; + NSWindow* nsWindow = nil; + NSView* contentView = nil; + CAMetalLayer* metalLayer = nil; + id metalDevice = nil; + + BarOnMVKPrivateCocoaSurface privateSurface; + + EGLDisplay display = EGL_NO_DISPLAY; + EGLConfig config = nullptr; + EGLContext context = EGL_NO_CONTEXT; + EGLSurface surface = EGL_NO_SURFACE; + bool initialized = false; +}; + +DisplayGeometry QueryDisplayGeometry(SDL_Window* window) +{ + DisplayGeometry geometry; + + geometry.displayIndex = window != nullptr ? SDL_GetWindowDisplayIndex(window) : DetectDisplayIndexFromMouse(); + geometry.displayName = SDL_GetDisplayName(geometry.displayIndex); + SDL_GetDisplayBounds(geometry.displayIndex, &geometry.displayBounds); + SDL_GetDisplayUsableBounds(geometry.displayIndex, &geometry.usableBounds); + geometry.safeBounds = geometry.usableBounds.w > 0 && geometry.usableBounds.h > 0 ? geometry.usableBounds : geometry.displayBounds; + + if (window != nullptr) { + SDL_GetWindowSize(window, &geometry.logicalWindowWidth, &geometry.logicalWindowHeight); + SDL_GetWindowSizeInPixels(window, &geometry.drawableWidth, &geometry.drawableHeight); + geometry.sdlScale = SDL_GetWindowDisplayScale(window); + NSWindow* nsWindow = GetNSWindow(window); + geometry.cocoaScale = CocoaBackingScale(nsWindow); + + const CGSize layerDrawable = CurrentLayerDrawableSize(nsWindow); + if (layerDrawable.width > 0.0 && layerDrawable.height > 0.0) { + geometry.drawableWidth = static_cast(std::max(0.0, static_cast(layerDrawable.width))); + geometry.drawableHeight = static_cast(std::max(0.0, static_cast(layerDrawable.height))); + } else { + const CGFloat drawableScale = NativeCocoaDrawableScale(nsWindow); + geometry.drawableWidth = static_cast(std::max(0.0, static_cast(geometry.logicalWindowWidth * drawableScale))); + geometry.drawableHeight = static_cast(std::max(0.0, static_cast(geometry.logicalWindowHeight * drawableScale))); + } + } + + geometry.m4Pro14NotchPolicyForced = EnvEnabled("RECOIL_MACOS_FORCE_M4_14_NOTCH_POLICY"); + if (geometry.m4Pro14NotchPolicyForced || LooksLikeM4Pro14Panel(geometry)) + ApplyNotchPolicy(&geometry); + + return geometry; +} + +SDL_Rect GetSafeDisplayBounds(SDL_Window* window) +{ + return QueryDisplayGeometry(window).safeBounds; +} + +void LogDisplayGeometry(SDL_Window* window, const char* phase) +{ + const DisplayGeometry g = QueryDisplayGeometry(window); + LOG("[MacSDL3EGL::%s] phase=%s displayIndex=%d displayName=\"%s\" displayBounds=%d,%d %dx%d usableBounds=%d,%d %dx%d safeBounds=%d,%d %dx%d m4Pro14NotchPolicy=%d forced=%d logicalWindow=%dx%d drawable=%dx%d sdlScale=%.3f cocoaScale=%.3f fixedPolicy=top64Physical_top32Logical_safe1512x950_fillBottomUp", + __func__, phase, + g.displayIndex, + g.displayName != nullptr ? g.displayName : "unknown", + g.displayBounds.x, g.displayBounds.y, g.displayBounds.w, g.displayBounds.h, + g.usableBounds.x, g.usableBounds.y, g.usableBounds.w, g.usableBounds.h, + g.safeBounds.x, g.safeBounds.y, g.safeBounds.w, g.safeBounds.h, + int(g.m4Pro14NotchPolicy), + int(g.m4Pro14NotchPolicyForced), + g.logicalWindowWidth, g.logicalWindowHeight, + g.drawableWidth, g.drawableHeight, + g.sdlScale, + g.cocoaScale + ); +} + +void* GetGLProcAddress(const char* name) +{ + return reinterpret_cast(eglGetProcAddress(name)); +} + +Bridge::Bridge() + : impl(new Impl()) +{ +} + +Bridge::~Bridge() +{ + Destroy(); + delete impl; + impl = nullptr; +} + +bool Bridge::Initialize(SDL_Window* window, int requestMajor, int requestMinor, bool requestCoreProfile) +{ + if (impl->initialized) + return true; + + impl->window = window; + LogRuntimeEnvironment(); + + if (std::getenv("EGL_PLATFORM") == nullptr) { + setenv("EGL_PLATFORM", "cocoa", 0); + LOG("[MacSDL3EGL::%s] EGL_PLATFORM was unset; set to cocoa for project-local Mesa bridge", __func__); + } + + impl->nsWindow = GetNSWindow(window); + if (impl->nsWindow == nil || impl->nsWindow.contentView == nil) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] SDL Cocoa NSWindow/contentView unavailable via %s", + __func__, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER); + return false; + } + + impl->contentView = impl->nsWindow.contentView; + impl->metalDevice = MTLCreateSystemDefaultDevice(); + if (impl->metalDevice == nil) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] MTLCreateSystemDefaultDevice returned nil", __func__); + return false; + } + + impl->metalLayer = [[CAMetalLayer layer] retain]; + impl->metalLayer.device = impl->metalDevice; + impl->metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; + impl->metalLayer.framebufferOnly = YES; + impl->metalLayer.opaque = YES; + impl->metalLayer.displaySyncEnabled = YES; + impl->metalLayer.presentsWithTransaction = NO; + impl->metalLayer.delegate = static_cast>(impl->contentView); + impl->contentView.wantsLayer = YES; + impl->contentView.layer = impl->metalLayer; + + UpdateDrawableSize("initial-owned-layer"); + LOG("[MacSDL3EGL::%s] cocoa nswindow=%p nsview=%p metalDevice=\"%s\" cametallayer=%p pixelFormat=%s(%lu) appOwned=true", + __func__, + (__bridge void*)impl->nsWindow, + (__bridge void*)impl->contentView, + impl->metalDevice.name.UTF8String, + (__bridge void*)impl->metalLayer, + PixelFormatName(impl->metalLayer.pixelFormat), + static_cast(impl->metalLayer.pixelFormat) + ); + + impl->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (impl->display == EGL_NO_DISPLAY) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglGetDisplay(EGL_DEFAULT_DISPLAY) failed eglGetError=0x%04x", __func__, eglGetError()); + return false; + } + + EGLint eglMajor = 0; + EGLint eglMinor = 0; + if (!eglInitialize(impl->display, &eglMajor, &eglMinor)) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglInitialize failed eglGetError=0x%04x", __func__, eglGetError()); + return false; + } + + LOG("[MacSDL3EGL::%s] EGL_VERSION=%d.%d EGL_VENDOR=\"%s\" EGL_VERSION_STRING=\"%s\"", + __func__, eglMajor, eglMinor, + eglQueryString(impl->display, EGL_VENDOR), + eglQueryString(impl->display, EGL_VERSION) + ); + + if (!eglBindAPI(EGL_OPENGL_API)) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglBindAPI(EGL_OPENGL_API) failed eglGetError=0x%04x", __func__, eglGetError()); + return false; + } + + if (!ChooseConfig(impl->display, &impl->config)) + return false; + + impl->context = CreateContext(impl->display, impl->config, requestMajor, requestMinor, requestCoreProfile); + if (impl->context == EGL_NO_CONTEXT) + return false; + + impl->surface = eglCreateWindowSurface( + impl->display, + impl->config, + reinterpret_cast(&impl->privateSurface), + nullptr + ); + + if (impl->surface == EGL_NO_SURFACE) { + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglCreateWindowSurface target=BarOnMVKPrivateCocoaSurface failed eglGetError=0x%04x", + __func__, eglGetError()); + return false; + } + + LOG("[MacSDL3EGL::%s] eglCreateWindowSurface target=BarOnMVKPrivateCocoaSurface surface=%p privateSurface=%p magic=0x%08x version=%u layer=%p size=%ux%u", + __func__, + impl->surface, + &impl->privateSurface, + impl->privateSurface.magic, + impl->privateSurface.version, + impl->privateSurface.metalLayer, + impl->privateSurface.width, + impl->privateSurface.height + ); + + if (!MakeCurrent(false)) + return false; + + impl->initialized = true; + LogContextDiagnostics("after-egl-current-before-glad"); + LogGeometry("after-egl-current"); + return true; +} + +void Bridge::Destroy() +{ + if (impl == nullptr) + return; + + if (impl->display != EGL_NO_DISPLAY) + eglMakeCurrent(impl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (impl->display != EGL_NO_DISPLAY && impl->surface != EGL_NO_SURFACE) { + eglDestroySurface(impl->display, impl->surface); + impl->surface = EGL_NO_SURFACE; + } + + if (impl->display != EGL_NO_DISPLAY && impl->context != EGL_NO_CONTEXT) { + eglDestroyContext(impl->display, impl->context); + impl->context = EGL_NO_CONTEXT; + } + + if (impl->display != EGL_NO_DISPLAY) { + eglTerminate(impl->display); + impl->display = EGL_NO_DISPLAY; + } + + if (impl->contentView != nil && impl->contentView.layer == impl->metalLayer) + impl->contentView.layer = nil; + + if (impl->metalLayer != nil) { + [impl->metalLayer release]; + impl->metalLayer = nil; + } + + if (impl->metalDevice != nil) { + [impl->metalDevice release]; + impl->metalDevice = nil; + } + + impl->privateSurface = {}; + impl->window = nullptr; + impl->nsWindow = nil; + impl->contentView = nil; + impl->initialized = false; +} + +bool Bridge::MakeCurrent(bool clear) const +{ + if (impl->display == EGL_NO_DISPLAY) + return false; + + const EGLSurface draw = clear ? EGL_NO_SURFACE : impl->surface; + const EGLSurface read = clear ? EGL_NO_SURFACE : impl->surface; + const EGLContext context = clear ? EGL_NO_CONTEXT : impl->context; + + if (eglMakeCurrent(impl->display, draw, read, context)) + return true; + + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglMakeCurrent(clear=%d) failed eglGetError=0x%04x", __func__, int(clear), eglGetError()); + return false; +} + +bool Bridge::SwapBuffers() +{ + UpdateDrawableSize("pre-swap"); + if (eglSwapBuffers(impl->display, impl->surface)) + return true; + + LOG_L(L_ERROR, "[MacSDL3EGL::%s] eglSwapBuffers failed eglGetError=0x%04x", __func__, eglGetError()); + return false; +} + +void Bridge::UpdateDrawableSize(const char* reason) +{ + if (impl->metalLayer == nil) + return; + + const CGSize drawable = UpdateLayerDrawableSize(impl->nsWindow, impl->contentView, impl->metalLayer, reason); + impl->privateSurface.magic = BAR_ON_MVK_COCOA_EGL_SURFACE_MAGIC; + impl->privateSurface.version = BAR_ON_MVK_COCOA_EGL_SURFACE_VERSION; + impl->privateSurface.metalLayer = (__bridge void*)impl->metalLayer; + impl->privateSurface.width = static_cast(std::max(0.0, static_cast(drawable.width))); + impl->privateSurface.height = static_cast(std::max(0.0, static_cast(drawable.height))); +} + +void* Bridge::GetContextOpaque() const +{ + return impl->context; +} + +bool Bridge::IsInitialized() const +{ + return impl->initialized; +} + +int Bridge::GetSwapInterval() const +{ + return SDL_GL_GetSwapInterval(); +} + +uint64_t Bridge::GetRecommendedMaxWorkingSetSizeBytes() const +{ + if (impl == nullptr || impl->metalDevice == nil) + return 0; + + return static_cast(impl->metalDevice.recommendedMaxWorkingSetSize); +} + +void Bridge::LogRuntimeEnvironment() const +{ + LOG("[MacSDL3EGL::%s] SDL3 Cocoa + Mesa EGL runtime env VK_DRIVER_FILES=%s MESA_LOADER_DRIVER_OVERRIDE=%s GALLIUM_DRIVER=%s EGL_PLATFORM=%s MESA_GL_VERSION_OVERRIDE=%s MESA_GLSL_VERSION_OVERRIDE=%s VK_ICD_FILENAMES=%s VK_ADD_DRIVER_FILES=%s DYLD_LIBRARY_PATH=%s DYLD_FALLBACK_LIBRARY_PATH=%s", + __func__, + EnvValue("VK_DRIVER_FILES"), + EnvValue("MESA_LOADER_DRIVER_OVERRIDE"), + EnvValue("GALLIUM_DRIVER"), + EnvValue("EGL_PLATFORM"), + EnvValue("MESA_GL_VERSION_OVERRIDE"), + EnvValue("MESA_GLSL_VERSION_OVERRIDE"), + EnvValue("VK_ICD_FILENAMES"), + EnvValue("VK_ADD_DRIVER_FILES"), + EnvValue("DYLD_LIBRARY_PATH"), + EnvValue("DYLD_FALLBACK_LIBRARY_PATH") + ); + LOG("[MacSDL3EGL::%s] nullDescriptorBypassRisk=present_feasibility_only privateCocoaEGLBridge=BarOnMVKPrivateCocoaSurface", __func__); +} + +void Bridge::LogContextDiagnostics(const char* phase) const +{ + if (impl->display != EGL_NO_DISPLAY) { + LOG("[MacSDL3EGL::%s] phase=%s EGL_VENDOR=\"%s\" EGL_VERSION=\"%s\" EGL_EXTENSIONS=\"%s\"", + __func__, phase, + eglQueryString(impl->display, EGL_VENDOR), + eglQueryString(impl->display, EGL_VERSION), + eglQueryString(impl->display, EGL_EXTENSIONS) + ); + } + + PFNGLGETSTRINGPROC_LOCAL glGetStringLocal = + reinterpret_cast(eglGetProcAddress("glGetString")); + if (glGetStringLocal == nullptr) + return; + + LOG("[MacSDL3EGL::%s] phase=%s GL_VENDOR=\"%s\" GL_RENDERER=\"%s\" GL_VERSION=\"%s\" GL_SHADING_LANGUAGE_VERSION=\"%s\"", + __func__, phase, + reinterpret_cast(glGetStringLocal(GL_VENDOR_LOCAL)), + reinterpret_cast(glGetStringLocal(GL_RENDERER_LOCAL)), + reinterpret_cast(glGetStringLocal(GL_VERSION_LOCAL)), + reinterpret_cast(glGetStringLocal(GL_SHADING_LANGUAGE_VERSION_LOCAL)) + ); +} + +void Bridge::LogGeometry(const char* phase) const +{ + LogDisplayGeometry(impl->window, phase); +} + +} // namespace MacSDL3EGL diff --git a/rts/System/Platform/Mac/ThreadSupport.cpp b/rts/System/Platform/Mac/ThreadSupport.cpp new file mode 100644 index 00000000000..38def953b7e --- /dev/null +++ b/rts/System/Platform/Mac/ThreadSupport.cpp @@ -0,0 +1,47 @@ +/* This file is part of the Recoil engine (GPL v2 or later), see LICENSE.html */ + +#include "System/Platform/Threading.h" + +#include +#include + +namespace Threading { + +void SetupCurrentThreadControls(std::shared_ptr& threadCtls) +{ + threadCtls.reset(new Threading::ThreadControls()); + threadCtls->handle = pthread_self(); + + // macOS has no pthread_setaffinity_np equivalent. Scheduling locality is + // expressed with QoS hints instead; this asks Apple Silicon to prefer the + // performance cluster for worker threads that enter through ThreadStart. + pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); +} + +void ThreadStart( + std::function taskFunc, + std::shared_ptr* threadCtls, + ThreadControls* tempCtls +) { + if (threadCtls != nullptr) + SetupCurrentThreadControls(*threadCtls); + + { + std::lock_guard lock(tempCtls->mutSuspend); + tempCtls->condInitialized.notify_one(); + } + + taskFunc(); +} + +SuspendResult ThreadControls::Suspend() +{ + return Threading::THREADERR_NOT_RUNNING; +} + +SuspendResult ThreadControls::Resume() +{ + return Threading::THREADERR_NONE; +} + +} // namespace Threading diff --git a/rts/System/Platform/ThreadAffinityGuard.cpp b/rts/System/Platform/ThreadAffinityGuard.cpp index 713b7b5a401..d946541a1fc 100644 --- a/rts/System/Platform/ThreadAffinityGuard.cpp +++ b/rts/System/Platform/ThreadAffinityGuard.cpp @@ -3,22 +3,22 @@ #include "System/Log/ILog.h" #ifdef _WIN32 #include -#else +#elif defined(__linux__) #include #include -#include +#include #endif // Constructor: Saves the current thread's affinity ThreadAffinityGuard::ThreadAffinityGuard() : affinitySaved(false) { -#ifdef _WIN32 + #ifdef _WIN32 threadHandle = GetCurrentThread(); // Get the current thread handle savedAffinity = SetThreadAffinityMask(threadHandle, ~0); affinitySaved = ( savedAffinity != 0 ); if (!affinitySaved) { LOG_L(L_WARNING, "GetThreadAffinityMask failed with error code: %lu", GetLastError()); } -#else + #elif defined(__linux__) tid = syscall(SYS_gettid); // Get thread ID CPU_ZERO(&savedAffinity); if (sched_getaffinity(tid, sizeof(cpu_set_t), &savedAffinity) == 0) { @@ -26,20 +26,22 @@ ThreadAffinityGuard::ThreadAffinityGuard() : affinitySaved(false) { } else { LOG_L(L_WARNING, "Failed to save thread affinity."); } -#endif + #else + affinitySaved = false; + #endif } // Destructor: Restores the saved affinity if it was successfully stored ThreadAffinityGuard::~ThreadAffinityGuard() { if (affinitySaved) { -#ifdef _WIN32 + #ifdef _WIN32 if (!SetThreadAffinityMask(threadHandle, savedAffinity)) { LOG_L(L_WARNING, "SetThreadAffinityMask failed with error code: %lu", GetLastError()); } -#else + #elif defined(__linux__) if (sched_setaffinity(tid, sizeof(cpu_set_t), &savedAffinity) != 0) { LOG_L(L_WARNING, "Failed to restore thread affinity."); } -#endif + #endif } } diff --git a/rts/System/Platform/ThreadAffinityGuard.h b/rts/System/Platform/ThreadAffinityGuard.h index 2e456c56e48..7c684e33427 100644 --- a/rts/System/Platform/ThreadAffinityGuard.h +++ b/rts/System/Platform/ThreadAffinityGuard.h @@ -3,8 +3,9 @@ #ifdef _WIN32 #include -#else +#elif defined(__linux__) #include +#include #endif class ThreadAffinityGuard { @@ -12,7 +13,7 @@ class ThreadAffinityGuard { #ifdef _WIN32 DWORD_PTR savedAffinity; HANDLE threadHandle; -#else +#elif defined(__linux__) cpu_set_t savedAffinity; pid_t tid; #endif diff --git a/rts/System/SDLCompat/SDL.h b/rts/System/SDLCompat/SDL.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL2/SDL_keyboard.h b/rts/System/SDLCompat/SDL2/SDL_keyboard.h new file mode 100644 index 00000000000..8589c647766 --- /dev/null +++ b/rts/System/SDLCompat/SDL2/SDL_keyboard.h @@ -0,0 +1 @@ +#include "../SDL_keyboard.h" diff --git a/rts/System/SDLCompat/SDL2/SDL_mouse.h b/rts/System/SDLCompat/SDL2/SDL_mouse.h new file mode 100644 index 00000000000..238ad814160 --- /dev/null +++ b/rts/System/SDLCompat/SDL2/SDL_mouse.h @@ -0,0 +1 @@ +#include "../SDL_mouse.h" diff --git a/rts/System/SDLCompat/SDL2/SDL_rect.h b/rts/System/SDLCompat/SDL2/SDL_rect.h new file mode 100644 index 00000000000..3986dd7bbb0 --- /dev/null +++ b/rts/System/SDLCompat/SDL2/SDL_rect.h @@ -0,0 +1 @@ +#include "../SDL_rect.h" diff --git a/rts/System/SDLCompat/SDLRecoilCompat.h b/rts/System/SDLCompat/SDLRecoilCompat.h new file mode 100644 index 00000000000..b15700f96d7 --- /dev/null +++ b/rts/System/SDLCompat/SDLRecoilCompat.h @@ -0,0 +1,361 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ + +#ifndef RECOIL_SDL_RECOIL_COMPAT_H +#define RECOIL_SDL_RECOIL_COMPAT_H + +#if defined(RECOIL_MACOS_SDL3_EGL) + +/* + * The macOS SDL3/EGL runtime keeps Recoil's existing SDL2-shaped source + * contract at this include boundary. This is a source-compatibility facade: + * the linked runtime remains SDL3. + */ + +#ifndef SDL_ENABLE_OLD_NAMES +#define SDL_ENABLE_OLD_NAMES 1 +#endif + +#if defined(camera) +#define RECOIL_SDL_COMPAT_RESTORE_CAMERA_MACRO 1 +#pragma push_macro("camera") +#undef camera +#endif + +#include + +#if defined(RECOIL_SDL_COMPAT_RESTORE_CAMERA_MACRO) +#pragma pop_macro("camera") +#undef RECOIL_SDL_COMPAT_RESTORE_CAMERA_MACRO +#endif + +typedef struct SDL_version { + int major; + int minor; + int patch; +} SDL_version; + +static inline void SDLCompat_VersionFromNumber(int version, SDL_version* outVersion) +{ + if (outVersion == NULL) + return; + + outVersion->major = version / 1000000; + outVersion->minor = (version / 1000) % 1000; + outVersion->patch = version % 1000; +} + +static inline void SDLCompat_GetCompiledVersion(SDL_version* outVersion) +{ + if (outVersion == NULL) + return; + + outVersion->major = SDL_MAJOR_VERSION; + outVersion->minor = SDL_MINOR_VERSION; + outVersion->patch = SDL_MICRO_VERSION; +} + +static inline void SDLCompat_GetLinkedVersion(SDL_version* outVersion) +{ + SDLCompat_VersionFromNumber(SDL_GetVersion(), outVersion); +} + +#undef SDL_VERSION +#define SDL_VERSION(outVersion) SDLCompat_GetCompiledVersion(outVersion) +#define SDL_GetVersion(outVersion) SDLCompat_GetLinkedVersion(outVersion) + +#ifndef SDL_BPP +#define SDL_BPP(format) SDL_BITSPERPIXEL(format) +#endif + +#ifndef SDL_QUERY +#define SDL_QUERY -1 +#endif +#ifndef SDL_DISABLE +#define SDL_DISABLE 0 +#endif +#ifndef SDL_ENABLE +#define SDL_ENABLE 1 +#endif +#ifndef SDL_HINT_MOUSE_RELATIVE_MODE_WARP +#define SDL_HINT_MOUSE_RELATIVE_MODE_WARP "SDL_MOUSE_RELATIVE_MODE_WARP" +#endif + +static inline SDL_DisplayID SDLCompat_GetDisplayIDFromIndex(int displayIndex) +{ + int count = 0; + SDL_DisplayID* displays = SDL_GetDisplays(&count); + if (displays == NULL || count <= 0) + return 0; + + const int boundedIndex = displayIndex < 0 ? 0 : (displayIndex >= count ? count - 1 : displayIndex); + const SDL_DisplayID displayID = displays[boundedIndex]; + SDL_free(displays); + return displayID; +} + +static inline int SDLCompat_GetNumVideoDisplays() +{ + int count = 0; + SDL_DisplayID* displays = SDL_GetDisplays(&count); + SDL_free(displays); + return count; +} + +static inline int SDLCompat_GetDisplayBounds(int displayIndex, SDL_Rect* rect) +{ + return SDL_GetDisplayBounds(SDLCompat_GetDisplayIDFromIndex(displayIndex), rect) ? 0 : -1; +} + +static inline int SDLCompat_GetDisplayUsableBounds(int displayIndex, SDL_Rect* rect) +{ + return SDL_GetDisplayUsableBounds(SDLCompat_GetDisplayIDFromIndex(displayIndex), rect) ? 0 : -1; +} + +static inline const char* SDLCompat_GetDisplayName(int displayIndex) +{ + const char* name = SDL_GetDisplayName(SDLCompat_GetDisplayIDFromIndex(displayIndex)); + return name != NULL ? name : "unknown"; +} + +static inline int SDLCompat_GetNumDisplayModes(int displayIndex) +{ + int count = 0; + SDL_DisplayMode** modes = SDL_GetFullscreenDisplayModes(SDLCompat_GetDisplayIDFromIndex(displayIndex), &count); + SDL_free(modes); + return count; +} + +static inline int SDLCompat_CopyDisplayMode(const SDL_DisplayMode* mode, SDL_DisplayMode* outMode) +{ + if (mode == NULL || outMode == NULL) + return -1; + + *outMode = *mode; + return 0; +} + +static inline int SDLCompat_GetDisplayMode(int displayIndex, int modeIndex, SDL_DisplayMode* outMode) +{ + int count = 0; + SDL_DisplayMode** modes = SDL_GetFullscreenDisplayModes(SDLCompat_GetDisplayIDFromIndex(displayIndex), &count); + if (modes == NULL || modeIndex < 0 || modeIndex >= count) { + SDL_free(modes); + return -1; + } + + const int result = SDLCompat_CopyDisplayMode(modes[modeIndex], outMode); + SDL_free(modes); + return result; +} + +static inline int SDLCompat_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode* outMode) +{ + return SDLCompat_CopyDisplayMode(SDL_GetDesktopDisplayMode(SDLCompat_GetDisplayIDFromIndex(displayIndex)), outMode); +} + +static inline int SDLCompat_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode* outMode) +{ + return SDLCompat_CopyDisplayMode(SDL_GetCurrentDisplayMode(SDLCompat_GetDisplayIDFromIndex(displayIndex)), outMode); +} + +static inline int SDLCompat_GetWindowDisplayMode(SDL_Window* window, SDL_DisplayMode* outMode) +{ + return SDLCompat_CopyDisplayMode(SDL_GetWindowFullscreenMode(window), outMode); +} + +static inline int SDLCompat_GetWindowDisplayIndex(SDL_Window* window) +{ + const SDL_DisplayID windowDisplay = SDL_GetDisplayForWindow(window); + int count = 0; + SDL_DisplayID* displays = SDL_GetDisplays(&count); + if (displays == NULL) + return 0; + + int index = 0; + for (int i = 0; i < count; ++i) { + if (displays[i] == windowDisplay) { + index = i; + break; + } + } + + SDL_free(displays); + return index; +} + +static inline int SDLCompat_SetWindowFullscreen(SDL_Window* window, Uint32 flags) +{ + return SDL_SetWindowFullscreen(window, flags != 0) ? 0 : -1; +} + +static inline SDL_Window* SDLCompat_CreateWindow4(const char* title, int w, int h, SDL_WindowFlags flags) +{ + return SDL_CreateWindow(title, w, h, flags); +} + +static inline SDL_Window* SDLCompat_CreateWindow6(const char* title, int x, int y, int w, int h, Uint32 flags) +{ + SDL_Window* window = SDL_CreateWindow(title, w, h, (SDL_WindowFlags)flags); + if (window != NULL) + SDL_SetWindowPosition(window, x, y); + return window; +} + +static inline void SDLCompat_SetWindowGrab(SDL_Window* window, bool grabbed) +{ + SDL_SetWindowMouseGrab(window, grabbed); +} + +static inline bool SDLCompat_GetWindowGrab(SDL_Window* window) +{ + return SDL_GetWindowMouseGrab(window); +} + +static inline int SDLCompat_GL_SetSwapInterval(int interval) +{ + return SDL_GL_SetSwapInterval(interval) ? 0 : -1; +} + +static inline int SDLCompat_GL_GetSwapInterval() +{ + int interval = 0; + if (!SDL_GL_GetSwapInterval(&interval)) + return 0; + return interval; +} + +static inline int SDLCompat_ShowCursor(int toggle) +{ + if (toggle == SDL_QUERY) + return SDL_CursorVisible() ? SDL_ENABLE : SDL_DISABLE; + + const bool result = (toggle == SDL_ENABLE) ? SDL_ShowCursor() : SDL_HideCursor(); + return result ? SDL_ENABLE : SDL_DISABLE; +} + +static inline int SDLCompat_PauseAudioDevice(SDL_AudioDeviceID deviceID, int pauseOn) +{ + return (pauseOn ? SDL_PauseAudioDevice(deviceID) : SDL_ResumeAudioDevice(deviceID)) ? 0 : -1; +} + +static inline bool SDLCompat_StartTextInput0() +{ + SDL_Window* window = SDL_GetKeyboardFocus(); + return window != NULL ? SDL_StartTextInput(window) : false; +} + +static inline bool SDLCompat_StartTextInput1(SDL_Window* window) +{ + return SDL_StartTextInput(window); +} + +static inline bool SDLCompat_StopTextInput0() +{ + SDL_Window* window = SDL_GetKeyboardFocus(); + return window != NULL ? SDL_StopTextInput(window) : false; +} + +static inline bool SDLCompat_StopTextInput1(SDL_Window* window) +{ + return SDL_StopTextInput(window); +} + +static inline bool SDLCompat_SetTextInputRect(const SDL_Rect* rect) +{ + SDL_Window* window = SDL_GetKeyboardFocus(); + return window != NULL ? SDL_SetTextInputArea(window, rect, 0) : false; +} + +static inline SDL_Surface* SDLCompat_CreateRGBSurfaceFrom(void* pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) +{ + const SDL_PixelFormat format = SDL_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask); + if (format == SDL_PIXELFORMAT_UNKNOWN) + return NULL; + + return SDL_CreateSurfaceFrom(width, height, format, pixels, pitch); +} + +static inline SDL_MouseButtonFlags SDLCompat_GetMouseState(int* x, int* y) +{ + float xf = 0.0f; + float yf = 0.0f; + const SDL_MouseButtonFlags buttons = SDL_GetMouseState(&xf, &yf); + + if (x != NULL) + *x = static_cast(xf); + if (y != NULL) + *y = static_cast(yf); + + return buttons; +} + +static inline SDL_Keycode SDLCompat_GetKeyFromScancode(SDL_Scancode scancode) +{ + return SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); +} + +static inline SDL_Scancode SDLCompat_GetScancodeFromKey(SDL_Keycode keycode) +{ + SDL_Keymod modstate = SDL_KMOD_NONE; + return SDL_GetScancodeFromKey(keycode, &modstate); +} + +#undef SDL_GetNumVideoDisplays +#undef SDL_GetDisplayBounds +#undef SDL_GetDisplayUsableBounds +#undef SDL_GetDisplayName +#undef SDL_GetNumDisplayModes +#undef SDL_GetDisplayMode +#undef SDL_GetDesktopDisplayMode +#undef SDL_GetCurrentDisplayMode +#undef SDL_GetWindowDisplayMode +#undef SDL_GetWindowDisplayIndex +#undef SDL_CreateWindow +#undef SDL_SetWindowFullscreen +#undef SDL_SetWindowGrab +#undef SDL_GetWindowGrab +#undef SDL_GL_SetSwapInterval +#undef SDL_GL_GetSwapInterval +#undef SDL_ShowCursor +#undef SDL_PauseAudioDevice +#undef SDL_StartTextInput +#undef SDL_StopTextInput +#undef SDL_SetTextInputRect +#undef SDL_CreateRGBSurfaceFrom +#undef SDL_GetMouseState +#undef SDL_GetKeyFromScancode +#undef SDL_GetScancodeFromKey + +#define SDL_GetNumVideoDisplays() SDLCompat_GetNumVideoDisplays() +#define SDL_GetDisplayBounds(displayIndex, rect) SDLCompat_GetDisplayBounds(displayIndex, rect) +#define SDL_GetDisplayUsableBounds(displayIndex, rect) SDLCompat_GetDisplayUsableBounds(displayIndex, rect) +#define SDL_GetDisplayName(displayIndex) SDLCompat_GetDisplayName(displayIndex) +#define SDL_GetNumDisplayModes(displayIndex) SDLCompat_GetNumDisplayModes(displayIndex) +#define SDL_GetDisplayMode(displayIndex, modeIndex, outMode) SDLCompat_GetDisplayMode(displayIndex, modeIndex, outMode) +#define SDL_GetDesktopDisplayMode(displayIndex, outMode) SDLCompat_GetDesktopDisplayMode(displayIndex, outMode) +#define SDL_GetCurrentDisplayMode(displayIndex, outMode) SDLCompat_GetCurrentDisplayMode(displayIndex, outMode) +#define SDL_GetWindowDisplayMode(window, outMode) SDLCompat_GetWindowDisplayMode(window, outMode) +#define SDL_GetWindowDisplayIndex(window) SDLCompat_GetWindowDisplayIndex(window) +#define SDLCompat_CreateWindowSelect(_1, _2, _3, _4, _5, _6, NAME, ...) NAME +#define SDL_CreateWindow(...) SDLCompat_CreateWindowSelect(__VA_ARGS__, SDLCompat_CreateWindow6, SDLCompat_CreateWindow5_unsupported, SDLCompat_CreateWindow4)(__VA_ARGS__) +#define SDL_SetWindowFullscreen(window, flags) SDLCompat_SetWindowFullscreen(window, flags) +#define SDL_SetWindowGrab(window, grabbed) SDLCompat_SetWindowGrab(window, grabbed) +#define SDL_GetWindowGrab(window) SDLCompat_GetWindowGrab(window) +#define SDL_GL_SetSwapInterval(interval) SDLCompat_GL_SetSwapInterval(interval) +#define SDL_GL_GetSwapInterval() SDLCompat_GL_GetSwapInterval() +#define SDL_ShowCursor(toggle) SDLCompat_ShowCursor(toggle) +#define SDL_PauseAudioDevice(deviceID, pauseOn) SDLCompat_PauseAudioDevice(deviceID, pauseOn) +#define SDLCompat_TextInputSelect(_0, _1, NAME, ...) NAME +#define SDL_StartTextInput(...) SDLCompat_TextInputSelect(_, ##__VA_ARGS__, SDLCompat_StartTextInput1, SDLCompat_StartTextInput0)(__VA_ARGS__) +#define SDL_StopTextInput(...) SDLCompat_TextInputSelect(_, ##__VA_ARGS__, SDLCompat_StopTextInput1, SDLCompat_StopTextInput0)(__VA_ARGS__) +#define SDL_SetTextInputRect(rect) SDLCompat_SetTextInputRect(rect) +#define SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask) SDLCompat_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask) +#define SDL_GetMouseState(x, y) SDLCompat_GetMouseState(x, y) +#define SDL_GetKeyFromScancode(scancode) SDLCompat_GetKeyFromScancode(scancode) +#define SDL_GetScancodeFromKey(keycode) SDLCompat_GetScancodeFromKey(keycode) + +#else +#include +#endif + +#endif // RECOIL_SDL_RECOIL_COMPAT_H diff --git a/rts/System/SDLCompat/SDL_clipboard.h b/rts/System/SDLCompat/SDL_clipboard.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_clipboard.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_config.h b/rts/System/SDLCompat/SDL_config.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_config.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_events.h b/rts/System/SDLCompat/SDL_events.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_events.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_hints.h b/rts/System/SDLCompat/SDL_hints.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_hints.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_keyboard.h b/rts/System/SDLCompat/SDL_keyboard.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_keyboard.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_keycode.h b/rts/System/SDLCompat/SDL_keycode.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_keycode.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_mouse.h b/rts/System/SDLCompat/SDL_mouse.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_mouse.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_rect.h b/rts/System/SDLCompat/SDL_rect.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_rect.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_scancode.h b/rts/System/SDLCompat/SDL_scancode.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_scancode.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_stdinc.h b/rts/System/SDLCompat/SDL_stdinc.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_stdinc.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_syswm.h b/rts/System/SDLCompat/SDL_syswm.h new file mode 100644 index 00000000000..e5ac382b236 --- /dev/null +++ b/rts/System/SDLCompat/SDL_syswm.h @@ -0,0 +1,30 @@ +#include "SDLRecoilCompat.h" + +#if !defined(SDL_SYSWM_TYPE) +typedef enum SDL_SYSWM_TYPE { + SDL_SYSWM_UNKNOWN, + SDL_SYSWM_WINDOWS, + SDL_SYSWM_X11, + SDL_SYSWM_WAYLAND, + SDL_SYSWM_COCOA +} SDL_SYSWM_TYPE; +#endif + +typedef struct SDL_SysWMinfo { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + struct { + struct { + void* window; + } win; + struct { + void* display; + void* window; + } x11; + } info; +} SDL_SysWMinfo; + +static inline int SDL_GetWindowWMInfo(SDL_Window*, SDL_SysWMinfo*) +{ + return 0; +} diff --git a/rts/System/SDLCompat/SDL_version.h b/rts/System/SDLCompat/SDL_version.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_version.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/SDLCompat/SDL_video.h b/rts/System/SDLCompat/SDL_video.h new file mode 100644 index 00000000000..698be56548f --- /dev/null +++ b/rts/System/SDLCompat/SDL_video.h @@ -0,0 +1 @@ +#include "SDLRecoilCompat.h" diff --git a/rts/System/Sound/OpenAL/Sound.cpp b/rts/System/Sound/OpenAL/Sound.cpp index 832f506bfcc..90bd7c26d4d 100644 --- a/rts/System/Sound/OpenAL/Sound.cpp +++ b/rts/System/Sound/OpenAL/Sound.cpp @@ -130,7 +130,9 @@ void CSound::Cleanup() { if (hasAlcSoftLoopBack && sdlDeviceID != 0) { LOG("[Sound::%s][SDL_CloseAudioDevice(%d)]", __func__, sdlDeviceID); SDL_CloseAudioDevice(sdlDeviceID); +#if !defined(RECOIL_MACOS_SDL3_EGL) SDL_CloseAudio(); +#endif SDL_QuitSubSystem(SDL_INIT_AUDIO); sdlDeviceID = -1; @@ -348,6 +350,10 @@ bool CSound::Mute() void CSound::DeviceChanged(uint32_t sdlDeviceIndex) { // handles SDL_AUDIODEVICEREMOVED and SDL_AUDIODEVICEADDED +#if defined(RECOIL_MACOS_SDL3_EGL) + LOG("[Sound::%s] SDL3 audio device change observed for device=%u; SDL audio stream migration is deferred for Thread 11+", __func__, sdlDeviceIndex); + return; +#endif if (!hasAlcSoftLoopBack || sdlDeviceIndex != sdlDeviceID) return; @@ -490,6 +496,12 @@ static const char* TypeName(ALCenum type) bool CSound::OpenSdlDevice(const std::string& deviceName, SDL_AudioSpec& obtainedSpec) { +#if defined(RECOIL_MACOS_SDL3_EGL) + (void)deviceName; + (void)obtainedSpec; + LOG("[Sound::%s] SDL3 audio stream migration deferred in Thread 10; falling back to openal-soft backends", __func__); + return false; +#else if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { LOG("[Sound::%s] failed to initialize SDL audio, error: \"%s\"", __func__, SDL_GetError()); return false; @@ -561,6 +573,7 @@ bool CSound::OpenSdlDevice(const std::string& deviceName, SDL_AudioSpec& obtaine } return true; +#endif } void CSound::OpenLoopbackDevice(const std::string& deviceName) @@ -614,13 +627,15 @@ void CSound::OpenLoopbackDevice(const std::string& deviceName) attrs[2] = ALC_FORMAT_TYPE_SOFT; - switch (obtainedSpec.format) { - case AUDIO_U8 : { attrs[3] = ALC_UNSIGNED_BYTE_SOFT ; } break; - case AUDIO_S8 : { attrs[3] = ALC_BYTE_SOFT ; } break; - case AUDIO_U16SYS: { attrs[3] = ALC_UNSIGNED_SHORT_SOFT; } break; - case AUDIO_S16SYS: { attrs[3] = ALC_SHORT_SOFT ; } break; - case AUDIO_F32 : { attrs[3] = ALC_FLOAT_SOFT ; } break; - default: { + switch (obtainedSpec.format) { + case AUDIO_U8 : { attrs[3] = ALC_UNSIGNED_BYTE_SOFT ; } break; + case AUDIO_S8 : { attrs[3] = ALC_BYTE_SOFT ; } break; +#ifdef AUDIO_U16SYS + case AUDIO_U16SYS: { attrs[3] = ALC_UNSIGNED_SHORT_SOFT; } break; +#endif + case AUDIO_S16SYS: { attrs[3] = ALC_SHORT_SOFT ; } break; + case AUDIO_F32 : { attrs[3] = ALC_FLOAT_SOFT ; } break; + default: { LOG("[Sound::%s] unhandled SDL format: 0x%04x", __func__, obtainedSpec.format); Cleanup(); return; diff --git a/rts/System/SpringApp.cpp b/rts/System/SpringApp.cpp index a2274b6f9e5..9051ab5a1df 100644 --- a/rts/System/SpringApp.cpp +++ b/rts/System/SpringApp.cpp @@ -1065,8 +1065,24 @@ void SpringApp::Kill(bool fromRun) bool SpringApp::MainEventHandler(const SDL_Event& event) { switch (event.type) { +#if defined(RECOIL_MACOS_SDL3_EGL) + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_DISPLAY_CHANGED: + case SDL_WINDOWEVENT_CLOSE: { + const auto recoilWindowEvent = event.type; +#else case SDL_WINDOWEVENT: { - switch (event.window.event) { + const auto recoilWindowEvent = event.window.event; +#endif + switch (recoilWindowEvent) { case SDL_WINDOWEVENT_MOVED: { LOG("[SpringApp::%s][SDL_WINDOWEVENT_MOVED][1] di=%d, ssx=%d, ssy=%d, wsx=%d, wsy=%d, wpx=%d, wpy=%d" , __func__ @@ -1189,7 +1205,11 @@ bool SpringApp::MainEventHandler(const SDL_Event& event) SDL_Event event; event.type = event.button.type = SDL_MOUSEBUTTONUP; +#if defined(RECOIL_MACOS_SDL3_EGL) + event.button.down = false; +#else event.button.state = SDL_RELEASED; +#endif event.button.which = 0; event.button.button = i; event.button.x = -1; @@ -1205,8 +1225,12 @@ bool SpringApp::MainEventHandler(const SDL_Event& event) // and make sure to un-capture mouse globalRendering->SetWindowInputGrabbing(false); } break; +#if defined(RECOIL_MACOS_SDL3_EGL) + case SDL_WINDOWEVENT_DISPLAY_CHANGED: { +#else // replace with normal SDL_WINDOWEVENT_DISPLAY_CHANGED when our Linux SDL2 is updated case RECOIL_SDL_WINDOWEVENT_DISPLAY_CHANGED: { +#endif LOG("[SpringApp::%s][SDL_WINDOWEVENT_DISPLAY_CHANGED] to display %d\n", __func__, event.window.data1); // try to reinit GL context globalRendering->MakeCurrentContext(false); @@ -1218,11 +1242,19 @@ bool SpringApp::MainEventHandler(const SDL_Event& event) }; } break; case SDL_AUDIODEVICEREMOVED: { +#if defined(RECOIL_MACOS_SDL3_EGL) + LOG("[SpringApp::%s][SDL_AUDIODEVICEREMOVED][1] type=%u, which=%u, recording=%u", __func__, event.adevice.type, event.adevice.which, static_cast(event.adevice.recording)); +#else LOG("[SpringApp::%s][SDL_AUDIODEVICEREMOVED][1] type=%u, which=%u, iscapture=%u", __func__, event.adevice.type, event.adevice.which, static_cast(event.adevice.iscapture)); +#endif sound->DeviceChanged(event.adevice.which); } break; case SDL_AUDIODEVICEADDED: { +#if defined(RECOIL_MACOS_SDL3_EGL) + LOG("[SpringApp::%s][SDL_AUDIODEVICEADDED][1] type=%u, which=%u, recording=%u", __func__, event.adevice.type, event.adevice.which, static_cast(event.adevice.recording)); +#else LOG("[SpringApp::%s][SDL_AUDIODEVICEADDED][1] type=%u, which=%u, iscapture=%u", __func__, event.adevice.type, event.adevice.which, static_cast(event.adevice.iscapture)); +#endif sound->DeviceChanged(event.adevice.which); } break; case SDL_QUIT: { @@ -1242,8 +1274,13 @@ bool SpringApp::MainEventHandler(const SDL_Event& event) KeyInput::Update(keyBindings.GetFakeMetaKey()); if (activeController != nullptr) { +#if defined(RECOIL_MACOS_SDL3_EGL) + int keyCode = CKeyCodes::GetNormalizedSymbol(event.key.key); + int scanCode = CScanCodes::GetNormalizedSymbol(event.key.scancode); +#else int keyCode = CKeyCodes::GetNormalizedSymbol(event.key.keysym.sym); int scanCode = CScanCodes::GetNormalizedSymbol(event.key.keysym.scancode); +#endif activeController->KeyPressed(keyCode, scanCode, event.key.repeat); } @@ -1253,8 +1290,13 @@ bool SpringApp::MainEventHandler(const SDL_Event& event) if (activeController != nullptr) { gameTextInput.ignoreNextChar = false; +#if defined(RECOIL_MACOS_SDL3_EGL) + int keyCode = CKeyCodes::GetNormalizedSymbol(event.key.key); + int scanCode = CScanCodes::GetNormalizedSymbol(event.key.scancode); +#else int keyCode = CKeyCodes::GetNormalizedSymbol(event.key.keysym.sym); int scanCode = CScanCodes::GetNormalizedSymbol(event.key.keysym.scancode); +#endif activeController->KeyReleased(keyCode, scanCode); } } break; diff --git a/rts/System/float3.h b/rts/System/float3.h index bf47dd90c2a..453f36dc436 100644 --- a/rts/System/float3.h +++ b/rts/System/float3.h @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include "lib/fmt/include/fmt/format.h" #include "System/BranchPrediction.h" #include "System/creg/creg_cond.h" @@ -839,7 +840,7 @@ class float3 static constexpr float nrm_eps() { return 1e-12f; } std::string str() const { - return std::format("float3({:.3f}, {:.3f}, {:.3f})", x, y, z); + return fmt::format("float3({:.3f}, {:.3f}, {:.3f})", x, y, z); } /** @@ -902,4 +903,3 @@ static constexpr float3 XZVector(1.0f, 0.0f, 1.0f); static constexpr float3 YZVector(0.0f, 1.0f, 1.0f); #endif /* FLOAT3_H */ - diff --git a/rts/System/float4.h b/rts/System/float4.h index 347f0d2e822..a3d33660c43 100644 --- a/rts/System/float4.h +++ b/rts/System/float4.h @@ -109,7 +109,9 @@ struct float4 : public float3 } std::string str() const { - return std::format("float4({:.3f}, {:.3f}, {:.3f}, {:.3f})", x, y, z, w); + char buf[80]; + std::snprintf(buf, sizeof(buf), "float4(%.3f, %.3f, %.3f, %.3f)", x, y, z, w); + return buf; } diff --git a/rts/aGui/LineEdit.cpp b/rts/aGui/LineEdit.cpp index cc9cb60c6e7..7f8a4fa43ef 100644 --- a/rts/aGui/LineEdit.cpp +++ b/rts/aGui/LineEdit.cpp @@ -100,7 +100,13 @@ bool LineEdit::HandleEventSelf(const SDL_Event& ev) if (!hasFocus) { break; } - switch(ev.key.keysym.sym) + const int key = +#if defined(RECOIL_MACOS_SDL3_EGL) + ev.key.key; +#else + ev.key.keysym.sym; +#endif + switch(key) { case SDLK_BACKSPACE: { if (cursorPos > 0) { diff --git a/rts/aGui/List.cpp b/rts/aGui/List.cpp index f3a95699036..2f1ea52ca90 100644 --- a/rts/aGui/List.cpp +++ b/rts/aGui/List.cpp @@ -341,12 +341,18 @@ bool List::HandleEventSelf(const SDL_Event& ev) case SDL_KEYDOWN: { if (!hasFocus) break; - if(ev.key.keysym.sym == SDLK_ESCAPE) + const int key = +#if defined(RECOIL_MACOS_SDL3_EGL) + ev.key.key; +#else + ev.key.keysym.sym; +#endif + if(key == SDLK_ESCAPE) { hasFocus = false; break; } - return KeyPressed(ev.key.keysym.sym, false); + return KeyPressed(key, false); } } return false; diff --git a/rts/aGui/Window.cpp b/rts/aGui/Window.cpp index 0a809c88cff..1b7e1a86d9a 100644 --- a/rts/aGui/Window.cpp +++ b/rts/aGui/Window.cpp @@ -103,7 +103,13 @@ bool Window::HandleEventSelf(const SDL_Event& ev) break; } case SDL_KEYDOWN: { - if (ev.key.keysym.sym == SDLK_ESCAPE) + const int key = +#if defined(RECOIL_MACOS_SDL3_EGL) + ev.key.key; +#else + ev.key.keysym.sym; +#endif + if (key == SDLK_ESCAPE) { WantClose(); return true; diff --git a/rts/build/cmake/FindLibunwind.cmake b/rts/build/cmake/FindLibunwind.cmake index 2b166993ed9..3efd2eea850 100644 --- a/rts/build/cmake/FindLibunwind.cmake +++ b/rts/build/cmake/FindLibunwind.cmake @@ -36,9 +36,19 @@ find_path(LIBUNWIND_PKGCONFIG_DIR libunwind.pc ) if (APPLE AND LIBUNWIND_INCLUDE_DIR) - # FIXME: OS X 10.10 doesn't have static libunwind.a only dynamic libunwind.dylib; - # link with "-framework Cocoa" - set(LIBUNWIND_LIBRARY "-framework Cocoa") + find_library(LIBUNWIND_LIBRARY + NAMES unwind + PATHS + "${CMAKE_OSX_SYSROOT}/usr/lib/system" + "${CMAKE_OSX_SYSROOT}/usr/lib" + /usr/lib/system + /usr/lib + NO_DEFAULT_PATH + ) + + if (NOT LIBUNWIND_LIBRARY) + find_library(LIBUNWIND_LIBRARY Cocoa REQUIRED) + endif() else () find_library(LIBUNWIND_LIBRARY NAMES unwind ${LIB_STD_ARGS}) endif () diff --git a/rts/build/cmake/FindMesaEGL.cmake b/rts/build/cmake/FindMesaEGL.cmake new file mode 100644 index 00000000000..8708b1dd40f --- /dev/null +++ b/rts/build/cmake/FindMesaEGL.cmake @@ -0,0 +1,35 @@ +# Project-local Mesa EGL discovery for the macOS SDL3/EGL source-port route. + +set(_RECOIL_MESA_EGL_HINTS + "${MESA_EGL_ROOT}" + "${MesaEGL_ROOT}" + "$ENV{MESA_EGL_ROOT}" + "/Users/yeojun/Desktop/BAR/artifacts/runtime/thread-06-mesa-zink" +) + +find_path(MesaEGL_INCLUDE_DIR + NAMES EGL/egl.h + PATHS ${_RECOIL_MESA_EGL_HINTS} + PATH_SUFFIXES include + NO_DEFAULT_PATH +) + +find_library(MesaEGL_LIBRARY + NAMES EGL libEGL.1.dylib + PATHS ${_RECOIL_MESA_EGL_HINTS} + PATH_SUFFIXES lib + NO_DEFAULT_PATH +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MesaEGL DEFAULT_MSG MesaEGL_INCLUDE_DIR MesaEGL_LIBRARY) + +if (MesaEGL_FOUND AND NOT TARGET MesaEGL::EGL) + add_library(MesaEGL::EGL UNKNOWN IMPORTED) + set_target_properties(MesaEGL::EGL PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MesaEGL_INCLUDE_DIR}" + IMPORTED_LOCATION "${MesaEGL_LIBRARY}" + ) +endif() + +unset(_RECOIL_MESA_EGL_HINTS) diff --git a/rts/build/cmake/FindSDL2.cmake b/rts/build/cmake/FindSDL2.cmake index 465e270a93d..fa75361a808 100644 --- a/rts/build/cmake/FindSDL2.cmake +++ b/rts/build/cmake/FindSDL2.cmake @@ -2,6 +2,24 @@ # and doesn't provide a proper config file. # We need to create imported targets for the config +if (APPLE AND RECOIL_MACOS_SDL3_EGL) + find_package(SDL3 MODULE REQUIRED) + + if (SDL3_FOUND AND NOT TARGET SDL2::SDL2) + # Recoil still has an SDL2-shaped source/CMake contract. The macOS + # SDL3/EGL path keeps that contract at the boundary while linking SDL3. + add_library(RecoilSDL2SourceCompat INTERFACE) + add_library(SDL2::SDL2 ALIAS RecoilSDL2SourceCompat) + target_link_libraries(RecoilSDL2SourceCompat INTERFACE SDL3::SDL3) + target_include_directories(RecoilSDL2SourceCompat BEFORE INTERFACE "${CMAKE_SOURCE_DIR}/rts/System/SDLCompat") + target_compile_definitions(RecoilSDL2SourceCompat INTERFACE RECOIL_MACOS_SDL3_EGL=1 SDL_ENABLE_OLD_NAMES=1) + endif() + + set(SDL2_FOUND ${SDL3_FOUND}) + set(SDL2_LIBRARIES SDL2::SDL2) + return() +endif() + find_package(SDL2 QUIET CONFIG) find_library(SDL2_LIBRARY diff --git a/rts/build/cmake/FindSDL3.cmake b/rts/build/cmake/FindSDL3.cmake new file mode 100644 index 00000000000..1f04bb61787 --- /dev/null +++ b/rts/build/cmake/FindSDL3.cmake @@ -0,0 +1,43 @@ +# Project-local SDL3 discovery for the macOS SDL3/EGL source-port route. + +set(_RECOIL_SDL3_HINTS + "${SDL3_ROOT}" + "${SDL3_DIR}" + "$ENV{SDL3_ROOT}" + "/Users/yeojun/Desktop/BAR/artifacts/runtime/thread-03-sdl3" +) + +find_package(SDL3 QUIET CONFIG + PATHS ${_RECOIL_SDL3_HINTS} + PATH_SUFFIXES lib/cmake/SDL3 + NO_DEFAULT_PATH +) + +if (NOT SDL3_FOUND) + find_path(SDL3_INCLUDE_DIR + NAMES SDL3/SDL.h + PATHS ${_RECOIL_SDL3_HINTS} + PATH_SUFFIXES include + NO_DEFAULT_PATH + ) + + find_library(SDL3_LIBRARY + NAMES SDL3 SDL3.0 + PATHS ${_RECOIL_SDL3_HINTS} + PATH_SUFFIXES lib + NO_DEFAULT_PATH + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SDL3 DEFAULT_MSG SDL3_INCLUDE_DIR SDL3_LIBRARY) + + if (SDL3_FOUND AND NOT TARGET SDL3::SDL3) + add_library(SDL3::SDL3 UNKNOWN IMPORTED) + set_target_properties(SDL3::SDL3 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SDL3_INCLUDE_DIR}" + IMPORTED_LOCATION "${SDL3_LIBRARY}" + ) + endif() +endif() + +unset(_RECOIL_SDL3_HINTS) diff --git a/rts/builds/legacy/CMakeLists.txt b/rts/builds/legacy/CMakeLists.txt index f1d19ede7e3..3e4625d7afd 100644 --- a/rts/builds/legacy/CMakeLists.txt +++ b/rts/builds/legacy/CMakeLists.txt @@ -19,20 +19,22 @@ target_link_libraries(Game PRIVATE prd::jsoncpp streflop fmt::fmt -) + nowide::nowide + ) ### Assemble libraries find_package(SDL2 MODULE REQUIRED) - - - - set(engineLibraries SDL2::SDL2) -set(OpenGL_GL_PREFERENCE LEGACY) -find_package_static(OpenGL 3.0 REQUIRED) -list(APPEND engineLibraries OpenGL::GL) +if (APPLE AND RECOIL_MACOS_SDL3_EGL) + find_package(MesaEGL MODULE REQUIRED) + list(APPEND engineLibraries MesaEGL::EGL) +else() + set(OpenGL_GL_PREFERENCE LEGACY) + find_package_static(OpenGL 3.0 REQUIRED) + list(APPEND engineLibraries OpenGL::GL) +endif() find_fontconfig_hack() find_package_static(Fontconfig 2.11 REQUIRED) @@ -53,12 +55,12 @@ if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) target_link_libraries(Game PRIVATE X11::Xcursor) list(APPEND engineLibraries ${X11_Xcursor_LIB} ${X11_X11_LIB}) -endif (UNIX) +endif (UNIX AND NOT APPLE) -if (APPLE) +if (APPLE AND NOT RECOIL_MACOS_SDL3_EGL) find_library(COREFOUNDATION_LIBRARY Foundation) list(APPEND engineLibraries ${COREFOUNDATION_LIBRARY}) -endif (APPLE) +endif() list(APPEND engineLibraries squish rgetc1) list(APPEND engineLibraries ${sound-impl}) @@ -84,6 +86,19 @@ include_directories(${engineIncludes}) add_executable(engine-legacy ${EXE_FLAGS} ${engineSources} ${ENGINE_ICON} ${engineHeaders}) target_link_libraries(engine-legacy ${engineLibraries} Game) +if (APPLE AND RECOIL_MACOS_SDL3_EGL) + target_link_options(engine-legacy PRIVATE + "SHELL:-framework Cocoa" + "SHELL:-framework Foundation" + "SHELL:-framework QuartzCore" + "SHELL:-framework Metal" + ) + set_target_properties(engine-legacy PROPERTIES + BUILD_RPATH "@loader_path/../../artifacts/runtime/thread-03-sdl3/lib;@loader_path/../../artifacts/runtime/thread-06-mesa-zink/lib;@loader_path/../../artifacts/runtime/thread-05-continuation-vulkan-loader/lib;@loader_path/../../artifacts/runtime/thread-05-moltenvk-production-pr-2746-spirv-cross/lib" + INSTALL_RPATH "@loader_path/../lib" + ) +endif() + # Export symbols for plugin access (replaces CMP0065 OLD behavior) set_target_properties(engine-legacy PROPERTIES ENABLE_EXPORTS TRUE) diff --git a/rts/lib/glad/CMakeLists.txt b/rts/lib/glad/CMakeLists.txt index a67e710dd36..f1c0b6d58a9 100644 --- a/rts/lib/glad/CMakeLists.txt +++ b/rts/lib/glad/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.5) project(Glad) -if (UNIX AND NOT MINGW) +if (UNIX AND NOT MINGW AND NOT APPLE) add_library(glad glad.c glad_glx.c) -else (UNIX AND NOT MINGW) +else (UNIX AND NOT MINGW AND NOT APPLE) add_library(glad glad.c) -endif (UNIX AND NOT MINGW) +endif (UNIX AND NOT MINGW AND NOT APPLE) -target_include_directories(glad PUBLIC /) \ No newline at end of file +target_include_directories(glad PUBLIC /) diff --git a/rts/lib/smmalloc/smmalloc.h b/rts/lib/smmalloc/smmalloc.h index 4bc0786b3a3..05fe34e4ec9 100644 --- a/rts/lib/smmalloc/smmalloc.h +++ b/rts/lib/smmalloc/smmalloc.h @@ -24,10 +24,12 @@ #include #include #include +#include #include #include #include #include +#include //#define SMMALLOC_STATS_SUPPORT