mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 03:00:05 +02:00
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
75 lines
4.8 KiB
Markdown
75 lines
4.8 KiB
Markdown
# 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 <redacted-serial>, or wireless <redacted-ip>: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.
|
||
|
||
## Recommended next steps
|
||
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).
|