Files
daniel-lynch--ovrplugin-ope…/docs/handoffs/HANDOFF-2026-06-24.md
T
Daniel LynchandClaude Opus 4.8 a72a79ad29 Initial public release: OVRPlugin→OpenXR interoperability shim
An independent reimplementation of Meta's libOVRPlugin ABI on top of OpenXR, so
VrApi-era Meta Quest VR titles can run on non-Meta OpenXR runtimes (Monado,
Steam Frame) instead of being locked to Meta hardware. Original code only — no
Meta/Epic/Capcom binaries, headers, or assets. Includes a desktop harness that
drives the shim against Monado headless.

Scope/legal: interoperability; entitlement handling is out of scope. See README
for the legal/scope section and docs/ for the research trail and design notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6sFYGXZPsq3v7xtcDES6g
2026-06-29 00:48:48 -04:00

4.8 KiB
Raw Blame History

RE4 VR Shim — Handoff (2026-06-24 session)

Entry point for the next session. Detailed investigation log: NEXT-SESSION-LATENCY.md.

TL;DR

  • The head-motion ghosting is frame drops caused by the synchronous tile-memory flush-wait serializing CPU↔GPU (proven: removing it on the title dropped frame time 13–36ms → ~3ms and killed the ghosting).
  • Shipped fix (always on): perf levels. We were no-op'ing the game's CPU/GPU level requests; now forwarded via XR_EXT_performance_settings. Confirmed on device: GPU clock 305→587MHz, CPU steady 2419MHz, framerate up. The default build is playable (synchronous path + perf fix).
  • Copy-ring (the real serialization fix) is built but blocked on an MSAA-resolve interaction in gameplay — see below. Default OFF (debug.re4vr.copyring).
  • Caveat — the title still ghosts INITIALLY in every build we tried. Default build: title ghosting reduced by the perf fix but not gone. Copy-ring on: title ghosts at first, then "looks good after a while" (settles, not clean from frame 1). Likely cause of the initial ghosting in both: the title's asset streaming / load hitches (the ~49ms CPU&GPU spikes in VrApi, Stale frames), which NONE of the fixes address. So: perf fix + copy-ring attack the steady-state flush-wait drops; the initial load-driven hitching is a separate, still-open cause.

Build / deploy

./shim/build_android.sh && ./packaging/repack.sh && adb install -r packaging/out/re4vr-shim.apk
adb logcat -d -s xrr        # shim logs

Device = Quest 2 (USB serial , or wireless :5555). Package com.Armature.VR4, activity com.epicgames.ue4.GameActivity.

Runtime debug toggles (system props; relaunch unless noted)

  • debug.re4vr.copyring 1 — shim copy-ring (eye-only; gameplay black, see below)
  • debug.re4vr.sscap 1 — cap supersample 1.2x→1.0x (free GPU/bandwidth)
  • debug.re4vr.diag 1|2|3 — 1=projection only(drop quads), 2=force mono, 3=2x zoom (live)
  • debug.re4vr.dump N — one-shot eye-texture readback to /sdcard/Android/data/com.Armature.VR4/files/
  • debug.re4vr.depth 1 — DEAD END (UE passes None; Meta ignores plain KHR depth)
  • debug.re4vr.pipeline 1 — DEAD END (render-ahead by holding OpenXR images; breaks UE stage lockstep)
  • debug.re4vr.noflushwait 1 — perf probe: skip flush-wait (renders black; FPS recovers → confirms serialization)

The open problem: copy-ring gameplay = MSAA resolve into our shim

RenderDoc (offline replay over USB) showed:

  • UE renders the eye with 2× MSAA into its own target, render pass resolve attachment = our copy-ring shim (the image we copy to OpenXR). No stage mismatch.
  • On the title that resolve lands → copy-ring works. In gameplay the resolved shim isn't the scene at our copy (white/garbage) → black/flashing.
  • So: UE's MSAA resolve into our externally-created shim VkImage works on the title but not in gameplay. Next suspects: the shim's MUTABLE_FORMAT/sRGB-vs-UNORM resolve view, create flags vs a valid resolve-dst, or Adreno tile-MSAA specifics. Single-shim made it worse (UE needs distinct per-stage images for TAA/buffering).
  • The fundamental tension: removing the flush-wait needs pipelining, which breaks UE's 1:1 stage↔acquire lockstep (the synchronous path relies on it). Copy-ring decouples via shim images but inherits the MSAA-resolve issue.

RenderDoc offline-replay pipeline (set up this session — no headset needed to analyze)

  • App must be debuggable: tools/apktool.jar decode → add android:debuggable="true" → rebuild → sign (debug.keystore) → packaging/out/re4vr-dbg.apk. (RE-uses the shim.)
  • RenderDoc server installed (org.renderdoc.renderdoccmd.arm64). Capture over USB (wireless adb drops the replay; local desktop replay of an Android capture fails).
  • Headless analysis: qrenderdoc --python script.py with adb://<usb-serial> → CreateRemoteServerConnection → CopyCaptureToRemote → remote.OpenCapture. Scripts + captures in ~/renderdoc-captures/ (rd_*.py, RE4/black.rdc = gameplay, RE4/title.rdc). Use action.outputs for RT attribution and SaveTexture (NOT GetMinMax/Typeless — it lies) for content.
  1. Chase the MSAA-resolve-into-shim failure (RenderDoc pipeline is ready): compare the title resolve (works) vs gameplay (fails) render-pass setup; try shim WITHOUT MUTABLE_FORMAT, or matching UE's exact resolve-target image create info.
  2. If copy-ring stays blocked, the perf fix is the shipped win; consider lighter levers (sscap) and accept residual menu-screen ghosting.

Notes

  • Diagnostic logging is still in the shim (rate-limited; strip before public release).
  • Depth format mapping was corrected (None=10; D16=6/D24_S8=7/D32_FP=8/D32_S824=9).