# 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`](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 ```text 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](https://vulkan.lunarg.com/sdk/home) (`VULKAN_SDK` on `PATH`) ## Build ```powershell 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`](config/openxr_simulator_config.json). Editable via the configurator (built alongside the runtime): ```powershell .\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/`): ```powershell $env:XR_RUNTIME_JSON = "C:\Users\\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: ```powershell New-Item -Path "HKLM:\SOFTWARE\Khronos\OpenXR\1" -Force | Out-Null Set-ItemProperty -Path "HKLM:\SOFTWARE\Khronos\OpenXR\1" -Name "ActiveRuntime" ` -Value "C:\Users\\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.