mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Feat] (MG_Impl): add macOS CGL and NSOpenGL frontends
This commit is contained in:
+15
-2
@@ -273,6 +273,15 @@ set(SOURCE_FILES
|
|||||||
MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp
|
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
|
set(MOBILEGL_LINK_LIBRARIES
|
||||||
glslang::glslang
|
glslang::glslang
|
||||||
spirv-cross-c
|
spirv-cross-c
|
||||||
@@ -388,12 +397,16 @@ if (APPLE)
|
|||||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||||
"-framework Cocoa"
|
"-framework Cocoa"
|
||||||
"-framework QuartzCore"
|
"-framework QuartzCore"
|
||||||
"-framework Foundation")
|
"-framework Foundation"
|
||||||
|
"-framework OpenGL"
|
||||||
|
objc)
|
||||||
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
if(TARGET ${CMAKE_PROJECT_NAME}_s)
|
||||||
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
|
||||||
"-framework Cocoa"
|
"-framework Cocoa"
|
||||||
"-framework QuartzCore"
|
"-framework QuartzCore"
|
||||||
"-framework Foundation")
|
"-framework Foundation"
|
||||||
|
"-framework OpenGL"
|
||||||
|
objc)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#define MOBILEGL_GLX_API MOBILEGL_API
|
#define MOBILEGL_GLX_API MOBILEGL_API
|
||||||
#define MOBILEGL_GL_API MOBILEGL_API
|
#define MOBILEGL_GL_API MOBILEGL_API
|
||||||
#define MOBILEGL_EGL_API MOBILEGL_API
|
#define MOBILEGL_EGL_API MOBILEGL_API
|
||||||
|
#define MOBILEGL_CGL_API MOBILEGL_API
|
||||||
|
#define MOBILEGL_NSOPENGL_API MOBILEGL_API
|
||||||
|
|
||||||
// ====================== MobileGL configurations ======================= //
|
// ====================== MobileGL configurations ======================= //
|
||||||
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
#ifndef MOBILEGL_LOG_ACTIVE_LEVEL
|
||||||
|
|||||||
+30
-17
@@ -18,6 +18,29 @@
|
|||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
namespace {
|
namespace {
|
||||||
Bool g_isInitialized = false;
|
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() {
|
void Initialize() {
|
||||||
@@ -43,22 +66,7 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Destroy() {
|
void Destroy() {
|
||||||
if (!g_isInitialized) {
|
DestroyImpl(true);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
@@ -70,7 +78,12 @@ namespace MobileGL {
|
|||||||
if (std::getenv("MOBILEGL_TRACE_SKIP_AUTODESTROY") != nullptr) {
|
if (std::getenv("MOBILEGL_TRACE_SKIP_AUTODESTROY") != nullptr) {
|
||||||
return;
|
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
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -7,6 +7,13 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "GetProcAddress.h"
|
#include "GetProcAddress.h"
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#ifndef GL_SILENCE_DEPRECATION
|
||||||
|
#define GL_SILENCE_DEPRECATION
|
||||||
|
#endif
|
||||||
|
#include <OpenGL/OpenGL.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define GETPROC(name, var) \
|
#define GETPROC(name, var) \
|
||||||
if (strcmp(#name, var) == 0) { \
|
if (strcmp(#name, var) == 0) { \
|
||||||
return (void*)name; \
|
return (void*)name; \
|
||||||
@@ -60,6 +67,32 @@ namespace MobileGL::MG_Impl {
|
|||||||
GETPROC(eglCreatePlatformPixmapSurface, name);
|
GETPROC(eglCreatePlatformPixmapSurface, name);
|
||||||
GETPROC(eglWaitSync, 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(glCullFace, name);
|
||||||
GETPROC(glFrontFace, name);
|
GETPROC(glFrontFace, name);
|
||||||
GETPROC(glHint, name);
|
GETPROC(glHint, name);
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
|
|
||||||
#include "GLImpl/Texture/ProxyTexture.h"
|
#include "GLImpl/Texture/ProxyTexture.h"
|
||||||
#include "GLImpl/Framebuffer/GL_Framebuffer.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/TextureObject1D.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureObject2D.h>
|
#include <MG_State/GLState/TextureState/TextureObject2D.h>
|
||||||
@@ -45,5 +48,8 @@ namespace MobileGL::MG_Impl {
|
|||||||
MakeUnique<GLImpl::FramebufferImpl::DefaultFramebufferInfo>(fbo0, colorTex, depthTex, stencilTex);
|
MakeUnique<GLImpl::FramebufferImpl::DefaultFramebufferInfo>(fbo0, colorTex, depthTex, stencilTex);
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).Bind(fbo0);
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).Bind(fbo0);
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).Bind(fbo0);
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).Bind(fbo0);
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
NSOpenGLImpl::InstallHooks();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_Impl
|
} // 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();
|
||||||
|
}
|
||||||
@@ -11,7 +11,11 @@
|
|||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
namespace MG_Util::Debug {
|
namespace MG_Util::Debug {
|
||||||
static FILE* s_logFile = nullptr;
|
static FILE* s_logFile = nullptr;
|
||||||
static std::mutex s_mutex;
|
|
||||||
|
std::mutex& LogMutex() {
|
||||||
|
static auto* mutex = new std::mutex();
|
||||||
|
return *mutex;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr char* GetOSName() {
|
constexpr char* GetOSName() {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
@@ -81,7 +85,7 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Log(const char* levelTag, android_LogPriority androidLogLevel, const char* fmt, ...) {
|
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
|
#if MOBILEGL_LOG_ENABLE_STACKTRACE
|
||||||
auto trace = std::stacktrace::current();
|
auto trace = std::stacktrace::current();
|
||||||
|
|||||||
Reference in New Issue
Block a user