diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java index a3038f2c..512f4004 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java @@ -45,7 +45,10 @@ public final class TraceReplayActivity extends Activity { request = TraceReplayRequest.from( intent, getFilesDir(), - getString(top.mobilegl.plugin.R.string.mobilegl_default_backend) + getString(top.mobilegl.plugin.R.string.mobilegl_default_backend), + // P6: the ONE place that knows where an exec-able file lives. See + // resolveSpawnServerPath. + getApplicationInfo().nativeLibraryDir ); statusView = new TextView(this); statusView.setText("Waiting for render surface\n" + request.outputDir); @@ -354,7 +357,37 @@ public final class TraceReplayActivity extends Activity { this.envOverrides = envOverrides; } - static TraceReplayRequest from(Intent intent, File filesDir, String defaultBackend) { + // P6: MOBILEGL_TRANSPORT=spawn NEEDS AN ABSOLUTE PATH TO AN EXEC-ABLE FILE, and on + // Android there is exactly one such place. An APK's lib// is the only directory an + // untrusted_app may exec from - W^X has forbidden the app's own data dirs since API 29 - + // and the packager only puts a file there if it is named lib*.so, which is why the server + // executable wears that name (P0 spike A proved the arrangement on two devices). + // + // NOTHING OFF THE DEVICE CAN SPELL THAT PATH. nativeLibraryDir contains an + // install-time hash, so the host-side runner cannot pass MOBILEGL_IPC_SERVER_PATH the way + // it passes every other knob; the path has to be resolved HERE, by the process that is + // about to launch the server. Left unresolved, LaunchServer refuses BY NAME and the arm + // reds on "unresolvable image" - honest, but about the wrong thing. + // + // AN EXPLICIT VALUE WINS. A caller that set MOBILEGL_IPC_SERVER_PATH itself is pointing + // somewhere deliberately, and silently replacing it would make that knob untestable. + static String resolveSpawnServerPath(String envOverrides, String nativeLibraryDir) { + if (nativeLibraryDir == null || nativeLibraryDir.isEmpty()) { + return envOverrides; + } + if (!envOverrides.contains("MOBILEGL_TRANSPORT=spawn")) { + return envOverrides; + } + if (envOverrides.contains("MOBILEGL_IPC_SERVER_PATH=")) { + return envOverrides; + } + String serverPath = new File(nativeLibraryDir, "libMobileGLServer.so").getAbsolutePath(); + String entry = "MOBILEGL_IPC_SERVER_PATH=" + serverPath; + return envOverrides.isEmpty() ? entry : envOverrides + ";" + entry; + } + + static TraceReplayRequest from(Intent intent, File filesDir, String defaultBackend, + String nativeLibraryDir) { String outputDir = readString(intent, "output_dir", new File(filesDir, "trace-replay").getAbsolutePath()); String diffPath = readString(intent, "diff_path", ""); String benchmarkResultPath = @@ -389,7 +422,7 @@ public final class TraceReplayActivity extends Activity { intent.getIntExtra("benchmark_tail_frames", 200), intent.getBooleanExtra("benchmark_finish", true), benchmarkResultPath, - readString(intent, "mobilegl_env", "") + resolveSpawnServerPath(readString(intent, "mobilegl_env", ""), nativeLibraryDir) ); } diff --git a/tools/trace_replay/run_android_retrace_local.py b/tools/trace_replay/run_android_retrace_local.py index 9d48a919..46f54069 100644 --- a/tools/trace_replay/run_android_retrace_local.py +++ b/tools/trace_replay/run_android_retrace_local.py @@ -403,6 +403,18 @@ def parse_args(): help="Do not glFinish at every frame boundary, so frame times measure CPU submission " "only instead of GPU completion.", ) + parser.add_argument( + "--transport", + choices=("monolith", "inproc", "spawn"), + default="monolith", + help="MOBILEGL_TRANSPORT for the replay. `spawn` runs the server role in a SECOND PROCESS " + "on the device, launched out of the APK's lib// - which requires an APK built " + "with -Pmobilegl.buildDisaggregated=ON, or ConfigLoader has no parser, accepts the " + "value and ignores it (CONTRACT-P5 rule 5) and the run is monolith under a name that " + "says otherwise. MOBILEGL_IPC_SERVER_PATH is NOT passed from here: nativeLibraryDir " + "carries an install-time hash, so only the app can spell it, and " + "TraceReplayActivity.resolveSpawnServerPath does.", + ) parser.add_argument( "--benchmark-timeout-seconds", type=int, @@ -412,6 +424,16 @@ def parse_args(): return parser.parse_args() +def transport_env(args): + """The transport knob as env overrides, or nothing at all for monolith. + + An EMPTY list on monolith rather than MOBILEGL_TRANSPORT=monolith, so the default arm's + environment is byte-identical to what it was before this option existed - the control arm has + to stay a control. + """ + return [] if args.transport == "monolith" else [f"MOBILEGL_TRANSPORT={args.transport}"] + + def main(): args = parse_args() selected_backends = args.backend or list(BACKENDS) @@ -435,7 +457,7 @@ def main(): failures += run_benchmark_case(case, backend, args) continue print(f"=== Android retrace: {case['name']} / {backend} ===", flush=True) - rc = run_case(case, backend, env_overrides=args.env) + rc = run_case(case, backend, env_overrides=transport_env(args) + list(args.env)) try: render_summary() except Exception as error: