A lightweight OpenXR runtime that enables VR applications to run in a desktop window for development and testing without requiring a physical VR headset. Supports D3D11, D3D12, Vulkan and OpenGL graphics backends.
- Multi-API Support - Supports D3D11, D3D12, Vulkan and OpenGL graphics backends
- Desktop VR Preview - Run VR applications in a resizable desktop window with side-by-side stereo view
- Live XR Performance - Title-bar FPS and frametime are measured from stereo
xrEndFramesubmissions, not window repaints; press F3 for rolling p50/p95 frametimes - Headset Emulation - Reproduces the measured per-eye FOV, panel resolution and IPD of ten popular headsets (Quest 2/3/Pro, Index, Vive Pro 2, Reverb G2, PS VR2, PICO 4, Bigscreen Beyond)
- Mouse & Keyboard Controls - Navigate the virtual space using standard input devices
- Proper sRGB Handling - Automatic gamma correction for accurate color reproduction
- Unity & Unreal Compatible - Tested with Unity's OpenXR plugin and Unreal Engine (via UEVR)
- Steam Overlay Compatible - D3D12 uses GDI-based rendering to avoid hook conflicts with Steam overlay
- Minimal Dependencies - Only requires Windows and a compatible GPU
- Easy Setup - Simple PowerShell scripts for registration/unregistration
- Windows 10/11 (64-bit)
- DirectX 11/12 or OpenGL compatible GPU
- Visual Studio 2022 (for building from source)
- CMake 3.20 or later (for building from source)
- Download the latest release from the Releases page
- Extract the archive to your preferred location
- Run PowerShell as Administrator
- Navigate to the
scriptsfolder - Run
.\register-runtime.ps1to set as active OpenXR runtime
cd C:\Path\To\OpenXR-Simulator\scripts
.\register-runtime.ps1Once registered, any OpenXR application will automatically use the simulator:
- Launch your VR application (e.g., Unity project with OpenXR)
- A desktop window will appear showing left/right eye views
- Use the following controls:
- Mouse: Look around (hold right-click)
- WASD: Move forward/backward/strafe
- Q/E: Move up/down
- Shift: Hold to move faster
- , / .: Slower/faster movement
- ESC: Release mouse capture
- F3: Show detailed XR p50/p95 frametime statistics in the title bar
To restore your previous OpenXR runtime:
cd C:\Path\To\OpenXR-Simulator\scripts
.\unregister-runtime.ps1git clone https://github.com/yourusername/OpenXR-Simulator.git
cd OpenXR-Simulatormkdir build
cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config ReleaseThe built runtime will be in build/bin/Release/openxr_simulator.dll
The simulator implements the OpenXR runtime interface, intercepting all OpenXR calls from applications:
- Instance & Session Management - Handles OpenXR instance creation and session lifecycle
- Swapchain Rendering - Creates swapchains for D3D11, D3D12, and OpenGL that applications render into
- View Composition - Blits stereo views to a desktop window (D3D11: DXGI swapchain, D3D12: GPU downscale to window size then readback, OpenGL: pixel buffer readback)
- Input Simulation - Converts mouse/keyboard input to head pose and controller data
- โ Core OpenXR 1.0 specification
- โ
D3D11 graphics binding (
XR_KHR_D3D11_enable) - โ
D3D12 graphics binding (
XR_KHR_D3D12_enable) - โ
Vulkan graphics binding (
XR_KHR_vulkan_enableandXR_KHR_vulkan_enable2) - โ
OpenGL graphics binding (
XR_KHR_opengl_enable) - โ
Win32 time conversion (
XR_KHR_win32_convert_performance_counter_time) - โ Multiple swapchain formats (sRGB, UNORM, HDR, typeless, depth)
- โ Mutable format swapchains (typeless backing for sRGB/non-sRGB views)
- โ Stereo rendering with configurable FOV
- โ Reference space tracking (LOCAL, STAGE, VIEW)
- โ Basic action system for input
- โ Screenshot capture (D3D11, D3D12, and OpenGL)
The compositor is D3D12 whatever the app binds. A Vulkan session's swapchain images are
D3D12 committed resources created SHARED, imported into the app's own VkDevice through
VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT, and handed back as VkImages โ so the
app renders in Vulkan and the preview, quad layers, screenshots and burst capture read the
same pixels as ID3D12Resources with no second code path. xrGetVulkanGraphicsDevice2KHR
returns the VkPhysicalDevice whose deviceLUID matches the DXGI adapter the compositor
runs on, which is what makes the shared-handle import legal.
xrEnumerateSwapchainFormats reports VkFormat values under a Vulkan session. Images are
handed over in VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL (depth:
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) and must be released in the same layout,
as the spec requires โ an app renders straight into an acquired image with no barrier of its
own, so the runtime owes it that layout and never changes it afterwards.
The app's VkQueue and the compositor's D3D12 queue are ordered by one shared ID3D12Fence
imported as a timeline VkSemaphore, driven by a single strictly increasing counter that
each side signals in turn. SIMXR_VK_NO_TIMELINE=1 falls back to CPU waits, which is correct
but serialises the frame โ useful when a driver's timeline import misbehaves.
Colour resources carry D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS (no DCC, so the bytes
Vulkan wrote are readable through a D3D12 SRV), depth uses a typeless DXGI format, and both
set D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT explicitly. Those three come from BetterVR's
own Vulkan/D3D12 bridge and are there for AMD.
- โ No hand tracking
- โ No haptic feedback
- โ No foveated rendering
- โ Limited to seated/standing experiences
Settings are changed from the menu bar and persist across restarts in
%LOCALAPPDATA%\OpenXR-Simulator\settings.json. Delete the file to go back to
defaults.
Pick a headset from FOV โ Headset Profile to preset the per-eye frustum,
physical panel shape, and a nominal IPD. The values are measured ones from the
HMD Geometry Database. Add a profile by
appending a row to ui::kHeadsetSpecs in
ui_enhancements.h; the enum, menu and settings keys
follow from the table.
Tools โ Render Resolution independently chooses the per-eye size reported to
xrEnumerateViewConfigurationViews. The default is 1280x1400 (Performance);
Headset Native uses the active profile's panel size, while the other presets
trade image quality for GPU cost. Restart the OpenXR application after changing
this setting so it recreates its color and depth swapchains.
For an exact custom size, set render_width and render_height in
%LOCALAPPDATA%\OpenXR-Simulator\settings.json while the application is closed.
Values are clamped to the runtime's 4096x4096 maximum; 0 for both selects the
active headset's native panel resolution.
Render resolution does not determine desktop-preview size. Zoom โ Fill Window
(or F) scales lower-resolution images up and supersampled images down to cover
the entire preview client area. Maximizing the window keeps it maximized instead
of forcing it back to the headset aspect ratio. The numbered zoom modes remain
available for pixel inspection.
WASD/QE move the head at 3 m/s by default, and holding Shift multiplies that
by 4. Both numbers are set from Tools โ Movement Speed: presets from 0.5 to
10 m/s, , and . to step off them, and a submenu for the Shift multiplier.
The preview window is a mirror of what the headset would show, and drawing it costs the app a little time on every frame it updates. Tools โ Mirror Rate caps how often that happens: 60 Hz by default, or 30/15 Hz, Every Frame, or Off to freeze the mirror entirely while the application keeps running normally. Lower is cheaper โ turning it off leaves the runtime costing essentially nothing per frame, which is worth doing while profiling the application itself. It applies to every backend, and a screenshot request always forces a fresh frame through regardless of the setting.
The preview remembers its client size. In Fill Window mode it accepts any window or monitor aspect and scales the image to every client pixel; manual zoom modes keep the headset-content aspect for predictable pixel inspection.
The preview window background can be customized:
const float clearColor[4] = {0.1f, 0.1f, 0.2f, 1.0f}; // Dark blue- Verify registration:
Get-Content "$env:LOCALAPPDATA\openxr\1\active_runtime.json"- Check for conflicting API layers:
reg query "HKLM\SOFTWARE\Khronos\OpenXR\1\ApiLayers\Implicit"- Ensure no other VR runtime is running (SteamVR, Oculus, etc.)
- Disable any OpenXR API layers that modify rendering
- Ensure your application uses sRGB swapchain formats
- Check that no post-processing is double-applying gamma
- Check the log file:
%LOCALAPPDATA%\OpenXR-Simulator\openxr_simulator.log - Verify D3D11/D3D12/OpenGL support on your system
- Try running the application as administrator
- This is typically caused by DXGI Present hook conflicts with Steam overlay or UEVR. The simulator uses GDI-based rendering for D3D12 to avoid this, so make sure you're on the latest version.
- Turn Tools โ Mirror Rate down, or Off. Mirroring the eyes to the window is the only per-frame work the runtime does that scales with resolution.
- Leave
SIMXR_VERBOSEunset. Setting it makes the runtime log every frame, and each line is flushed to disk โ useful when diagnosing a frame, expensive as a default. - Reduce swapchain resolution in your application
- Disable MSAA if enabled
- Close other GPU-intensive applications
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Khronos Group for the OpenXR specification
- OpenXR SDK for headers and loader interfaces
- HMD Geometry Database by risa2000 for the headset profile FOV data
- Unity OpenXR Plugin team for compatibility testing
- Community contributors and testers
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Wiki
- Linux support
- Configurable controller emulation
- Multi-monitor support
- Recording and playback functionality
- OpenXR validation layer integration
- GUI configuration tool
Note: This is a development tool and not intended for end-user VR experiences. For production VR applications, use a proper VR headset and runtime.
