mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 04:04:09 +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-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).
|