From 3fc6357f28a4f0051bc9f421492b27742662ac1b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 28 Jun 2026 23:22:33 +0800 Subject: [PATCH] [Fix] (MG_Impl): handle Retina drawable resize on macOS --- MobileGL/MG_Backend/BackendObject.cpp | 3 +- MobileGL/MG_Backend/BackendObject.h | 2 + .../BackendObject_DirectVulkan.cpp | 3 +- MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp | 58 +++++++--- MobileGL/MG_Impl/CGLImpl/CGLImpl.h | 2 +- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 16 +++ .../MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp | 101 +++++++++++++----- 7 files changed, 142 insertions(+), 43 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index 32e72ae2..6c8cb60a 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -211,7 +211,8 @@ namespace MobileGL::MG_Backend { } if (m_eglSurfaceInitialized && m_eglSurfaceKind == SurfaceKind::Window && - m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle) { + m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle && + m_windowHandle.Width == handle.Width && m_windowHandle.Height == handle.Height) { return true; } diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 464eff8e..8c76d7ea 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -232,6 +232,8 @@ namespace MobileGL { struct WindowHandle { WindowBackend Backend = WindowBackend::Unknown; void* Handle = nullptr; + Uint32 Width = 0; + Uint32 Height = 0; }; class BackendObject { diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index deedda00..0e98b6a9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -420,7 +420,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Bool sameHandle = m_eglSurfaceInitialized && m_eglSurfaceKind == SurfaceKind::Window && - m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle; + m_windowHandle.Backend == handle.Backend && m_windowHandle.Handle == handle.Handle && + m_windowHandle.Width == handle.Width && m_windowHandle.Height == handle.Height; if (sameHandle) { return true; } diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp index b9f5b309..ae09a832 100644 --- a/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp @@ -232,6 +232,30 @@ namespace MobileGL::MG_Impl::CGLImpl { } 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(object.SurfaceBackingSize[0], 1), + EGL_HEIGHT, std::max(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; + } } // namespace CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) { @@ -460,9 +484,19 @@ namespace MobileGL::MG_Impl::CGLImpl { EGLImpl::SwapInterval(object->Display, object->SwapInterval); return kCGLNoError; case kCGLCPSurfaceBackingSize: - object->SurfaceBackingSize[0] = params[0]; - object->SurfaceBackingSize[1] = params[1]; + { + const GLint width = std::max(params[0], 1); + const GLint height = std::max(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) { + return RecreateSurfaceLocked(ctx, *object); + } return kCGLNoError; + } case kCGLCPSurfaceOpacity: case kCGLCPSurfaceOrder: case kCGLCPMPSwapsInFlight: @@ -600,7 +634,7 @@ namespace MobileGL::MG_Impl::CGLImpl { } } - CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer) { + CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height) { const std::lock_guard lock(RegistryMutex()); auto* object = TryGetContext(ctx); if (!object) { @@ -609,7 +643,12 @@ namespace MobileGL::MG_Impl::CGLImpl { if (!metalLayer) { return kCGLBadDrawable; } - if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) { + width = std::max(width, 1); + height = std::max(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; @@ -618,16 +657,11 @@ namespace MobileGL::MG_Impl::CGLImpl { 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); + const auto recreateError = RecreateSurfaceLocked(ctx, *object); + if (recreateError != kCGLNoError) { + return recreateError; } return kCGLNoError; } diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.h b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h index a0e4951e..7f6bf28b 100644 --- a/MobileGL/MG_Impl/CGLImpl/CGLImpl.h +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h @@ -42,7 +42,7 @@ namespace MobileGL::MG_Impl::CGLImpl { void GetVersion(GLint* majorvers, GLint* minorvers); const char* ErrorString(CGLError error); - CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer); + CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height); void* GetContextNSObject(CGLContextObj ctx); void SetContextNSObject(CGLContextObj ctx, void* nsObject); } diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 5a30f930..f73e3846 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -55,6 +55,18 @@ namespace MobileGL::MG_Impl::EGLImpl { return defaultValue; } + EGLint GetAttribValue(const EGLAttrib* attribList, EGLint attrib, EGLint defaultValue) { + if (!attribList) { + return defaultValue; + } + for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) { + if (attribList[i] == attrib) { + return static_cast(attribList[i + 1]); + } + } + return defaultValue; + } + template Bool IsNullNativeHandle(NativeType nativeHandle) { if constexpr (std::is_pointer_v) { @@ -102,6 +114,8 @@ namespace MobileGL::MG_Impl::EGLImpl { const MG_Backend::WindowHandle windowHandle = { .Backend = DetectWindowBackend(), .Handle = ToVoidHandle(window), + .Width = static_cast(std::max(GetAttribValue(attrib_list, EGL_WIDTH, 0), 0)), + .Height = static_cast(std::max(GetAttribValue(attrib_list, EGL_HEIGHT, 0), 0)), }; if (!backendObject->CreateEGLWindowSurface(windowHandle)) { state->SetError(EGL_BAD_NATIVE_WINDOW); @@ -598,6 +612,8 @@ namespace MobileGL::MG_Impl::EGLImpl { const MG_Backend::WindowHandle windowHandle = { .Backend = DetectWindowBackend(), .Handle = native_window, + .Width = static_cast(std::max(GetAttribValue(attrib_list, EGL_WIDTH, 0), 0)), + .Height = static_cast(std::max(GetAttribValue(attrib_list, EGL_HEIGHT, 0), 0)), }; if (!backendObject->CreateEGLWindowSurface(windowHandle)) { state->SetError(EGL_BAD_NATIVE_WINDOW); diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp index 70966951..b23b848b 100644 --- a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp @@ -77,6 +77,12 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } } + void SendVoidCGFloat(id receiver, const char* selector, CGFloat value) { + if (receiver) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + } + bool SendBool(id receiver, const char* selector) { return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector)) : false; } @@ -88,6 +94,13 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { return ObjcMsgSend()(receiver, sel_registerName(selector)); } + CGRect SendCGRectCGRect(id receiver, const char* selector, CGRect value) { + if (!receiver) { + return {}; + } + return ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + CGFloat SendCGFloat(id receiver, const char* selector) { return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector)) : 1.0; } @@ -200,7 +213,48 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { return self; } - id CreateMetalLayerForView(id view) { + struct DrawableGeometry { + CGRect Bounds = {}; + CGSize DrawableSize = {1.0, 1.0}; + CGFloat Scale = 1.0; + }; + + DrawableGeometry GetDrawableGeometry(id view) { + DrawableGeometry geometry; + geometry.Bounds = SendCGRect(view, "bounds"); + if (geometry.Bounds.size.width <= 0.0 || geometry.Bounds.size.height <= 0.0) { + geometry.Bounds.size.width = 1.0; + geometry.Bounds.size.height = 1.0; + } + + id window = SendId(view, "window"); + if (window) { + geometry.Scale = SendCGFloat(window, "backingScaleFactor"); + } + if (geometry.Scale <= 0.0) { + geometry.Scale = 1.0; + } + + CGRect backingBounds = SendCGRectCGRect(view, "convertRectToBacking:", geometry.Bounds); + if (backingBounds.size.width > 0.0 && backingBounds.size.height > 0.0) { + geometry.DrawableSize = backingBounds.size; + } else { + geometry.DrawableSize = {geometry.Bounds.size.width * geometry.Scale, + geometry.Bounds.size.height * geometry.Scale}; + } + geometry.DrawableSize.width = std::max(std::round(geometry.DrawableSize.width), 1.0); + geometry.DrawableSize.height = std::max(std::round(geometry.DrawableSize.height), 1.0); + return geometry; + } + + void ConfigureMetalLayerForView(id layer, id view, const DrawableGeometry& geometry) { + SendVoidCGRect(layer, "setFrame:", geometry.Bounds); + SendVoidCGFloat(layer, "setContentsScale:", geometry.Scale); + SendVoidCGSize(layer, "setDrawableSize:", geometry.DrawableSize); + SendVoidBool(layer, "setNeedsDisplayOnBoundsChange:", true); + } + + id CreateMetalLayerForView(id view, DrawableGeometry* outGeometry) { if (!view) { return nil; } @@ -216,23 +270,12 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { 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); + const auto geometry = GetDrawableGeometry(view); + ConfigureMetalLayerForView(layer, view, geometry); SendVoidId(view, "setLayer:", layer); + if (outGeometry) { + *outGeometry = geometry; + } return layer; } @@ -247,13 +290,16 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { SetAssoc(self, &kContextLayerKey, nil, kAssociationRetain); return; } - id layer = CreateMetalLayerForView(view); + DrawableGeometry geometry; + id layer = CreateMetalLayerForView(view, &geometry); if (!layer) { return; } SetAssoc(self, &kContextViewKey, view, kAssociationAssign); SetAssoc(self, &kContextLayerKey, layer, kAssociationRetain); - const auto error = CGLImpl::AttachDrawable(context, view, layer); + const auto error = CGLImpl::AttachDrawable(context, view, layer, + static_cast(geometry.DrawableSize.width), + static_cast(geometry.DrawableSize.height)); if (error != kCGLNoError) { MGLOG_E("NSOpenGLImpl: failed to attach drawable: %s", CGLImpl::ErrorString(error)); } @@ -316,16 +362,15 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { ContextSetView(self, nullptr, view); return; } - CGRect bounds = SendCGRect(view, "bounds"); - CGFloat scale = 1.0; - if (id window = SendId(view, "window")) { - scale = SendCGFloat(window, "backingScaleFactor"); + const auto geometry = GetDrawableGeometry(view); + ConfigureMetalLayerForView(layer, view, geometry); + const auto error = CGLImpl::AttachDrawable(context, view, layer, + static_cast(geometry.DrawableSize.width), + static_cast(geometry.DrawableSize.height)); + if (error != kCGLNoError) { + MGLOG_E("NSOpenGLImpl: update failed to attach drawable: %s", CGLImpl::ErrorString(error)); + return; } - 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); }