diff --git a/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp b/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp index 2fd14bd5..d5e8e2a9 100644 --- a/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp +++ b/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp @@ -7,10 +7,12 @@ #include #include +#include #include #include #include #include +#include namespace { @@ -87,6 +89,28 @@ void *Lookup(const char *name) { return dlsym(RTLD_DEFAULT, name); } +void HoldAfterTargetPresent(const char *callNo) { + const char *holdMsText = std::getenv("MOBILEGL_TRACE_HOLD_MS"); + if (holdMsText == nullptr || holdMsText[0] == '\0') { + return; + } + const int holdMs = std::atoi(holdMsText); + if (holdMs <= 0) { + return; + } + const char *holdDone = std::getenv("MOBILEGL_TRACE_HOLD_DONE"); + if (holdDone != nullptr && std::strcmp(holdDone, "1") == 0) { + return; + } + const char *holdCall = std::getenv("MOBILEGL_TRACE_HOLD_CALL"); + if (holdCall != nullptr && holdCall[0] != '\0' && std::strcmp(holdCall, callNo) != 0) { + return; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(holdMs)); + setenv("MOBILEGL_TRACE_HOLD_DONE", "1", 1); +} + template bool Load(T &slot, const char *name) { slot = reinterpret_cast(Lookup(name)); @@ -175,6 +199,7 @@ public: setenv("MOBILEGL_PRESENT_CURRENT_CALL", callNo, 1); gEgl.swapBuffers(gDisplay, surface); unsetenv("MOBILEGL_PRESENT_CURRENT_CALL"); + HoldAfterTargetPresent(callNo); } } 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 2d29d88a..bbc4d059 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.cpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +33,10 @@ extern "C" int MOBILEGL_APITRACE_RETRACE_MAIN(int argc, char** argv); +#if defined(__GNUC__) || defined(__clang__) +extern "C" void mobilegl_trace_pump_events() __attribute__((weak)); +#endif + namespace mobilegl_trace { namespace { @@ -148,6 +154,26 @@ bool LoadMobileGL(const Request& request, std::string& error) { return true; } +void ConfigureHoldEnv(const Request& request) { + if (request.holdMs <= 0) { + unsetenv("MOBILEGL_TRACE_HOLD_MS"); + unsetenv("MOBILEGL_TRACE_HOLD_CALL"); + unsetenv("MOBILEGL_TRACE_HOLD_DONE"); + return; + } + + const std::string holdMs = std::to_string(request.holdMs); + const std::string holdCall = std::to_string(request.targetCall); + setenv("MOBILEGL_TRACE_HOLD_MS", holdMs.c_str(), 1); + setenv("MOBILEGL_TRACE_HOLD_CALL", holdCall.c_str(), 1); + unsetenv("MOBILEGL_TRACE_HOLD_DONE"); +} + +bool TraceHoldAlreadyRan() { + const char* holdDone = getenv("MOBILEGL_TRACE_HOLD_DONE"); + return holdDone != nullptr && std::strcmp(holdDone, "1") == 0; +} + bool CopyFile(const std::string& from, const std::string& to) { std::ifstream input(from, std::ios::binary); std::ofstream output(to, std::ios::binary | std::ios::trunc); @@ -436,6 +462,7 @@ int RunRetraceMain(const Request& request, bool usePresentDump) { bool RunRetrace(const Request& request, bool usePresentDump, Result& result) { int status = 0; + ConfigureHoldEnv(request); try { ScopedFdRedirect redirect(request.outputDir + "/retrace.log"); status = RunRetraceMain(request, usePresentDump); @@ -567,6 +594,28 @@ bool WriteDifferenceImage(const Result& result, return WritePngRgba(result.diffPath, diff, error); } +void PumpTraceEvents() { +#if defined(__GNUC__) || defined(__clang__) + if (mobilegl_trace_pump_events != nullptr) { + mobilegl_trace_pump_events(); + } +#endif +} + +void HoldAfterRetrace(const Request& request) { + if (request.holdMs <= 0 || TraceHoldAlreadyRan()) { + return; + } + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(request.holdMs); + while (std::chrono::steady_clock::now() < deadline) { + PumpTraceEvents(); + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } + PumpTraceEvents(); + setenv("MOBILEGL_TRACE_HOLD_DONE", "1", 1); +} + struct GoldenComparison { std::string path; RgbaImage image; @@ -816,6 +865,7 @@ bool WriteResultJson(const Request& request, const Result& result) { file << " \"ssimThreshold\": " << request.ssimThreshold << ",\n"; file << " \"useAngle\": " << (UseAngleForRequest(request) ? "true" : "false") << ",\n"; file << " \"usePbuffer\": " << (request.usePbuffer ? "true" : "false") << ",\n"; + file << " \"holdMs\": " << request.holdMs << ",\n"; file << " \"mismatchPixels\": " << result.mismatchPixels << "\n"; file << "}\n"; return true; @@ -885,9 +935,11 @@ Result RunTraceReplay(const Request& request) { } if (!RunRetrace(request, usePresentDump, result)) { + HoldAfterRetrace(request); return result; } + HoldAfterRetrace(request); CompareWithGolden(request, result); return result; } 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 883bc8a4..9f259e1c 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.hpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.hpp @@ -35,6 +35,7 @@ struct Request { double ssimThreshold = 0.99; bool useAngle = false; bool usePbuffer = true; + int holdMs = 0; }; struct Result { diff --git a/tools/trace_replay/apitrace_glws_egl.cpp b/tools/trace_replay/apitrace_glws_egl.cpp index 76096862..ecce8e13 100644 --- a/tools/trace_replay/apitrace_glws_egl.cpp +++ b/tools/trace_replay/apitrace_glws_egl.cpp @@ -4,11 +4,13 @@ #include #include #include +#include #include #include #include #include #include +#include #if defined(__APPLE__) #include @@ -81,6 +83,12 @@ id SendId(id receiver, const char *selector) { return ObjcMsgSend()(receiver, sel_registerName(selector)); } +id NSStringFromUtf8(const char *value) { + id stringClass = reinterpret_cast(objc_getClass("NSString")); + return ObjcMsgSend()( + stringClass, sel_registerName("stringWithUTF8String:"), value); +} + void SendVoid(id receiver, const char *selector) { ObjcMsgSend()(receiver, sel_registerName(selector)); } @@ -141,12 +149,18 @@ void PumpMacOSEvents() { SendVoid(app, "updateWindows"); } +extern "C" void mobilegl_trace_pump_events() { + PumpMacOSEvents(); +} + void EnsureApplicationActive() { id app = SharedApplication(); if (!app) { return; } + SendVoid(app, "finishLaunching"); SendVoidLong(app, "setActivationPolicy:", kNSApplicationActivationPolicyRegular); + SendVoidId(app, "unhide:", nil); SendVoidBool(app, "activateIgnoringOtherApps:", true); } @@ -172,6 +186,8 @@ id CreateMetalWindow(int width, int height, id *outLayer) { std::cerr << "error: failed to create NSWindow\n"; return nil; } + SendVoidId(window, "setTitle:", NSStringFromUtf8("MobileGL Trace Replay")); + SendVoidBool(window, "setReleasedWhenClosed:", false); id contentView = SendId(window, "contentView"); id layer = SendId(metalLayerClass, "layer"); @@ -185,14 +201,53 @@ id CreateMetalWindow(int width, int height, id *outLayer) { 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; } + +void ShowMetalWindow(id window) { + if (!window) { + return; + } + EnsureApplicationActive(); + ObjcMsgSend()(window, sel_registerName("makeKeyAndOrderFront:"), nil); + SendVoid(window, "orderFrontRegardless"); + PumpMacOSEvents(); +} #endif +void HoldAfterTargetPresent(const char *callNo) { + const char *holdMsText = std::getenv("MOBILEGL_TRACE_HOLD_MS"); + if (holdMsText == nullptr || holdMsText[0] == '\0') { + return; + } + const int holdMs = std::atoi(holdMsText); + if (holdMs <= 0) { + return; + } + const char *holdDone = std::getenv("MOBILEGL_TRACE_HOLD_DONE"); + if (holdDone != nullptr && std::strcmp(holdDone, "1") == 0) { + return; + } + const char *holdCall = std::getenv("MOBILEGL_TRACE_HOLD_CALL"); + if (holdCall != nullptr && holdCall[0] != '\0' && std::strcmp(holdCall, callNo) != 0) { + return; + } + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(holdMs); + while (std::chrono::steady_clock::now() < deadline) { +#if defined(__APPLE__) + PumpMacOSEvents(); +#endif + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } +#if defined(__APPLE__) + PumpMacOSEvents(); +#endif + setenv("MOBILEGL_TRACE_HOLD_DONE", "1", 1); +} + int ResolveWidth(int width) { if (gRequestedWidth > 0) { return gRequestedWidth; @@ -308,6 +363,7 @@ public: #if defined(__APPLE__) id window = nil; id metalLayer = nil; + bool windowShown = false; #endif EglDrawable(const EglVisual *visual, int width, int height, bool pbuffer) @@ -343,9 +399,8 @@ public: void show() override { visible = true; #if defined(__APPLE__) - if (window) { - ObjcMsgSend()(window, sel_registerName("makeKeyAndOrderFront:"), nil); - PumpMacOSEvents(); + if (windowShown) { + ShowMetalWindow(window); } #endif } @@ -366,7 +421,12 @@ public: unsetenv("MOBILEGL_PRESENT_CURRENT_CALL"); #if defined(__APPLE__) PumpMacOSEvents(); + if (window && !windowShown) { + ShowMetalWindow(window); + windowShown = true; + } #endif + HoldAfterTargetPresent(callNo); } private: @@ -552,8 +612,10 @@ Visual *createVisual(bool doubleBuffer, unsigned samples, Profile profile) { } Drawable *createDrawable(const Visual *visual, int width, int height, const pbuffer_info *pbInfo) { + (void)pbInfo; + const bool usePbuffer = !TraceReplayWantsWindowSurface(); return new EglDrawable(static_cast(visual), ResolveWidth(width), - ResolveHeight(height), true); + ResolveHeight(height), usePbuffer); } Context *createContext(const Visual *visual, Context *shareContext, bool) { diff --git a/tools/trace_replay/trace_replay_cli.cpp b/tools/trace_replay/trace_replay_cli.cpp index e79dcada..09ddf779 100644 --- a/tools/trace_replay/trace_replay_cli.cpp +++ b/tools/trace_replay/trace_replay_cli.cpp @@ -22,6 +22,7 @@ void PrintUsage(const char *argv0) { << " --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" + << " --hold-ms N Keep the replay process alive after retrace (default: 0)\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" @@ -100,6 +101,8 @@ bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) { request.usePbuffer = false; } else if (arg == "--pbuffer-surface") { request.usePbuffer = true; + } else if (arg == "--hold-ms") { + if (!ReadInt(argc, argv, i, request.holdMs)) return false; } else if (arg == "--ssim-threshold") { if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false; } else if (arg == "--crop-x") { @@ -130,6 +133,10 @@ bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) { std::cerr << "--target-call is required\n"; return false; } + if (request.holdMs < 0) { + std::cerr << "--hold-ms must be non-negative\n"; + return false; + } return true; }