mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Feat] (MG_Backend/DirectVulkan): support macOS Metal surfaces
This commit is contained in:
+14
-1
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user