[Feat] (MG_Backend/DirectVulkan): support macOS Metal surfaces

This commit is contained in:
2026-06-28 14:19:51 +08:00
parent 46f3192c67
commit 0ef9c76224
12 changed files with 410 additions and 15 deletions
+14 -1
View File
@@ -6,7 +6,7 @@ option(MOBILEGL_BUILD_TEST "Build MobileGL tests"
option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" ON )
option(MOBILEGL_ENABLE_TRACY "Enable tracy for profiling" OFF)
option(MOBILEGL_BUILD_TRACE_REPLAY "Build Linux apitrace replay runner" OFF)
option(MOBILEGL_BUILD_TRACE_REPLAY "Build desktop apitrace replay runner" OFF)
set(MOBILEGL_LOG_ACTIVE_LEVEL "MOBILEGL_LOG_LEVEL_INFO" CACHE STRING "MobileGL active log level macro")
if (ANDROID)
@@ -384,6 +384,19 @@ if (ANDROID)
)
endif()
if (APPLE)
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
"-framework Cocoa"
"-framework QuartzCore"
"-framework Foundation")
if(TARGET ${CMAKE_PROJECT_NAME}_s)
target_link_libraries(${CMAKE_PROJECT_NAME}_s PUBLIC
"-framework Cocoa"
"-framework QuartzCore"
"-framework Foundation")
endif()
endif()
if (NOT ANDROID)
find_package(Vulkan)
if (Vulkan_FOUND)
+2 -1
View File
@@ -223,7 +223,8 @@ namespace MobileGL {
enum class WindowBackend {
Android,
X11,
// TODO: X11, Wayland, Windows, macOS, etc.
MetalLayer,
// TODO: Wayland, Windows, etc.
WindowBackendCount,
Unknown = -1
};
@@ -412,8 +412,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_E("DirectVulkan backend not initialized");
return false;
}
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11)) {
MGLOG_E("DirectVulkan backend only supports Android and X11 native windows");
if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
handle.Backend != WindowBackend::X11 &&
handle.Backend != WindowBackend::MetalLayer)) {
MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows");
return false;
}
@@ -25,7 +25,90 @@
#include <cstring>
#include <vulkan/vulkan_core.h>
#if defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#endif
namespace MobileGL::MG_Backend::DirectVulkan {
#if defined(__APPLE__)
namespace {
constexpr unsigned long kNSWindowStyleMaskBorderless = 0;
constexpr unsigned long kNSBackingStoreBuffered = 2;
template <typename Fn>
Fn ObjcMsgSend() {
return reinterpret_cast<Fn>(objc_msgSend);
}
id SendId(id receiver, const char* selector) {
return ObjcMsgSend<id (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
void SendVoid(id receiver, const char* selector) {
ObjcMsgSend<void (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
void SendVoidBool(id receiver, const char* selector, bool value) {
ObjcMsgSend<void (*)(id, SEL, bool)>()(receiver, sel_registerName(selector), value);
}
void SendVoidId(id receiver, const char* selector, id value) {
ObjcMsgSend<void (*)(id, SEL, id)>()(receiver, sel_registerName(selector), value);
}
void SendVoidCGRect(id receiver, const char* selector, CGRect value) {
ObjcMsgSend<void (*)(id, SEL, CGRect)>()(receiver, sel_registerName(selector), value);
}
void SendVoidCGSize(id receiver, const char* selector, CGSize value) {
ObjcMsgSend<void (*)(id, SEL, CGSize)>()(receiver, sel_registerName(selector), value);
}
id Retain(id object) {
return object ? SendId(object, "retain") : nil;
}
void Release(id object) {
if (object) {
SendVoid(object, "release");
}
}
void* CreateInternalMetalLayer(Uint32 width, Uint32 height, void** outWindow) {
const auto surfaceWidth = static_cast<CGFloat>(std::max<Uint32>(width, 1));
const auto surfaceHeight = static_cast<CGFloat>(std::max<Uint32>(height, 1));
id windowClass = reinterpret_cast<id>(objc_getClass("NSWindow"));
id metalLayerClass = reinterpret_cast<id>(objc_getClass("CAMetalLayer"));
MOBILEGL_ASSERT(windowClass && metalLayerClass,
"Failed to resolve NSWindow/CAMetalLayer for DirectVulkan pbuffer");
CGRect frame = {{0.0, 0.0}, {surfaceWidth, surfaceHeight}};
id window = SendId(windowClass, "alloc");
window = ObjcMsgSend<id (*)(id, SEL, CGRect, unsigned long, unsigned long, bool)>()(
window, sel_registerName("initWithContentRect:styleMask:backing:defer:"),
frame, kNSWindowStyleMaskBorderless, kNSBackingStoreBuffered, true);
MOBILEGL_ASSERT(window, "Failed to create hidden NSWindow for DirectVulkan pbuffer");
id contentView = SendId(window, "contentView");
MOBILEGL_ASSERT(contentView, "Failed to query hidden NSWindow contentView");
SendVoidBool(contentView, "setWantsLayer:", true);
id metalLayer = SendId(metalLayerClass, "layer");
MOBILEGL_ASSERT(metalLayer, "Failed to create hidden CAMetalLayer for DirectVulkan pbuffer");
Retain(metalLayer);
SendVoidCGRect(metalLayer, "setFrame:", frame);
SendVoidCGSize(metalLayer, "setDrawableSize:", frame.size);
SendVoidId(contentView, "setLayer:", metalLayer);
*outWindow = window;
return metalLayer;
}
} // namespace
#endif
static Bool IsPowerVRDevice(const VkPhysicalDeviceProperties& properties) {
return std::strstr(properties.deviceName, "PowerVR") != nullptr;
}
@@ -1868,6 +1951,17 @@ void main() {
m_surface = VK_NULL_HANDLE;
}
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (m_platformLibrary != nullptr) {
Release(reinterpret_cast<id>(m_platformLibrary));
m_platformLibrary = nullptr;
}
if (m_platformDisplay != nullptr) {
Release(reinterpret_cast<id>(m_platformDisplay));
m_platformDisplay = nullptr;
}
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (m_platformDisplay != nullptr) {
using XCloseDisplayFn = int (*)(Display*);
@@ -5325,7 +5419,11 @@ void main() {
// Extensions
Vector<const char*> exts = {VK_KHR_SURFACE_EXTENSION_NAME};
if (!m_window) {
#ifdef VK_USE_PLATFORM_METAL_EXT
exts.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
#else
exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
#endif
} else {
#ifdef VK_USE_PLATFORM_ANDROID_KHR
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
@@ -5743,6 +5841,11 @@ void main() {
void VulkanRenderer::CreateSurface() {
if (!m_window) {
#if defined VK_USE_PLATFORM_METAL_EXT
m_window = reinterpret_cast<NativeWindowType>(
CreateInternalMetalLayer(m_config.SurfaceWidth, m_config.SurfaceHeight, &m_platformDisplay));
m_platformLibrary = reinterpret_cast<void*>(m_window);
#else
auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
@@ -5752,6 +5855,7 @@ void main() {
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
"vkCreateHeadlessSurfaceEXT failed");
return;
#endif
}
#if defined VK_USE_PLATFORM_ANDROID_KHR
auto* nativeWindow = static_cast<ANativeWindow*>(m_window);
+2
View File
@@ -34,6 +34,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
MG_Backend::WindowBackend DetectWindowBackend() {
#if defined(ANDROID) || defined(__ANDROID__)
return MG_Backend::WindowBackend::Android;
#elif defined(__APPLE__)
return MG_Backend::WindowBackend::MetalLayer;
#elif defined(__linux__)
return MG_Backend::WindowBackend::X11;
#else
@@ -128,6 +128,7 @@ bool LoadMobileGL(const Request& request, std::string& error) {
setenv("MOBILEGL_BACKEND_TYPE", request.backend.c_str(), 1);
setenv("MOBILEGL_TRACE_LIBRARY", request.mobileGlLibrary.c_str(), 1);
setenv("MOBILEGL_TRACE_SKIP_AUTODESTROY", "1", 1);
setenv("MOBILEGL_TRACE_SURFACE", request.usePbuffer ? "pbuffer" : "window", 1);
if (UseAngleForRequest(request)) {
setenv("MOBILEGL_RETRACE_USE_ANGLE", "1", 1);
if (!request.angleLibraryDir.empty()) {
@@ -34,7 +34,7 @@ struct Request {
int cropHeight = 0;
double ssimThreshold = 0.99;
bool useAngle = false;
bool usePbuffer = false;
bool usePbuffer = true;
};
struct Result {
+29 -7
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.22.1)
if(NOT UNIX OR APPLE OR ANDROID)
message(STATUS "MobileGL trace replay runner is only enabled on Linux")
if(NOT UNIX OR ANDROID)
message(STATUS "MobileGL trace replay runner is only enabled on desktop Unix")
return()
endif()
@@ -140,12 +140,16 @@ target_link_libraries(mobilegl_trace_common PUBLIC
add_library(mobilegl_trace_glproc STATIC
"${APITRACE_GENERATED_DIR}/glproc.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glproc_mobilegl.cpp")
if(APPLE)
target_compile_definitions(mobilegl_trace_glproc PRIVATE GL_SILENCE_DEPRECATION)
endif()
target_include_directories(mobilegl_trace_glproc PUBLIC
"${CMAKE_SOURCE_DIR}/include"
"${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/thirdparty/khronos")
target_link_libraries(mobilegl_trace_glproc PUBLIC mobilegl_trace_apitrace_os dl)
target_link_libraries(mobilegl_trace_glproc PUBLIC mobilegl_trace_apitrace_os ${CMAKE_DL_LIBS})
execute_process(
COMMAND ${Python3_EXECUTABLE} "${APITRACE_ROOT}/retrace/glretrace.py"
@@ -221,6 +225,15 @@ add_library(mobilegl_trace_glretrace_common STATIC
"${APITRACE_ROOT}/retrace/metric_helper.cpp"
"${APITRACE_ROOT}/retrace/metric_writer.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glws_egl.cpp")
if(APPLE)
set(MOBILEGL_TRACE_APPLE_FRAMEWORKS
"-framework Cocoa"
"-framework QuartzCore"
"-framework Foundation")
target_sources(mobilegl_trace_glretrace_common PRIVATE
"${MOBILEGL_TRACE_ROOT}/macos_cgs_surface_bounds.cpp")
target_compile_definitions(mobilegl_trace_glretrace_common PRIVATE GL_SILENCE_DEPRECATION)
endif()
target_include_directories(mobilegl_trace_glretrace_common PUBLIC
"${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch"
@@ -244,8 +257,7 @@ target_compile_definitions(mobilegl_trace_replay PRIVATE
target_include_directories(mobilegl_trace_replay PRIVATE
"${MOBILEGL_TRACE_SHARED_CPP_DIR}"
"${APITRACE_ROOT}/lib/image")
target_link_libraries(mobilegl_trace_replay PRIVATE
"-Wl,--start-group"
set(MOBILEGL_TRACE_REPLAY_LINK_LIBRARIES
mobilegl_trace_glretrace_common
mobilegl_trace_retrace_common
mobilegl_trace_image
@@ -253,8 +265,18 @@ target_link_libraries(mobilegl_trace_replay PRIVATE
mobilegl_trace_glhelpers
mobilegl_trace_glproc
mobilegl_trace_apitrace_os
"-Wl,--end-group"
dl)
${CMAKE_DL_LIBS})
if(APPLE)
target_link_libraries(mobilegl_trace_replay PRIVATE
${MOBILEGL_TRACE_REPLAY_LINK_LIBRARIES}
${MOBILEGL_TRACE_APPLE_FRAMEWORKS})
else()
target_link_libraries(mobilegl_trace_replay PRIVATE
"-Wl,--start-group"
${MOBILEGL_TRACE_REPLAY_LINK_LIBRARIES}
"-Wl,--end-group")
endif()
if(MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY)
set(mobilegl_trace_replay_mobilegl_library "${MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY}")
@@ -1,6 +1,16 @@
#include "glproc.hpp"
#if defined(__has_include)
#if __has_include(<GLES3/gl3.h>)
#include <GLES3/gl3.h>
#elif __has_include(<GLES3/gl32.h>)
#include <GLES3/gl32.h>
#else
#error "OpenGL ES 3 headers are required"
#endif
#else
#include <GLES3/gl3.h>
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
+213 -3
View File
@@ -10,12 +10,20 @@
#include <dlfcn.h>
#include <iostream>
#if defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#endif
namespace {
using PfnEglBindApi = EGLBoolean (*)(EGLenum);
using PfnEglChooseConfig = EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
using PfnEglCreateContext = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
using PfnEglCreatePbufferSurface = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint *);
using PfnEglCreateWindowSurface = EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
using PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext);
using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface);
using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint *);
@@ -33,6 +41,7 @@ struct EglFns {
PfnEglChooseConfig chooseConfig = nullptr;
PfnEglCreateContext createContext = nullptr;
PfnEglCreatePbufferSurface createPbufferSurface = nullptr;
PfnEglCreateWindowSurface createWindowSurface = nullptr;
PfnEglDestroyContext destroyContext = nullptr;
PfnEglDestroySurface destroySurface = nullptr;
PfnEglGetConfigAttrib getConfigAttrib = nullptr;
@@ -54,6 +63,136 @@ int gRequestedWidth = 0;
int gRequestedHeight = 0;
bool gPrintedGlIdentity = false;
#if defined(__APPLE__)
constexpr unsigned long kNSWindowStyleMaskTitled = 1ul << 0;
constexpr unsigned long kNSWindowStyleMaskClosable = 1ul << 1;
constexpr unsigned long kNSWindowStyleMaskMiniaturizable = 1ul << 2;
constexpr unsigned long kNSWindowStyleMaskResizable = 1ul << 3;
constexpr unsigned long kNSBackingStoreBuffered = 2;
constexpr long kNSApplicationActivationPolicyRegular = 0;
constexpr unsigned long long kNSEventMaskAny = ~0ull;
template <typename Fn>
Fn ObjcMsgSend() {
return reinterpret_cast<Fn>(objc_msgSend);
}
id SendId(id receiver, const char *selector) {
return ObjcMsgSend<id (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
void SendVoid(id receiver, const char *selector) {
ObjcMsgSend<void (*)(id, SEL)>()(receiver, sel_registerName(selector));
}
void SendVoidId(id receiver, const char *selector, id value) {
ObjcMsgSend<void (*)(id, SEL, id)>()(receiver, sel_registerName(selector), value);
}
void SendVoidBool(id receiver, const char *selector, bool value) {
ObjcMsgSend<void (*)(id, SEL, bool)>()(receiver, sel_registerName(selector), value);
}
void SendVoidLong(id receiver, const char *selector, long value) {
ObjcMsgSend<void (*)(id, SEL, long)>()(receiver, sel_registerName(selector), value);
}
void SendVoidCGRect(id receiver, const char *selector, CGRect value) {
ObjcMsgSend<void (*)(id, SEL, CGRect)>()(receiver, sel_registerName(selector), value);
}
void SendVoidCGSize(id receiver, const char *selector, CGSize value) {
ObjcMsgSend<void (*)(id, SEL, CGSize)>()(receiver, sel_registerName(selector), value);
}
id Retain(id object) {
return object ? SendId(object, "retain") : nil;
}
void Release(id object) {
if (object) {
SendVoid(object, "release");
}
}
id SharedApplication() {
id appClass = reinterpret_cast<id>(objc_getClass("NSApplication"));
return appClass ? SendId(appClass, "sharedApplication") : nil;
}
void PumpMacOSEvents() {
id app = SharedApplication();
if (!app) {
return;
}
id distantPast = SendId(reinterpret_cast<id>(objc_getClass("NSDate")), "distantPast");
id stringClass = reinterpret_cast<id>(objc_getClass("NSString"));
id defaultRunLoopMode = ObjcMsgSend<id (*)(id, SEL, const char *)>()(
stringClass, sel_registerName("stringWithUTF8String:"), "kCFRunLoopDefaultMode");
while (true) {
id event = ObjcMsgSend<id (*)(id, SEL, unsigned long long, id, id, bool)>()(
app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"),
kNSEventMaskAny, distantPast, defaultRunLoopMode, true);
if (!event) {
break;
}
SendVoidId(app, "sendEvent:", event);
}
SendVoid(app, "updateWindows");
}
void EnsureApplicationActive() {
id app = SharedApplication();
if (!app) {
return;
}
SendVoidLong(app, "setActivationPolicy:", kNSApplicationActivationPolicyRegular);
SendVoidBool(app, "activateIgnoringOtherApps:", true);
}
id CreateMetalWindow(int width, int height, id *outLayer) {
EnsureApplicationActive();
id windowClass = reinterpret_cast<id>(objc_getClass("NSWindow"));
id metalLayerClass = reinterpret_cast<id>(objc_getClass("CAMetalLayer"));
if (!windowClass || !metalLayerClass) {
std::cerr << "error: failed to resolve NSWindow/CAMetalLayer\n";
return nil;
}
const auto surfaceWidth = static_cast<CGFloat>(std::max(width, 1));
const auto surfaceHeight = static_cast<CGFloat>(std::max(height, 1));
const CGRect frame = {{80.0, 80.0}, {surfaceWidth, surfaceHeight}};
constexpr unsigned long style = kNSWindowStyleMaskTitled | kNSWindowStyleMaskClosable |
kNSWindowStyleMaskMiniaturizable | kNSWindowStyleMaskResizable;
id window = SendId(windowClass, "alloc");
window = ObjcMsgSend<id (*)(id, SEL, CGRect, unsigned long, unsigned long, bool)>()(
window, sel_registerName("initWithContentRect:styleMask:backing:defer:"),
frame, style, kNSBackingStoreBuffered, false);
if (!window) {
std::cerr << "error: failed to create NSWindow\n";
return nil;
}
id contentView = SendId(window, "contentView");
id layer = SendId(metalLayerClass, "layer");
if (!contentView || !layer) {
Release(window);
std::cerr << "error: failed to create CAMetalLayer\n";
return nil;
}
Retain(layer);
SendVoidBool(contentView, "setWantsLayer:", true);
SendVoidCGRect(layer, "setFrame:", {{0.0, 0.0}, {surfaceWidth, surfaceHeight}});
SendVoidCGSize(layer, "setDrawableSize:", {surfaceWidth, surfaceHeight});
SendVoidId(contentView, "setLayer:", layer);
ObjcMsgSend<void (*)(id, SEL, id)>()(window, sel_registerName("makeKeyAndOrderFront:"), nil);
PumpMacOSEvents();
*outLayer = layer;
return window;
}
#endif
int ResolveWidth(int width) {
if (gRequestedWidth > 0) {
return gRequestedWidth;
@@ -108,6 +247,7 @@ bool LoadEgl() {
Load(gEgl.chooseConfig, "eglChooseConfig") &&
Load(gEgl.createContext, "eglCreateContext") &&
Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") &&
Load(gEgl.createWindowSurface, "eglCreateWindowSurface") &&
Load(gEgl.destroyContext, "eglDestroyContext") &&
Load(gEgl.destroySurface, "eglDestroySurface") &&
Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") &&
@@ -120,6 +260,11 @@ bool LoadEgl() {
Load(gEgl.terminate, "eglTerminate");
}
bool TraceReplayWantsWindowSurface() {
const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE");
return mode != nullptr && std::strcmp(mode, "window") == 0;
}
void PrintGlIdentityOnce() {
if (gPrintedGlIdentity) {
return;
@@ -160,6 +305,10 @@ public:
class EglDrawable final : public glws::Drawable {
public:
EGLSurface surface = EGL_NO_SURFACE;
#if defined(__APPLE__)
id window = nil;
id metalLayer = nil;
#endif
EglDrawable(const EglVisual *visual, int width, int height, bool pbuffer)
: Drawable(visual, width, height, pbuffer) {
@@ -168,6 +317,12 @@ public:
~EglDrawable() override {
destroySurface();
#if defined(__APPLE__)
Release(metalLayer);
metalLayer = nil;
Release(window);
window = nil;
#endif
}
void resize(int w, int h) override {
@@ -187,6 +342,12 @@ public:
void show() override {
visible = true;
#if defined(__APPLE__)
if (window) {
ObjcMsgSend<void (*)(id, SEL, id)>()(window, sel_registerName("makeKeyAndOrderFront:"), nil);
PumpMacOSEvents();
}
#endif
}
void swapBuffers() override {
@@ -203,13 +364,50 @@ public:
setenv("MOBILEGL_PRESENT_CURRENT_CALL", callNo, 1);
gEgl.swapBuffers(gDisplay, surface);
unsetenv("MOBILEGL_PRESENT_CURRENT_CALL");
#if defined(__APPLE__)
PumpMacOSEvents();
#endif
}
private:
bool shouldCreateWindowSurface() const {
if (pbuffer) {
return false;
}
const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE");
return mode != nullptr && std::strcmp(mode, "window") == 0;
}
void createSurface() {
const int surfaceWidth = ResolveWidth(width);
const int surfaceHeight = ResolveHeight(height);
#if defined(__APPLE__)
if (shouldCreateWindowSurface()) {
if (!window) {
window = CreateMetalWindow(surfaceWidth, surfaceHeight, &metalLayer);
} else if (metalLayer) {
const auto w = static_cast<CGFloat>(std::max(surfaceWidth, 1));
const auto h = static_cast<CGFloat>(std::max(surfaceHeight, 1));
SendVoidCGSize(metalLayer, "setDrawableSize:", {w, h});
SendVoidCGRect(metalLayer, "setFrame:", {{0.0, 0.0}, {w, h}});
}
if (metalLayer) {
surface = gEgl.createWindowSurface(
gDisplay,
static_cast<const EglVisual *>(visual)->config,
reinterpret_cast<EGLNativeWindowType>(metalLayer),
nullptr);
}
if (surface == EGL_NO_SURFACE) {
std::cerr << "error: EGL window surface creation failed: 0x" << std::hex
<< gEgl.getError() << std::dec << "\n";
}
return;
}
#endif
const EGLint attribs[] = {
EGL_WIDTH, ResolveWidth(width),
EGL_HEIGHT, ResolveHeight(height),
EGL_WIDTH, surfaceWidth,
EGL_HEIGHT, surfaceHeight,
EGL_NONE,
};
surface = gEgl.createPbufferSurface(gDisplay, static_cast<const EglVisual *>(visual)->config, attribs);
@@ -224,6 +422,14 @@ private:
gEgl.destroySurface(gDisplay, surface);
surface = EGL_NO_SURFACE;
}
#if defined(__APPLE__)
if (!shouldCreateWindowSurface()) {
Release(metalLayer);
metalLayer = nil;
Release(window);
window = nil;
}
#endif
}
};
@@ -323,8 +529,9 @@ Visual *createVisual(bool doubleBuffer, unsigned samples, Profile profile) {
return nullptr;
}
const EGLint surfaceType = TraceReplayWantsWindowSurface() ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT;
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_SURFACE_TYPE, surfaceType,
EGL_RENDERABLE_TYPE, visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
@@ -373,6 +580,9 @@ bool makeCurrentInternal(Drawable *drawable, Drawable *readable, Context *contex
}
bool processEvents() {
#if defined(__APPLE__)
PumpMacOSEvents();
#endif
return false;
}
@@ -0,0 +1,24 @@
#include <Carbon/Carbon.h>
using CGSConnectionID = void*;
using CGSSurfaceID = int;
extern "C" bool mobilegl_trace_get_drawable_bounds(int* width, int* height);
extern "C" OSStatus CGSGetSurfaceBounds(CGSConnectionID, CGWindowID, CGSSurfaceID, CGRect* rect) {
if (rect == nullptr) {
return -1;
}
int width = 0;
int height = 0;
if (!mobilegl_trace_get_drawable_bounds(&width, &height) || width <= 0 || height <= 0) {
return -1;
}
rect->origin.x = 0.0;
rect->origin.y = 0.0;
rect->size.width = static_cast<CGFloat>(width);
rect->size.height = static_cast<CGFloat>(height);
return 0;
}
+6
View File
@@ -20,6 +20,8 @@ void PrintUsage(const char *argv0) {
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
<< " --width N Replay surface width override\n"
<< " --height N Replay surface height override\n"
<< " --window-surface Replay to a native window surface\n"
<< " --pbuffer-surface Replay to an EGL pbuffer surface (default)\n"
<< " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n"
<< " --crop-x N Compare crop x\n"
<< " --crop-y N Compare crop y\n"
@@ -94,6 +96,10 @@ bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) {
if (!ReadInt(argc, argv, i, request.width)) return false;
} else if (arg == "--height") {
if (!ReadInt(argc, argv, i, request.height)) return false;
} else if (arg == "--window-surface") {
request.usePbuffer = false;
} else if (arg == "--pbuffer-surface") {
request.usePbuffer = true;
} else if (arg == "--ssim-threshold") {
if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false;
} else if (arg == "--crop-x") {