[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_BUILD_BENCHMARK "Build MobileGL benchmarks" ON )
option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" 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_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") set(MOBILEGL_LOG_ACTIVE_LEVEL "MOBILEGL_LOG_LEVEL_INFO" CACHE STRING "MobileGL active log level macro")
if (ANDROID) if (ANDROID)
@@ -384,6 +384,19 @@ if (ANDROID)
) )
endif() 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) if (NOT ANDROID)
find_package(Vulkan) find_package(Vulkan)
if (Vulkan_FOUND) if (Vulkan_FOUND)
+2 -1
View File
@@ -223,7 +223,8 @@ namespace MobileGL {
enum class WindowBackend { enum class WindowBackend {
Android, Android,
X11, X11,
// TODO: X11, Wayland, Windows, macOS, etc. MetalLayer,
// TODO: Wayland, Windows, etc.
WindowBackendCount, WindowBackendCount,
Unknown = -1 Unknown = -1
}; };
@@ -412,8 +412,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_E("DirectVulkan backend not initialized"); MGLOG_E("DirectVulkan backend not initialized");
return false; return false;
} }
if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11)) { if (!handle.Handle || (handle.Backend != WindowBackend::Android &&
MGLOG_E("DirectVulkan backend only supports Android and X11 native windows"); handle.Backend != WindowBackend::X11 &&
handle.Backend != WindowBackend::MetalLayer)) {
MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows");
return false; return false;
} }
@@ -25,7 +25,90 @@
#include <cstring> #include <cstring>
#include <vulkan/vulkan_core.h> #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 { 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) { static Bool IsPowerVRDevice(const VkPhysicalDeviceProperties& properties) {
return std::strstr(properties.deviceName, "PowerVR") != nullptr; return std::strstr(properties.deviceName, "PowerVR") != nullptr;
} }
@@ -1868,6 +1951,17 @@ void main() {
m_surface = VK_NULL_HANDLE; 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 defined(VK_USE_PLATFORM_XLIB_KHR)
if (m_platformDisplay != nullptr) { if (m_platformDisplay != nullptr) {
using XCloseDisplayFn = int (*)(Display*); using XCloseDisplayFn = int (*)(Display*);
@@ -5325,7 +5419,11 @@ void main() {
// Extensions // Extensions
Vector<const char*> exts = {VK_KHR_SURFACE_EXTENSION_NAME}; Vector<const char*> exts = {VK_KHR_SURFACE_EXTENSION_NAME};
if (!m_window) { 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); exts.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
#endif
} else { } else {
#ifdef VK_USE_PLATFORM_ANDROID_KHR #ifdef VK_USE_PLATFORM_ANDROID_KHR
exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); exts.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
@@ -5743,6 +5841,11 @@ void main() {
void VulkanRenderer::CreateSurface() { void VulkanRenderer::CreateSurface() {
if (!m_window) { 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 = auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>( reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT")); vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
@@ -5752,6 +5855,7 @@ void main() {
VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface), VK_VERIFY(createHeadlessSurface(m_instance, &sci, nullptr, &m_surface),
"vkCreateHeadlessSurfaceEXT failed"); "vkCreateHeadlessSurfaceEXT failed");
return; return;
#endif
} }
#if defined VK_USE_PLATFORM_ANDROID_KHR #if defined VK_USE_PLATFORM_ANDROID_KHR
auto* nativeWindow = static_cast<ANativeWindow*>(m_window); auto* nativeWindow = static_cast<ANativeWindow*>(m_window);
+2
View File
@@ -34,6 +34,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
MG_Backend::WindowBackend DetectWindowBackend() { MG_Backend::WindowBackend DetectWindowBackend() {
#if defined(ANDROID) || defined(__ANDROID__) #if defined(ANDROID) || defined(__ANDROID__)
return MG_Backend::WindowBackend::Android; return MG_Backend::WindowBackend::Android;
#elif defined(__APPLE__)
return MG_Backend::WindowBackend::MetalLayer;
#elif defined(__linux__) #elif defined(__linux__)
return MG_Backend::WindowBackend::X11; return MG_Backend::WindowBackend::X11;
#else #else
@@ -128,6 +128,7 @@ bool LoadMobileGL(const Request& request, std::string& error) {
setenv("MOBILEGL_BACKEND_TYPE", request.backend.c_str(), 1); setenv("MOBILEGL_BACKEND_TYPE", request.backend.c_str(), 1);
setenv("MOBILEGL_TRACE_LIBRARY", request.mobileGlLibrary.c_str(), 1); setenv("MOBILEGL_TRACE_LIBRARY", request.mobileGlLibrary.c_str(), 1);
setenv("MOBILEGL_TRACE_SKIP_AUTODESTROY", "1", 1); setenv("MOBILEGL_TRACE_SKIP_AUTODESTROY", "1", 1);
setenv("MOBILEGL_TRACE_SURFACE", request.usePbuffer ? "pbuffer" : "window", 1);
if (UseAngleForRequest(request)) { if (UseAngleForRequest(request)) {
setenv("MOBILEGL_RETRACE_USE_ANGLE", "1", 1); setenv("MOBILEGL_RETRACE_USE_ANGLE", "1", 1);
if (!request.angleLibraryDir.empty()) { if (!request.angleLibraryDir.empty()) {
@@ -34,7 +34,7 @@ struct Request {
int cropHeight = 0; int cropHeight = 0;
double ssimThreshold = 0.99; double ssimThreshold = 0.99;
bool useAngle = false; bool useAngle = false;
bool usePbuffer = false; bool usePbuffer = true;
}; };
struct Result { struct Result {
+29 -7
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.22.1) cmake_minimum_required(VERSION 3.22.1)
if(NOT UNIX OR APPLE OR ANDROID) if(NOT UNIX OR ANDROID)
message(STATUS "MobileGL trace replay runner is only enabled on Linux") message(STATUS "MobileGL trace replay runner is only enabled on desktop Unix")
return() return()
endif() endif()
@@ -140,12 +140,16 @@ target_link_libraries(mobilegl_trace_common PUBLIC
add_library(mobilegl_trace_glproc STATIC add_library(mobilegl_trace_glproc STATIC
"${APITRACE_GENERATED_DIR}/glproc.cpp" "${APITRACE_GENERATED_DIR}/glproc.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glproc_mobilegl.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 target_include_directories(mobilegl_trace_glproc PUBLIC
"${CMAKE_SOURCE_DIR}/include"
"${APITRACE_GENERATED_DIR}" "${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch" "${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/lib/os" "${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/thirdparty/khronos") "${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( execute_process(
COMMAND ${Python3_EXECUTABLE} "${APITRACE_ROOT}/retrace/glretrace.py" 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_helper.cpp"
"${APITRACE_ROOT}/retrace/metric_writer.cpp" "${APITRACE_ROOT}/retrace/metric_writer.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glws_egl.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 target_include_directories(mobilegl_trace_glretrace_common PUBLIC
"${APITRACE_GENERATED_DIR}" "${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch" "${APITRACE_ROOT}/dispatch"
@@ -244,8 +257,7 @@ target_compile_definitions(mobilegl_trace_replay PRIVATE
target_include_directories(mobilegl_trace_replay PRIVATE target_include_directories(mobilegl_trace_replay PRIVATE
"${MOBILEGL_TRACE_SHARED_CPP_DIR}" "${MOBILEGL_TRACE_SHARED_CPP_DIR}"
"${APITRACE_ROOT}/lib/image") "${APITRACE_ROOT}/lib/image")
target_link_libraries(mobilegl_trace_replay PRIVATE set(MOBILEGL_TRACE_REPLAY_LINK_LIBRARIES
"-Wl,--start-group"
mobilegl_trace_glretrace_common mobilegl_trace_glretrace_common
mobilegl_trace_retrace_common mobilegl_trace_retrace_common
mobilegl_trace_image mobilegl_trace_image
@@ -253,8 +265,18 @@ target_link_libraries(mobilegl_trace_replay PRIVATE
mobilegl_trace_glhelpers mobilegl_trace_glhelpers
mobilegl_trace_glproc mobilegl_trace_glproc
mobilegl_trace_apitrace_os mobilegl_trace_apitrace_os
"-Wl,--end-group" ${CMAKE_DL_LIBS})
dl)
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) if(MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY)
set(mobilegl_trace_replay_mobilegl_library "${MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY}") set(mobilegl_trace_replay_mobilegl_library "${MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY}")
@@ -1,6 +1,16 @@
#include "glproc.hpp" #include "glproc.hpp"
#if defined(__has_include)
#if __has_include(<GLES3/gl3.h>)
#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 <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
+213 -3
View File
@@ -10,12 +10,20 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <iostream> #include <iostream>
#if defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#endif
namespace { namespace {
using PfnEglBindApi = EGLBoolean (*)(EGLenum); using PfnEglBindApi = EGLBoolean (*)(EGLenum);
using PfnEglChooseConfig = EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *); using PfnEglChooseConfig = EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
using PfnEglCreateContext = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *); using PfnEglCreateContext = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
using PfnEglCreatePbufferSurface = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint *); using PfnEglCreatePbufferSurface = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint *);
using PfnEglCreateWindowSurface = EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
using PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext); using PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext);
using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface); using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface);
using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint *); using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint *);
@@ -33,6 +41,7 @@ struct EglFns {
PfnEglChooseConfig chooseConfig = nullptr; PfnEglChooseConfig chooseConfig = nullptr;
PfnEglCreateContext createContext = nullptr; PfnEglCreateContext createContext = nullptr;
PfnEglCreatePbufferSurface createPbufferSurface = nullptr; PfnEglCreatePbufferSurface createPbufferSurface = nullptr;
PfnEglCreateWindowSurface createWindowSurface = nullptr;
PfnEglDestroyContext destroyContext = nullptr; PfnEglDestroyContext destroyContext = nullptr;
PfnEglDestroySurface destroySurface = nullptr; PfnEglDestroySurface destroySurface = nullptr;
PfnEglGetConfigAttrib getConfigAttrib = nullptr; PfnEglGetConfigAttrib getConfigAttrib = nullptr;
@@ -54,6 +63,136 @@ int gRequestedWidth = 0;
int gRequestedHeight = 0; int gRequestedHeight = 0;
bool gPrintedGlIdentity = false; 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) { int ResolveWidth(int width) {
if (gRequestedWidth > 0) { if (gRequestedWidth > 0) {
return gRequestedWidth; return gRequestedWidth;
@@ -108,6 +247,7 @@ bool LoadEgl() {
Load(gEgl.chooseConfig, "eglChooseConfig") && Load(gEgl.chooseConfig, "eglChooseConfig") &&
Load(gEgl.createContext, "eglCreateContext") && Load(gEgl.createContext, "eglCreateContext") &&
Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") && Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") &&
Load(gEgl.createWindowSurface, "eglCreateWindowSurface") &&
Load(gEgl.destroyContext, "eglDestroyContext") && Load(gEgl.destroyContext, "eglDestroyContext") &&
Load(gEgl.destroySurface, "eglDestroySurface") && Load(gEgl.destroySurface, "eglDestroySurface") &&
Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") && Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") &&
@@ -120,6 +260,11 @@ bool LoadEgl() {
Load(gEgl.terminate, "eglTerminate"); Load(gEgl.terminate, "eglTerminate");
} }
bool TraceReplayWantsWindowSurface() {
const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE");
return mode != nullptr && std::strcmp(mode, "window") == 0;
}
void PrintGlIdentityOnce() { void PrintGlIdentityOnce() {
if (gPrintedGlIdentity) { if (gPrintedGlIdentity) {
return; return;
@@ -160,6 +305,10 @@ public:
class EglDrawable final : public glws::Drawable { class EglDrawable final : public glws::Drawable {
public: public:
EGLSurface surface = EGL_NO_SURFACE; 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) EglDrawable(const EglVisual *visual, int width, int height, bool pbuffer)
: Drawable(visual, width, height, pbuffer) { : Drawable(visual, width, height, pbuffer) {
@@ -168,6 +317,12 @@ public:
~EglDrawable() override { ~EglDrawable() override {
destroySurface(); destroySurface();
#if defined(__APPLE__)
Release(metalLayer);
metalLayer = nil;
Release(window);
window = nil;
#endif
} }
void resize(int w, int h) override { void resize(int w, int h) override {
@@ -187,6 +342,12 @@ public:
void show() override { void show() override {
visible = true; visible = true;
#if defined(__APPLE__)
if (window) {
ObjcMsgSend<void (*)(id, SEL, id)>()(window, sel_registerName("makeKeyAndOrderFront:"), nil);
PumpMacOSEvents();
}
#endif
} }
void swapBuffers() override { void swapBuffers() override {
@@ -203,13 +364,50 @@ public:
setenv("MOBILEGL_PRESENT_CURRENT_CALL", callNo, 1); setenv("MOBILEGL_PRESENT_CURRENT_CALL", callNo, 1);
gEgl.swapBuffers(gDisplay, surface); gEgl.swapBuffers(gDisplay, surface);
unsetenv("MOBILEGL_PRESENT_CURRENT_CALL"); unsetenv("MOBILEGL_PRESENT_CURRENT_CALL");
#if defined(__APPLE__)
PumpMacOSEvents();
#endif
} }
private: 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() { 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[] = { const EGLint attribs[] = {
EGL_WIDTH, ResolveWidth(width), EGL_WIDTH, surfaceWidth,
EGL_HEIGHT, ResolveHeight(height), EGL_HEIGHT, surfaceHeight,
EGL_NONE, EGL_NONE,
}; };
surface = gEgl.createPbufferSurface(gDisplay, static_cast<const EglVisual *>(visual)->config, attribs); surface = gEgl.createPbufferSurface(gDisplay, static_cast<const EglVisual *>(visual)->config, attribs);
@@ -224,6 +422,14 @@ private:
gEgl.destroySurface(gDisplay, surface); gEgl.destroySurface(gDisplay, surface);
surface = EGL_NO_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; return nullptr;
} }
const EGLint surfaceType = TraceReplayWantsWindowSurface() ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT;
const EGLint attribs[] = { 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_RENDERABLE_TYPE, visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_RED_SIZE, 8, EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8, EGL_GREEN_SIZE, 8,
@@ -373,6 +580,9 @@ bool makeCurrentInternal(Drawable *drawable, Drawable *readable, Context *contex
} }
bool processEvents() { bool processEvents() {
#if defined(__APPLE__)
PumpMacOSEvents();
#endif
return false; 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" << " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
<< " --width N Replay surface width override\n" << " --width N Replay surface width override\n"
<< " --height N Replay surface height 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" << " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n"
<< " --crop-x N Compare crop x\n" << " --crop-x N Compare crop x\n"
<< " --crop-y N Compare crop y\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; if (!ReadInt(argc, argv, i, request.width)) return false;
} else if (arg == "--height") { } else if (arg == "--height") {
if (!ReadInt(argc, argv, i, request.height)) return false; 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") { } else if (arg == "--ssim-threshold") {
if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false; if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false;
} else if (arg == "--crop-x") { } else if (arg == "--crop-x") {