[Feat] (trace-replay): add Linux retrace CI

This commit is contained in:
2026-06-15 18:08:55 +08:00
parent c2d6134cfa
commit ef3e8da1b5
14 changed files with 1223 additions and 4 deletions
+80
View File
@@ -0,0 +1,80 @@
name: Retrace APK
on:
push:
branches:
- dev
- Feat/Backend-Direct-GLES
- Feat/Backend-Direct-Vulkan
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set artifact metadata
run: |
echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: 8.10.2
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install Android NDK
run: |
sdkmanager "ndk;27.3.13750724"
echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties
- name: Update glslang external sources
working-directory: 3rdparty/glslang
run: python update_glslang_sources.py
- name: Build retrace APK
run: |
gradle --no-daemon -p android-plugin :app:assembleEsprytTraceRelease
gradle --no-daemon -p android-plugin :app:assembleMagmaTraceRelease
env:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
- name: Verify signed APK
run: |
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
mapfile -t APKS < <(find android-plugin/app/build/outputs/apk -type f -path '*/release/*.apk' | sort)
if [ "${#APKS[@]}" -eq 0 ]; then
echo "::error::No release APKs found under android-plugin/app/build/outputs/apk"
find android-plugin/app/build/outputs/apk -type f -print || true
exit 1
fi
for APK in "${APKS[@]}"; do
if [[ "$APK" == *-unsigned.apk ]]; then
echo "::error::Unsigned release APK produced: $APK"
exit 1
fi
"$APKSIGNER" verify --verbose "$APK"
done
- name: Upload retrace APK
uses: actions/upload-artifact@v4
with:
name: MobileGL-retrace-apk-${{ env.date_today }}-${{ env.short_sha }}
path: android-plugin/app/build/outputs/apk/**/release/*.apk
+80
View File
@@ -0,0 +1,80 @@
name: Retrace
on:
push:
branches:
- dev
- Feat/Backend-Direct-GLES
- Feat/Backend-Direct-Vulkan
workflow_dispatch:
jobs:
retrace:
runs-on: ubuntu-latest
env:
TEST_ROOT: ${{github.workspace}}
steps:
- name: Set Swap Space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 32
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: true
- name: Get CMake
uses: lukka/get-cmake@latest
- name: Prepare Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.1
with:
vulkan-query-version: 1.4.304.1
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true
- name: Update glslang external sources
working-directory: ${{env.TEST_ROOT}}/3rdparty/glslang
run: python update_glslang_sources.py
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers
- name: Show installed toolchain
run: |
clang-20 --version
clang++-20 --version
ld.lld-20 --version || ld.lld --version || true
dpkg -l 'libc++*' 'libegl*' 'libgles*' 'mesa*' || true
- name: Configure CMake
working-directory: ${{env.TEST_ROOT}}
run: |
if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" == "true" ]; then
cmake -S . -B build-retrace -G Ninja -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_BUILD_TYPE=Debug -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF -DMOBILEGL_BUILD_TRACE_REPLAY=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
else
cmake -S . -B build-retrace -G Ninja -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_BUILD_TYPE=Release -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF -DMOBILEGL_BUILD_TRACE_REPLAY=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5
fi
- name: Build trace replay
working-directory: ${{env.TEST_ROOT}}/build-retrace
run: cmake --build . --target mobilegl_trace_replay
- name: OpenRA retrace
working-directory: ${{env.TEST_ROOT}}/build-retrace
run: ctest -V -R MobileGLTraceReplay.OpenRA.DirectGLES
- name: Upload OpenRA actual
if: always()
uses: actions/upload-artifact@v4
with:
name: OpenRA-retrace-actual
path: |
build-retrace/tools/trace_replay/openra/output/actual.png
build-retrace/tools/trace_replay/openra/output/result.json
build-retrace/tools/trace_replay/openra/output/retrace.log
if-no-files-found: warn
+6
View File
@@ -6,6 +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)
if (ANDROID)
set(MOBILEGL_BUILD_TEST OFF CACHE BOOL "Build MobileGL tests" FORCE)
@@ -396,4 +397,9 @@ if (NOT ANDROID)
if (MOBILEGL_BUILD_BENCHMARK)
add_subdirectory(MobileGL/MG_Benchmark)
endif()
if (MOBILEGL_BUILD_TRACE_REPLAY)
enable_testing()
add_subdirectory(tools/trace_replay)
endif()
endif()
@@ -170,7 +170,9 @@ add_library(retrace_common STATIC
"${APITRACE_ROOT}/retrace/state_writer_ubjson.cpp"
"${APITRACE_ROOT}/retrace/ws.cpp")
target_compile_definitions(retrace_common PUBLIC RETRACE)
target_compile_definitions(retrace_common PRIVATE exit=mobilegl_apitrace_exit)
target_compile_definitions(retrace_common PRIVATE
exit=mobilegl_apitrace_exit
main=mobilegl_apitrace_main)
target_compile_options(retrace_common PRIVATE "-include${CMAKE_CURRENT_LIST_DIR}/apitrace_exit.hpp")
target_include_directories(retrace_common PUBLIC
"${APITRACE_GENERATED_DIR}"
@@ -216,7 +218,9 @@ target_include_directories(glretrace_common PUBLIC
"${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/helpers"
"${APITRACE_ROOT}/retrace")
target_compile_definitions(glretrace_common PRIVATE exit=mobilegl_apitrace_exit)
target_compile_definitions(glretrace_common PRIVATE
exit=mobilegl_apitrace_exit
main=mobilegl_apitrace_main)
target_compile_options(glretrace_common PRIVATE "-include${CMAKE_CURRENT_LIST_DIR}/apitrace_exit.hpp")
target_link_libraries(glretrace_common PUBLIC retrace_common glhelpers glproc)
@@ -225,6 +229,8 @@ add_library(trace_replay_runner SHARED
trace_replay_jni.cpp)
target_compile_features(trace_replay_runner PRIVATE cxx_std_17)
target_compile_definitions(trace_replay_runner PRIVATE
MOBILEGL_APITRACE_RETRACE_MAIN=mobilegl_apitrace_main)
target_include_directories(trace_replay_runner PRIVATE
"${APITRACE_ROOT}/lib/image")
@@ -1,5 +1,19 @@
#pragma once
#ifdef exit
#pragma push_macro("exit")
#undef exit
#define MOBILEGL_TRACE_RESTORE_EXIT_MACRO
#endif
#include <cstdlib>
#include <stdlib.h>
#ifdef MOBILEGL_TRACE_RESTORE_EXIT_MACRO
#pragma pop_macro("exit")
#undef MOBILEGL_TRACE_RESTORE_EXIT_MACRO
#endif
struct MobileGLRetraceExit {
int status;
};
@@ -22,7 +22,11 @@
#include <sys/stat.h>
#include <unistd.h>
extern "C" int main(int argc, char** argv);
#ifndef MOBILEGL_APITRACE_RETRACE_MAIN
#define MOBILEGL_APITRACE_RETRACE_MAIN main
#endif
extern "C" int MOBILEGL_APITRACE_RETRACE_MAIN(int argc, char** argv);
namespace mobilegl_trace {
namespace {
@@ -108,6 +112,7 @@ std::string JsonEscape(const std::string& value) {
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);
void* handle = dlopen(request.mobileGlLibrary.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (handle == nullptr) {
@@ -366,7 +371,7 @@ int RunRetraceMain(const Request& request) {
tracePath.data(),
nullptr,
};
return main(10, argv);
return MOBILEGL_APITRACE_RETRACE_MAIN(10, argv);
}
bool RunRetrace(const Request& request, Result& result) {
+270
View File
@@ -0,0 +1,270 @@
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")
return()
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_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/apitrace")
set(APITRACE_VERSION "mobilegl-trace")
find_package(Python3 REQUIRED)
find_package(Threads REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include("${APITRACE_ROOT}/cmake/ConvenienceLibrary.cmake")
function(target_optimize TARGET)
target_compile_definitions(${TARGET} PRIVATE -DNDEBUG)
target_compile_options(${TARGET} PRIVATE -O3)
endfunction()
function(include_with_scope)
include(${ARGV})
endfunction()
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(ENABLE_STATIC_SNAPPY ON CACHE BOOL "" FORCE)
set(DOC_INSTALL_DIR "doc" CACHE PATH "" FORCE)
set(HAVE_X86 OFF CACHE BOOL "" FORCE)
set(ZLIB_FOUND OFF CACHE BOOL "" FORCE)
set(PNG_FOUND OFF CACHE BOOL "" FORCE)
set(Snappy_FOUND OFF CACHE BOOL "" FORCE)
set(BROTLIDEC_FOUND OFF CACHE BOOL "" FORCE)
set(BROTLIENC_FOUND OFF CACHE BOOL "" FORCE)
set(ZSTD_FOUND OFF CACHE BOOL "" FORCE)
if(NOT TARGET check)
add_custom_target(check)
endif()
add_subdirectory("${APITRACE_ROOT}/thirdparty" "${APITRACE_BINARY_DIR}/thirdparty")
set(APITRACE_GENERATED_DIR "${APITRACE_BINARY_DIR}/generated")
file(MAKE_DIRECTORY "${APITRACE_GENERATED_DIR}")
configure_file("${APITRACE_ROOT}/version.h.in" "${APITRACE_GENERATED_DIR}/version.h" @ONLY)
add_custom_command(
OUTPUT
"${APITRACE_GENERATED_DIR}/glproc.hpp"
"${APITRACE_GENERATED_DIR}/glproc.cpp"
COMMAND ${Python3_EXECUTABLE}
"${APITRACE_ROOT}/dispatch/glproc.py"
"${APITRACE_GENERATED_DIR}/glproc.hpp"
"${APITRACE_GENERATED_DIR}/glproc.cpp"
DEPENDS
"${APITRACE_ROOT}/dispatch/glproc.py"
"${APITRACE_ROOT}/dispatch/dispatch.py"
"${APITRACE_ROOT}/specs/wglapi.py"
"${APITRACE_ROOT}/specs/glxapi.py"
"${APITRACE_ROOT}/specs/cglapi.py"
"${APITRACE_ROOT}/specs/eglapi.py"
"${APITRACE_ROOT}/specs/glapi.py"
"${APITRACE_ROOT}/specs/gltypes.py"
"${APITRACE_ROOT}/specs/stdapi.py")
add_library(mobilegl_trace_apitrace_os STATIC
"${APITRACE_ROOT}/lib/os/os_backtrace.cpp"
"${APITRACE_ROOT}/lib/os/os_crtdbg.cpp"
"${APITRACE_ROOT}/lib/os/os_posix.cpp")
target_include_directories(mobilegl_trace_apitrace_os PUBLIC
"${APITRACE_ROOT}/compat"
"${APITRACE_ROOT}/thirdparty"
"${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/lib/trace")
target_link_libraries(mobilegl_trace_apitrace_os PUBLIC Threads::Threads)
add_library(mobilegl_trace_highlight STATIC "${APITRACE_ROOT}/lib/highlight/highlight.cpp")
target_include_directories(mobilegl_trace_highlight PUBLIC "${APITRACE_ROOT}/lib/highlight")
add_library(mobilegl_trace_guids STATIC "${APITRACE_ROOT}/lib/guids/guids.cpp")
target_include_directories(mobilegl_trace_guids PUBLIC
"${APITRACE_ROOT}/lib/guids"
"${APITRACE_ROOT}/lib/os")
add_library(mobilegl_trace_image STATIC
"${APITRACE_ROOT}/lib/image/image_bmp.cpp"
"${APITRACE_ROOT}/lib/image/image_png.cpp"
"${APITRACE_ROOT}/lib/image/image_pnm.cpp"
"${APITRACE_ROOT}/lib/image/image_raw.cpp"
"${APITRACE_ROOT}/lib/image/image_md5.cpp")
target_include_directories(mobilegl_trace_image PUBLIC "${APITRACE_ROOT}/lib/image")
target_link_libraries(mobilegl_trace_image PUBLIC md5 PNG::PNG)
add_library(mobilegl_trace_common STATIC
"${APITRACE_ROOT}/lib/trace/trace_callset.cpp"
"${APITRACE_ROOT}/lib/trace/trace_dump.cpp"
"${APITRACE_ROOT}/lib/trace/trace_fast_callset.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_read.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_zlib.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_brotli.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_snappy.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_zstd.cpp"
"${APITRACE_ROOT}/lib/trace/trace_file_zstd_seekable.cpp"
"${APITRACE_ROOT}/lib/trace/trace_model.cpp"
"${APITRACE_ROOT}/lib/trace/trace_option.cpp"
"${APITRACE_ROOT}/lib/trace/trace_ostream_snappy.cpp"
"${APITRACE_ROOT}/lib/trace/trace_ostream_zlib.cpp"
"${APITRACE_ROOT}/lib/trace/trace_ostream_zstd.cpp"
"${APITRACE_ROOT}/lib/trace/trace_parser.cpp"
"${APITRACE_ROOT}/lib/trace/trace_parser_flags.cpp"
"${APITRACE_ROOT}/lib/trace/trace_parser_loop.cpp"
"${APITRACE_ROOT}/lib/trace/trace_profiler.cpp"
"${APITRACE_ROOT}/lib/trace/trace_writer.cpp"
"${APITRACE_ROOT}/lib/trace/trace_writer_local.cpp"
"${APITRACE_ROOT}/lib/trace/trace_writer_model.cpp")
target_include_directories(mobilegl_trace_common PUBLIC
"${APITRACE_ROOT}/compat"
"${APITRACE_ROOT}/thirdparty"
"${APITRACE_ROOT}/lib/guids"
"${APITRACE_ROOT}/lib/highlight"
"${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/lib/trace"
"${APITRACE_ROOT}/lib/ubjson")
target_link_libraries(mobilegl_trace_common PUBLIC
mobilegl_trace_guids
mobilegl_trace_highlight
mobilegl_trace_apitrace_os
Snappy::snappy
ZLIB::ZLIB
PkgConfig::BROTLIDEC
PkgConfig::ZSTD
zstd_seekable)
add_library(mobilegl_trace_glproc STATIC
"${APITRACE_GENERATED_DIR}/glproc.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glproc_mobilegl.cpp")
target_include_directories(mobilegl_trace_glproc PUBLIC
"${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)
execute_process(
COMMAND ${Python3_EXECUTABLE} "${APITRACE_ROOT}/retrace/glretrace.py"
OUTPUT_FILE "${APITRACE_GENERATED_DIR}/glretrace_gl.cpp"
COMMAND_ERROR_IS_FATAL ANY)
execute_process(
COMMAND ${Python3_EXECUTABLE} "${APITRACE_ROOT}/retrace/glstate_params.py"
OUTPUT_FILE "${APITRACE_GENERATED_DIR}/glstate_params.cpp"
COMMAND_ERROR_IS_FATAL ANY)
add_library(mobilegl_trace_glhelpers STATIC
"${APITRACE_ROOT}/helpers/glfeatures.cpp"
"${APITRACE_ROOT}/helpers/eglsize.cpp")
target_include_directories(mobilegl_trace_glhelpers PUBLIC
"${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/helpers"
"${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/thirdparty/khronos")
target_link_libraries(mobilegl_trace_glhelpers PUBLIC mobilegl_trace_glproc mobilegl_trace_apitrace_os)
add_library(mobilegl_trace_retrace_common STATIC
"${APITRACE_ROOT}/retrace/json.cpp"
"${APITRACE_ROOT}/retrace/process_name.cpp"
"${APITRACE_ROOT}/retrace/retrace.cpp"
"${APITRACE_ROOT}/retrace/retrace_main.cpp"
"${APITRACE_ROOT}/retrace/retrace_stdc.cpp"
"${APITRACE_ROOT}/retrace/retrace_swizzle.cpp"
"${APITRACE_ROOT}/retrace/state_writer.cpp"
"${APITRACE_ROOT}/retrace/state_writer_json.cpp"
"${APITRACE_ROOT}/retrace/state_writer_ubjson.cpp"
"${APITRACE_ROOT}/retrace/ws.cpp")
target_compile_definitions(mobilegl_trace_retrace_common PUBLIC RETRACE)
target_compile_definitions(mobilegl_trace_retrace_common PRIVATE
exit=mobilegl_apitrace_exit
main=mobilegl_apitrace_main)
target_compile_options(mobilegl_trace_retrace_common PRIVATE "-include${MOBILEGL_TRACE_SHARED_CPP_DIR}/apitrace_exit.hpp")
target_include_directories(mobilegl_trace_retrace_common PUBLIC
"${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/compat"
"${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/helpers"
"${APITRACE_ROOT}/lib/highlight"
"${APITRACE_ROOT}/lib/image"
"${APITRACE_ROOT}/lib/os"
"${APITRACE_ROOT}/lib/trace"
"${APITRACE_ROOT}/lib/ubjson"
"${APITRACE_ROOT}/retrace"
"${APITRACE_ROOT}/thirdparty"
"${APITRACE_ROOT}/thirdparty/khronos")
target_link_libraries(mobilegl_trace_retrace_common PUBLIC mobilegl_trace_image mobilegl_trace_common)
add_library(mobilegl_trace_glretrace_common STATIC
"${APITRACE_GENERATED_DIR}/glretrace_gl.cpp"
"${APITRACE_GENERATED_DIR}/glstate_params.cpp"
"${APITRACE_ROOT}/retrace/glretrace_cgl.cpp"
"${APITRACE_ROOT}/retrace/glretrace_egl.cpp"
"${APITRACE_ROOT}/retrace/glretrace_glx.cpp"
"${APITRACE_ROOT}/retrace/glretrace_main.cpp"
"${APITRACE_ROOT}/retrace/glretrace_wgl.cpp"
"${APITRACE_ROOT}/retrace/glretrace_wgl_font_bitmaps.cpp"
"${APITRACE_ROOT}/retrace/glretrace_wgl_font_outlines.cpp"
"${APITRACE_ROOT}/retrace/glretrace_ws.cpp"
"${APITRACE_ROOT}/retrace/glstate.cpp"
"${APITRACE_ROOT}/retrace/glstate_formats.cpp"
"${APITRACE_ROOT}/retrace/glstate_images.cpp"
"${APITRACE_ROOT}/retrace/glstate_shaders.cpp"
"${APITRACE_ROOT}/retrace/glws.cpp"
"${APITRACE_ROOT}/retrace/metric_backend_amd_perfmon.cpp"
"${APITRACE_ROOT}/retrace/metric_backend_intel_perfquery.cpp"
"${APITRACE_ROOT}/retrace/metric_backend_opengl.cpp"
"${APITRACE_ROOT}/retrace/metric_helper.cpp"
"${APITRACE_ROOT}/retrace/metric_writer.cpp"
"${MOBILEGL_TRACE_ROOT}/apitrace_glws_egl.cpp")
target_include_directories(mobilegl_trace_glretrace_common PUBLIC
"${APITRACE_GENERATED_DIR}"
"${APITRACE_ROOT}/dispatch"
"${APITRACE_ROOT}/helpers"
"${APITRACE_ROOT}/retrace")
target_compile_definitions(mobilegl_trace_glretrace_common PRIVATE
exit=mobilegl_apitrace_exit
main=mobilegl_apitrace_main)
target_compile_options(mobilegl_trace_glretrace_common PRIVATE "-include${MOBILEGL_TRACE_SHARED_CPP_DIR}/apitrace_exit.hpp")
target_link_libraries(mobilegl_trace_glretrace_common PUBLIC
mobilegl_trace_retrace_common
mobilegl_trace_glhelpers
mobilegl_trace_glproc)
add_executable(mobilegl_trace_replay
"${MOBILEGL_TRACE_SHARED_CPP_DIR}/trace_replay_core.cpp"
"${MOBILEGL_TRACE_ROOT}/trace_replay_cli.cpp")
target_compile_features(mobilegl_trace_replay PRIVATE cxx_std_17)
target_compile_definitions(mobilegl_trace_replay PRIVATE
MOBILEGL_APITRACE_RETRACE_MAIN=mobilegl_apitrace_main)
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"
mobilegl_trace_glretrace_common
mobilegl_trace_retrace_common
mobilegl_trace_image
mobilegl_trace_common
mobilegl_trace_glhelpers
mobilegl_trace_glproc
mobilegl_trace_apitrace_os
"-Wl,--end-group"
dl)
add_dependencies(mobilegl_trace_replay MobileGL)
enable_testing()
add_test(
NAME MobileGLTraceReplay.OpenRA.DirectGLES
COMMAND "${CMAKE_COMMAND}"
-DTRACE_REPLAY_EXE=$<TARGET_FILE:mobilegl_trace_replay>
-DMOBILEGL_LIBRARY=$<TARGET_FILE:MobileGL>
-DOPENRA_TRACE_ARCHIVE=${MOBILEGL_TRACE_ROOT}/fixtures/openra.tgz
-DOPENRA_GOLDEN=${MOBILEGL_TRACE_ROOT}/fixtures/openra.0000031249.png
-DOPENRA_OUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/openra
-P ${MOBILEGL_TRACE_ROOT}/run_openra_replay.cmake)
set_tests_properties(MobileGLTraceReplay.OpenRA.DirectGLES PROPERTIES
ENVIRONMENT "EGL_PLATFORM=surfaceless;LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330")
+44
View File
@@ -0,0 +1,44 @@
# MobileGL trace replay
This directory builds a Linux command line replay runner for apitrace files. It is meant for CI coverage of MobileGL
without Android, APK packaging, FCL, or plugin runtime dependencies.
Build from the MobileGL repository root:
```sh
cmake -S . -B build-test -G Ninja \
-DMOBILEGL_BUILD_TEST=ON \
-DMOBILEGL_BUILD_BENCHMARK=OFF \
-DMOBILEGL_BUILD_TRACE_REPLAY=ON
cmake --build build-test
```
Run the OpenRA fixture test:
```sh
ctest --test-dir build-test -V -R MobileGLTraceReplay.OpenRA.DirectGLES
```
Run the CLI directly:
```sh
build-test/tools/trace_replay/mobilegl_trace_replay \
--trace openra.trace \
--golden openra.0000031249.png \
--output out/openra \
--backend DirectGLES \
--mobilegl-library build-test/libMobileGL.so \
--target-call 31249 \
--width 640 \
--height 480 \
--crop-x 1 \
--crop-y 1 \
--crop-width 638 \
--crop-height 478 \
--tolerance 20 \
--fuzz-percent 20
```
The Linux runner uses an EGL pbuffer shim and loads MobileGL by path with `dlopen`. The current CI test covers the
OpenRA core-profile trace through the `DirectGLES` backend. `DirectVulkan` is still available as a CLI argument, but it
requires a native window/swapchain path and is not part of the headless CI test yet.
@@ -0,0 +1,142 @@
#include "glproc.hpp"
#include <GLES3/gl3.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
void *_libGlHandle = nullptr;
namespace {
void *LookupSymbol(const char *procName);
void *GetMobileGlHandle() {
if (_libGlHandle != nullptr) {
return _libGlHandle;
}
const char *library = std::getenv("MOBILEGL_TRACE_LIBRARY");
if (library != nullptr && library[0] != '\0') {
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
if (_libGlHandle == nullptr) {
_libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL);
}
}
if (_libGlHandle == nullptr) {
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
}
if (_libGlHandle == nullptr) {
_libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL);
}
return _libGlHandle;
}
using GlReadBuffer = void (*)(GLenum mode);
using GlReadPixels = void (*)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void *pixels);
using GlGetIntegerv = void (*)(GLenum pname, GLint *data);
using GlGetError = GLenum (*)();
GlReadBuffer gRealGlReadBuffer = nullptr;
GlReadPixels gRealGlReadPixels = nullptr;
GlGetIntegerv gRealGlGetIntegerv = nullptr;
GlGetError gRealGlGetError = nullptr;
int gSuppressInvalidEnumCount = 0;
void MobileGLTraceReadBuffer(GLenum mode) {
if (mode == GL_BACK) {
++gSuppressInvalidEnumCount;
return;
}
if (gRealGlReadBuffer == nullptr) {
gRealGlReadBuffer = reinterpret_cast<GlReadBuffer>(LookupSymbol("glReadBuffer"));
}
if (gRealGlReadBuffer != nullptr) {
gRealGlReadBuffer(mode);
}
}
void MobileGLTraceReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void *pixels) {
if (gRealGlReadPixels == nullptr) {
gRealGlReadPixels = reinterpret_cast<GlReadPixels>(LookupSymbol("glReadPixels"));
}
if (gRealGlReadPixels != nullptr) {
gRealGlReadPixels(x, y, width, height, format, type, pixels);
}
}
void MobileGLTraceGetIntegerv(GLenum pname, GLint *data) {
if (pname == GL_READ_BUFFER) {
if (data != nullptr) {
*data = GL_BACK;
}
++gSuppressInvalidEnumCount;
return;
}
if (gRealGlGetIntegerv == nullptr) {
gRealGlGetIntegerv = reinterpret_cast<GlGetIntegerv>(LookupSymbol("glGetIntegerv"));
}
if (gRealGlGetIntegerv != nullptr) {
gRealGlGetIntegerv(pname, data);
}
}
GLenum MobileGLTraceGetError() {
if (gRealGlGetError == nullptr) {
gRealGlGetError = reinterpret_cast<GlGetError>(LookupSymbol("glGetError"));
}
if (gRealGlGetError == nullptr) {
return GL_NO_ERROR;
}
GLenum error = gRealGlGetError();
if (error == GL_INVALID_ENUM && gSuppressInvalidEnumCount > 0) {
--gSuppressInvalidEnumCount;
return GL_NO_ERROR;
}
return error;
}
void *LookupSymbol(const char *procName) {
void *mobileGl = GetMobileGlHandle();
if (mobileGl != nullptr) {
void *proc = dlsym(mobileGl, procName);
if (proc != nullptr) {
return proc;
}
}
return dlsym(RTLD_DEFAULT, procName);
}
} // namespace
void *_getPublicProcAddress(const char *procName) {
if (strcmp(procName, "glReadPixels") == 0) {
return reinterpret_cast<void *>(&MobileGLTraceReadPixels);
}
if (strcmp(procName, "glReadBuffer") == 0) {
return reinterpret_cast<void *>(&MobileGLTraceReadBuffer);
}
if (strcmp(procName, "glGetIntegerv") == 0) {
return reinterpret_cast<void *>(&MobileGLTraceGetIntegerv);
}
if (strcmp(procName, "glGetError") == 0) {
return reinterpret_cast<void *>(&MobileGLTraceGetError);
}
return LookupSymbol(procName);
}
void *_getPrivateProcAddress(const char *procName) {
void *proc = LookupSymbol(procName);
if (proc != nullptr) {
return proc;
}
if (_eglGetProcAddress != nullptr) {
return reinterpret_cast<void *>(_eglGetProcAddress(procName));
}
return nullptr;
}
+373
View File
@@ -0,0 +1,373 @@
#include "glws.hpp"
#include "retrace.hpp"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <iostream>
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 PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext);
using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface);
using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint *);
using PfnEglGetDisplay = EGLDisplay (*)(EGLNativeDisplayType);
using PfnEglGetError = EGLint (*)();
using PfnEglInitialize = EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *);
using PfnEglMakeCurrent = EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
using PfnEglQueryString = const char *(*)(EGLDisplay, EGLint);
using PfnEglSwapBuffers = EGLBoolean (*)(EGLDisplay, EGLSurface);
using PfnEglTerminate = EGLBoolean (*)(EGLDisplay);
struct EglFns {
PfnEglBindApi bindApi = nullptr;
PfnEglChooseConfig chooseConfig = nullptr;
PfnEglCreateContext createContext = nullptr;
PfnEglCreatePbufferSurface createPbufferSurface = nullptr;
PfnEglDestroyContext destroyContext = nullptr;
PfnEglDestroySurface destroySurface = nullptr;
PfnEglGetConfigAttrib getConfigAttrib = nullptr;
PfnEglGetDisplay getDisplay = nullptr;
PfnEglGetError getError = nullptr;
PfnEglInitialize initialize = nullptr;
PfnEglMakeCurrent makeCurrent = nullptr;
PfnEglQueryString queryString = nullptr;
PfnEglSwapBuffers swapBuffers = nullptr;
PfnEglTerminate terminate = nullptr;
};
EglFns gEgl;
EGLDisplay gDisplay = EGL_NO_DISPLAY;
void *gMobileGl = nullptr;
const glws::Drawable *gCurrentDrawable = nullptr;
EGLContext gCurrentContext = EGL_NO_CONTEXT;
int gRequestedWidth = 0;
int gRequestedHeight = 0;
int ResolveWidth(int width) {
if (gRequestedWidth > 0) {
return gRequestedWidth;
}
return width > 0 ? width : 1;
}
int ResolveHeight(int height) {
if (gRequestedHeight > 0) {
return gRequestedHeight;
}
return height > 0 ? height : 1;
}
void *Lookup(const char *name) {
if (gMobileGl == nullptr) {
const char *library = std::getenv("MOBILEGL_TRACE_LIBRARY");
if (library != nullptr && library[0] != '\0') {
gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
if (gMobileGl == nullptr) {
gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL);
}
}
if (gMobileGl == nullptr) {
gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD);
}
if (gMobileGl == nullptr) {
gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL);
}
}
if (gMobileGl != nullptr) {
void *symbol = dlsym(gMobileGl, name);
if (symbol != nullptr) {
return symbol;
}
}
return dlsym(RTLD_DEFAULT, name);
}
template <typename T>
bool Load(T &slot, const char *name) {
slot = reinterpret_cast<T>(Lookup(name));
if (slot == nullptr) {
std::cerr << "error: failed to resolve " << name << " from MobileGL\n";
return false;
}
return true;
}
bool LoadEgl() {
return Load(gEgl.bindApi, "eglBindAPI") &&
Load(gEgl.chooseConfig, "eglChooseConfig") &&
Load(gEgl.createContext, "eglCreateContext") &&
Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") &&
Load(gEgl.destroyContext, "eglDestroyContext") &&
Load(gEgl.destroySurface, "eglDestroySurface") &&
Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") &&
Load(gEgl.getDisplay, "eglGetDisplay") &&
Load(gEgl.getError, "eglGetError") &&
Load(gEgl.initialize, "eglInitialize") &&
Load(gEgl.makeCurrent, "eglMakeCurrent") &&
Load(gEgl.queryString, "eglQueryString") &&
Load(gEgl.swapBuffers, "eglSwapBuffers") &&
Load(gEgl.terminate, "eglTerminate");
}
class EglVisual final : public glws::Visual {
public:
EGLConfig config = nullptr;
EGLenum api = EGL_OPENGL_API;
explicit EglVisual(glws::Profile prof) : Visual(prof) {}
};
class EglDrawable final : public glws::Drawable {
public:
EGLSurface surface = EGL_NO_SURFACE;
EglDrawable(const EglVisual *visual, int width, int height, bool pbuffer)
: Drawable(visual, width, height, pbuffer) {
createSurface();
}
~EglDrawable() override {
destroySurface();
}
void resize(int w, int h) override {
w = ResolveWidth(w);
h = ResolveHeight(h);
if (w == width && h == height) {
return;
}
destroySurface();
width = w;
height = h;
createSurface();
if (gCurrentDrawable == this && gCurrentContext != EGL_NO_CONTEXT) {
gEgl.makeCurrent(gDisplay, surface, surface, gCurrentContext);
}
}
void show() override {
visible = true;
}
void swapBuffers() override {
if (surface == EGL_NO_SURFACE) {
return;
}
char callNo[32];
const char *overrideCallNo = getenv("MOBILEGL_TRACE_CURRENT_CALL_OVERRIDE");
if (overrideCallNo != nullptr && overrideCallNo[0] != '\0') {
snprintf(callNo, sizeof(callNo), "%s", overrideCallNo);
} else {
snprintf(callNo, sizeof(callNo), "%u", retrace::callNo);
}
setenv("MOBILEGL_PRESENT_CURRENT_CALL", callNo, 1);
gEgl.swapBuffers(gDisplay, surface);
unsetenv("MOBILEGL_PRESENT_CURRENT_CALL");
}
private:
void createSurface() {
const EGLint attribs[] = {
EGL_WIDTH, ResolveWidth(width),
EGL_HEIGHT, ResolveHeight(height),
EGL_NONE,
};
surface = gEgl.createPbufferSurface(gDisplay, static_cast<const EglVisual *>(visual)->config, attribs);
if (surface == EGL_NO_SURFACE) {
std::cerr << "error: EGL pbuffer creation failed: 0x" << std::hex
<< gEgl.getError() << std::dec << "\n";
}
}
void destroySurface() {
if (surface != EGL_NO_SURFACE) {
gEgl.destroySurface(gDisplay, surface);
surface = EGL_NO_SURFACE;
}
}
};
class EglContext final : public glws::Context {
public:
EGLContext context = EGL_NO_CONTEXT;
EglContext(const EglVisual *visual, glws::Context *shareContext)
: Context(visual) {
const EGLContext share = shareContext == nullptr ? EGL_NO_CONTEXT : static_cast<EglContext *>(shareContext)->context;
if (!gEgl.bindApi(visual->api)) {
std::cerr << "error: eglBindAPI failed: 0x" << std::hex << gEgl.getError()
<< std::dec << "\n";
}
EGLint attribs[9];
int index = 0;
if (visual->api == EGL_OPENGL_ES_API) {
attribs[index++] = EGL_CONTEXT_CLIENT_VERSION;
attribs[index++] = static_cast<EGLint>(std::max<unsigned>(profile.major, 2));
} else {
attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR;
attribs[index++] = profile.major >= 3 ? profile.major : 3;
attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR;
attribs[index++] = profile.major >= 3 ? profile.minor : 3;
attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
attribs[index++] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
}
attribs[index++] = EGL_NONE;
context = gEgl.createContext(gDisplay, visual->config, share, attribs);
if (context == EGL_NO_CONTEXT && visual->api == EGL_OPENGL_API) {
const EGLint fallbackAttribs[] = {EGL_NONE};
context = gEgl.createContext(gDisplay, visual->config, share, fallbackAttribs);
}
if (context == EGL_NO_CONTEXT) {
std::cerr << "error: eglCreateContext failed: 0x" << std::hex << gEgl.getError()
<< std::dec << "\n";
}
}
~EglContext() override {
if (context != EGL_NO_CONTEXT) {
gEgl.destroyContext(gDisplay, context);
}
}
};
const EglDrawable *AsEglDrawable(const glws::Drawable *drawable) {
return static_cast<const EglDrawable *>(drawable);
}
const EglContext *AsEglContext(const glws::Context *context) {
return static_cast<const EglContext *>(context);
}
} // namespace
namespace glws {
void init() {
if (gDisplay != EGL_NO_DISPLAY) {
return;
}
if (!LoadEgl()) {
return;
}
gDisplay = gEgl.getDisplay(EGL_DEFAULT_DISPLAY);
if (gDisplay == EGL_NO_DISPLAY) {
std::cerr << "error: eglGetDisplay failed: 0x" << std::hex << gEgl.getError()
<< std::dec << "\n";
return;
}
EGLint major = 0;
EGLint minor = 0;
if (!gEgl.initialize(gDisplay, &major, &minor)) {
std::cerr << "error: eglInitialize failed: 0x" << std::hex << gEgl.getError()
<< std::dec << "\n";
}
}
void cleanup() {
if (gDisplay != EGL_NO_DISPLAY) {
gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
gEgl.terminate(gDisplay);
gDisplay = EGL_NO_DISPLAY;
}
}
Visual *createVisual(bool doubleBuffer, unsigned samples, Profile profile) {
auto *visual = new EglVisual(profile);
visual->doubleBuffer = doubleBuffer;
visual->api = profile.api == glfeatures::API_GLES ? EGL_OPENGL_ES_API : EGL_OPENGL_API;
if (!gEgl.bindApi(visual->api)) {
delete visual;
return nullptr;
}
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_SAMPLE_BUFFERS, samples > 1 ? 1 : 0,
EGL_SAMPLES, samples > 1 ? static_cast<EGLint>(samples) : 0,
EGL_NONE,
};
EGLint count = 0;
if (!gEgl.chooseConfig(gDisplay, attribs, &visual->config, 1, &count) || count < 1) {
delete visual;
return nullptr;
}
return visual;
}
Drawable *createDrawable(const Visual *visual, int width, int height, const pbuffer_info *pbInfo) {
return new EglDrawable(static_cast<const EglVisual *>(visual), ResolveWidth(width),
ResolveHeight(height), true);
}
Context *createContext(const Visual *visual, Context *shareContext, bool) {
return new EglContext(static_cast<const EglVisual *>(visual), shareContext);
}
bool makeCurrentInternal(Drawable *drawable, Drawable *readable, Context *context) {
EGLSurface drawSurface = drawable == nullptr ? EGL_NO_SURFACE : AsEglDrawable(drawable)->surface;
EGLSurface readSurface = readable == nullptr ? drawSurface : AsEglDrawable(readable)->surface;
EGLContext eglContext = context == nullptr ? EGL_NO_CONTEXT : AsEglContext(context)->context;
if (drawSurface == EGL_NO_SURFACE && readSurface == EGL_NO_SURFACE && eglContext == EGL_NO_CONTEXT) {
gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
gCurrentDrawable = nullptr;
gCurrentContext = EGL_NO_CONTEXT;
return true;
}
if (gEgl.makeCurrent(gDisplay, drawSurface, readSurface, eglContext) != EGL_TRUE) {
return false;
}
gCurrentDrawable = drawable;
gCurrentContext = eglContext;
return true;
}
bool processEvents() {
return false;
}
bool bindTexImage(Drawable *, int) {
return false;
}
bool releaseTexImage(Drawable *, int) {
return false;
}
bool setPbufferAttrib(Drawable *, const int *) {
return false;
}
} // namespace glws
extern "C" bool mobilegl_trace_get_drawable_bounds(int *width, int *height) {
if (gCurrentDrawable == nullptr || width == nullptr || height == nullptr) {
return false;
}
*width = gCurrentDrawable->width;
*height = gCurrentDrawable->height;
return *width > 0 && *height > 0;
}
extern "C" void mobilegl_trace_set_requested_size(int width, int height) {
gRequestedWidth = width > 0 ? width : 0;
gRequestedHeight = height > 0 ? height : 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.
@@ -0,0 +1,63 @@
foreach(required TRACE_REPLAY_EXE MOBILEGL_LIBRARY OPENRA_TRACE_ARCHIVE OPENRA_GOLDEN OPENRA_OUTPUT_DIR)
if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "")
message(FATAL_ERROR "${required} is required")
endif()
endforeach()
if(EXISTS "${OPENRA_OUTPUT_DIR}")
file(REMOVE_RECURSE "${OPENRA_OUTPUT_DIR}")
endif()
file(MAKE_DIRECTORY "${OPENRA_OUTPUT_DIR}/input")
file(MAKE_DIRECTORY "${OPENRA_OUTPUT_DIR}/output")
execute_process(
COMMAND "${CMAKE_COMMAND}" -E tar xzf "${OPENRA_TRACE_ARCHIVE}"
WORKING_DIRECTORY "${OPENRA_OUTPUT_DIR}/input"
RESULT_VARIABLE extract_result
OUTPUT_VARIABLE extract_stdout
ERROR_VARIABLE extract_stderr)
if(NOT extract_result EQUAL 0)
message(STATUS "${extract_stdout}")
message(STATUS "${extract_stderr}")
message(FATAL_ERROR "failed to extract ${OPENRA_TRACE_ARCHIVE}")
endif()
set(openra_trace "${OPENRA_OUTPUT_DIR}/input/openra.trace")
if(NOT EXISTS "${openra_trace}")
message(FATAL_ERROR "extracted OpenRA trace was not found at ${openra_trace}")
endif()
execute_process(
COMMAND "${TRACE_REPLAY_EXE}"
--trace "${openra_trace}"
--golden "${OPENRA_GOLDEN}"
--output "${OPENRA_OUTPUT_DIR}/output"
--backend DirectGLES
--mobilegl-library "${MOBILEGL_LIBRARY}"
--target-call 31249
--width 640
--height 480
--tolerance 20
--crop-x 1
--crop-y 1
--crop-width 638
--crop-height 478
--fuzz-percent 20
RESULT_VARIABLE replay_result
OUTPUT_VARIABLE replay_stdout
ERROR_VARIABLE replay_stderr)
message(STATUS "${replay_stdout}")
message(STATUS "${replay_stderr}")
set(result_json "${OPENRA_OUTPUT_DIR}/output/result.json")
if(EXISTS "${result_json}")
file(READ "${result_json}" result_contents)
message(STATUS "${result_contents}")
else()
message(FATAL_ERROR "trace replay did not write ${result_json}")
endif()
if(NOT replay_result EQUAL 0)
message(FATAL_ERROR "OpenRA trace replay failed with status ${replay_result}")
endif()
+136
View File
@@ -0,0 +1,136 @@
#include "trace_replay_core.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
extern "C" void mobilegl_trace_set_requested_size(int width, int height);
namespace {
void PrintUsage(const char *argv0) {
std::cerr
<< "Usage: " << argv0 << " --trace PATH --target-call N --output DIR [options]\n"
<< "\n"
<< "Options:\n"
<< " --golden PATH Golden PNG to compare against\n"
<< " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n"
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
<< " --width N Replay surface width override\n"
<< " --height N Replay surface height override\n"
<< " --tolerance N Allowed mismatch pixels\n"
<< " --fuzz-percent N Per-channel fuzz threshold, 0-100 (default: 20)\n"
<< " --crop-x N Compare crop x\n"
<< " --crop-y N Compare crop y\n"
<< " --crop-width N Compare crop width\n"
<< " --crop-height N Compare crop height\n";
}
bool ReadValue(int argc, char **argv, int &index, std::string &out) {
if (index + 1 >= argc) {
std::cerr << "Missing value for " << argv[index] << "\n";
return false;
}
out = argv[++index];
return true;
}
bool ReadInt(int argc, char **argv, int &index, int &out) {
std::string value;
if (!ReadValue(argc, argv, index, value)) {
return false;
}
out = std::atoi(value.c_str());
return true;
}
bool ReadLongLong(int argc, char **argv, int &index, long long &out) {
std::string value;
if (!ReadValue(argc, argv, index, value)) {
return false;
}
out = std::strtoll(value.c_str(), nullptr, 10);
return true;
}
bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) {
request.backend = "DirectGLES";
request.fuzzPercent = 20;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--trace") {
if (!ReadValue(argc, argv, i, request.tracePath)) return false;
} else if (arg == "--golden") {
if (!ReadValue(argc, argv, i, request.goldenPath)) return false;
} else if (arg == "--output") {
if (!ReadValue(argc, argv, i, request.outputDir)) return false;
} else if (arg == "--backend") {
if (!ReadValue(argc, argv, i, request.backend)) return false;
} else if (arg == "--mobilegl-library") {
if (!ReadValue(argc, argv, i, request.mobileGlLibrary)) return false;
} else if (arg == "--target-frame") {
if (!ReadInt(argc, argv, i, request.targetFrame)) return false;
} else if (arg == "--target-call") {
if (!ReadLongLong(argc, argv, i, request.targetCall)) return false;
} else if (arg == "--width") {
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 == "--tolerance") {
if (!ReadInt(argc, argv, i, request.tolerance)) return false;
} else if (arg == "--crop-x") {
if (!ReadInt(argc, argv, i, request.cropX)) return false;
} else if (arg == "--crop-y") {
if (!ReadInt(argc, argv, i, request.cropY)) return false;
} else if (arg == "--crop-width") {
if (!ReadInt(argc, argv, i, request.cropWidth)) return false;
} else if (arg == "--crop-height") {
if (!ReadInt(argc, argv, i, request.cropHeight)) return false;
} else if (arg == "--fuzz-percent") {
if (!ReadInt(argc, argv, i, request.fuzzPercent)) return false;
} else if (arg == "--help" || arg == "-h") {
return false;
} else {
std::cerr << "Unknown argument: " << arg << "\n";
return false;
}
}
if (request.tracePath.empty()) {
std::cerr << "--trace is required\n";
return false;
}
if (request.outputDir.empty()) {
std::cerr << "--output is required\n";
return false;
}
if (request.targetCall < 0) {
std::cerr << "--target-call is required\n";
return false;
}
return true;
}
} // namespace
int main(int argc, char **argv) {
mobilegl_trace::Request request;
if (!ParseArgs(argc, argv, request)) {
PrintUsage(argv[0]);
return 2;
}
mobilegl_trace_set_requested_size(request.width, request.height);
mobilegl_trace::Result result = mobilegl_trace::RunTraceReplay(request);
mobilegl_trace_set_requested_size(0, 0);
if (!result.resultPath.empty()) {
mobilegl_trace::WriteResultJson(request, result);
}
std::cout << result.message << "\n"
<< "result: " << result.resultPath << "\n"
<< "actual: " << result.actualPath << "\n";
return result.passed ? 0 : result.statusCode;
}