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
Desktop harness — drive the shim against Monado on a PC
The shim is normally exercised only on a Quest (inside RE4's APK). This harness lets you
run its OpenXR path on a Linux desktop, headless, against
Monado's simulated HMD — no headset, no game, no
libUE4. It's the fast iteration loop for the Steam Frame / Monado / Lepton bring-up.
What it is
harness.c stands in for the game (UE4 + OculusHMD). It creates a Vulkan
instance/device the way UE's VulkanRHI does, then calls our ovrp_* exports in UE's
order:
PreInitialize3 -> Get{Instance,Device}ExtensionsVk -> Initialize5
-> CalculateEyeLayerDesc2 -> SetupLayer (once)
-> per frame: Update3, WaitToBeginFrame, BeginFrame4, GetLayerTexture2,
(clear the eye image), EndFrame4
-> Shutdown2
The shim does the real OpenXR work underneath — xrCreateInstance, xrGetSystem,
xrCreateSession (Vulkan binding), xrCreateSwapchain, the xrWaitFrame/Begin/EndFrame
loop — against whatever runtime the OpenXR loader selects. Here that's Monado's simulated
HMD with the NULL compositor (renders nowhere), so it runs over SSH / in CI.
It ships nothing from Capcom/Epic/Meta — it only calls our own public ovrp_* ABI.
Prereqs (Debian/Ubuntu)
sudo apt-get install monado-service libopenxr1-monado libopenxr-loader1 libopenxr-dev \
libvulkan-dev
Build & run
shim/build_host.sh # builds build/host/libOVRPlugin.so + build/host/harness
tools/desktop-harness/run.sh # brings up monado-service headless, runs 300 frames
tools/desktop-harness/run.sh 1000 # custom frame count
Logs land in build/host/monado.log and build/host/harness.log.
The scene (pose→view validation)
Each frame the harness queries the shim's per-eye pose (ovrp_GetNodePoseState3 for
EyeLeft/EyeRight, with true IPD separation) and FOV, builds per-pixel world rays, and
renders a world-locked procedural scene — checkerboard floor 1.6 m below the eye, sky
gradient, and an orbiting sun — into the acquired eye image. This exercises the shim's
pose/FOV math: the two eyes show correct stereo parallax, and the world counter-moves as the
head pose changes (Monado's simulated HMD sways, so there's real motion). The first few
frames log per-eye pose + FOV. CPU-rendered (fine at the sim's 128×128; it's a test tool, not
a fast path).
Watch it (windowed)
VISIBLE=1 uses Monado's main compositor (mirror window) + the imgui debug GUI instead of
the NULL compositor, so you can watch the scene (floor grid, horizon, orbiting sun, stereo
parallax) and inspect swapchains. Needs a display — run it from the physical desktop session,
not over SSH, and give it a big frame count:
VISIBLE=1 tools/desktop-harness/run.sh 3600
What "pass" looks like
harness.log should show the lifecycle succeed and frames present:
[harness] PreInitialize3 OK (XrInstance + system up)
[harness] VkInstance created
[harness] VkDevice + graphics queue (family 0) created
[harness] Initialize5 OK (XrSession created)
[harness] SetupLayer OK layerId=0 swapchainStages=3
[harness] ...
[harness] loop done: NNN/NNN frames presented
[harness] Shutdown2 OK — clean exit
Exit code 0 = frames presented; 2 = ran but presented nothing (session never reached the
running state — check monado.log); 1 = a hard failure.
Limits / notes
- This validates the OpenXR + Vulkan binding + frame loop + swapchain path. It does not reproduce the game-thread/render-thread pacing or GPU load that drove the on-Quest "ghost"; those are device-side behaviours. It's for ABI/path correctness and porting to new runtimes, not perf tuning.
- The Android session path (
XR_KHR_android_create_instance,xrInitializeLoaderKHR, the JavaVM/Activity chain) is#ifdef __ANDROID__-guarded inxr_runtime.c/android_init.c, so the same sources serve both targets. passthru.c(P4 native forwarding) is Android-only; on host its arm64 trampolines fall back to plain stubs and passthru stays inactive.