Files

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), xrLocateViews with 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 Controls window.
  • 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_SDK on PATH)

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 from PATH. 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 to openxr_simulator.dll. Inspect dependencies with objdump -p openxr_simulator.dll | findstr "DLL Name".
  • library_path must contain a path separator. A bare openxr_simulator.dll is resolved from the global library search path, not the manifest directory; the generated manifest uses ./openxr_simulator.dll for this reason.