5.8 KiB
OpenXR Simulator
A Windows OpenXR runtime that pretends to be a headset. OpenXR applications
run unmodified and render into runtime-owned Vulkan swapchains; the runtime
displays those swapchain images in a desktop window (GLFW + Dear ImGui docking).
Primary target is the gl3-vulkan / "Get Beaned" engine.
See docs/DESIGN.md for the full architecture and roadmap.
Status
Implemented:
- Loader negotiation, dispatch table, instance/system/view-config metadata.
- Vulkan instance/device creation on behalf of the app (augmenting the required surface/swapchain extensions).
- Session lifecycle + session-state events, multiview swapchains, frame loop
(
xrWaitFrame/xrBeginFrame/xrEndFrame),xrLocateViewswith mouse/WASD tracking. - Action API with emulated controllers: advertises an Oculus Touch profile
and maps keyboard/mouse to trigger, grip, thumbstick, buttons, menu, and
aim/grip poses. The mapping is shown in the ImGui
Controlswindow. - GLFW + Dear ImGui (docking) compositor showing each eye in a dockable window.
Set XRSIM_NO_WINDOW=1 to run headless (no window, no eye display).
Remaining: Phase 5 polish (session-state edges, haptics, settings UI).
Layout
CMakeLists.txt Build the runtime DLL + configurator.
CMakePresets.json Configure/build presets.
config/openxr_simulator_config.json Reference config.
docs/DESIGN.md Architecture and roadmap.
manifest/openxr_simulator.json.in Runtime manifest template.
src/Common.h Logging.
src/Config.h / Config.cpp Shared config (JSON), used by DLL + configurator.
src/Runtime.h / Runtime.cpp Runtime state + xr* entry points.
src/Negotiation.cpp Export + xrGetInstanceProcAddr dispatch.
src/Compositor.cpp GLFW + ImGui display window.
tools/configurator/ GLFW + ImGui config editor.
third_party/openxr/ Vendored OpenXR 1.0.28 headers + negotiation.
Requirements
- CMake 3.24+, Ninja
- MinGW-w64 GCC or MSVC
- Vulkan SDK (
VULKAN_SDKonPATH)
Build
cmake --preset windows-mingw-release
cmake --build --preset windows-mingw-release
Outputs out/build/windows-mingw-release/openxr_simulator.dll and
openxr_simulator.json side by side.
Configuration
The runtime reads a JSON config from XRSIM_CONFIG, or else
openxr_simulator_config.json next to openxr_simulator.dll. A reference file
lives in config/openxr_simulator_config.json.
Editable via the configurator (built alongside the runtime):
.\out\build\windows-msvc-release\openxr_simulator_configurator.exe
The configurator includes headset presets — Meta Quest 2 / 3 / 3S / Pro, Valve Index, Valve Steam Frame, HTC Vive / Vive Pro 2 / Focus 3, HP Reverb G2, Pico 4 / 4 Ultra / Neo 3, Varjo Aero / XR-4, Pimax Crystal, Bigscreen Beyond 2, PlayStation VR2, Samsung Galaxy XR, and a generic 1280 headset — or you can set values manually.
Settings:
| Key | Meaning |
|---|---|
systemName, vendorId |
Reported by xrGetSystemProperties. |
eyeWidth, eyeHeight |
Per-eye recommended render resolution. |
refreshRateHz |
Virtual headset strobe rate used by xrWaitFrame. |
fovLeftDeg/Right/Up/Down |
Per-eye half-angle field of view. |
ipdMeters |
Default interpupillary distance. |
swapchainImageCount |
Number of images per XR swapchain. |
windowTitle, windowWidth, windowHeight |
Simulator window. |
presentMode |
mailbox/immediate (uncapped) or fifo (vsync). |
displayMode |
sideBySide, singleEyeLeft, singleEyeRight. |
Frame rate / 60 Hz cap
presentMode: "fifo" presents with vsync, which caps the whole app to the
monitor refresh (e.g. 60 Hz) because presentation happens on the app's thread.
The default "mailbox" (or "immediate") presents without vsync. refreshRateHz
controls the simulated headset strobe independently of presentation.
Testing against the engine
The engine already honours XR_RUNTIME_JSON, so no registry changes are needed.
From the engine build directory (gl3-vulkan/code/out/build/<preset>):
$env:XR_RUNTIME_JSON = "C:\Users\<you>\openxr-simulator\out\build\windows-mingw-release\openxr_simulator.json"
.\Game.exe
Expected in this phase: the loader loads the simulator, xrCreateInstance and
xrGetSystem succeed, and the engine creates its Vulkan instance/device through
the runtime. It will then fail on the first unimplemented call (swapchain), which
is the Phase 2 milestone.
Installing system-wide (optional, later)
Point the active runtime at the manifest:
New-Item -Path "HKLM:\SOFTWARE\Khronos\OpenXR\1" -Force | Out-Null
Set-ItemProperty -Path "HKLM:\SOFTWARE\Khronos\OpenXR\1" -Name "ActiveRuntime" `
-Value "C:\Users\<you>\openxr-simulator\out\build\windows-mingw-release\openxr_simulator.json"
Remember to restore the previous value (SteamVR/Oculus) when done.
Troubleshooting
- Loader logs
Failed to open dynamic library ... error 126(ERROR_MOD_NOT_FOUND) when launching from Explorer: the runtime DLL's dependencies are missing fromPATH. The MinGW build statically links the GCC runtime (-static-libgcc -static-libstdc++ -static) so this should not happen; if you build with another toolchain, either link the CRT statically or ship the required runtime DLLs next toopenxr_simulator.dll. Inspect dependencies withobjdump -p openxr_simulator.dll | findstr "DLL Name". library_pathmust contain a path separator. A bareopenxr_simulator.dllis resolved from the global library search path, not the manifest directory; the generated manifest uses./openxr_simulator.dllfor this reason.