[Feat] (MG_Impl): add macOS CGL and NSOpenGL frontends

This commit is contained in:
2026-06-28 20:33:14 +08:00
parent d61a0b6904
commit f121aeb57f
12 changed files with 1406 additions and 21 deletions
+15 -2
View File
@@ -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()
+2
View File
@@ -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
+30 -17
View File
@@ -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
+649
View File
@@ -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<CGLPixelFormatObj, PixelFormatObject>& PixelFormats() {
static auto* formats = new UnorderedMap<CGLPixelFormatObj, PixelFormatObject>();
return *formats;
}
UnorderedMap<CGLContextObj, ContextObject>& Contexts() {
static auto* contexts = new UnorderedMap<CGLContextObj, ContextObject>();
return *contexts;
}
UnorderedMap<std::thread::id, CGLContextObj>& CurrentContexts() {
static auto* contexts = new UnorderedMap<std::thread::id, CGLContextObj>();
return *contexts;
}
CGLPixelFormatObj EncodePixelFormat(Uint64 handle) {
return reinterpret_cast<CGLPixelFormatObj>(static_cast<SizeT>(handle));
}
CGLContextObj EncodeContext(Uint64 handle) {
return reinterpret_cast<CGLContextObj>(static_cast<SizeT>(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<std::recursive_mutex>&) {
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<std::recursive_mutex> lock(RegistryMutex());
if (!pix || !npix) {
return kCGLBadAddress;
}
PixelFormatObject object;
if (attribs) {
for (SizeT i = 0; attribs[i] != static_cast<CGLPixelFormatAttribute>(0); ++i) {
const auto attrib = attribs[i];
GLint value = 1;
if (AttributeHasValue(attrib)) {
value = static_cast<GLint>(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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
auto* pixelFormat = TryGetPixelFormat(pix);
if (pixelFormat) {
++pixelFormat->RetainCount;
}
return pix;
}
GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* pixelFormat = TryGetPixelFormat(pix);
return pixelFormat ? pixelFormat->RetainCount : 0;
}
CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
const std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (object) {
++object->RetainCount;
}
return ctx;
}
void ReleaseContext(CGLContextObj ctx) {
const std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
return object ? object->RetainCount : 0;
}
CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
return object ? object->PixelFormat : nullptr;
}
CGLError SetCurrentContext(CGLContextObj ctx) {
const std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
return TryGetContext(ctx) ? kCGLNoError : kCGLBadContext;
}
CGLError ClearDrawable(CGLContextObj ctx) {
const std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (!object) {
return kCGLBadContext;
}
object->Locked = true;
return kCGLNoError;
}
CGLError UnlockContext(CGLContextObj ctx) {
const std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
return object ? object->NSObject : nullptr;
}
void SetContextNSObject(CGLContextObj ctx, void* nsObject) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (object) {
object->NSObject = nsObject;
}
}
} // namespace MobileGL::MG_Impl::CGLImpl
#endif
+49
View File
@@ -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 <Includes.h>
#if defined(__APPLE__)
#ifndef GL_SILENCE_DEPRECATION
#define GL_SILENCE_DEPRECATION
#endif
#include <OpenGL/OpenGL.h>
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
@@ -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
@@ -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 <Includes.h>
#if defined(__APPLE__)
#include "MG_Impl/GetProcAddress.h"
#include <dlfcn.h>
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<const void*>(MobileGLDlsym), reinterpret_cast<const void*>(dlsym)},
};
} // namespace
#endif
+33
View File
@@ -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 <OpenGL/OpenGL.h>
#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);
+6
View File
@@ -10,6 +10,9 @@
#include "GLImpl/Texture/ProxyTexture.h"
#include "GLImpl/Framebuffer/GL_Framebuffer.h"
#if defined(__APPLE__)
#include "NSOpenGLImpl/NSOpenGLImpl.h"
#endif
#include <MG_State/GLState/TextureState/TextureObject1D.h>
#include <MG_State/GLState/TextureState/TextureObject2D.h>
@@ -45,5 +48,8 @@ namespace MobileGL::MG_Impl {
MakeUnique<GLImpl::FramebufferImpl::DefaultFramebufferInfo>(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
@@ -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 <CoreGraphics/CoreGraphics.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
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 <typename Fn>
Fn ObjcMsgSend() {
return reinterpret_cast<Fn>(objc_msgSend);
}
id SendId(id receiver, const char* selector) {
return receiver ? ObjcMsgSend<id (*)(id, SEL)>()(receiver, sel_registerName(selector)) : nil;
}
void SendVoid(id receiver, const char* selector) {
if (receiver) {
ObjcMsgSend<void (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
}
void SendVoidBool(id receiver, const char* selector, bool value) {
if (receiver) {
ObjcMsgSend<void (*)(id, SEL, bool)>()(receiver, sel_registerName(selector), value);
}
}
void SendVoidId(id receiver, const char* selector, id value) {
if (receiver) {
ObjcMsgSend<void (*)(id, SEL, id)>()(receiver, sel_registerName(selector), value);
}
}
id SendIdCGLPixelFormat(id receiver, const char* selector, CGLPixelFormatObj value) {
return receiver ? ObjcMsgSend<id (*)(id, SEL, CGLPixelFormatObj)>()(receiver, sel_registerName(selector), value)
: nil;
}
void SendVoidCGRect(id receiver, const char* selector, CGRect value) {
if (receiver) {
ObjcMsgSend<void (*)(id, SEL, CGRect)>()(receiver, sel_registerName(selector), value);
}
}
void SendVoidCGSize(id receiver, const char* selector, CGSize value) {
if (receiver) {
ObjcMsgSend<void (*)(id, SEL, CGSize)>()(receiver, sel_registerName(selector), value);
}
}
bool SendBool(id receiver, const char* selector) {
return receiver ? ObjcMsgSend<bool (*)(id, SEL)>()(receiver, sel_registerName(selector)) : false;
}
CGRect SendCGRect(id receiver, const char* selector) {
if (!receiver) {
return {};
}
return ObjcMsgSend<CGRect (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
CGFloat SendCGFloat(id receiver, const char* selector) {
return receiver ? ObjcMsgSend<CGFloat (*)(id, SEL)>()(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<objc_AssociationPolicy>(policy));
}
id GetAssoc(id object, const void* key) {
return object ? objc_getAssociatedObject(object, key) : nil;
}
CGLPixelFormatObj GetPixelFormatHandle(id object) {
return reinterpret_cast<CGLPixelFormatObj>(GetAssoc(object, &kPixelFormatHandleKey));
}
void SetPixelFormatHandle(id object, CGLPixelFormatObj handle) {
SetAssoc(object, &kPixelFormatHandleKey, reinterpret_cast<id>(handle), kAssociationAssign);
}
CGLContextObj GetContextHandle(id object) {
return reinterpret_cast<CGLContextObj>(GetAssoc(object, &kContextHandleKey));
}
void SetContextHandle(id object, CGLContextObj handle) {
SetAssoc(object, &kContextHandleKey, reinterpret_cast<id>(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<void (*)(id, SEL)>(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<id>(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<id>(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<id>(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<CGLContextParameter>(parameter), values);
}
}
void ContextGetValues(id self, SEL, GLint* values, long parameter) {
auto context = GetContextHandle(self);
if (context) {
CGLImpl::GetParameter(context, static_cast<CGLContextParameter>(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<void (*)(id, SEL)>(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<IMP>(PixelFormatInitWithAttributes));
ReplaceInstanceMethod(pixelFormatClass, "initWithCGLPixelFormatObj:",
reinterpret_cast<IMP>(PixelFormatInitWithCGLPixelFormatObj));
ReplaceInstanceMethod(pixelFormatClass, "getValues:forAttribute:forVirtualScreen:",
reinterpret_cast<IMP>(PixelFormatGetValues));
ReplaceInstanceMethod(pixelFormatClass, "numberOfVirtualScreens",
reinterpret_cast<IMP>(PixelFormatNumberOfVirtualScreens));
ReplaceInstanceMethod(pixelFormatClass, "CGLPixelFormatObj",
reinterpret_cast<IMP>(PixelFormatCGLPixelFormatObj));
ReplaceInstanceMethod(pixelFormatClass, "dealloc", reinterpret_cast<IMP>(PixelFormatDealloc),
&g_pixelFormatDealloc);
ReplaceInstanceMethod(contextClass, "initWithFormat:shareContext:",
reinterpret_cast<IMP>(ContextInitWithFormatShare));
ReplaceInstanceMethod(contextClass, "initWithCGLContextObj:",
reinterpret_cast<IMP>(ContextInitWithCGLContextObj));
ReplaceInstanceMethod(contextClass, "setView:", reinterpret_cast<IMP>(ContextSetView));
ReplaceInstanceMethod(contextClass, "view", reinterpret_cast<IMP>(ContextView));
ReplaceInstanceMethod(contextClass, "makeCurrentContext", reinterpret_cast<IMP>(ContextMakeCurrent));
ReplaceClassMethod(contextClass, "clearCurrentContext", reinterpret_cast<IMP>(ContextClearCurrent));
ReplaceClassMethod(contextClass, "currentContext", reinterpret_cast<IMP>(ContextCurrentContext));
ReplaceInstanceMethod(contextClass, "flushBuffer", reinterpret_cast<IMP>(ContextFlushBuffer));
ReplaceInstanceMethod(contextClass, "clearDrawable", reinterpret_cast<IMP>(ContextClearDrawable));
ReplaceInstanceMethod(contextClass, "update", reinterpret_cast<IMP>(ContextUpdate));
ReplaceInstanceMethod(contextClass, "setValues:forParameter:", reinterpret_cast<IMP>(ContextSetValues));
ReplaceInstanceMethod(contextClass, "getValues:forParameter:", reinterpret_cast<IMP>(ContextGetValues));
ReplaceInstanceMethod(contextClass, "CGLContextObj", reinterpret_cast<IMP>(ContextCGLContextObj));
ReplaceInstanceMethod(contextClass, "pixelFormat", reinterpret_cast<IMP>(ContextPixelFormat));
ReplaceInstanceMethod(contextClass, "dealloc", reinterpret_cast<IMP>(ContextDealloc), &g_contextDealloc);
MGLOG_I("NSOpenGLImpl hooks installed");
}
} // namespace
void InstallHooks() {
std::call_once(g_installOnce, InstallHooksOnce);
}
} // namespace MobileGL::MG_Impl::NSOpenGLImpl
#endif
@@ -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 <Includes.h>
namespace MobileGL::MG_Impl::NSOpenGLImpl {
void InstallHooks();
}
+6 -2
View File
@@ -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<std::mutex> lock(s_mutex);
std::lock_guard<std::mutex> lock(LogMutex());
#if MOBILEGL_LOG_ENABLE_STACKTRACE
auto trace = std::stacktrace::current();