---
name: mismatch-retrace-debugging
description: Localize the first divergent render pass and draw call when a MobileGL apitrace fixture replays correctly in a golden environment but renders differently under mobilegl_trace_replay, Android trace replay, or another backend. Use to binary-search pass/draw endpoints, diff GL state around the first bad call, and classify the fault as a vertex/VS, fragment/FS, or framebuffer/composition mismatch.
---

# Mismatch retrace debugging

Use this when an apitrace fixture replays correctly on one environment but
`mobilegl_trace_replay`, Android trace replay, or another backend produces a
different image.

## Goal

Find the first call where the replayed output diverges from a trusted golden
environment, then reduce the problem to one of:

- vertex input, index, transform, or VS output mismatch
- fragment shader, uniform, texture, sampler, or buffer input mismatch
- framebuffer, blend, depth/stencil, scissor, color mask, sRGB, or format
  mismatch
- missing/incorrect resource binding or unsupported backend format/path

Do not start by guessing from the final image. First identify the earliest
divergent render pass and draw call.

## Variables

```sh
export REPO="$PWD"
export WORK="$PWD/.trace-debug"
export CASE="case-name"
export TRACE="$WORK/$CASE/trace.trace"
export APITRACE="$REPO/build-apitrace/apitrace"
export GOLDEN_GLRETRACE="/path/to/golden/glretrace"
export TARGET_GLRETRACE="/path/to/target/glretrace"
export MOBILEGL_REPLAY="$REPO/build-linux/tools/trace_replay/mobilegl_trace_replay"
export MOBILEGL_LIB="$REPO/build-linux/libMobileGL.so"
export WIDTH=854
export HEIGHT=480
export TARGET_CALL=0
export BACKEND=DirectGLES
```

The golden and target environments must use the same trace, surface size, target
call numbers, and comparison crop. They do not need to report the same GL vendor,
renderer, version, or extension set. The only requirement for the golden
environment is that it can replay the case and pass the fixture's golden image
test. Disable overlays and use a strict SSIM threshold while localizing the
first divergent call unless the known golden already has small nondeterministic
noise.

## Prepare two replay environments

1. Golden environment: a known-good driver/backend that can replay the trace,
   produce snapshots, and pass the case against the committed or otherwise
   accepted golden PNG. Prefer upstream `glretrace` on desktop GL/Mesa when it
   is the simplest environment that satisfies that test.
2. Target environment: the backend being troubleshot. This can be another
   `glretrace` environment, `mobilegl_trace_replay` on Linux, or the Android
   trace APK.

First prove the golden environment is acceptable by comparing its target-call
snapshot against the accepted golden PNG:

```sh
mkdir -p "$WORK/$CASE"
"$GOLDEN_GLRETRACE" --headless -S "$TARGET_CALL" -s "$WORK/$CASE/golden." \
  --call-nos "$TRACE" > "$WORK/$CASE/golden-retrace.log" 2>&1
```

If the generated snapshot does not pass the fixture golden comparison, choose a
different golden environment before doing mismatch localization.

Then run the target with that accepted golden snapshot or with the fixture's
committed golden PNG:

```sh
mkdir -p "$WORK/$CASE"

"$MOBILEGL_REPLAY" \
  --trace "$TRACE" \
  --golden "$WORK/$CASE/golden.$(printf '%010d' "$TARGET_CALL").png" \
  --output "$WORK/$CASE/target-id" \
  --backend "$BACKEND" \
  --mobilegl-library "$MOBILEGL_LIB" \
  --target-call "$TARGET_CALL" \
  --width "$WIDTH" \
  --height "$HEIGHT" \
  --ssim-threshold 1.0 > "$WORK/$CASE/target-retrace.log" 2>&1 || true
```

Keep target GL identity lines such as `MOBILEGL_TRACE_GL_VENDOR`,
`MOBILEGL_TRACE_GL_RENDERER`, `MOBILEGL_TRACE_GL_VERSION`, and
`MOBILEGL_TRACE_GL_SHADING_LANGUAGE_VERSION` as diagnostic context only. Do not
reject a golden environment because its GL strings differ from the target.

For Android, use `android-plugin/trace-replay-ci.sh` with the same trace,
golden, target call, width, height, crop, and SSIM threshold values. Pull
`result.json`, `actual.png`, `diff.png`, `retrace.log`, and `logcat.txt` before
continuing.

## Build a call map

Dump a narrow call listing around the failing frame or target call:

```sh
"$APITRACE" dump --calls="$START_CALL-$END_CALL" "$TRACE" \
  > "$WORK/$CASE/calls-$START_CALL-$END_CALL.txt"
```

Mark:

- FBO/render-target changes: `glBindFramebuffer`, `glFramebuffer*`,
  `glDrawBuffer*`, `glReadBuffer`, `glViewport`, `glScissor`
- pass-producing calls: `glClear*`, `glDraw*`, `glMultiDraw*`,
  `glDispatchCompute`, `glBlitFramebuffer`, `glCopyTex*`,
  `glGenerateMipmap`
- sync/resource hazards: `glMemoryBarrier`, `glFenceSync`, `glWaitSync`,
  `glFlush`, `glFinish`
- texture/buffer mutation before the pass: `glTex(Sub)Image*`,
  `glCompressedTex(Sub)Image*`, `glBuffer(Sub)Data`, `glMapBuffer*`,
  `glUnmapBuffer`

Treat a render pass as a contiguous run of producer calls that writes the same
draw framebuffer attachments with the same logical viewport/scissor region,
ending just before the next framebuffer/attachment change, resolve/blit,
mipmap generation, or swap.

## Find the first divergent render pass

Use snapshots at pass endpoints rather than every draw call first. For regular
`glretrace`, snapshot call numbers are explicit:

```sh
mkdir -p "$WORK/$CASE/pass"
CALLS="1000,1250,1430,1700"
"$GOLDEN_GLRETRACE" --headless --call-nos \
  --snapshot-prefix "$WORK/$CASE/pass/golden." \
  --snapshot "$CALLS" "$TRACE"
"$TARGET_GLRETRACE" --headless --call-nos \
  --snapshot-prefix "$WORK/$CASE/pass/target." \
  --snapshot "$CALLS" "$TRACE"
```

If the target is MobileGL, run `mobilegl_trace_replay` once per endpoint:

```sh
for call in 1000 1250 1430 1700; do
  "$MOBILEGL_REPLAY" \
    --trace "$TRACE" \
    --golden "$WORK/$CASE/pass/golden.$(printf '%010d' "$call").png" \
    --diff "$WORK/$CASE/pass/mobilegl.$(printf '%010d' "$call").diff.png" \
    --output "$WORK/$CASE/pass/mobilegl-$call" \
    --backend "$BACKEND" \
    --mobilegl-library "$MOBILEGL_LIB" \
    --target-call "$call" \
    --width "$WIDTH" \
    --height "$HEIGHT" \
    --ssim-threshold 1.0 || true
done
```

Binary search the pass endpoints:

- If endpoint N matches golden, every completed pass up to N is probably OK.
- If endpoint N differs, search earlier pass endpoints.
- Stop when the previous pass endpoint matches and this pass endpoint differs.

For offscreen passes, do not force backbuffer snapshots. The default retrace
snapshot reads the current draw buffer, which is usually what is needed for FBO
attachment debugging. Use `--snapshot-force-backbuffer` only when explicitly
checking the final default framebuffer.

## Find the first divergent draw call inside the pass

List producer calls inside the failing pass and binary search those call
numbers. Include clears, blits, compute dispatches, and mipmap generation; a
non-draw producer can be the first bad write.

```sh
mkdir -p "$WORK/$CASE/draw"
DRAWS="1430,1442,1459,1490,1512,1550"
"$GOLDEN_GLRETRACE" --headless --call-nos \
  --snapshot-prefix "$WORK/$CASE/draw/golden." \
  --snapshot "$DRAWS" "$TRACE"
```

Compare the target at the same call numbers. The first bad producer is the
lowest call where:

- previous producer output matches golden
- current producer output differs from golden

Keep the previous-good call and first-bad call. Most state that matters to the
bad call was established between those two calls or immediately before the
previous-good call.

## Dump and diff GL state

Use apitrace state dumps around the boundary:

```sh
"$GOLDEN_GLRETRACE" --headless --dump-state "$PREVIOUS_GOOD_CALL" "$TRACE" \
  > "$WORK/$CASE/state-golden-prev.json"
"$GOLDEN_GLRETRACE" --headless --dump-state "$FIRST_BAD_CALL" "$TRACE" \
  > "$WORK/$CASE/state-golden-bad.json"
"$TARGET_GLRETRACE" --headless --dump-state "$FIRST_BAD_CALL" "$TRACE" \
  > "$WORK/$CASE/state-target-bad.json"
```

If both environments are standard `glretrace`, use apitrace's helper to compare
snapshots and optionally state:

```sh
python3 "$REPO/3rdparty/apitrace/scripts/retracediff.py" \
  --retrace "$GOLDEN_GLRETRACE" \
  --ref-env NAME=VALUE \
  --src-env NAME=VALUE \
  --diff-prefix "$WORK/$CASE/retracediff" \
  --snapshot-frequency "$DRAWS" \
  --diff-state \
  "$TRACE"
```

For MobileGL target state, inspect MobileGL logs and add temporary focused
logging in the relevant state sync path. Keep logs scoped to the bad call range
using retrace-side call-number markers rather than backend-specific snapshot
hooks.

## Classify the bad call

Start from the bad call's command and bound pipeline. Use the call dump,
`--dump-state`, shader sources, and MobileGL backend logs.

### Vertex and VS stage

Check:

- current program, vertex shader source, translated shader source, compile/link
  log, and reflected attribute locations
- VAO binding and every enabled attribute: size, type, normalized/integer flag,
  stride, relative offset, divisor, binding index, and bound VBO
- index buffer binding, index type, byte offset, base vertex, draw count, and
  primitive mode
- UBO/SSBO bindings consumed by the VS, including offset alignment and range
  size
- transform-relevant uniforms and uniform block contents
- primitive restart, provoking vertex, clip/cull distance, front-face, cull
  mode, depth clamp, and viewport transform

Common MobileGL failures here are attribute location remapping, integer vs float
attribute path mixups, stale VAO/EBO binding, base-vertex handling, wrong buffer
range offset, and translated VS precision/type differences.

### Fragment and FS stage

Check:

- fragment shader source, translated shader source, compile/link log, reflected
  sampler/image/uniform bindings, and output locations
- all textures sampled by the FS: target, level, layer, internal format,
  dimensions, swizzle, base/max level, compare mode/function, border/wrap/filter,
  and whether the backend allocated a supported equivalent format
- sampler object binding versus texture object sampler state
- plain uniforms, UBO/SSBO ranges, image bindings, atomic counters, and texture
  buffer bindings
- discard/alpha-test-equivalent logic, precision-sensitive math, NaN/min/max
  behavior, shadow compare, and integer/unsigned sampling

Common MobileGL failures here are stale sampler binding, texture unit remap
errors, depth-compare defaults, unsupported or substituted texture formats,
incorrect swizzle, missing DSA texture parameter sync, and shader translation
differences around precision or undefined values.

### Framebuffer and composition stage

Check:

- draw framebuffer object, every color/depth/stencil attachment, attachment
  level/layer, backend object id, internal format, sample count, and dimensions
- draw buffer/read buffer mapping, MRT compacting/remapping, and default-FBO
  handling
- viewport, scissor, depth range, color mask, depth mask, stencil masks,
  clear values, and per-target blend enable
- blend equation/function/color, logic op, dither, sRGB enable, multisample
  state, sample mask, alpha-to-coverage, depth/stencil test and ops
- resolve/blit path, invalidate/discard behavior, mipmap generation, and
  readback orientation/channel order

Common MobileGL failures here are stale FBO sync, draw/read framebuffer
confusion, wrong MRT attachment order, missing per-target blend state, format
substitution, sRGB mismatch, and backend readback from the wrong buffer.

## Resource validity checks

Before changing code, rule out invalid trace/backend combinations:

- Does the target backend advertise every extension used by the trace path?
- Is the internal format renderable/filterable/sampleable on the target GLES or
  Vulkan path?
- Is the attachment complete under target rules, especially depth/stencil,
  integer color, multisample, layered, and texture-buffer cases?
- Are buffer ranges aligned to the target backend's UBO/SSBO/TBO requirements?
- Are compressed textures, texture views, immutable storage, and DSA calls fully
  implemented in the target path?
- Is any GL error logged at or before the first bad call? The first GL error is
  usually more useful than the final pixel diff.

## Confirm the root cause

Make one narrow diagnostic change at a time. Record it in `attempts.txt` when
the case is already tracked there:

- what was changed
- exact case/backend/device
- first bad pass/call before and after
- final `mismatchPixels`
- whether the change moved, fixed, or did not affect the divergence

A real fix should make the first-bad call match, not just reduce the final
mismatch. After fixing, rerun:

```sh
ctest --test-dir "$REPO/build-linux/tools/trace_replay" -V \
  -R "MobileGLTraceReplay\\.$CASE\\.$BACKEND"
```

For Android-only bugs, rerun `android-plugin/trace-replay-ci.sh` and keep
`result.json`, `actual.png`, `diff.png`, `retrace.log`, and `logcat.txt` as the
evidence bundle.
