diff --git a/CMakeLists.txt b/CMakeLists.txt index aa7c791e..34955fce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index adf863fd..464eff8e 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -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 }; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 60ad0fef..deedda00 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 489780da..2bfff5ca 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -25,7 +25,90 @@ #include #include +#if defined(__APPLE__) +#include +#include +#include +#include +#endif + namespace MobileGL::MG_Backend::DirectVulkan { +#if defined(__APPLE__) + namespace { + constexpr unsigned long kNSWindowStyleMaskBorderless = 0; + constexpr unsigned long kNSBackingStoreBuffered = 2; + + template + Fn ObjcMsgSend() { + return reinterpret_cast(objc_msgSend); + } + + id SendId(id receiver, const char* selector) { + return ObjcMsgSend()(receiver, sel_registerName(selector)); + } + + void SendVoid(id receiver, const char* selector) { + ObjcMsgSend()(receiver, sel_registerName(selector)); + } + + void SendVoidBool(id receiver, const char* selector, bool value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidId(id receiver, const char* selector, id value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidCGRect(id receiver, const char* selector, CGRect value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidCGSize(id receiver, const char* selector, CGSize value) { + ObjcMsgSend()(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(std::max(width, 1)); + const auto surfaceHeight = static_cast(std::max(height, 1)); + id windowClass = reinterpret_cast(objc_getClass("NSWindow")); + id metalLayerClass = reinterpret_cast(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()( + 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(m_platformLibrary)); + m_platformLibrary = nullptr; + } + if (m_platformDisplay != nullptr) { + Release(reinterpret_cast(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 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( + CreateInternalMetalLayer(m_config.SurfaceWidth, m_config.SurfaceHeight, &m_platformDisplay)); + m_platformLibrary = reinterpret_cast(m_window); +#else auto* createHeadlessSurface = reinterpret_cast( 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(m_window); diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index d1a12543..5a30f930 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -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 diff --git a/android-plugin/app/src/trace/cpp/trace_replay_core.cpp b/android-plugin/app/src/trace/cpp/trace_replay_core.cpp index 04edea5f..2d29d88a 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.cpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.cpp @@ -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()) { diff --git a/android-plugin/app/src/trace/cpp/trace_replay_core.hpp b/android-plugin/app/src/trace/cpp/trace_replay_core.hpp index 1b48dbf6..883bc8a4 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.hpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.hpp @@ -34,7 +34,7 @@ struct Request { int cropHeight = 0; double ssimThreshold = 0.99; bool useAngle = false; - bool usePbuffer = false; + bool usePbuffer = true; }; struct Result { diff --git a/tools/trace_replay/CMakeLists.txt b/tools/trace_replay/CMakeLists.txt index 15d02fa2..1e103b5a 100644 --- a/tools/trace_replay/CMakeLists.txt +++ b/tools/trace_replay/CMakeLists.txt @@ -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}") diff --git a/tools/trace_replay/apitrace_glproc_mobilegl.cpp b/tools/trace_replay/apitrace_glproc_mobilegl.cpp index 20ca3534..0d6e0e0f 100644 --- a/tools/trace_replay/apitrace_glproc_mobilegl.cpp +++ b/tools/trace_replay/apitrace_glproc_mobilegl.cpp @@ -1,6 +1,16 @@ #include "glproc.hpp" +#if defined(__has_include) +#if __has_include() #include +#elif __has_include() +#include +#else +#error "OpenGL ES 3 headers are required" +#endif +#else +#include +#endif #include #include #include diff --git a/tools/trace_replay/apitrace_glws_egl.cpp b/tools/trace_replay/apitrace_glws_egl.cpp index d189de26..76096862 100644 --- a/tools/trace_replay/apitrace_glws_egl.cpp +++ b/tools/trace_replay/apitrace_glws_egl.cpp @@ -10,12 +10,20 @@ #include #include +#if defined(__APPLE__) +#include +#include +#include +#include +#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 +Fn ObjcMsgSend() { + return reinterpret_cast(objc_msgSend); +} + +id SendId(id receiver, const char *selector) { + return ObjcMsgSend()(receiver, sel_registerName(selector)); +} + +void SendVoid(id receiver, const char *selector) { + ObjcMsgSend()(receiver, sel_registerName(selector)); +} + +void SendVoidId(id receiver, const char *selector, id value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); +} + +void SendVoidBool(id receiver, const char *selector, bool value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); +} + +void SendVoidLong(id receiver, const char *selector, long value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); +} + +void SendVoidCGRect(id receiver, const char *selector, CGRect value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); +} + +void SendVoidCGSize(id receiver, const char *selector, CGSize value) { + ObjcMsgSend()(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(objc_getClass("NSApplication")); + return appClass ? SendId(appClass, "sharedApplication") : nil; +} + +void PumpMacOSEvents() { + id app = SharedApplication(); + if (!app) { + return; + } + id distantPast = SendId(reinterpret_cast(objc_getClass("NSDate")), "distantPast"); + id stringClass = reinterpret_cast(objc_getClass("NSString")); + id defaultRunLoopMode = ObjcMsgSend()( + stringClass, sel_registerName("stringWithUTF8String:"), "kCFRunLoopDefaultMode"); + while (true) { + id event = ObjcMsgSend()( + 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(objc_getClass("NSWindow")); + id metalLayerClass = reinterpret_cast(objc_getClass("CAMetalLayer")); + if (!windowClass || !metalLayerClass) { + std::cerr << "error: failed to resolve NSWindow/CAMetalLayer\n"; + return nil; + } + + const auto surfaceWidth = static_cast(std::max(width, 1)); + const auto surfaceHeight = static_cast(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()( + 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()(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()(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(std::max(surfaceWidth, 1)); + const auto h = static_cast(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(visual)->config, + reinterpret_cast(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(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; } diff --git a/tools/trace_replay/macos_cgs_surface_bounds.cpp b/tools/trace_replay/macos_cgs_surface_bounds.cpp new file mode 100644 index 00000000..3a8ec5bc --- /dev/null +++ b/tools/trace_replay/macos_cgs_surface_bounds.cpp @@ -0,0 +1,24 @@ +#include + +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(width); + rect->size.height = static_cast(height); + return 0; +} diff --git a/tools/trace_replay/trace_replay_cli.cpp b/tools/trace_replay/trace_replay_cli.cpp index 36da7714..e79dcada 100644 --- a/tools/trace_replay/trace_replay_cli.cpp +++ b/tools/trace_replay/trace_replay_cli.cpp @@ -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") {