Files
MobileGL/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
T

734 lines
25 KiB
C++

// 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;
GLint DisplayMask = 0;
};
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;
case kCGLPFADisplayMask:
pixelFormat.DisplayMask = 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;
}
CGLError RecreateSurfaceLocked(CGLContextObj ctx, ContextObject& object) {
if (!object.MetalLayer) {
return kCGLBadDrawable;
}
if (object.Surface != EGL_NO_SURFACE) {
EGLImpl::DestroySurface(object.Display, object.Surface);
object.Surface = EGL_NO_SURFACE;
}
const EGLAttrib attribs[] = {
EGL_WIDTH, std::max<GLint>(object.SurfaceBackingSize[0], 1),
EGL_HEIGHT, std::max<GLint>(object.SurfaceBackingSize[1], 1),
EGL_NONE,
};
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config,
object.MetalLayer, attribs);
if (surface == EGL_NO_SURFACE) {
object.HasDrawable = false;
return kCGLBadDrawable;
}
object.Surface = surface;
object.HasDrawable = true;
return GetCurrentContext() == ctx ? MakeCurrentLocked(ctx, object) : kCGLNoError;
}
CGLError ResizeSurfaceLocked(ContextObject& object) {
if (object.Surface == EGL_NO_SURFACE) {
return kCGLBadDrawable;
}
return EGLImpl::ResizePlatformWindowSurface(
object.Display, object.Surface,
std::max<GLint>(object.SurfaceBackingSize[0], 1),
std::max<GLint>(object.SurfaceBackingSize[1], 1))
? kCGLNoError
: kCGLBadDrawable;
}
} // 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 kCGLPFADisplayMask:
*value = pixelFormat->DisplayMask;
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 SetVirtualScreen(CGLContextObj ctx, GLint screen) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (!object) {
return kCGLBadContext;
}
if (screen != 0) {
return kCGLBadValue;
}
object->VirtualScreen = screen;
return kCGLNoError;
}
CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (!object) {
return kCGLBadContext;
}
if (!screen) {
return kCGLBadAddress;
}
*screen = object->VirtualScreen;
return kCGLNoError;
}
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:
{
const GLint width = std::max<GLint>(params[0], 1);
const GLint height = std::max<GLint>(params[1], 1);
if (object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height) {
return kCGLNoError;
}
object->SurfaceBackingSize[0] = width;
object->SurfaceBackingSize[1] = height;
if (object->MetalLayer && object->Surface != EGL_NO_SURFACE) {
return ResizeSurfaceLocked(*object);
}
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, GLint width, GLint height) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(ctx);
if (!object) {
return kCGLBadContext;
}
if (!metalLayer) {
return kCGLBadDrawable;
}
width = std::max<GLint>(width, 1);
height = std::max<GLint>(height, 1);
const Bool sameSize = object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height;
object->SurfaceBackingSize[0] = width;
object->SurfaceBackingSize[1] = height;
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer && sameSize) {
object->View = nsView;
object->HasDrawable = true;
return kCGLNoError;
}
if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) {
object->View = nsView;
object->HasDrawable = true;
return ResizeSurfaceLocked(*object);
}
if (object->Surface != EGL_NO_SURFACE) {
EGLImpl::DestroySurface(object->Display, object->Surface);
object->Surface = EGL_NO_SURFACE;
}
object->View = nsView;
object->MetalLayer = metalLayer;
const auto recreateError = RecreateSurfaceLocked(ctx, *object);
if (recreateError != kCGLNoError) {
return recreateError;
}
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