Files

139 lines
5.8 KiB
Markdown

# 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/<preset>`):
```powershell
$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:
```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\<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.