diff --git a/.github/workflows/apk.yml b/.github/workflows/apk.yml index b6e4a3c9..bcfbfe5f 100644 --- a/.github/workflows/apk.yml +++ b/.github/workflows/apk.yml @@ -84,3 +84,97 @@ jobs: name: MobileGL-retrace-apk-${{ env.date_today }}-${{ env.short_sha }} path: android-plugin/app/build/outputs/apk/*Trace/release/*.apk if-no-files-found: error + + - name: Build emulator retrace APKs + run: gradle --no-daemon -p android-plugin :app:assembleEsprytTraceDebug :app:assembleMagmaTraceDebug -Pmobilegl.abis=x86_64 --parallel --max-workers "$(nproc)" + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Test retrace APK on Android AVD + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + target: google_apis + arch: x86_64 + profile: pixel_6 + disable-animations: true + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none + script: | + set -euo pipefail + + mkdir -p android-retrace-fixture android-retrace-result + tar -xzf tools/trace_replay/fixtures/minecraft-1.21.4-startup.tgz -C android-retrace-fixture + cp tools/trace_replay/fixtures/minecraft-1.21.4-startup.0000092195.png android-retrace-fixture/golden.png + adb push android-retrace-fixture/trace.trace /data/local/tmp/mobilegl-trace.trace + adb push android-retrace-fixture/golden.png /data/local/tmp/mobilegl-golden.png + + run_retrace() { + local case_name="$1" + local package_name="$2" + local apk_path="$3" + local backend="$4" + local app_dir="/data/user/0/${package_name}/files/trace-replay" + local result_dir="android-retrace-result/${case_name}" + + mkdir -p "${result_dir}" + adb install -r "${apk_path}" + adb shell run-as "${package_name}" rm -rf files/trace-replay + adb shell run-as "${package_name}" mkdir -p files/trace-replay/input files/trace-replay/output + adb shell run-as "${package_name}" cp /data/local/tmp/mobilegl-trace.trace files/trace-replay/input/trace.trace + adb shell run-as "${package_name}" cp /data/local/tmp/mobilegl-golden.png files/trace-replay/input/golden.png + adb shell am force-stop "${package_name}" + adb logcat -c + adb shell am start -W -a top.mobilegl.plugin.TRACE_REPLAY \ + -n "${package_name}/top.mobilegl.plugin.trace.TraceReplayActivity" \ + --es trace_path "${app_dir}/input/trace.trace" \ + --es golden_path "${app_dir}/input/golden.png" \ + --es output_dir "${app_dir}/output" \ + --es diff_path "${app_dir}/output/${case_name}-diff.png" \ + --es backend "${backend}" \ + --el target_call 92195 \ + --ei width 854 \ + --ei height 480 \ + --ei tolerance 20 \ + --ei fuzz_percent 20 + + for _ in $(seq 1 180); do + if adb shell run-as "${package_name}" ls files/trace-replay/output/result.json >/dev/null 2>&1; then + break + fi + sleep 1 + done + + adb logcat -d -t 1000 > "${result_dir}/logcat.txt" || true + adb shell run-as "${package_name}" ls files/trace-replay/output/result.json + adb exec-out run-as "${package_name}" cat files/trace-replay/output/result.json > "${result_dir}/result.json" + cat "${result_dir}/result.json" + adb exec-out run-as "${package_name}" cat files/trace-replay/output/actual.png > "${result_dir}/${case_name}-actual.png" + adb exec-out run-as "${package_name}" cat files/trace-replay/output/${case_name}-diff.png > "${result_dir}/${case_name}-diff.png" + adb exec-out run-as "${package_name}" cat files/trace-replay/output/retrace.log > "${result_dir}/retrace.log" || true + + python3 -c 'import json, sys; result = json.load(open(sys.argv[1], encoding="utf-8")); sys.exit(0 if result.get("passed") else f"trace replay failed: {result}")' "${result_dir}/result.json" + } + + run_retrace \ + minecraft-1.21.4-startup-DirectGLES \ + top.mobilegl.plugin.espryt.trace \ + android-plugin/app/build/outputs/apk/esprytTrace/debug/MobileGL-EsprytTrace-debug.apk \ + DirectGLES + + run_retrace \ + minecraft-1.21.4-startup-DirectVulkan \ + top.mobilegl.plugin.magma.trace \ + android-plugin/app/build/outputs/apk/magmaTrace/debug/MobileGL-MagmaTrace-debug.apk \ + DirectVulkan + + - name: Upload Android retrace result + if: always() + uses: actions/upload-artifact@v4 + with: + name: MobileGL-android-retrace-result-${{ env.date_today }}-${{ env.short_sha }} + path: android-retrace-result/** + if-no-files-found: warn diff --git a/.gitmodules b/.gitmodules index c5d62241..5f573729 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,6 +28,6 @@ [submodule "3rdparty/SPIRV-Reflect"] path = 3rdparty/SPIRV-Reflect url = https://github.com/KhronosGroup/SPIRV-Reflect.git -[submodule "android-plugin/third_party/apitrace"] - path = android-plugin/third_party/apitrace +[submodule "3rdparty/apitrace"] + path = 3rdparty/apitrace url = https://github.com/MobileGL-Dev/apitrace.git diff --git a/android-plugin/third_party/apitrace b/3rdparty/apitrace similarity index 100% rename from android-plugin/third_party/apitrace rename to 3rdparty/apitrace diff --git a/android-plugin/TRACE_REPLAY.md b/android-plugin/TRACE_REPLAY.md index 257b3e9e..cd75a8d2 100644 --- a/android-plugin/TRACE_REPLAY.md +++ b/android-plugin/TRACE_REPLAY.md @@ -26,6 +26,7 @@ Intent extras: trace_path absolute path to the apitrace file golden_path optional absolute path to a golden PNG output_dir directory for result.json and actual.png +diff_path optional absolute path for a golden-difference PNG backend DirectGLES or DirectVulkan; defaults to the backend flavor target_frame target frame index, or -1 target_call target call number, or -1 @@ -61,6 +62,7 @@ adb shell am start -a top.mobilegl.plugin.TRACE_REPLAY \ --es trace_path /data/user/0/top.mobilegl.plugin.espryt.trace/files/trace-replay/input/app.trace \ --es golden_path /data/user/0/top.mobilegl.plugin.espryt.trace/files/trace-replay/input/app.golden.png \ --es output_dir /data/user/0/top.mobilegl.plugin.espryt.trace/files/trace-replay/output \ + --es diff_path /data/user/0/top.mobilegl.plugin.espryt.trace/files/trace-replay/output/app-diff.png \ --es backend DirectGLES \ --el target_call 31249 \ --ei tolerance 0 \ diff --git a/android-plugin/app/build.gradle b/android-plugin/app/build.gradle index fbec6f9e..ca4e3c8f 100644 --- a/android-plugin/app/build.gradle +++ b/android-plugin/app/build.gradle @@ -17,6 +17,12 @@ def releaseSigningReady = signingStoreFile.exists() && signingStorePassword && signingKeyAlias && signingKeyPassword +def mobileGlAbiFilters = { + ((findProperty('mobilegl.abis') ?: System.getenv('MOBILEGL_ABIS') ?: 'arm64-v8a') as String) + .split(',') + .collect { it.trim() } + .findAll { !it.isEmpty() } +} android { namespace 'top.mobilegl.plugin' @@ -30,7 +36,7 @@ android { versionName 'dev' ndk { - abiFilters 'arm64-v8a' + abiFilters(*mobileGlAbiFilters()) } } diff --git a/android-plugin/app/src/trace/cpp/CMakeLists.txt b/android-plugin/app/src/trace/cpp/CMakeLists.txt index 52fb8aa9..7bf49dc9 100644 --- a/android-plugin/app/src/trace/cpp/CMakeLists.txt +++ b/android-plugin/app/src/trace/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.22.1) project(trace_replay_runner) -set(APITRACE_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../../third_party/apitrace") +set(APITRACE_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../../../3rdparty/apitrace") set(APITRACE_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/apitrace") set(APITRACE_VERSION "mobilegl-trace") diff --git a/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp b/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp index e2d3b845..f0cc92e0 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp @@ -28,18 +28,21 @@ jobject MakeResult(JNIEnv* env, const mobilegl_trace::Result& result) { if (clazz == nullptr) { return nullptr; } - jmethodID ctor = env->GetMethodID(clazz, "", "(ZILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + jmethodID ctor = env->GetMethodID(clazz, "", + "(ZILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); if (ctor == nullptr) { return nullptr; } jstring message = env->NewStringUTF(result.message.c_str()); jstring resultPath = env->NewStringUTF(result.resultPath.c_str()); jstring actualPath = env->NewStringUTF(result.actualPath.c_str()); + jstring diffPath = env->NewStringUTF(result.diffPath.c_str()); jobject object = env->NewObject(clazz, ctor, result.passed ? JNI_TRUE : JNI_FALSE, result.statusCode, message, - resultPath, actualPath); + resultPath, actualPath, diffPath); env->DeleteLocalRef(message); env->DeleteLocalRef(resultPath); env->DeleteLocalRef(actualPath); + env->DeleteLocalRef(diffPath); return object; } @@ -52,6 +55,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv* jstring tracePath, jstring goldenPath, jstring outputDir, + jstring diffPath, jstring backend, jint targetFrame, jlong targetCall, @@ -67,6 +71,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv* request.tracePath = ToString(env, tracePath); request.goldenPath = ToString(env, goldenPath); request.outputDir = ToString(env, outputDir); + request.diffPath = ToString(env, diffPath); request.backend = ToString(env, backend); request.targetFrame = targetFrame; request.targetCall = targetCall; 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 4040e20d..8c150617 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 @@ -98,6 +98,7 @@ public final class TraceReplayActivity extends Activity { request.tracePath, request.goldenPath, request.outputDir, + request.diffPath, request.backend, request.targetFrame, request.targetCall, @@ -123,6 +124,7 @@ public final class TraceReplayActivity extends Activity { String tracePath, String goldenPath, String outputDir, + String diffPath, String backend, int targetFrame, long targetCall, @@ -140,6 +142,7 @@ public final class TraceReplayActivity extends Activity { final String tracePath; final String goldenPath; final String outputDir; + final String diffPath; final String backend; final int targetFrame; final long targetCall; @@ -156,6 +159,7 @@ public final class TraceReplayActivity extends Activity { String tracePath, String goldenPath, String outputDir, + String diffPath, String backend, int targetFrame, long targetCall, @@ -171,6 +175,7 @@ public final class TraceReplayActivity extends Activity { this.tracePath = tracePath; this.goldenPath = goldenPath; this.outputDir = outputDir; + this.diffPath = diffPath; this.backend = backend; this.targetFrame = targetFrame; this.targetCall = targetCall; @@ -186,10 +191,12 @@ public final class TraceReplayActivity extends Activity { static TraceReplayRequest from(Intent intent, File filesDir, String defaultBackend) { String outputDir = readString(intent, "output_dir", new File(filesDir, "trace-replay").getAbsolutePath()); + String diffPath = readString(intent, "diff_path", ""); return new TraceReplayRequest( readString(intent, "trace_path", ""), readString(intent, "golden_path", ""), outputDir, + diffPath, readString(intent, "backend", defaultBackend), intent.getIntExtra("target_frame", -1), intent.getLongExtra("target_call", -1L), @@ -216,19 +223,22 @@ public final class TraceReplayActivity extends Activity { public final String message; public final String resultPath; public final String actualPath; + public final String diffPath; public TraceReplayResult( boolean passed, int statusCode, String message, String resultPath, - String actualPath + String actualPath, + String diffPath ) { this.passed = passed; this.statusCode = statusCode; this.message = message; this.resultPath = resultPath; this.actualPath = actualPath; + this.diffPath = diffPath; } @Override @@ -239,6 +249,7 @@ public final class TraceReplayActivity extends Activity { ", message='" + message + '\'' + ", resultPath='" + resultPath + '\'' + ", actualPath='" + actualPath + '\'' + + ", diffPath='" + diffPath + '\'' + '}'; } } diff --git a/android-plugin/build.gradle b/android-plugin/build.gradle index 705e65e8..51cb9ba1 100644 --- a/android-plugin/build.gradle +++ b/android-plugin/build.gradle @@ -3,12 +3,19 @@ plugins { id 'com.android.library' version '8.6.0' apply false } +def mobileGlAbiFilters = { + ((rootProject.findProperty('mobilegl.abis') ?: System.getenv('MOBILEGL_ABIS') ?: 'arm64-v8a') as String) + .split(',') + .collect { it.trim() } + .findAll { !it.isEmpty() } +} + subprojects { subproject -> subproject.plugins.withId('com.android.library') { subproject.android { defaultConfig { ndk { - abiFilters 'arm64-v8a' + abiFilters(*mobileGlAbiFilters()) } externalNativeBuild { cmake { diff --git a/tools/trace_replay/CMakeLists.txt b/tools/trace_replay/CMakeLists.txt index 746dbf43..d06ba214 100644 --- a/tools/trace_replay/CMakeLists.txt +++ b/tools/trace_replay/CMakeLists.txt @@ -7,7 +7,7 @@ endif() set(MOBILEGL_TRACE_ROOT "${CMAKE_CURRENT_LIST_DIR}") set(MOBILEGL_TRACE_SHARED_CPP_DIR "${CMAKE_SOURCE_DIR}/android-plugin/app/src/trace/cpp") -set(APITRACE_ROOT "${CMAKE_SOURCE_DIR}/android-plugin/third_party/apitrace") +set(APITRACE_ROOT "${CMAKE_SOURCE_DIR}/3rdparty/apitrace") set(APITRACE_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/apitrace") set(APITRACE_VERSION "mobilegl-trace") set(MOBILEGL_TRACE_REPLAY_MOBILEGL_LIBRARY "" CACHE FILEPATH "Existing libMobileGL.so to use for trace replay tests") diff --git a/tools/trace_replay/README.md b/tools/trace_replay/README.md index 2e5615a8..e8a681ee 100644 --- a/tools/trace_replay/README.md +++ b/tools/trace_replay/README.md @@ -47,3 +47,60 @@ build-test/tools/trace_replay/mobilegl_trace_replay \ --tolerance 20 \ --fuzz-percent 20 ``` + +## Android device replay + +Build and install a trace APK from the repository root: + +```sh +gradle --no-daemon -p android-plugin :app:assembleEsprytTraceDebug +adb install -r android-plugin/app/build/outputs/apk/esprytTrace/debug/MobileGL-EsprytTrace-debug.apk +``` + +Prepare a fixture and copy it into the app-private directory: + +```sh +mkdir -p /tmp/mobilegl-openra +tar -xzf tools/trace_replay/fixtures/openra.tgz -C /tmp/mobilegl-openra +adb push /tmp/mobilegl-openra/openra.trace /data/local/tmp/mobilegl-openra.trace +adb push tools/trace_replay/fixtures/openra.0000031249.png /data/local/tmp/mobilegl-openra.golden.png + +PKG=top.mobilegl.plugin.espryt.trace +APP_DIR=/data/user/0/$PKG/files/trace-replay +adb shell run-as $PKG rm -rf files/trace-replay +adb shell run-as $PKG mkdir -p files/trace-replay/input files/trace-replay/output +adb shell run-as $PKG cp /data/local/tmp/mobilegl-openra.trace files/trace-replay/input/openra.trace +adb shell run-as $PKG cp /data/local/tmp/mobilegl-openra.golden.png files/trace-replay/input/openra.golden.png +``` + +Launch the standalone trace runner Activity: + +```sh +adb shell am start -W -a top.mobilegl.plugin.TRACE_REPLAY \ + -n $PKG/top.mobilegl.plugin.trace.TraceReplayActivity \ + --es trace_path $APP_DIR/input/openra.trace \ + --es golden_path $APP_DIR/input/openra.golden.png \ + --es output_dir $APP_DIR/output \ + --es diff_path $APP_DIR/output/openra-diff.png \ + --es backend DirectGLES \ + --el target_call 31249 \ + --ei width 640 \ + --ei height 480 \ + --ei crop_x 1 \ + --ei crop_y 1 \ + --ei crop_width 638 \ + --ei crop_height 478 \ + --ei tolerance 20 \ + --ei fuzz_percent 20 +``` + +Read back the result and images: + +```sh +adb shell run-as $PKG cat files/trace-replay/output/result.json +adb exec-out run-as $PKG cat files/trace-replay/output/actual.png > openra-actual.png +adb exec-out run-as $PKG cat files/trace-replay/output/openra-diff.png > openra-diff.png +``` + +For the Vulkan backend, build and install `:app:assembleMagmaTraceDebug`, set +`PKG=top.mobilegl.plugin.magma.trace`, and pass `--es backend DirectVulkan`.