diff --git a/CMakeLists.txt b/CMakeLists.txt index 34955fce..7c31b413 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,15 @@ set(SOURCE_FILES MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp ) +if (APPLE) + list(APPEND SOURCE_FILES + MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp + MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp + MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp + MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp + ) +endif() + set(MOBILEGL_LINK_LIBRARIES glslang::glslang spirv-cross-c @@ -388,12 +397,16 @@ if (APPLE) target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC "-framework Cocoa" "-framework QuartzCore" - "-framework Foundation") + "-framework Foundation" + "-framework OpenGL" + objc) if(TARGET ${CMAKE_PROJECT_NAME}_s) target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC "-framework Cocoa" "-framework QuartzCore" - "-framework Foundation") + "-framework Foundation" + "-framework OpenGL" + objc) endif() endif() diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index a123d187..76afb047 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -32,6 +32,8 @@ #define MOBILEGL_GLX_API MOBILEGL_API #define MOBILEGL_GL_API MOBILEGL_API #define MOBILEGL_EGL_API MOBILEGL_API +#define MOBILEGL_CGL_API MOBILEGL_API +#define MOBILEGL_NSOPENGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // #ifndef MOBILEGL_LOG_ACTIVE_LEVEL diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index b23f1635..d61b5a82 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -18,6 +18,29 @@ namespace MobileGL { namespace { Bool g_isInitialized = false; + + void DestroyImpl(Bool logLifecycle) { + if (!g_isInitialized) { + return; + } + + if (logLifecycle) { + MGLOG_I("MobileGL closing..."); + } + glslang::FinalizeProcess(); + MG_Backend::pActiveBackendObject.reset(); + MG_State::pGLContext.reset(); + MG_State::pEGLContext.reset(); + MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset(); + MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset(); + MG_Backend::gBackendFunctionsTable = {}; + g_isInitialized = false; + if (logLifecycle) { + MG_Util::Debug::Close(); + } + + // TODO: add and use Destroy functions for other subsystems + } } void Initialize() { @@ -43,22 +66,7 @@ namespace MobileGL { } void Destroy() { - if (!g_isInitialized) { - return; - } - - MGLOG_I("MobileGL closing..."); - glslang::FinalizeProcess(); - MG_Backend::pActiveBackendObject.reset(); - MG_State::pGLContext.reset(); - MG_State::pEGLContext.reset(); - MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset(); - MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset(); - MG_Backend::gBackendFunctionsTable = {}; - g_isInitialized = false; - MG_Util::Debug::Close(); - - // TODO: add and use Destroy functions for other subsystems + DestroyImpl(true); } #if defined(__linux__) || defined(__APPLE__) @@ -70,7 +78,12 @@ namespace MobileGL { if (std::getenv("MOBILEGL_TRACE_SKIP_AUTODESTROY") != nullptr) { return; } - Destroy(); +#if defined(__APPLE__) + // macOS injected dylibs can run destructors after logging/backend static state is already torn down. + return; +#else + DestroyImpl(false); +#endif } #endif diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp new file mode 100644 index 00000000..b9f5b309 --- /dev/null +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp @@ -0,0 +1,649 @@ +// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "CGLImpl.h" + +#if defined(__APPLE__) +#include "../EGLImpl/EGLImpl.h" + +namespace MobileGL::MG_Impl::CGLImpl { + namespace { + struct PixelFormatObject { + Uint32 RetainCount = 1; + Bool DoubleBuffer = true; + GLint ColorSize = 24; + GLint AlphaSize = 8; + GLint DepthSize = 24; + GLint StencilSize = 8; + GLint SampleBuffers = 0; + GLint Samples = 0; + GLint Profile = kCGLOGLPVersion_3_2_Core; + GLint RendererId = 0x4d474c; + }; + + struct ContextObject { + Uint32 RetainCount = 1; + CGLPixelFormatObj PixelFormat = nullptr; + CGLContextObj Share = nullptr; + EGLDisplay Display = EGL_NO_DISPLAY; + EGLConfig Config = nullptr; + EGLContext Context = EGL_NO_CONTEXT; + EGLSurface Surface = EGL_NO_SURFACE; + void* NSObject = nullptr; + void* View = nullptr; + void* MetalLayer = nullptr; + GLint SwapInterval = 1; + GLint VirtualScreen = 0; + GLint SurfaceBackingSize[2] = {0, 0}; + Bool HasDrawable = false; + Bool Locked = false; + }; + + std::recursive_mutex& RegistryMutex() { + static auto* mutex = new std::recursive_mutex(); + return *mutex; + } + + Uint64& NextPixelFormatHandle() { + static auto* handle = new Uint64(1); + return *handle; + } + + Uint64& NextContextHandle() { + static auto* handle = new Uint64(1); + return *handle; + } + + UnorderedMap& PixelFormats() { + static auto* formats = new UnorderedMap(); + return *formats; + } + + UnorderedMap& Contexts() { + static auto* contexts = new UnorderedMap(); + return *contexts; + } + + UnorderedMap& CurrentContexts() { + static auto* contexts = new UnorderedMap(); + return *contexts; + } + + CGLPixelFormatObj EncodePixelFormat(Uint64 handle) { + return reinterpret_cast(static_cast(handle)); + } + + CGLContextObj EncodeContext(Uint64 handle) { + return reinterpret_cast(static_cast(handle)); + } + + std::thread::id CurrentThreadKey() { + return std::this_thread::get_id(); + } + + Bool AttributeHasValue(CGLPixelFormatAttribute attrib) { + switch (attrib) { + case kCGLPFAColorSize: + case kCGLPFAAlphaSize: + case kCGLPFADepthSize: + case kCGLPFAStencilSize: + case kCGLPFASampleBuffers: + case kCGLPFASamples: + case kCGLPFARendererID: + case kCGLPFADisplayMask: + case kCGLPFAOpenGLProfile: + return true; + default: + return false; + } + } + + void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat, + CGLPixelFormatAttribute attrib, + GLint value) { + switch (attrib) { + case kCGLPFADoubleBuffer: + pixelFormat.DoubleBuffer = true; + break; + case kCGLPFAColorSize: + pixelFormat.ColorSize = value; + break; + case kCGLPFAAlphaSize: + pixelFormat.AlphaSize = value; + break; + case kCGLPFADepthSize: + pixelFormat.DepthSize = value; + break; + case kCGLPFAStencilSize: + pixelFormat.StencilSize = value; + break; + case kCGLPFASampleBuffers: + pixelFormat.SampleBuffers = value; + break; + case kCGLPFASamples: + pixelFormat.Samples = value; + break; + case kCGLPFAOpenGLProfile: + pixelFormat.Profile = value; + break; + case kCGLPFARendererID: + pixelFormat.RendererId = value; + break; + default: + break; + } + } + + Bool InitEGLContext(ContextObject& object, CGLPixelFormatObj pix, CGLContextObj share) { + auto* pixelFormat = [&]() -> PixelFormatObject* { + auto& pixelFormats = PixelFormats(); + auto it = pixelFormats.find(pix); + return it == pixelFormats.end() ? nullptr : &it->second; + }(); + if (!pixelFormat) { + return false; + } + + EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return false; + } + if (!EGLImpl::Initialize(display, nullptr, nullptr)) { + return false; + } + EGLImpl::BindAPI(EGL_OPENGL_API); + + const EGLint attribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, std::max(pixelFormat->AlphaSize, 0), + EGL_DEPTH_SIZE, std::max(pixelFormat->DepthSize, 0), + EGL_STENCIL_SIZE, std::max(pixelFormat->StencilSize, 0), + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE, + }; + + EGLConfig config = nullptr; + EGLint count = 0; + if (!EGLImpl::ChooseConfig(display, attribs, &config, 1, &count) || count <= 0) { + return false; + } + + EGLContext shareContext = EGL_NO_CONTEXT; + if (share != nullptr) { + auto& contexts = Contexts(); + auto shareIt = contexts.find(share); + if (shareIt == contexts.end()) { + return false; + } + shareContext = shareIt->second.Context; + } + + const EGLint contextAttribs[] = { + EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, 3, + EGL_NONE, + }; + EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); + if (eglContext == EGL_NO_CONTEXT) { + return false; + } + + object.Display = display; + object.Config = config; + object.Context = eglContext; + object.PixelFormat = pix; + object.Share = share; + return true; + } + + ContextObject* TryGetContext(CGLContextObj ctx) { + auto& contexts = Contexts(); + auto it = contexts.find(ctx); + return it == contexts.end() ? nullptr : &it->second; + } + + const ContextObject* TryGetContext(CGLContextObj ctx, const std::lock_guard&) { + auto& contexts = Contexts(); + auto it = contexts.find(ctx); + return it == contexts.end() ? nullptr : &it->second; + } + + PixelFormatObject* TryGetPixelFormat(CGLPixelFormatObj pix) { + auto& pixelFormats = PixelFormats(); + auto it = pixelFormats.find(pix); + return it == pixelFormats.end() ? nullptr : &it->second; + } + + CGLError MakeCurrentLocked(CGLContextObj ctx, ContextObject& object) { + CurrentContexts()[CurrentThreadKey()] = ctx; + if (!object.HasDrawable || object.Surface == EGL_NO_SURFACE) { + return kCGLNoError; + } + if (!EGLImpl::MakeCurrent(object.Display, object.Surface, object.Surface, object.Context)) { + return kCGLBadState; + } + return kCGLNoError; + } + } // namespace + + CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) { + const std::lock_guard lock(RegistryMutex()); + if (!pix || !npix) { + return kCGLBadAddress; + } + + PixelFormatObject object; + if (attribs) { + for (SizeT i = 0; attribs[i] != static_cast(0); ++i) { + const auto attrib = attribs[i]; + GLint value = 1; + if (AttributeHasValue(attrib)) { + value = static_cast(attribs[++i]); + } + ApplyPixelFormatAttribute(object, attrib, value); + } + } + + const auto handle = EncodePixelFormat(NextPixelFormatHandle()++); + PixelFormats()[handle] = object; + *pix = handle; + *npix = 1; + return kCGLNoError; + } + + CGLError DestroyPixelFormat(CGLPixelFormatObj pix) { + ReleasePixelFormat(pix); + return kCGLNoError; + } + + CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value) { + const std::lock_guard lock(RegistryMutex()); + if (!value) { + return kCGLBadAddress; + } + if (pixNum != 0 && pixNum != 1) { + return kCGLBadValue; + } + auto* pixelFormat = TryGetPixelFormat(pix); + if (!pixelFormat) { + return kCGLBadPixelFormat; + } + + switch (attrib) { + case kCGLPFADoubleBuffer: + *value = pixelFormat->DoubleBuffer ? 1 : 0; + return kCGLNoError; + case kCGLPFAAccelerated: + case kCGLPFAAcceleratedCompute: + case kCGLPFASupportsAutomaticGraphicsSwitching: + *value = 1; + return kCGLNoError; + case kCGLPFAColorSize: + *value = pixelFormat->ColorSize; + return kCGLNoError; + case kCGLPFAAlphaSize: + *value = pixelFormat->AlphaSize; + return kCGLNoError; + case kCGLPFADepthSize: + *value = pixelFormat->DepthSize; + return kCGLNoError; + case kCGLPFAStencilSize: + *value = pixelFormat->StencilSize; + return kCGLNoError; + case kCGLPFASampleBuffers: + *value = pixelFormat->SampleBuffers; + return kCGLNoError; + case kCGLPFASamples: + *value = pixelFormat->Samples; + return kCGLNoError; + case kCGLPFARendererID: + *value = pixelFormat->RendererId; + return kCGLNoError; + case kCGLPFAOpenGLProfile: + *value = pixelFormat->Profile; + return kCGLNoError; + case kCGLPFAVirtualScreenCount: + *value = 1; + return kCGLNoError; + default: + *value = 0; + return kCGLNoError; + } + } + + void ReleasePixelFormat(CGLPixelFormatObj pix) { + const std::lock_guard lock(RegistryMutex()); + auto* pixelFormat = TryGetPixelFormat(pix); + if (!pixelFormat) { + return; + } + if (pixelFormat->RetainCount > 1) { + --pixelFormat->RetainCount; + return; + } + PixelFormats().erase(pix); + } + + CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix) { + const std::lock_guard lock(RegistryMutex()); + auto* pixelFormat = TryGetPixelFormat(pix); + if (pixelFormat) { + ++pixelFormat->RetainCount; + } + return pix; + } + + GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix) { + const std::lock_guard lock(RegistryMutex()); + auto* pixelFormat = TryGetPixelFormat(pix); + return pixelFormat ? pixelFormat->RetainCount : 0; + } + + CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) { + const std::lock_guard lock(RegistryMutex()); + if (!ctx) { + return kCGLBadAddress; + } + if (!TryGetPixelFormat(pix)) { + return kCGLBadPixelFormat; + } + if (share && !TryGetContext(share)) { + return kCGLBadMatch; + } + + ContextObject object; + if (!InitEGLContext(object, pix, share)) { + return kCGLBadAlloc; + } + RetainPixelFormat(pix); + const auto handle = EncodeContext(NextContextHandle()++); + Contexts()[handle] = object; + *ctx = handle; + return kCGLNoError; + } + + CGLError DestroyContext(CGLContextObj ctx) { + ReleaseContext(ctx); + return kCGLNoError; + } + + CGLContextObj RetainContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (object) { + ++object->RetainCount; + } + return ctx; + } + + void ReleaseContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return; + } + if (object->RetainCount > 1) { + --object->RetainCount; + return; + } + if (object->Surface != EGL_NO_SURFACE) { + EGLImpl::DestroySurface(object->Display, object->Surface); + } + if (object->Context != EGL_NO_CONTEXT) { + EGLImpl::DestroyContext(object->Display, object->Context); + } + ReleasePixelFormat(object->PixelFormat); + auto& currentContexts = CurrentContexts(); + for (auto it = currentContexts.begin(); it != currentContexts.end();) { + if (it->second == ctx) { + it = currentContexts.erase(it); + } else { + ++it; + } + } + Contexts().erase(ctx); + } + + GLuint GetContextRetainCount(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + return object ? object->RetainCount : 0; + } + + CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + return object ? object->PixelFormat : nullptr; + } + + CGLError SetCurrentContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + if (!ctx) { + CurrentContexts().erase(CurrentThreadKey()); + EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + return kCGLNoError; + } + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + return MakeCurrentLocked(ctx, *object); + } + + CGLContextObj GetCurrentContext() { + const std::lock_guard lock(RegistryMutex()); + auto& currentContexts = CurrentContexts(); + auto it = currentContexts.find(CurrentThreadKey()); + return it == currentContexts.end() ? nullptr : it->second; + } + + CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + if (!params && pname != kCGLCPReclaimResources) { + return kCGLBadAddress; + } + switch (pname) { + case kCGLCPSwapInterval: + object->SwapInterval = params[0]; + EGLImpl::SwapInterval(object->Display, object->SwapInterval); + return kCGLNoError; + case kCGLCPSurfaceBackingSize: + object->SurfaceBackingSize[0] = params[0]; + object->SurfaceBackingSize[1] = params[1]; + return kCGLNoError; + case kCGLCPSurfaceOpacity: + case kCGLCPSurfaceOrder: + case kCGLCPMPSwapsInFlight: + case kCGLCPReclaimResources: + return kCGLNoError; + default: + return kCGLNoError; + } + } + + CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + if (!params) { + return kCGLBadAddress; + } + switch (pname) { + case kCGLCPSwapInterval: + params[0] = object->SwapInterval; + return kCGLNoError; + case kCGLCPSurfaceBackingSize: + params[0] = object->SurfaceBackingSize[0]; + params[1] = object->SurfaceBackingSize[1]; + return kCGLNoError; + case kCGLCPCurrentRendererID: + params[0] = 0x4d474c; + return kCGLNoError; + case kCGLCPGPUVertexProcessing: + case kCGLCPGPUFragmentProcessing: + case kCGLCPHasDrawable: + params[0] = object->HasDrawable ? 1 : 0; + return kCGLNoError; + case kCGLCPMPSwapsInFlight: + params[0] = 1; + return kCGLNoError; + default: + params[0] = 0; + return kCGLNoError; + } + } + + CGLError UpdateContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + return TryGetContext(ctx) ? kCGLNoError : kCGLBadContext; + } + + CGLError ClearDrawable(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + if (object->Surface != EGL_NO_SURFACE) { + EGLImpl::DestroySurface(object->Display, object->Surface); + } + object->Surface = EGL_NO_SURFACE; + object->View = nullptr; + object->MetalLayer = nullptr; + object->HasDrawable = false; + return kCGLNoError; + } + + CGLError FlushDrawable(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + if (!object->HasDrawable || object->Surface == EGL_NO_SURFACE) { + return kCGLBadDrawable; + } + const auto currentError = MakeCurrentLocked(ctx, *object); + if (currentError != kCGLNoError) { + return currentError; + } + return EGLImpl::SwapBuffers(object->Display, object->Surface) ? kCGLNoError : kCGLBadDrawable; + } + + CGLError LockContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + object->Locked = true; + return kCGLNoError; + } + + CGLError UnlockContext(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + object->Locked = false; + return kCGLNoError; + } + + void GetVersion(GLint* majorvers, GLint* minorvers) { + if (majorvers) { + *majorvers = 1; + } + if (minorvers) { + *minorvers = 0; + } + } + + const char* ErrorString(CGLError error) { + switch (error) { + case kCGLNoError: + return "no error"; + case kCGLBadAttribute: + return "invalid pixel format attribute"; + case kCGLBadPixelFormat: + return "invalid pixel format"; + case kCGLBadContext: + return "invalid context"; + case kCGLBadDrawable: + return "invalid drawable"; + case kCGLBadState: + return "invalid context state"; + case kCGLBadValue: + return "invalid numerical value"; + case kCGLBadMatch: + return "invalid share context"; + case kCGLBadAddress: + return "invalid pointer"; + case kCGLBadAlloc: + return "invalid memory allocation"; + default: + return "unknown CGL error"; + } + } + + CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (!object) { + return kCGLBadContext; + } + if (!metalLayer) { + return kCGLBadDrawable; + } + if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) { + object->View = nsView; + object->HasDrawable = true; + return kCGLNoError; + } + if (object->Surface != EGL_NO_SURFACE) { + EGLImpl::DestroySurface(object->Display, object->Surface); + object->Surface = EGL_NO_SURFACE; + } + EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object->Display, object->Config, metalLayer, nullptr); + if (surface == EGL_NO_SURFACE) { + return kCGLBadDrawable; + } + object->Surface = surface; + object->View = nsView; + object->MetalLayer = metalLayer; + object->HasDrawable = true; + if (GetCurrentContext() == ctx) { + return MakeCurrentLocked(ctx, *object); + } + return kCGLNoError; + } + + void* GetContextNSObject(CGLContextObj ctx) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + return object ? object->NSObject : nullptr; + } + + void SetContextNSObject(CGLContextObj ctx, void* nsObject) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(ctx); + if (object) { + object->NSObject = nsObject; + } + } +} // namespace MobileGL::MG_Impl::CGLImpl +#endif diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.h b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h new file mode 100644 index 00000000..a0e4951e --- /dev/null +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h @@ -0,0 +1,49 @@ +// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +#if defined(__APPLE__) +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif +#include + +namespace MobileGL::MG_Impl::CGLImpl { + CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix); + CGLError DestroyPixelFormat(CGLPixelFormatObj pix); + CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value); + void ReleasePixelFormat(CGLPixelFormatObj pix); + CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix); + GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix); + + CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx); + CGLError DestroyContext(CGLContextObj ctx); + CGLContextObj RetainContext(CGLContextObj ctx); + void ReleaseContext(CGLContextObj ctx); + GLuint GetContextRetainCount(CGLContextObj ctx); + CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx); + + CGLError SetCurrentContext(CGLContextObj ctx); + CGLContextObj GetCurrentContext(); + CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params); + CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params); + CGLError UpdateContext(CGLContextObj ctx); + CGLError ClearDrawable(CGLContextObj ctx); + CGLError FlushDrawable(CGLContextObj ctx); + CGLError LockContext(CGLContextObj ctx); + CGLError UnlockContext(CGLContextObj ctx); + void GetVersion(GLint* majorvers, GLint* minorvers); + const char* ErrorString(CGLError error); + + CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer); + void* GetContextNSObject(CGLContextObj ctx); + void SetContextNSObject(CGLContextObj ctx, void* nsObject); +} +#endif diff --git a/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp new file mode 100644 index 00000000..fa066450 --- /dev/null +++ b/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp @@ -0,0 +1,110 @@ +// MobileGL - MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "../CGLImpl.h" + +#if defined(__APPLE__) + +MOBILEGL_CGL_API CGLError CGLChoosePixelFormat(const CGLPixelFormatAttribute* attribs, + CGLPixelFormatObj* pix, + GLint* npix) { + return MobileGL::MG_Impl::CGLImpl::ChoosePixelFormat(attribs, pix, npix); +} + +MOBILEGL_CGL_API CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pix) { + return MobileGL::MG_Impl::CGLImpl::DestroyPixelFormat(pix); +} + +MOBILEGL_CGL_API CGLError CGLDescribePixelFormat(CGLPixelFormatObj pix, + GLint pix_num, + CGLPixelFormatAttribute attrib, + GLint* value) { + return MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(pix, pix_num, attrib, value); +} + +MOBILEGL_CGL_API void CGLReleasePixelFormat(CGLPixelFormatObj pix) { + MobileGL::MG_Impl::CGLImpl::ReleasePixelFormat(pix); +} + +MOBILEGL_CGL_API CGLPixelFormatObj CGLRetainPixelFormat(CGLPixelFormatObj pix) { + return MobileGL::MG_Impl::CGLImpl::RetainPixelFormat(pix); +} + +MOBILEGL_CGL_API GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pix) { + return MobileGL::MG_Impl::CGLImpl::GetPixelFormatRetainCount(pix); +} + +MOBILEGL_CGL_API CGLError CGLCreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) { + return MobileGL::MG_Impl::CGLImpl::CreateContext(pix, share, ctx); +} + +MOBILEGL_CGL_API CGLError CGLDestroyContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::DestroyContext(ctx); +} + +MOBILEGL_CGL_API CGLContextObj CGLRetainContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::RetainContext(ctx); +} + +MOBILEGL_CGL_API void CGLReleaseContext(CGLContextObj ctx) { + MobileGL::MG_Impl::CGLImpl::ReleaseContext(ctx); +} + +MOBILEGL_CGL_API GLuint CGLGetContextRetainCount(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::GetContextRetainCount(ctx); +} + +MOBILEGL_CGL_API CGLPixelFormatObj CGLGetPixelFormat(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::GetPixelFormat(ctx); +} + +MOBILEGL_CGL_API CGLError CGLSetCurrentContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::SetCurrentContext(ctx); +} + +MOBILEGL_CGL_API CGLContextObj CGLGetCurrentContext(void) { + return MobileGL::MG_Impl::CGLImpl::GetCurrentContext(); +} + +MOBILEGL_CGL_API CGLError CGLSetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) { + return MobileGL::MG_Impl::CGLImpl::SetParameter(ctx, pname, params); +} + +MOBILEGL_CGL_API CGLError CGLGetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) { + return MobileGL::MG_Impl::CGLImpl::GetParameter(ctx, pname, params); +} + +MOBILEGL_CGL_API CGLError CGLUpdateContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::UpdateContext(ctx); +} + +MOBILEGL_CGL_API CGLError CGLClearDrawable(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::ClearDrawable(ctx); +} + +MOBILEGL_CGL_API CGLError CGLFlushDrawable(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::FlushDrawable(ctx); +} + +MOBILEGL_CGL_API CGLError CGLLockContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::LockContext(ctx); +} + +MOBILEGL_CGL_API CGLError CGLUnlockContext(CGLContextObj ctx) { + return MobileGL::MG_Impl::CGLImpl::UnlockContext(ctx); +} + +MOBILEGL_CGL_API void CGLGetVersion(GLint* majorvers, GLint* minorvers) { + MobileGL::MG_Impl::CGLImpl::GetVersion(majorvers, minorvers); +} + +MOBILEGL_CGL_API const char* CGLErrorString(CGLError error) { + return MobileGL::MG_Impl::CGLImpl::ErrorString(error); +} + +#endif diff --git a/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp b/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp new file mode 100644 index 00000000..3ee561f6 --- /dev/null +++ b/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp @@ -0,0 +1,56 @@ +// MobileGL - MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include + +#if defined(__APPLE__) + +#include "MG_Impl/GetProcAddress.h" + +#include + +namespace { + struct DyldInterposeEntry { + const void* Replacement; + const void* Replacee; + }; + + bool IsGLProcName(const char* name) { + if (name == nullptr) { + return false; + } + + if (strncmp(name, "CGL", 3) == 0) { + return true; + } + + if (strncmp(name, "gl", 2) != 0) { + return false; + } + + // Avoid stealing glfw*/glib*/glX*/global application symbols. + return name[2] >= 'A' && name[2] <= 'Z' && name[2] != 'X'; + } + + void* MobileGLDlsym(void* handle, const char* symbol) { + if (IsGLProcName(symbol)) { + if (void* proc = MobileGL::MG_Impl::GetProcAddress(symbol)) { + return proc; + } + } + + return dlsym(handle, symbol); + } + + __attribute__((used)) static const DyldInterposeEntry kMobileGLDyldInterpose[] + __attribute__((section("__DATA,__interpose"))) = { + {reinterpret_cast(MobileGLDlsym), reinterpret_cast(dlsym)}, + }; +} // namespace + +#endif diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index b3b96e88..7a959ee4 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -7,6 +7,13 @@ // End of Source File Header #include "GetProcAddress.h" +#if defined(__APPLE__) +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif +#include +#endif + #define GETPROC(name, var) \ if (strcmp(#name, var) == 0) { \ return (void*)name; \ @@ -60,6 +67,32 @@ namespace MobileGL::MG_Impl { GETPROC(eglCreatePlatformPixmapSurface, name); GETPROC(eglWaitSync, name); +#if defined(__APPLE__) + GETPROC(CGLChoosePixelFormat, name); + GETPROC(CGLDestroyPixelFormat, name); + GETPROC(CGLDescribePixelFormat, name); + GETPROC(CGLReleasePixelFormat, name); + GETPROC(CGLRetainPixelFormat, name); + GETPROC(CGLGetPixelFormatRetainCount, name); + GETPROC(CGLCreateContext, name); + GETPROC(CGLDestroyContext, name); + GETPROC(CGLRetainContext, name); + GETPROC(CGLReleaseContext, name); + GETPROC(CGLGetContextRetainCount, name); + GETPROC(CGLGetPixelFormat, name); + GETPROC(CGLSetCurrentContext, name); + GETPROC(CGLGetCurrentContext, name); + GETPROC(CGLSetParameter, name); + GETPROC(CGLGetParameter, name); + GETPROC(CGLUpdateContext, name); + GETPROC(CGLClearDrawable, name); + GETPROC(CGLFlushDrawable, name); + GETPROC(CGLLockContext, name); + GETPROC(CGLUnlockContext, name); + GETPROC(CGLGetVersion, name); + GETPROC(CGLErrorString, name); +#endif + GETPROC(glCullFace, name); GETPROC(glFrontFace, name); GETPROC(glHint, name); diff --git a/MobileGL/MG_Impl/Init.cpp b/MobileGL/MG_Impl/Init.cpp index b80a8a96..fca3a261 100644 --- a/MobileGL/MG_Impl/Init.cpp +++ b/MobileGL/MG_Impl/Init.cpp @@ -10,6 +10,9 @@ #include "GLImpl/Texture/ProxyTexture.h" #include "GLImpl/Framebuffer/GL_Framebuffer.h" +#if defined(__APPLE__) +#include "NSOpenGLImpl/NSOpenGLImpl.h" +#endif #include #include @@ -45,5 +48,8 @@ namespace MobileGL::MG_Impl { MakeUnique(fbo0, colorTex, depthTex, stencilTex); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).Bind(fbo0); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).Bind(fbo0); +#if defined(__APPLE__) + NSOpenGLImpl::InstallHooks(); +#endif } } // namespace MobileGL::MG_Impl diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp new file mode 100644 index 00000000..70966951 --- /dev/null +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp @@ -0,0 +1,436 @@ +// MobileGL - MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "NSOpenGLImpl.h" + +#if defined(__APPLE__) +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif +#include "../CGLImpl/CGLImpl.h" +#include +#include +#include +#include + +namespace MobileGL::MG_Impl::NSOpenGLImpl { + namespace { + constexpr intptr_t kAssociationRetain = 1; + constexpr intptr_t kAssociationAssign = 0; + + char kPixelFormatHandleKey; + char kContextHandleKey; + char kContextPixelFormatObjectKey; + char kContextViewKey; + char kContextLayerKey; + + std::once_flag g_installOnce; + IMP g_pixelFormatDealloc = nullptr; + IMP g_contextDealloc = nullptr; + + template + Fn ObjcMsgSend() { + return reinterpret_cast(objc_msgSend); + } + + id SendId(id receiver, const char* selector) { + return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector)) : nil; + } + + void SendVoid(id receiver, const char* selector) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector)); + } + } + + void SendVoidBool(id receiver, const char* selector, bool value) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + } + + void SendVoidId(id receiver, const char* selector, id value) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + } + + id SendIdCGLPixelFormat(id receiver, const char* selector, CGLPixelFormatObj value) { + return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector), value) + : nil; + } + + void SendVoidCGRect(id receiver, const char* selector, CGRect value) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + } + + void SendVoidCGSize(id receiver, const char* selector, CGSize value) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + } + + bool SendBool(id receiver, const char* selector) { + return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector)) : false; + } + + CGRect SendCGRect(id receiver, const char* selector) { + if (!receiver) { + return {}; + } + return ObjcMsgSend()(receiver, sel_registerName(selector)); + } + + CGFloat SendCGFloat(id receiver, const char* selector) { + return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector)) : 1.0; + } + + void SetAssoc(id object, const void* key, id value, intptr_t policy) { + objc_setAssociatedObject(object, key, value, static_cast(policy)); + } + + id GetAssoc(id object, const void* key) { + return object ? objc_getAssociatedObject(object, key) : nil; + } + + CGLPixelFormatObj GetPixelFormatHandle(id object) { + return reinterpret_cast(GetAssoc(object, &kPixelFormatHandleKey)); + } + + void SetPixelFormatHandle(id object, CGLPixelFormatObj handle) { + SetAssoc(object, &kPixelFormatHandleKey, reinterpret_cast(handle), kAssociationAssign); + } + + CGLContextObj GetContextHandle(id object) { + return reinterpret_cast(GetAssoc(object, &kContextHandleKey)); + } + + void SetContextHandle(id object, CGLContextObj handle) { + SetAssoc(object, &kContextHandleKey, reinterpret_cast(handle), kAssociationAssign); + } + + id PixelFormatInitWithAttributes(id self, SEL, const CGLPixelFormatAttribute* attribs) { + CGLPixelFormatObj pixelFormat = nullptr; + GLint count = 0; + if (CGLImpl::ChoosePixelFormat(attribs, &pixelFormat, &count) != kCGLNoError) { + return nil; + } + SetPixelFormatHandle(self, pixelFormat); + return self; + } + + id PixelFormatInitWithCGLPixelFormatObj(id self, SEL, CGLPixelFormatObj format) { + if (!format) { + return nil; + } + SetPixelFormatHandle(self, CGLImpl::RetainPixelFormat(format)); + return self; + } + + void PixelFormatGetValues(id self, SEL, GLint* vals, CGLPixelFormatAttribute attrib, GLint screen) { + (void)screen; + if (!vals) { + return; + } + GLint value = 0; + if (CGLImpl::DescribePixelFormat(GetPixelFormatHandle(self), 0, attrib, &value) == kCGLNoError) { + *vals = value; + } + } + + GLint PixelFormatNumberOfVirtualScreens(id, SEL) { + return 1; + } + + CGLPixelFormatObj PixelFormatCGLPixelFormatObj(id self, SEL) { + return GetPixelFormatHandle(self); + } + + void PixelFormatDealloc(id self, SEL selector) { + if (auto format = GetPixelFormatHandle(self)) { + CGLImpl::ReleasePixelFormat(format); + SetPixelFormatHandle(self, nullptr); + } + if (g_pixelFormatDealloc) { + reinterpret_cast(g_pixelFormatDealloc)(self, selector); + } + } + + id ContextInitWithFormatShare(id self, SEL, id format, id share) { + auto pixelFormat = GetPixelFormatHandle(format); + if (!pixelFormat) { + return nil; + } + auto shareContext = GetContextHandle(share); + CGLContextObj context = nullptr; + if (CGLImpl::CreateContext(pixelFormat, shareContext, &context) != kCGLNoError) { + return nil; + } + CGLImpl::SetContextNSObject(context, self); + SetContextHandle(self, context); + SetAssoc(self, &kContextPixelFormatObjectKey, format, kAssociationRetain); + SetAssoc(self, &kContextViewKey, nil, kAssociationAssign); + SetAssoc(self, &kContextLayerKey, nil, kAssociationRetain); + return self; + } + + id ContextInitWithCGLContextObj(id self, SEL, CGLContextObj context) { + if (!context) { + return nil; + } + CGLImpl::RetainContext(context); + CGLImpl::SetContextNSObject(context, self); + SetContextHandle(self, context); + if (auto pixelFormat = CGLImpl::GetPixelFormat(context)) { + id pixelFormatClass = reinterpret_cast(objc_getClass("NSOpenGLPixelFormat")); + id allocated = SendId(pixelFormatClass, "alloc"); + id pixelFormatObject = SendIdCGLPixelFormat(allocated, "initWithCGLPixelFormatObj:", pixelFormat); + SetAssoc(self, &kContextPixelFormatObjectKey, pixelFormatObject, kAssociationRetain); + SendVoid(pixelFormatObject, "release"); + } + SetAssoc(self, &kContextViewKey, nil, kAssociationAssign); + SetAssoc(self, &kContextLayerKey, nil, kAssociationRetain); + return self; + } + + id CreateMetalLayerForView(id view) { + if (!view) { + return nil; + } + id metalLayerClass = reinterpret_cast(objc_getClass("CAMetalLayer")); + if (!metalLayerClass) { + MGLOG_E("NSOpenGLImpl: CAMetalLayer class not found"); + return nil; + } + + SendVoidBool(view, "setWantsLayer:", true); + id layer = SendId(metalLayerClass, "layer"); + if (!layer) { + return nil; + } + + CGRect bounds = SendCGRect(view, "bounds"); + if (bounds.size.width <= 0.0 || bounds.size.height <= 0.0) { + bounds.size.width = 1.0; + bounds.size.height = 1.0; + } + CGFloat scale = 1.0; + id window = SendId(view, "window"); + if (window) { + scale = SendCGFloat(window, "backingScaleFactor"); + } + if (scale <= 0.0) { + scale = 1.0; + } + CGSize drawableSize = {bounds.size.width * scale, bounds.size.height * scale}; + SendVoidCGRect(layer, "setFrame:", bounds); + SendVoidCGSize(layer, "setDrawableSize:", drawableSize); + SendVoidId(view, "setLayer:", layer); + return layer; + } + + void ContextSetView(id self, SEL, id view) { + auto context = GetContextHandle(self); + if (!context) { + return; + } + if (!view) { + CGLImpl::ClearDrawable(context); + SetAssoc(self, &kContextViewKey, nil, kAssociationAssign); + SetAssoc(self, &kContextLayerKey, nil, kAssociationRetain); + return; + } + id layer = CreateMetalLayerForView(view); + if (!layer) { + return; + } + SetAssoc(self, &kContextViewKey, view, kAssociationAssign); + SetAssoc(self, &kContextLayerKey, layer, kAssociationRetain); + const auto error = CGLImpl::AttachDrawable(context, view, layer); + if (error != kCGLNoError) { + MGLOG_E("NSOpenGLImpl: failed to attach drawable: %s", CGLImpl::ErrorString(error)); + } + } + + id ContextView(id self, SEL) { + return GetAssoc(self, &kContextViewKey); + } + + void ContextMakeCurrent(id self, SEL) { + auto context = GetContextHandle(self); + if (!context) { + return; + } + const auto error = CGLImpl::SetCurrentContext(context); + if (error != kCGLNoError) { + MGLOG_E("NSOpenGLImpl: makeCurrentContext failed: %s", CGLImpl::ErrorString(error)); + } + } + + void ContextClearCurrent(id, SEL) { + CGLImpl::SetCurrentContext(nullptr); + } + + id ContextCurrentContext(id, SEL) { + auto context = CGLImpl::GetCurrentContext(); + return reinterpret_cast(CGLImpl::GetContextNSObject(context)); + } + + void ContextFlushBuffer(id self, SEL) { + auto context = GetContextHandle(self); + if (!context) { + return; + } + const auto error = CGLImpl::FlushDrawable(context); + if (error != kCGLNoError) { + MGLOG_E("NSOpenGLImpl: flushBuffer failed: %s", CGLImpl::ErrorString(error)); + } + } + + void ContextClearDrawable(id self, SEL) { + auto context = GetContextHandle(self); + if (context) { + CGLImpl::ClearDrawable(context); + } + } + + void ContextUpdate(id self, SEL) { + auto context = GetContextHandle(self); + if (!context) { + return; + } + id view = GetAssoc(self, &kContextViewKey); + if (!view) { + CGLImpl::UpdateContext(context); + return; + } + id layer = GetAssoc(self, &kContextLayerKey); + if (!layer) { + ContextSetView(self, nullptr, view); + return; + } + CGRect bounds = SendCGRect(view, "bounds"); + CGFloat scale = 1.0; + if (id window = SendId(view, "window")) { + scale = SendCGFloat(window, "backingScaleFactor"); + } + if (scale <= 0.0) { + scale = 1.0; + } + SendVoidCGRect(layer, "setFrame:", bounds); + SendVoidCGSize(layer, "setDrawableSize:", {bounds.size.width * scale, bounds.size.height * scale}); + CGLImpl::UpdateContext(context); + } + + void ContextSetValues(id self, SEL, const GLint* values, long parameter) { + auto context = GetContextHandle(self); + if (context) { + CGLImpl::SetParameter(context, static_cast(parameter), values); + } + } + + void ContextGetValues(id self, SEL, GLint* values, long parameter) { + auto context = GetContextHandle(self); + if (context) { + CGLImpl::GetParameter(context, static_cast(parameter), values); + } + } + + CGLContextObj ContextCGLContextObj(id self, SEL) { + return GetContextHandle(self); + } + + id ContextPixelFormat(id self, SEL) { + return GetAssoc(self, &kContextPixelFormatObjectKey); + } + + void ContextDealloc(id self, SEL selector) { + SetAssoc(self, &kContextPixelFormatObjectKey, nil, kAssociationRetain); + if (auto context = GetContextHandle(self)) { + CGLImpl::SetContextNSObject(context, nullptr); + CGLImpl::ReleaseContext(context); + SetContextHandle(self, nullptr); + } + if (g_contextDealloc) { + reinterpret_cast(g_contextDealloc)(self, selector); + } + } + + void ReplaceInstanceMethod(Class cls, const char* selectorName, IMP replacement, IMP* original = nullptr) { + SEL selector = sel_registerName(selectorName); + Method method = class_getInstanceMethod(cls, selector); + if (!method) { + MGLOG_W("NSOpenGLImpl: missing instance method %s", selectorName); + return; + } + if (original) { + *original = method_getImplementation(method); + } + method_setImplementation(method, replacement); + } + + void ReplaceClassMethod(Class cls, const char* selectorName, IMP replacement) { + SEL selector = sel_registerName(selectorName); + Method method = class_getClassMethod(cls, selector); + if (!method) { + MGLOG_W("NSOpenGLImpl: missing class method %s", selectorName); + return; + } + method_setImplementation(method, replacement); + } + + void InstallHooksOnce() { + Class pixelFormatClass = objc_getClass("NSOpenGLPixelFormat"); + Class contextClass = objc_getClass("NSOpenGLContext"); + if (!pixelFormatClass || !contextClass) { + MGLOG_W("NSOpenGLImpl: NSOpenGL classes are not loaded; hooks not installed"); + return; + } + + ReplaceInstanceMethod(pixelFormatClass, "initWithAttributes:", + reinterpret_cast(PixelFormatInitWithAttributes)); + ReplaceInstanceMethod(pixelFormatClass, "initWithCGLPixelFormatObj:", + reinterpret_cast(PixelFormatInitWithCGLPixelFormatObj)); + ReplaceInstanceMethod(pixelFormatClass, "getValues:forAttribute:forVirtualScreen:", + reinterpret_cast(PixelFormatGetValues)); + ReplaceInstanceMethod(pixelFormatClass, "numberOfVirtualScreens", + reinterpret_cast(PixelFormatNumberOfVirtualScreens)); + ReplaceInstanceMethod(pixelFormatClass, "CGLPixelFormatObj", + reinterpret_cast(PixelFormatCGLPixelFormatObj)); + ReplaceInstanceMethod(pixelFormatClass, "dealloc", reinterpret_cast(PixelFormatDealloc), + &g_pixelFormatDealloc); + + ReplaceInstanceMethod(contextClass, "initWithFormat:shareContext:", + reinterpret_cast(ContextInitWithFormatShare)); + ReplaceInstanceMethod(contextClass, "initWithCGLContextObj:", + reinterpret_cast(ContextInitWithCGLContextObj)); + ReplaceInstanceMethod(contextClass, "setView:", reinterpret_cast(ContextSetView)); + ReplaceInstanceMethod(contextClass, "view", reinterpret_cast(ContextView)); + ReplaceInstanceMethod(contextClass, "makeCurrentContext", reinterpret_cast(ContextMakeCurrent)); + ReplaceClassMethod(contextClass, "clearCurrentContext", reinterpret_cast(ContextClearCurrent)); + ReplaceClassMethod(contextClass, "currentContext", reinterpret_cast(ContextCurrentContext)); + ReplaceInstanceMethod(contextClass, "flushBuffer", reinterpret_cast(ContextFlushBuffer)); + ReplaceInstanceMethod(contextClass, "clearDrawable", reinterpret_cast(ContextClearDrawable)); + ReplaceInstanceMethod(contextClass, "update", reinterpret_cast(ContextUpdate)); + ReplaceInstanceMethod(contextClass, "setValues:forParameter:", reinterpret_cast(ContextSetValues)); + ReplaceInstanceMethod(contextClass, "getValues:forParameter:", reinterpret_cast(ContextGetValues)); + ReplaceInstanceMethod(contextClass, "CGLContextObj", reinterpret_cast(ContextCGLContextObj)); + ReplaceInstanceMethod(contextClass, "pixelFormat", reinterpret_cast(ContextPixelFormat)); + ReplaceInstanceMethod(contextClass, "dealloc", reinterpret_cast(ContextDealloc), &g_contextDealloc); + + MGLOG_I("NSOpenGLImpl hooks installed"); + } + } // namespace + + void InstallHooks() { + std::call_once(g_installOnce, InstallHooksOnce); + } +} // namespace MobileGL::MG_Impl::NSOpenGLImpl +#endif diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h new file mode 100644 index 00000000..1184d5bf --- /dev/null +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h @@ -0,0 +1,14 @@ +// MobileGL - MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +namespace MobileGL::MG_Impl::NSOpenGLImpl { + void InstallHooks(); +} diff --git a/MobileGL/MG_Util/Debug/Log.cpp b/MobileGL/MG_Util/Debug/Log.cpp index 7e076c08..815144ce 100644 --- a/MobileGL/MG_Util/Debug/Log.cpp +++ b/MobileGL/MG_Util/Debug/Log.cpp @@ -11,7 +11,11 @@ namespace MobileGL { namespace MG_Util::Debug { static FILE* s_logFile = nullptr; - static std::mutex s_mutex; + + std::mutex& LogMutex() { + static auto* mutex = new std::mutex(); + return *mutex; + } constexpr char* GetOSName() { #if defined(_WIN32) @@ -81,7 +85,7 @@ namespace MobileGL { } void Log(const char* levelTag, android_LogPriority androidLogLevel, const char* fmt, ...) { - std::lock_guard lock(s_mutex); + std::lock_guard lock(LogMutex()); #if MOBILEGL_LOG_ENABLE_STACKTRACE auto trace = std::stacktrace::current();