mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 01:00:05 +02:00
Repo hygiene round following a full review. No shim behaviour changes. Leak risk: - .gitignore: ignore CLAUDE.md (personal assistant-lane config, was one `git add -A` away from a public commit) and scratch_obj/. Docs vs. reality: - shim/README.md: rewritten. It described a pre-implementation skeleton with "core fns are TODO stubs returning -1005", three mutually inconsistent stub counts, and four completed milestones listed as open. Now carries the verified breakdown: 438/438 exports = 371 generated stubs + 46 core + 7 layers + 2 Vulkan queries + 12 passthru trampolines. - TESTING.md: dropped the self-contradicting "NOT yet" block (5 of 6 items were done or misstated, and contradicted the same file 45 lines above). Path B now points at tools/desktop-harness, which exists, instead of the orphaned shim/tests/harness.c. Path A prereqs marked as the record they are. - HOST.md: corrected the runtime assumption. The OpenXR runtime inside Lepton is SteamVR (vendor/etc/openxr/1/active_runtime.json -> vrclient.so), not Monado. Favourable: SteamVR emulates Oculus Touch by default and advertises the XR_FB_foveation family, so the existing input and foveation paths should carry over. The old "remaining unknowns" are resolved by Lepton's published source and replaced with the items to check before a first Frame boot. - README.md: same runtime correction. - docs/research/RECON.md: the four passages prescribing an entitlement NOP/stub/bypass are corrected in place rather than merely disclaimed by the top banner, which they contradicted. Build correctness: - shim/build_android.sh: missing patchelf is now fatal. It warned and exited 0, producing a .so that cannot resolve the OpenXR loader at runtime. - scripts/fetch_deps.sh + packaging/build_openxr_loader.sh: pin the OpenXR and Vulkan header versions (were tracking `main`), overridable via OPENXR_TAG / VULKAN_HEADERS_TAG; require cmake for the loader build. - packaging/steamframe_patches.sh: use the apktool.jar that fetch_deps.sh downloads. Its prereq check demanded an `apktool` binary on PATH that the documented setup never provides, so it could not run after a clean setup. - shim/gen_stubs.sh: it reads all_exports.txt, not shim_surface.txt; comment and emitted banner corrected. stubs.c regenerated (banner line only). - shim/src/core.c: split seven `if (out) ...; return ...;` one-liners. Host build now compiles with zero warnings, down from seven. Verified: host build 0 warnings; gen_stubs.sh output identical on regeneration; bash -n clean on all edited scripts; pinned header/tarball URLs return 200 and the tag tarball extracts to the expected directory name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
157 lines
8.8 KiB
Markdown
157 lines
8.8 KiB
Markdown
# Testing the shim — strategy & plan
|
|
|
|
We do NOT need Steam Frame to validate the hard part. The shim's whole job is
|
|
OVRPlugin -> OpenXR, and **the Quest 2 already runs an OpenXR runtime** (Meta's
|
|
Horizon OS runtime — the one that replaced VrApi). So the shim can be tested on
|
|
hardware we own, today.
|
|
|
|
## The three paths
|
|
|
|
| Path | Tests what | Available | Effort |
|
|
|------|-----------|-----------|--------|
|
|
| **A. Quest 2 + shim swap** | the real shim, real HW, real game, on Meta's OpenXR runtime | now | NDK arm64 build + Android instance handshake + manifest (real entitlement — you own it) |
|
|
| **B. Monado-sim harness** | the shim's OpenXR call logic, fast iteration | now | small C harness + Linux arm64 (VM on the Apple-Silicon MacBook) |
|
|
| **C. Steam Frame + Lepton** | the actual target (Monado under Lepton) | ~summer 2026 | everything |
|
|
|
|
Recommended order: **B for fast logic iteration, then A for the real proof.**
|
|
A is the thesis-validator; if RE4 renders on the Quest through our OpenXR shim
|
|
instead of libvrapi.so, the project is essentially proven.
|
|
|
|
---
|
|
|
|
## Path A — Quest 2 (the real test)
|
|
|
|
Idea: build the shim as an Android arm64 `.so`, drop it into the RE4 APK in place
|
|
of the real `libOVRPlugin.so`, sideload, run. Our shim calls the Quest's own
|
|
`libopenxr_loader` -> Meta's OpenXR runtime.
|
|
|
|
Prereqs (all done — kept as the record of what Path A needed):
|
|
1. **NDK arm64 build** of the shim — DONE. `shim/build_android.sh` -> NDK r27c ->
|
|
build/arm64/libOVRPlugin.so (aarch64, 438/438 drop-in, NEEDED libopenxr_loader).
|
|
2. **Instance handshake** — DONE. src/android_init.c: JNI_OnLoad captures the JavaVM;
|
|
xrr_pre_init calls xrInitializeLoaderKHR + enables XR_KHR_android_create_instance
|
|
+ chains XrInstanceCreateInfoAndroidKHR. Activity from Initialize5 arg4 with an
|
|
Application-context reflection fallback. [VERIFY-ON-HW] whether Meta's runtime
|
|
accepts the Application context vs requiring the real Activity, and the
|
|
PreInitialize3-creates-instance-before-activity ordering.
|
|
3. **Manifest** — no change needed in the end. RE4 VR is already a shipping Quest VR app
|
|
and declares `<uses-feature android:name="android.hardware.vr.headtracking">`;
|
|
`packaging/inspect_manifest.sh` reports any gaps.
|
|
4. **Entitlement** — on Quest you OWN RE4 and the Quest has the real Meta Horizon
|
|
platform service, so leave the ORIGINAL libovrplatformloader.so untouched and only
|
|
swap libOVRPlugin.so. Logged into the owning account, the real ovr_Entitlement check
|
|
passes legitimately ("you own it"). **CONFIRMED on device 2026-06-29:** a
|
|
debug-re-signed, legit-mode build (original libovrplatformloader.so, no stub) launches
|
|
into the game on a Quest 2 — re-signing does **not** break the entitlement check.
|
|
Entitlement handling on hardware with no Meta backend (e.g. Steam Frame) is out of
|
|
scope for this repo and is the user's responsibility.
|
|
|
|
Then:
|
|
```
|
|
# repack (you own the copy; patch-only distribution)
|
|
unzip base.apk -d apk/
|
|
cp shim/build/arm64/libOVRPlugin.so apk/lib/arm64-v8a/libOVRPlugin.so
|
|
# rebuild + zipalign + sign with your own debug key, then:
|
|
adb install -r re4vr-shim.apk # or push OBB + sideload
|
|
adb logcat | grep -iE 'xrr|OVRPlugin|openxr' # watch the [xrr] logs
|
|
```
|
|
`packaging/repack.sh` now does the repack, align and re-sign in one step; the manual
|
|
sequence above is kept only to show what it does.
|
|
|
|
First-run signal to look for: instance and session create succeeding in logcat, then the
|
|
frame loop spinning and the projection layer submitting. (Historically the rough edges on a
|
|
first run were depth, input and the swapchain index lockstep; input and rendering are now
|
|
working — see "Current state" below.)
|
|
|
|
## Path B — desktop OpenXR harness (fast iteration) — BUILT
|
|
|
|
`tools/desktop-harness/` drives the `ovrp_*` sequence against a real OpenXR runtime with no
|
|
headset and no RE4:
|
|
PreInitialize3 -> Initialize5 -> SetupLayer -> [WaitToBeginFrame -> BeginFrame4 ->
|
|
GetNodePoseState3 -> EndFrame4] xN -> Shutdown2
|
|
asserting each returns `ovrpSuccess`. Build and run:
|
|
```
|
|
shim/build_host.sh # -> build/host/{libOVRPlugin.so,harness}
|
|
tools/desktop-harness/run.sh # headless against monado-service
|
|
```
|
|
Needs libvulkan and an OpenXR loader (Debian: `libvulkan-dev libopenxr-loader1
|
|
libopenxr-dev`); without the loader `build_host.sh` compiles the objects and stops.
|
|
|
|
Worth adding when chasing a portability bug: run it with `VK_LAYER_KHRONOS_validation` and
|
|
the OpenXR core-validation API layer enabled. The harness currently passes without them, so
|
|
it does not yet catch usage-flag or queue-family mistakes that a strict runtime would reject.
|
|
|
|
Note: `shim/tests/harness.c` is the original superseded smoke test; its build line is stale
|
|
and no script references it.
|
|
|
|
## Path C — Steam Frame (the target)
|
|
|
|
Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid fork, now open
|
|
source) and the OpenXR runtime inside the container is **SteamVR** — Lepton's
|
|
`vendor/etc/openxr/1/active_runtime.json` names `steamvr` and points at a host-mounted
|
|
`vrclient.so`. (Earlier notes here and in `HOST.md` assumed Monado; that was wrong.)
|
|
|
|
What that buys us: SteamVR presents Frame controllers as emulating Oculus Touch by default,
|
|
so the shim's Touch bindings should bind; and it advertises the `XR_FB_foveation` family plus
|
|
`XR_META_foveation_eye_tracked`, so the existing foveation path survives. Lepton also mounts
|
|
the host's mesa/turnip/zink and gralloc into the container, which answers the old
|
|
`HOST.md` unknown about sharing swapchain images across the container boundary.
|
|
|
|
Steam Frame shipped 2026-09-14. Flow: NDK build -> `repack.sh` -> `steamframe_patches.sh`
|
|
(the Build spoof is required — Lepton reports `ro.product.manufacturer=Valve`) -> sideload
|
|
via adb into a Lepton container. Path A having worked makes this mostly packaging and
|
|
runtime-plumbing, but see `HOST.md` for the specific items to check first.
|
|
|
|
---
|
|
|
|
## Current state
|
|
|
|
**Path A is done and field-verified.** RE4 VR boots and is playable on a Quest 2 through the
|
|
shim on Meta's OpenXR runtime: stereo rendering, head and controller tracking, buttons,
|
|
grips, haptics, save loading. The frame-pacing "ghost" that dominated development is fixed
|
|
(game-thread pacing; see `docs/research/ghost-fix-2026-06-27.md`). Shipped and verified:
|
|
|
|
- Session lifecycle, event-driven state machine, Android instance handshake
|
|
(`src/android_init.c`).
|
|
- Frame loop with `xrWaitFrame` on the game thread and a FIFO frameState handoff to the
|
|
render thread.
|
|
- Poses via `xrLocateViews` and `xrLocateSpace`; `LOCAL_FLOOR` when the game asks for floor
|
|
level.
|
|
- Swapchains from `ovrpLayerDesc`, per-frame acquire/wait/release, real projection-layer
|
|
submit, layer z-order fix so splash quads composite above the eye layer.
|
|
- Input action sets for Touch controllers plus haptics (`src/xr_input.c`).
|
|
- Vulkan extension queries mapped to `xrGetVulkan*ExtensionsKHR` (`src/vk_session.c`).
|
|
- CPU/GPU perf levels forwarded via `XR_EXT_performance_settings`; game-driven foveation.
|
|
- 438/438 `ovrp_` symbols; around 48 OpenXR entry points.
|
|
|
|
**Open / known gaps:**
|
|
- Depth layer submission is built but gated off (`debug.re4vr.depth`); Meta's runtime accepts
|
|
but does not use plain KHR depth for reprojection.
|
|
- Swapchain index lockstep with UE's own `TextureStage` is assumed, not enforced — a
|
|
mismatch is logged but not corrected.
|
|
- Residual `XR_FRAME_DISCARDED` hiccups from the frameState ring dropping its oldest entry
|
|
under overflow instead of applying back-pressure.
|
|
- In-game black near load zones is a level-streaming / memory stall, mitigated rather than
|
|
fixed. See `docs/handoffs/HANDOFF-2026-06-27.md`.
|
|
- Several `debug.re4vr.*` paths are documented dead ends kept for reference.
|
|
|
|
## Next: Path C (Steam Frame)
|
|
|
|
Frame shipped 2026-09-14, so this is the live front. Highest-value items before a first boot
|
|
attempt, in order:
|
|
1. **Page alignment.** The shim's ELF segments and the repacked APK are 4 KB-aligned
|
|
(`zipalign -p 4`); Valve's Unreal docs reference a 16 KB page-alignment requirement. If
|
|
the Frame kernel uses 16 KB pages the library will not load. Check first: it is a
|
|
`-Wl,-z,max-page-size=16384` plus `zipalign -P 16` fix, but it would present as a
|
|
mystery launch failure.
|
|
2. **Swapchain usage flags.** `setup_layer` requests only COLOR_ATTACHMENT and SAMPLED.
|
|
Meta over-provisions mutable, broadly-usable images; a spec-following runtime gives you
|
|
exactly what you asked for, and UE 4.25 needs a mutable format for its linear UNORM view.
|
|
3. **Refresh rate.** 72 Hz is hardcoded in `ovrp_GetSystemDisplayFrequency2` and in the
|
|
frame-budget constant. Frame runs 72/90/120/144. Derive it from
|
|
`predictedDisplayPeriod`.
|
|
4. **Session events.** Only READY and STOPPING are handled, so focus loss and quit from the
|
|
Steam overlay never reach the game.
|
|
5. **The per-frame GPU flush-wait.** It exists because Meta's compositor does not sync
|
|
against our submit. A/B it on Frame with `debug.re4vr.noflushwait`.
|