From e3e890ed5da1d8d6e15a7472f03bc3bd1c7aa867 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 27 Sep 2026 06:28:00 -0400 Subject: [PATCH] [Feat] (Trace): the replay runner builds as an adb-shell executable for Android, and unix endpoints carry a headless window MOBILEGL_BUILD_TRACE_REPLAY now also produces mobilegl_trace_replay on ANDROID: the same CLI as desktop, linked against the APK runner's glws/glproc glue. On the device it dials a render server with no Activity at all - MOBILEGL_IPC_CONTROL=unix:@name with shm data to the offscreen supervisor (a cross-uid abstract connect needs root or an SELinux waiver; loopback TCP works from plain adb shell), or tcp:// with MOBILEGL_IPC_SURFACE=server to the on-screen display server. glws_android grows the desktop glws_egl's server-owned null-window createWindowSurface, gated on MOBILEGL_TRACE_SURFACE=window so the APK's pbuffer mode keeps its old answer, and the config carries EGL_WINDOW_BIT for it. The control screen refuses unix endpoints in on-screen mode: the in-process display server serves tcp:// only. Verified on-device with an OpenRA retrace, ssim=1.0 both ways: offscreen service on @mgl-cli with the CLI as root, and the display Activity on tcp://127.0.0.1:40613 with the replay's frames on the phone's screen. --- CMakeLists.txt | 6 +++++ .../src/trace/cpp/apitrace_glws_android.cpp | 27 ++++++++++++++++++- .../plugin/ServerControlActivity.java | 10 ++++++- tools/trace_replay/CMakeLists.txt | 16 ++++++++--- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cc98f2e..210eb994 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -982,6 +982,12 @@ if (ANDROID AND MOBILEGL_BUILD_INTEGRATION_TEST) add_subdirectory(MobileGL/MG_IntegrationTest) endif() +# The replay runner also builds for Android: an adb-shell executable that can +# dial a render server on the device (unix or TCP) without any Activity. +if (ANDROID AND MOBILEGL_BUILD_TRACE_REPLAY) + add_subdirectory(tools/trace_replay) +endif() + # --------------------------------------------------------------------------- # P0 spike A: the Android delivery chain for a second native executable. # 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 e62960a0..9fbb11e2 100644 --- a/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp +++ b/android-plugin/app/src/trace/cpp/apitrace_glws_android.cpp @@ -75,6 +75,14 @@ int ResolveHeight(int height) { return height > 0 ? height : 1; } +// A HEADLESS command-line replay (no ANativeWindow) that still wants a window surface: the +// window will be the MobileGL server's (MOBILEGL_IPC_SURFACE=server), so the config must +// carry EGL_WINDOW_BIT for the null-window eglCreateWindowSurface in createSurface. +bool WantsServerOwnedWindow() { + const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE"); + return gNativeWindow == nullptr && mode != nullptr && std::strcmp(mode, "window") == 0; +} + void *Lookup(const char *name) { if (gMobileGl == nullptr) { gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); @@ -206,6 +214,22 @@ private: static_cast(visual)->config, gNativeWindow, nullptr); + } else if (!pbuffer && WantsServerOwnedWindow()) { + // HEADLESS COMMAND-LINE REPLAY (P12 D9, same shape as the desktop glws_egl path): + // the window is the MobileGL SERVER's (MOBILEGL_IPC_SURFACE=server, + // WindowKind::ServerOwned), so the native window is none and the size the trace + // wants rides EGL_WIDTH/EGL_HEIGHT. Gated on MOBILEGL_TRACE_SURFACE=window so the + // APK's pbuffer mode (no ANativeWindow either) keeps its pbuffer. + const EGLint windowAttribs[] = { + EGL_WIDTH, surfaceWidth, + EGL_HEIGHT, surfaceHeight, + EGL_NONE, + }; + surface = gEgl.createWindowSurface( + gDisplay, + static_cast(visual)->config, + EGLNativeWindowType{}, + windowAttribs); } else { const EGLint attribs[] = { EGL_WIDTH, surfaceWidth, @@ -334,7 +358,8 @@ Visual *createVisual(bool doubleBuffer, unsigned samples, Profile profile) { } const EGLint attribs[] = { - EGL_SURFACE_TYPE, gNativeWindow == nullptr ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT, + EGL_SURFACE_TYPE, + gNativeWindow == nullptr && !WantsServerOwnedWindow() ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT, EGL_RED_SIZE, 8, diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java index c90b528c..efe5af6b 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/ServerControlActivity.java @@ -98,7 +98,7 @@ public final class ServerControlActivity extends Activity { envField = addField(contentLayout, "Extra env (optional)", "", dp(8)); onScreenBox = new CheckBox(this); - onScreenBox.setText("On-screen window (render client frames onto this screen)"); + onScreenBox.setText("On-screen window (render client frames onto this screen; tcp:// only)"); onScreenBox.setTextColor(COLOR_TEXT); onScreenBox.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13); LinearLayout.LayoutParams boxParams = new LinearLayout.LayoutParams( @@ -155,6 +155,14 @@ public final class ServerControlActivity extends Activity { String token = tokenField.getText().toString(); String env = envField.getText().toString().trim(); + // The in-process display server only accepts tcp:// (ServerMain's in-process arm); + // the unix forms belong to the offscreen supervisor. + if (onScreenBox.isChecked() && !endpoint.startsWith("tcp://")) { + setStatus("The on-screen display server serves tcp:// only; use a tcp:// endpoint " + + "or uncheck On-screen window.", COLOR_FAIL); + return; + } + String probeDisplay; Probe probe; if (endpoint.startsWith("tcp://")) { diff --git a/tools/trace_replay/CMakeLists.txt b/tools/trace_replay/CMakeLists.txt index 17388b81..a9a2b8d5 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 ANDROID) - message(STATUS "MobileGL trace replay runner is only enabled on desktop Unix") +if(NOT UNIX) + message(STATUS "MobileGL trace replay runner is only enabled on Unix (Android included)") return() endif() @@ -139,7 +139,9 @@ 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") + # Android resolves GL through the same loader glue as the APK runner; desktop + # keeps its own. + "$,${MOBILEGL_TRACE_SHARED_CPP_DIR}/apitrace_glproc_android.cpp,${MOBILEGL_TRACE_ROOT}/apitrace_glproc_mobilegl.cpp>") if(APPLE) target_compile_definitions(mobilegl_trace_glproc PRIVATE GL_SILENCE_DEPRECATION) endif() @@ -225,7 +227,9 @@ add_library(mobilegl_trace_glretrace_common STATIC "${APITRACE_ROOT}/retrace/metric_helper.cpp" "${APITRACE_ROOT}/retrace/metric_writer.cpp" "${MOBILEGL_TRACE_ROOT}/apitrace_fbo_dump.cpp" - "${MOBILEGL_TRACE_ROOT}/apitrace_glws_egl.cpp" + # Android uses the APK runner's glws/glproc glue (pbuffer, or the server-owned + # null-window surface for a headless replay); desktop uses the EGL one. + "$,${MOBILEGL_TRACE_SHARED_CPP_DIR}/apitrace_glws_android.cpp,${MOBILEGL_TRACE_ROOT}/apitrace_glws_egl.cpp>" "${MOBILEGL_TRACE_SHARED_CPP_DIR}/trace_benchmark.cpp") if(APPLE) set(MOBILEGL_TRACE_APPLE_FRAMEWORKS @@ -241,6 +245,7 @@ target_include_directories(mobilegl_trace_glretrace_common PUBLIC "${APITRACE_ROOT}/dispatch" "${APITRACE_ROOT}/helpers" "${APITRACE_ROOT}/retrace" + "${MOBILEGL_TRACE_ROOT}" "${MOBILEGL_TRACE_SHARED_CPP_DIR}") target_compile_definitions(mobilegl_trace_glretrace_common PRIVATE exit=mobilegl_apitrace_exit @@ -280,6 +285,9 @@ else() ${MOBILEGL_TRACE_REPLAY_LINK_LIBRARIES} "-Wl,--end-group") endif() +if(ANDROID) + target_link_libraries(mobilegl_trace_replay PRIVATE android log) +endif() # The `K=V;K=V` environment passthrough is the only hand-rolled parse between a CI flag # and setenv(), and the lane that exercises it end to end is an on-device retrace, which