Files
daniel-lynch--ovrplugin-ope…/docs/handoffs/HANDOFF-2026-06-26.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

75 lines
4.8 KiB
Markdown

# RE4 VR Shim — Handoff (2026-06-26 session)
Entry point for next session. Prior: `HANDOFF-2026-06-25.md`. Detailed findings in the
auto-memory (`MEMORY.md` index). Commits this session: `68a7093`, `5fa59f4` (branch
`latency-perffix-copyring`).
## TL;DR — the in-game black is a UE frame-drop, and we proved it
After a long, thorough investigation, the in-game black is **conclusively a UE-internal
frame-drop under load**, NOT a shim bug:
- RenderDoc (`~/renderdoc-captures/RE4/work_frame.rdc`): under load UE renders only
**~28 draws into its eye target vs ~198 in a normal frame** (a truncated frame), and the
**resolved/presented eye = pure black** (MEAN [0,0,0]).
- **It's draw / render-thread-time bound, NOT pixel-bound:** even quarter-res
(`resscale=50`) + max FFR (`ffr=3`) still blacks. Resolution doesn't change draw count.
- **Ruled out, decisively:** submit-timing (built a vkQueueSubmit hook + present-on-submit;
even a full one-frame defer still blacks), image-targeting (LAYER MISMATCH = 0,
stage==acquiredIndex), empty-frame submission (SUBMIT-BLACK/COMPOSE-EMPTY = 0),
load-reduction (FFR + quarter-res).
- Load-gated: bridge (sparse) never blacks; house/dense geometry blacks; worse while casting.
## NEXT TASK — Lever 2: detect the bad frame + reproject
Full design in `analysis/lever2-detect-skip-black-handoff.md`. Idea: when UE hands us a
truncated/black frame, DON'T present it — re-present the last-good eye image with the
current pose so the OpenXR compositor reprojects (timewarp) instead of showing black.
- **Bad-frame signal:** start with the **post-hitch heuristic** (the truncated frame is the
recovery frame after a `dt>20ms` hitch we already detect). The vkQueueSubmit hook's
submit-count probably WON'T distinguish bad frames (it's draw-bound; UE may batch).
- **Re-present:** reuse the copy-ring infra (`xrr_vk_alloc_images`, `shimImages[]`, the
copy path in end_frame) — hold a copy of the last-good eye image, blit it into the
acquired image on a bad frame. Avoids the cross-frame-hold instability that sank
`pipeline=1`.
- **Discipline:** implement the detector as **instrument-only first** (log "likely
truncated frame" after each hitch), validate the signal correlates with perceived black,
THEN build the reproject. Gate behind `debug.re4vr.skipblack` (default 0).
## What was built this session (all committed, gated off by default)
- **Dynamic-perf bridge** (`68a7093`): `ovrp_*TiledMultiRes*` (FFR) + `GetGPUFrameTime` +
`IsPerfMetricsSupported`/`GetPerfMetrics{Float,Int}` forwarded to OpenXR. Re-enables RE4's
perf APIs (were Unsupported). The game accepts them but does NOT self-scale from the feed.
Fixed a NULL-foveation-profile runtime crash (use `XR_FOVEATION_LEVEL_NONE_FB`).
- **UE vkQueueSubmit hook** (`5fa59f4`): dlsym-patch UE's exported global
`VulkanDynamicAPI::vkQueueSubmit` -> trampoline -> `xrr_on_ue_submit`. Robust + reusable
(e.g. for Lever 2 instrumentation). Gated: `debug.re4vr.submithook` 0/1/2.
- **resscale** (`5fa59f4`): `debug.re4vr.resscale` = % of eye size. Doesn't fix the black;
keep as a load knob.
## Settled side-findings (don't re-investigate)
- **Title ghost = cold-load reprojection judder** (cold-launch only; self-resolves warm;
single eye-fov layer, no quad). See memory `title-ghost-is-coldload-reprojection-judder`.
- **Bridge "vignette" = the GAME's comfort vignette** (user toggled it off; not a shim bug).
- **An in-game "crash" was an OOM SIGKILL** (lmkd), worsened by in-game recording (which is
broken — empty mp4). Capture via Meta Cast -> desktop record instead (scrcpy/screenrecord
can't see VR: display 0 is FLAG_SECURE). RenderDoc recipe: memory `renderdoc-remote-replay`.
## Build / deploy / config
```
./shim/build_android.sh && ./packaging/repack.sh
adb -s <redacted-serial> install -r packaging/out/re4vr-shim.apk
adb -s <redacted-serial> logcat | grep -i xrr
```
WARNING: `repack.sh` silently bundles the LAST successful build — confirm the build had no
errors + check `shim/build/arm64/libOVRPlugin.so` timestamp before repack.
Device Quest 2, USB serial `<redacted-serial>`. Pkg `com.Armature.VR4`, activity
`com.epicgames.ue4.GameActivity` (launching needs controllers awake, else a system dialog
blocks `am start`).
Props reset to baseline: ffr=2, resscale=100, sscap=0, submithook=0, defern=3, trace=0
(none fix the black — all cosmetic/diagnostic now).
## Key runtime toggles (system props; relaunch unless noted)
- `debug.re4vr.submithook 0|1|2` — UE submit hook: 0 off, 1 instrument, 2 present-on-submit.
- `debug.re4vr.defern N` — present-on-submit: present on Nth render submit (default 3).
- `debug.re4vr.resscale N` — eye buffer = N% of native (default 100).
- `debug.re4vr.ffr 0..3`, `debug.re4vr.sscap 1`, `debug.re4vr.trace 1|2` — FFR / supersample
cap / frame+hitch trace. `debug.re4vr.pipeline 1` (deferred-flush, unstable, shelved).