mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Docs] (trace-replay): refresh fixture-authoring skill from the Create fixture work
Registration now documents the trace_cases.json registry (the CMakeLists / apk.yml instructions were stale). Adds the field-tested guidance from authoring the Create fixtures: in-tree apitrace fork requirements (frametrim DSA/multi-bind, persistent-map shadowing) and the Windows wgltrace wrapper, frozen-world + unfocused-window capture discipline, late-frame selection, trim verification, brotli repack (with the stale-archive trap), golden content verification, Android signing/stale-package/emulator-flake and stale-result pitfalls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,19 +34,30 @@ Install:
|
||||
|
||||
## Build apitrace
|
||||
|
||||
Build the in-tree fork, not an upstream release: it carries the frametrim
|
||||
handlers for DSA and ARB_multi_bind call streams and shadow-based tracking of
|
||||
persistent-mapped buffers, all of which modern Minecraft mod stacks need.
|
||||
|
||||
```sh
|
||||
cmake -S "$REPO/3rdparty/apitrace" -B "$WORK/build-apitrace" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_GUI=OFF
|
||||
cmake --build "$WORK/build-apitrace" --target apitrace glretrace --parallel
|
||||
cmake --build "$WORK/build-apitrace" --target apitrace glretrace gltrim --parallel
|
||||
|
||||
export APITRACE="$(find "$WORK/build-apitrace" -type f -name apitrace -perm -111 | head -n 1)"
|
||||
export GLRETRACE="$(find "$WORK/build-apitrace" -type f -name glretrace -perm -111 | head -n 1)"
|
||||
export GLTRIM="$(find "$WORK/build-apitrace" -type f -name gltrim -perm -111 | head -n 1)"
|
||||
test -n "$APITRACE"
|
||||
test -n "$GLRETRACE"
|
||||
test -n "$GLTRIM"
|
||||
```
|
||||
|
||||
On Windows, set `APITRACE` and `GLRETRACE` to the corresponding `.exe` files.
|
||||
On Windows, set `APITRACE` and `GLRETRACE` to the corresponding `.exe` files
|
||||
and also build the `wgltrace` target: `apitrace trace` fails with "failed to
|
||||
find opengl32.dll wrapper" unless `wrappers/opengl32.dll` was built. `gltrim`
|
||||
may be built and run on a Linux/WSL checkout instead; traces are portable, and
|
||||
trimming a multi-hundred-MB trace is much faster from a native filesystem copy
|
||||
than through `/mnt/c`.
|
||||
|
||||
## Prepare the capture
|
||||
|
||||
@@ -58,6 +69,28 @@ On Windows, set `APITRACE` and `GLRETRACE` to the corresponding `.exe` files.
|
||||
- For Minecraft, document version, mod loader, mods, shader pack, language,
|
||||
world, time, and camera setup.
|
||||
|
||||
Minecraft specifics that keep the capture deterministic and small:
|
||||
|
||||
- Freeze the world in `level.dat`: `doDaylightCycle`, `doWeatherCycle`,
|
||||
`doMobSpawning`, `randomTickSpeed 0`, a fixed `DayTime`, and the player
|
||||
`Rotation` that frames the intended subject. The camera snaps to the saved
|
||||
rotation on world join, so composition is edited in the save, not in-game.
|
||||
- `options.txt`: `pauseOnLostFocus:false`, a low `maxFps` (10 works), and a
|
||||
small `renderDistance` (3). Frame rate and render distance are the two main
|
||||
levers on trace size; a ~35 s in-world session at 10 fps lands well under
|
||||
the archive budget after repack.
|
||||
- Enter the world non-interactively with `--quickPlaySingleplayer <world>` so
|
||||
every capture takes the same path from boot to gameplay.
|
||||
- Keep the game window UNFOCUSED for the whole capture (focus the desktop
|
||||
right after launch, and again before closing). A focused Minecraft window
|
||||
grabs the mouse, and any physical mouse motion rotates the camera - the
|
||||
resulting goldens show a drifted view that is easy to misread as a
|
||||
rendering bug.
|
||||
- On Windows with JDK 21+, pass
|
||||
`-Djdk.net.unixdomain.tmpdir=<short-path-without-spaces>`: NIO selectors
|
||||
create AF_UNIX sockets under `%TEMP%`, which fails on some hosts and kills
|
||||
the game at boot with "Unable to establish loopback connection".
|
||||
|
||||
## Capture
|
||||
|
||||
```sh
|
||||
@@ -75,8 +108,21 @@ For Java:
|
||||
-- "$JAVA_EXE" <jvm-args> <main-class-or-jar> <game-args>
|
||||
```
|
||||
|
||||
An `@argfile` with the full JVM+game command line keeps the invocation
|
||||
reproducible across recaptures.
|
||||
|
||||
Keep `full.trace` until both backends are validated.
|
||||
|
||||
Persistent-mapped buffers: apps may legally write a `GL_MAP_PERSISTENT_BIT`
|
||||
mapping and let the GPU read it without an explicit flush (Flywheel's indirect
|
||||
backend writes its compute scatter descriptors this way). Stock apitrace never
|
||||
records those writes, so the trimmed fixture silently loses the content that
|
||||
depends on them - the symptom is geometry that renders live but disappears in
|
||||
replay. The in-tree fork shadow-tracks persistent mappings unconditionally;
|
||||
if a replay of `full.trace` is already missing content that the live run
|
||||
showed, fix capture (wrapper) first - no amount of trimming will bring the
|
||||
data back, and the case must be recaptured.
|
||||
|
||||
## Select target frame
|
||||
|
||||
Fixture selection must be frame-based. Do not trim the fixture from a full trace
|
||||
@@ -88,8 +134,17 @@ frame, then trim with `gltrim -f`.
|
||||
> "$WORK/$CASE/frames.txt"
|
||||
```
|
||||
|
||||
Inspect `frames.txt`, identify the frame that contains the intended visual
|
||||
state, and set:
|
||||
A quick way to bound the choice is the total frame count from a benchmark
|
||||
replay:
|
||||
|
||||
```sh
|
||||
"$GLRETRACE" -b "$WORK/$CASE/full.trace" # "Rendered N frames in ..."
|
||||
```
|
||||
|
||||
Pick a LATE frame (roughly `N - 20`): early frames still contain loading
|
||||
screens, chunk pop-in, and animation warm-up, while the last few frames may
|
||||
overlap the window-close path. Inspect `frames.txt` or snapshot the candidate
|
||||
frame to confirm it contains the intended visual state, then set:
|
||||
|
||||
```sh
|
||||
export TARGET_FRAME=<chosen-frame-number>
|
||||
@@ -98,12 +153,22 @@ export TARGET_FRAME=<chosen-frame-number>
|
||||
## Trim and package
|
||||
|
||||
```sh
|
||||
"$APITRACE" gltrim \
|
||||
"$GLTRIM" \
|
||||
-f "$TARGET_FRAME" \
|
||||
--output "$WORK/$CASE/trace.trace" \
|
||||
"$WORK/$CASE/full.trace"
|
||||
```
|
||||
|
||||
Then VERIFY the trim before doing anything else: replay `trace.trace`,
|
||||
snapshot its final swap, and compare the content against the same frame of
|
||||
`full.trace`. gltrim bugs fail silently - the classic symptom is a trimmed
|
||||
trace whose static world renders fine while everything driven by less common
|
||||
call patterns (DSA texture binds, `glBindBuffersRange` multi-bind setup,
|
||||
compute-written buffers) is missing or garbled, often with "invalid buffer
|
||||
name"-style retrace warnings. If content is missing from the trimmed trace
|
||||
but present in the full trace, the fix belongs in `3rdparty/apitrace`'s
|
||||
frametrim, not in the fixture.
|
||||
|
||||
## Generate golden
|
||||
|
||||
Generate frame snapshots from the trimmed trace, then choose the snapshot that
|
||||
@@ -118,6 +183,16 @@ mkdir -p "$WORK/$CASE/golden"
|
||||
"$WORK/$CASE/trace.trace"
|
||||
```
|
||||
|
||||
For a single-frame trim the target is simply the trimmed trace's final swap;
|
||||
finding it and snapshotting just that call is much faster than dumping every
|
||||
frame:
|
||||
|
||||
```sh
|
||||
"$APITRACE" dump "$WORK/$CASE/trace.trace" | grep SwapBuffers | tail -n 1
|
||||
"$GLRETRACE" -s "$WORK/$CASE/golden/$CASE." -S <final-swap-call> \
|
||||
"$WORK/$CASE/trace.trace"
|
||||
```
|
||||
|
||||
Set `TARGET_CALL` to the call number in the chosen trimmed-trace snapshot
|
||||
filename:
|
||||
|
||||
@@ -147,20 +222,34 @@ Or copy directly:
|
||||
cp "$GOLDEN_SRC" "$WORK/$CASE/$CASE.$(printf '%010d' "$TARGET_CALL").png"
|
||||
```
|
||||
|
||||
Package:
|
||||
Verify the golden CONTENT against a reference (a screenshot of the live run,
|
||||
or the same scene on a known-good backend), not just that a file exists. The
|
||||
subject must be present, correctly shaped, and framed as intended - a golden
|
||||
captured through a drifted camera or a half-loaded scene will happily pass
|
||||
authoring and then permanently enshrine the wrong image.
|
||||
|
||||
Package. Compress the trace itself with `repack --brotli` first - it shrinks
|
||||
a gzip-resistant trace by an order of magnitude (a ~70 MB single-frame
|
||||
Minecraft trim lands around 7 MB) and glretrace reads it directly:
|
||||
|
||||
```sh
|
||||
"$APITRACE" repack --brotli "$WORK/$CASE/trace.trace" "$WORK/$CASE/trace-brotli.trace"
|
||||
mkdir -p "$WORK/$CASE/archive"
|
||||
cp "$WORK/$CASE/trace.trace" "$WORK/$CASE/archive/trace.trace"
|
||||
cp "$WORK/$CASE/trace-brotli.trace" "$WORK/$CASE/archive/trace.trace"
|
||||
tar -czf "$REPO/tools/trace_replay/fixtures/$CASE.tgz" \
|
||||
-C "$WORK/$CASE/archive" trace.trace
|
||||
cp "$WORK/$CASE/$CASE.$(printf '%010d' "$TARGET_CALL").png" \
|
||||
"$REPO/tools/trace_replay/fixtures/"
|
||||
```
|
||||
|
||||
If the case is ever re-trimmed, REDO the repack and the archive: a `.tgz`
|
||||
whose repack predates the latest trim silently packages the stale trace, and
|
||||
the mismatch only surfaces later as "did not create expected snapshot" when
|
||||
the registered target call no longer exists.
|
||||
|
||||
Check the final archive size. The committed fixture archive should be less than
|
||||
20 MiB, and should preferably be less than 10 MiB. If it is larger, recapture
|
||||
with a shorter run or trim a smaller frame-only fixture instead of adding
|
||||
with a shorter run or a lower frame rate / render distance instead of adding
|
||||
call-based filtering.
|
||||
|
||||
```sh
|
||||
@@ -178,43 +267,29 @@ git add .gitattributes tools/trace_replay/fixtures/$CASE.tgz \
|
||||
git lfs status
|
||||
```
|
||||
|
||||
## Register Linux replay
|
||||
## Register the case
|
||||
|
||||
Edit `tools/trace_replay/CMakeLists.txt`:
|
||||
Both Linux ctest and the Android CI matrix are generated from the single
|
||||
registry `tools/trace_replay/trace_cases.json` (via
|
||||
`tools/trace_replay/trace_cases.py`); there is nothing to edit in
|
||||
`CMakeLists.txt` or `apk.yml`. Append one object to `cases`:
|
||||
|
||||
```cmake
|
||||
add_trace_replay_test_for_backends(case-name
|
||||
TRACE_ARCHIVE ${MOBILEGL_TRACE_ROOT}/fixtures/case-name.tgz
|
||||
TRACE_FILE trace.trace
|
||||
GOLDEN ${MOBILEGL_TRACE_ROOT}/fixtures/case-name.0000000000.png
|
||||
TARGET_CALL 0
|
||||
WIDTH 854
|
||||
HEIGHT 480
|
||||
SSIM_THRESHOLD 0.99)
|
||||
```json
|
||||
{
|
||||
"name": "case-name",
|
||||
"trace_archive": "case-name.tgz",
|
||||
"golden": "case-name.0000000000.png",
|
||||
"target_call": 0,
|
||||
"timeout_seconds": 900
|
||||
}
|
||||
```
|
||||
|
||||
Optional crop:
|
||||
|
||||
```cmake
|
||||
CROP_X 1
|
||||
CROP_Y 1
|
||||
CROP_WIDTH 852
|
||||
CROP_HEIGHT 478
|
||||
```
|
||||
|
||||
## Register Android replay
|
||||
|
||||
Edit `.github/workflows/apk.yml` `TRACE_REPLAY_CASES`:
|
||||
|
||||
```text
|
||||
case-name|tools/trace_replay/fixtures/case-name.tgz|trace.trace|tools/trace_replay/fixtures/case-name.0000000000.png|0|854|480|0.99|0|0|0|0|900
|
||||
```
|
||||
|
||||
Field order:
|
||||
|
||||
```text
|
||||
case|trace archive|trace file|golden|target call|width|height|ssim threshold|crop x|crop y|crop width|crop height|timeout seconds
|
||||
```
|
||||
Values matching the `defaults` block (854x480, `trace.trace`, ssim 0.99, zero
|
||||
crop, 900 s timeout) may be omitted. Available per-case keys: `name`,
|
||||
`trace_archive`, `trace_file`, `golden`, `alternate_golden`, `target_call`,
|
||||
`width`, `height`, `ssim_threshold`, `crop_x/y/width/height`,
|
||||
`timeout_seconds`, `ci`. Long single-frame replays of heavy in-world scenes
|
||||
need a raised `timeout_seconds` (the Create fixtures use 1800).
|
||||
|
||||
Update `tools/trace_replay/README.md` with one fixture sentence and one golden
|
||||
image link.
|
||||
@@ -291,6 +366,32 @@ gradle --no-daemon -p "$REPO/android-plugin" \
|
||||
--parallel
|
||||
```
|
||||
|
||||
Release APKs are only signed when `SIGNING_STORE_PASSWORD`,
|
||||
`SIGNING_KEY_ALIAS`, and `SIGNING_KEY_PASSWORD` are set and
|
||||
`android-plugin/keystore.jks` exists - an unsigned build still "succeeds" but
|
||||
installs fail later with `INSTALL_PARSE_FAILED_NO_CERTIFICATES`. If the
|
||||
device or emulator has a trace package from a different keystore, uninstall
|
||||
`top.mobilegl.plugin.espryt.trace` / `top.mobilegl.plugin.magma.trace` first
|
||||
or the install fails with `INSTALL_FAILED_UPDATE_INCOMPATIBLE`.
|
||||
|
||||
Match the CI environment (`.github/workflows/apk.yml` matrix): the emulator
|
||||
boots with `--gpu software` + `MOBILEGL_RETRACE_USE_ANGLE=1` for `DirectGLES`
|
||||
and `--gpu lavapipe` + `MOBILEGL_VULKAN_R11G11B10F_FALLBACK=1` for
|
||||
`DirectVulkan`. The emulator's ANGLE-on-Vulkan GLES stack exercises genuinely
|
||||
different driver semantics than physical devices (e.g. indirect-draw
|
||||
`gl_InstanceID` handling), so treat AVD-only image mismatches as real signal,
|
||||
not emulator noise.
|
||||
|
||||
Known emulator flake: the FIRST DirectVulkan replay after a fresh lavapipe
|
||||
AVD boot segfaults intermittently (~50%), for any trace; subsequent runs in
|
||||
the same boot are stable. Burn a warm-up replay and discard its result before
|
||||
the measured runs.
|
||||
|
||||
Result directories keep `result.json` from previous runs - delete the case's
|
||||
result directory before each run, or an earlier failure/success can masquerade
|
||||
as the current one (identical-to-the-last-digit ssim across "different" runs
|
||||
is the tell).
|
||||
|
||||
Run DirectGLES:
|
||||
|
||||
```sh
|
||||
@@ -331,10 +432,14 @@ Inspect:
|
||||
|
||||
## Checklist
|
||||
|
||||
- Golden content is verified against a live-run reference (subject present,
|
||||
correct shapes, intended camera framing).
|
||||
- Golden matches the committed trace and target call.
|
||||
- Archive contains only `trace.trace`.
|
||||
- Both Linux backends pass before Linux CI registration.
|
||||
- Both Android backends pass before APK CI registration.
|
||||
- Archive contains only `trace.trace`, and that file is the brotli repack of
|
||||
the CURRENT trim.
|
||||
- Both Linux backends pass before registration in `trace_cases.json`.
|
||||
- Both Android backends pass on a CI-equivalent AVD (software/ANGLE +
|
||||
lavapipe) before relying on APK CI.
|
||||
- `actual.png` and `case-diff.png` are inspected.
|
||||
- Fixture `.tgz` and `.png` files are tracked by Git LFS.
|
||||
- No build output, extracted trace directory, temporary report, or debug text is
|
||||
|
||||
Reference in New Issue
Block a user