mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
[Feat] (trace-replay): add Linux retrace CI
This commit is contained in:
@@ -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
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user