Initial commit: OpenXR simulator runtime, compositor, configurator, docs

This commit is contained in:
Konstantin Uni
2026-09-30 16:30:27 +02:00
commit 10bfaebe53
22 changed files with 11688 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
out/
build/
.vs/
.vscode/
.idea/
openxr_simulator_imgui.ini
imgui.ini
*.dll
*.lib
*.exp
*.pdb
*.ilk
+144
View File
@@ -0,0 +1,144 @@
cmake_minimum_required(VERSION 3.24)
project(openxr_simulator VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
option(XRSIM_BUILD_COMPOSITOR "Build the GLFW + Dear ImGui display window" ON)
find_package(Vulkan REQUIRED)
include(FetchContent)
# ---------------------------------------------------------------------------
# GLFW
# ---------------------------------------------------------------------------
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
FetchContent_MakeAvailable(glfw)
# ---------------------------------------------------------------------------
# Dear ImGui (docking branch, matches the engine's 1.91.4-docking)
# ---------------------------------------------------------------------------
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.91.4-docking
)
# imgui has no CMakeLists.txt, so this only downloads it.
FetchContent_MakeAvailable(imgui)
add_library(imgui STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/imgui_demo.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(imgui SYSTEM PUBLIC
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)
target_link_libraries(imgui PUBLIC glfw Vulkan::Vulkan)
# ---------------------------------------------------------------------------
# JSON dependency + shared runtime config
# ---------------------------------------------------------------------------
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
add_library(xrsim_config STATIC src/Config.cpp)
target_include_directories(xrsim_config PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(xrsim_config PUBLIC nlohmann_json::nlohmann_json)
# ---------------------------------------------------------------------------
# OpenXR simulator runtime
# ---------------------------------------------------------------------------
add_library(openxr_simulator SHARED
src/Negotiation.cpp
src/Runtime.cpp
)
if(XRSIM_BUILD_COMPOSITOR)
target_sources(openxr_simulator PRIVATE src/Compositor.cpp)
target_link_libraries(openxr_simulator PRIVATE imgui)
else()
target_sources(openxr_simulator PRIVATE src/CompositorStub.cpp)
endif()
target_include_directories(openxr_simulator
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/third_party
)
target_compile_definitions(openxr_simulator
PRIVATE
XR_USE_PLATFORM_WIN32
XR_USE_GRAPHICS_API_VULKAN
XR_NO_PROTOTYPES
)
target_link_libraries(openxr_simulator PRIVATE Vulkan::Vulkan xrsim_config)
if(MINGW)
# Statically link the GCC runtime (libstdc++/libgcc/libwinpthread) into the DLL so the OpenXR
# loader can load it when the app is launched from Explorer (no MinGW bin on PATH).
target_link_options(openxr_simulator PRIVATE -static-libgcc -static-libstdc++ -static)
endif()
if(MSVC)
target_compile_options(openxr_simulator PRIVATE /W4)
else()
# Vulkan structs are routinely zero-initialized with `{ VK_STRUCTURE_TYPE_... }`.
target_compile_options(openxr_simulator PRIVATE -Wall -Wextra -Wno-missing-field-initializers)
endif()
set_target_properties(openxr_simulator PROPERTIES
PREFIX ""
OUTPUT_NAME "openxr_simulator"
)
# Runtime manifest consumed by the OpenXR loader (via XR_RUNTIME_JSON or registry).
set(SIM_RUNTIME_NAME "OpenXR Simulator")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/manifest/openxr_simulator.json.in
${CMAKE_CURRENT_BINARY_DIR}/openxr_simulator.json
@ONLY
)
# ---------------------------------------------------------------------------
# Configurator application (edits openxr_simulator_config.json)
# ---------------------------------------------------------------------------
if(XRSIM_BUILD_COMPOSITOR)
find_package(OpenGL REQUIRED)
add_executable(openxr_simulator_configurator WIN32 tools/configurator/ConfiguratorMain.cpp)
target_link_libraries(openxr_simulator_configurator PRIVATE xrsim_config imgui glfw OpenGL::GL)
if(MINGW)
target_link_options(
openxr_simulator_configurator PRIVATE -static-libgcc -static-libstdc++ -static
)
endif()
endif()
+38
View File
@@ -0,0 +1,38 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 24,
"patch": 0
},
"configurePresets": [
{
"name": "windows-mingw-release",
"displayName": "Windows MinGW Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/windows-mingw-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "windows-msvc-release",
"displayName": "Windows MSVC Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/windows-msvc-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{
"name": "windows-mingw-release",
"configurePreset": "windows-mingw-release"
},
{
"name": "windows-msvc-release",
"configurePreset": "windows-msvc-release"
}
]
}
+138
View File
@@ -0,0 +1,138 @@
# 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.
+18
View File
@@ -0,0 +1,18 @@
{
"systemName": "OpenXR Simulator",
"vendorId": 0,
"eyeWidth": 1280,
"eyeHeight": 1280,
"refreshRateHz": 90,
"fovLeftDeg": 45.0,
"fovRightDeg": 45.0,
"fovUpDeg": 45.0,
"fovDownDeg": 45.0,
"ipdMeters": 0.064,
"swapchainImageCount": 3,
"windowTitle": "OpenXR Simulator",
"windowWidth": 1600,
"windowHeight": 900,
"presentMode": "mailbox",
"displayMode": "sideBySide"
}
+323
View File
@@ -0,0 +1,323 @@
# OpenXR Simulator Runtime — Design
A Windows OpenXR **runtime** that pretends to be a headset. OpenXR applications
(especially the `gl3-vulkan` / "Get Beaned" engine) run unmodified and render
into runtime-owned swapchains; the runtime displays those swapchain images in a
desktop window built with GLFW + Dear ImGui (docking).
This document is the architecture of record for the scaffold in this repository.
---
## 1. What an OpenXR runtime is on Windows
The application links `openxr_loader.dll` (from OpenXR-SDK). The loader is not
the runtime; it locates one of the installed runtimes and forwards every `xr*`
call to it.
Resolution order for the active runtime:
1. `XR_RUNTIME_JSON` environment variable → path to a runtime manifest JSON.
2. Per-user registry `HKCU\SOFTWARE\Khronos\OpenXR\1\ActiveRuntime`.
3. Machine registry `HKLM\SOFTWARE\Khronos\OpenXR\1\ActiveRuntime`.
The manifest points at a DLL that exports `xrNegotiateLoaderRuntimeInterface`.
That function hands the loader an `XrNegotiateRuntimeRequest` containing a
`PFN_xrGetInstanceProcAddr`. The loader uses that GIPA to resolve every core and
extension entry point.
```json
{
"file_format_version": "1.0.0",
"runtime": {
"library_path": "openxr_simulator.dll",
"name": "OpenXR Simulator"
}
}
```
The negotiation structs are in `third_party/openxr/openxr_loader_negotiation.h`
(vendored from OpenXR-SDK-Source `src/common/loader_interfaces.h`).
### Development shortcut
The `gl3-vulkan` engine already supports overriding the runtime without touching
the registry: `ApplicationContext::createOpenXrInstance(customOpenXrRuntimePath)`
sets `XR_RUNTIME_JSON` (`code/Engine/src/Engine/Core/ApplicationContext.cpp:904`).
So the whole simulator can be tested by launching the game with that env var
pointing at `openxr_simulator.json`. No system-wide install required.
---
## 2. How `gl3-vulkan` uses OpenXR (the contract we must satisfy)
The engine is the primary target. Key facts, with source references:
### 2.1 The runtime owns Vulkan
The engine does **not** create its own `VkInstance`/`VkDevice`. It asks the
runtime to:
| Call | Location |
| --- | --- |
| `xrCreateVulkanInstanceKHR` | `ApplicationContext.cpp:1442` |
| `xrGetVulkanGraphicsDevice2KHR` | `ApplicationContext.cpp:177` |
| `xrCreateVulkanDeviceKHR` | `ApplicationContext.cpp:478` |
Consequence: the runtime creates the instance and device, so it can create a
Win32 `VkSurfaceKHR`/present queue on the same objects and display the app's
images with **zero cross-device copies**. This is the single most important
design lever.
### 2.2 Graphics binding and swapchains
- Session is created with `XrGraphicsBindingVulkan2KHR` (app's instance, physical
device, device, graphics queue family/index): `Headset.cpp:59`.
- One **multiview** swapchain, `arraySize = 2` (`Headset.cpp:657`). Two eye
layers in one image, submitted as two projection views with
`imageArrayIndex = 0/1` (`Headset.cpp:762`).
- Format negotiated via `xrEnumerateSwapchainFormats`; engine prefers
`VK_FORMAT_R8G8B8A8_SRGB` (`Headset.cpp:23`), fallback `R8G8B8A8_UNORM` +
`MUTABLE_FORMAT` (`Headset.cpp:24,667`).
- `recommendedSwapchainSampleCount`, `recommendedImageRectWidth/Height` come
from our view configuration.
- Reference space is `XR_REFERENCE_SPACE_TYPE_STAGE` (`Headset.cpp:20`).
### 2.3 Frame loop
Per frame: `xrWaitFrame` → `xrBeginFrame` → `xrLocateViews` →
`xrAcquireSwapchainImage` → `xrWaitSwapchainImage` → [app renders] →
`xrReleaseSwapchainImage` → `xrEndFrame` with one
`XrCompositionLayerProjection` (2 views). See `Headset.cpp:1070-1161`.
### 2.4 Required extensions and runtime behaviour
- `XR_KHR_vulkan_enable2` is hard-required (`ApplicationContext.cpp:963,979`).
- Debug builds also require `XR_EXT_debug_utils` and call
`xrCreateDebugUtilsMessengerEXT` (`ApplicationContext.cpp:965-967,1090`).
- `XR_ENVIRONMENT_BLEND_MODE_OPAQUE` must be advertised
(`ApplicationContext.cpp:66,1194`).
- Session state events drive startup: the engine waits for
`XR_SESSION_STATE_READY` to call `xrBeginSession`
(`Engine.cpp:284`, `Headset.cpp:925`), and only renders in
READY/SYNCHRONIZED/VISIBLE/FOCUSED (`Headset.cpp:990`). We must enqueue
`XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED`.
- Input: the engine builds actions for ~16 interaction profiles and attaches
them (`XrInputHandler.cpp`). We must implement the action API and at least one
profile (`khr/simple_controller` and/or `oculus/touch_controller`).
### 2.5 Gotcha: Vulkan requirement version encoding
`getVulkanGraphicsRequirements2KHR` must report `minApiVersionSupported` /
`maxApiVersionSupported` using **`XR_MAKE_VERSION`** (major in bits 48-63), not
`VK_MAKE_VERSION`. The engine decodes with `XR_VERSION_MAJOR/MINOR`
(`ApplicationContext.cpp:1289-1297`) and rejects the runtime otherwise. Report
`XR_MAKE_VERSION(1,0,0)` .. `XR_MAKE_VERSION(1,4,0)`.
---
## 3. Architecture
```
app process (Game.exe)
┌─────────────────────────────────────────────────────────────┐
│ engine ── xr* calls ──► openxr_loader.dll │
│ │ │
│ ▼ │
│ openxr_simulator.dll (this repo)│
│ ┌───────────────────────────────────────────────────────┐ │
│ │ xrNegotiateLoaderRuntimeInterface (DLL export) │ │
│ │ xrGetInstanceProcAddr (dispatch table) │ │
│ │ │ │
│ │ Instance ─ System ─ Session ─ Swapchain │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Vulkan backend (owns VkInstance/VkDevice) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Compositor (GLFW window + ImGui docking) │ │
│ │ - eye textures drawn in dockable ImGui windows │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 3.1 Layers
| Layer | Responsibility |
| --- | --- |
| Negotiation | Export the interface, build the GIPA table. |
| Instance | State per `XrInstance`; registry of handles. |
| System / View config | Advertise HMD system, stereo view config, resolution, formats, blend modes. |
| Vulkan backend | Create/hold `VkInstance`/`VkDevice`, allocate swapchain images, own the present queue. |
| Session / Frame loop | Session state events, `xrWaitFrame` pacing, `xrEndFrame` submission. |
| Tracking | Head pose + controller poses (MVP: mouse/WASD/keyboard). |
| Actions | Action sets, bindings, `xrSyncActions`, state queries. |
| Compositor | GLFW+ImGui window; blit submitted eye layers into ImGui-visible textures. |
### 3.2 Object model and handles
OpenXR handles are opaque pointers (`XR_DEFINE_HANDLE`). We allocate small
structs and reinterpret-cast to the handle type, keeping them in a global
registry guarded by a mutex:
```cpp
struct RuntimeInstance; struct RuntimeSession; struct RuntimeSwapchain; ...
RuntimeInstance* GetInstance(XrInstance);
RuntimeSession* GetSession(XrSession);
```
### 3.3 Vulkan ownership
Because the engine delegates instance/device creation to us:
- `xrCreateVulkanInstanceKHR`: call the app-supplied `pfnGetInstanceProcAddr` to
get `vkCreateInstance`, create the instance (augmenting the create info with
our own required extensions such as `VK_KHR_surface`,
`VK_KHR_win32_surface`).
- `xrGetVulkanGraphicsDevice2KHR`: enumerate devices, pick one that supports
graphics + present to our window surface, return it.
- `xrCreateVulkanDeviceKHR`: `vkCreateDevice` with the app's create info plus any
queue families/extensions we need (`VK_KHR_swapchain`).
- Swapchain images are allocated on this device; the compositor blits them on
the same device/queue. No interop needed.
### 3.4 Compositor (GLFW + ImGui docking)
Decision (MVP): the simulator window is a normal GLFW window rendered with the
Dear ImGui Vulkan backend, using ImGui docking so the user can freely arrange
windows. Rather than presenting to a raw swapchain, each submitted eye is shown
in a dockable `ImGui::Image` window.
**Thread affinity (important).** The `gl3-vulkan` engine calls `xrEndFrame` from
a **background thread** (`EngineKern::completeFrameAsync`) and `xrWaitFrame` from
a worker pool, but calls `xrBeginFrame` from its main thread. GLFW/ImGui are
thread-affine, so the compositor splits into two phases:
- `Present()` — called from `xrEndFrame` on any thread. Only *records* the
resolved eye images (`ResolveSubImage`, using the swapchain's
`lastReleasedIndex` because release already cleared the acquired index). No
GLFW/ImGui/Vulkan work.
- `Pump()` — called from `xrBeginFrame` and `xrWaitFrame`; does the real work
only when the caller is the thread that created the window (guarded by
`windowThreadId_`), otherwise returns immediately.
Pump steps:
1. Wait for the app's rendering to finish on the shared queue (MVP:
`vkQueueWaitIdle` on the app graphics queue; later: timeline/fence sync).
2. Copy the two array layers of the submitted XR swapchain image into two
persistent runtime "display" images (`TRANSFER_DST | SAMPLED`, kept in
`GENERAL` layout).
3. `ImGui::NewFrame()`; draw a menu + two image windows (`Eye L`, `Eye R`)
sampling the display images, plus simulator controls (tracking sliders).
4. Render ImGui into the window swapchain and present.
Registering two persistent display images as ImGui textures decouples the ImGui
texture set from the XR swapchain image count and lets us scale each eye to fit
its dock window.
Window/queue notes:
- The simulator window surface is created on the runtime-owned `VkInstance`.
- Use the app's graphics queue (from `XrGraphicsBindingVulkan2KHR`) as the ImGui
queue so ordering on the same queue provides implicit synchronization, and
ensure that family supports present to our surface. If it does not, do a queue
family ownership transfer (or fall back to a dedicated runtime present queue +
explicit waits).
- `gl3-vulkan` also creates its own GLFW mirror window on the same instance.
GLFW is statically linked into both; each copy keeps independent state, so
both can coexist. If this proves fragile, the ImGui **Win32** backend is the
fallback (no GLFW dependency).
### 3.5 Frame pacing and events
- `xrWaitFrame` throttles to a configurable virtual refresh rate (default 90 Hz)
and returns `predictedDisplayTime` + `shouldRender`.
- Session state event sequence we enqueue:
`IDLE → READY` at session creation, then after `xrBeginSession`
`SYNCHRONIZED → VISIBLE → FOCUSED`. This matches what the engine's event
handler expects.
### 3.6 Tracking and input
- Head: mouse look (yaw/pitch) + WASD translation, integrated into an
`XrPosef` returned from `xrLocateViews`. STAGE space; y- is up.
- Controllers: the runtime advertises an emulated `oculus/touch_controller`
profile from `xrGetCurrentInteractionProfile` for both hands, so the app
activates that profile and wires up its aim/grip pose spaces.
- Action state (`xrGetActionState*`) is classified from the action name suffix
(`trigger_value`, `grip_value`, `thumbstick`, `a_button`, …) and the
subaction path (hand), then read from a global `InputState` updated by the
compositor. `xrLocateSpace` on an action space returns the emulated hand pose.
Keyboard mapping (shown in the ImGui `Controls` window):
| Control | Left hand | Right hand |
| --- | --- | --- |
| Head look / move | right-drag, WASD, Q/E | |
| Trigger | `F` | `H` |
| Grip | `G` | `U` |
| Thumbstick | `I`/`K`/`J`/`L` | arrow keys |
| Stick click | `R` | `O` |
| Face buttons | `C` (X), `V` (Y) | `N` (A), `M` (B) |
| Menu | `Tab` | |
The hand poses are placed relative to the head, so they follow look direction.
A `TrackingProvider` interface is planned so a real HMD / OpenTrack / gamepad
bridge can be plugged in later without touching the session code.
### 3.7 Configuration
The runtime reads a JSON config (`src/Config.{h,cpp}`) from `XRSIM_CONFIG`, or
else `openxr_simulator_config.json` next to the DLL. It drives the emulated
headset: system name/vendor, per-eye resolution, refresh rate, FOV, IPD,
swapchain image count, and the compositor window (title/size/present mode/display
mode). The `configurator` tool (GLFW + ImGui, `tools/configurator/`) edits this
file and ships presets for common headsets; it does not render — it only writes
the config the DLL reads.
Presentation mode matters for throughput: `fifo` presents with vsync and, because
presentation runs on the app's thread inside `xrBeginFrame`, caps the app to the
monitor refresh. The default `mailbox` (or `immediate`) is uncapped. `refreshRateHz`
sets the simulated strobe used by `xrWaitFrame`, independent of presentation.
---
## 4. Phasing
| Phase | Deliverable | Exit criterion |
| --- | --- | --- |
| 0 | Negotiation + GIPA + metadata functions (this scaffold) | Loader loads the DLL; `xrCreateInstance`/`xrGetSystem` succeed. |
| 1 | Vulkan passthrough | Engine creates its `VkInstance`/`VkDevice` through us. |
| 2 | Session, swapchain, frame loop, GLFW+ImGui compositor | Eye images (test pattern, then real) appear in the ImGui window. |
| 3 | Tracking | Mouse/WASD head pose drives `xrLocateViews`. |
| 4 | Actions/controllers | Game hands/inputs work; a profile is bound. |
| 5 | Polish | Session state edges, visibility/focus, haptics no-op, settings UI. |
Phases 0–4 are implemented. Remaining: phase 5 polish.
## 5. Risks / open questions
- **Loader strictness**: GIPA must return non-null for every function the app
loads. Extension functions the engine loads (`xrGetVulkanGraphicsDevice2KHR`,
`xrGetVulkanGraphicsRequirements2KHR`, `xrCreateVulkanInstanceKHR`,
`xrCreateVulkanDeviceKHR`) must be real, or `OpenXrHelper::LoadXrFunction`
throws.
- **GPU sync without app-provided semaphores**: core OpenXR gives no sync
primitive for swapchain images; MVP uses `vkQueueWaitIdle`.
- **Present family mismatch** between the app's graphics family and our window
surface.
- **Duplicate GLFW instances** in one process (engine + runtime); fallback to
ImGui Win32 backend.
- **Format negotiation**: only advertise formats we can actually create and
sample.
- **Version encoding** for Vulkan requirements (§2.5).
## 6. References
- OpenXR-SDK `hello_xr` — what a runtime must answer.
- Monado — reference open-source runtime (negotiation + compositor).
- `openxr_loader_negotiation.h` — exact negotiation structs.
- Engine source: `code/Engine/src/Engine/Core/ApplicationContext.cpp`,
`code/Engine/src/Engine/Renderer/Headset.cpp`,
`code/Engine/src/Engine/Input/XrInputHandler.cpp`.
+7
View File
@@ -0,0 +1,7 @@
{
"file_format_version": "1.0.0",
"runtime": {
"library_path": "./openxr_simulator.dll",
"name": "@SIM_RUNTIME_NAME@"
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#endif
namespace xrsim
{
inline void Log( const char * format, ... )
{
char buffer[1024];
va_list args;
va_start( args, format );
vsnprintf( buffer, sizeof( buffer ), format, args );
va_end( args );
#ifdef _WIN32
OutputDebugStringA( "[xrsim] " );
OutputDebugStringA( buffer );
OutputDebugStringA( "\n" );
#endif
fputs( "[xrsim] ", stderr );
fputs( buffer, stderr );
fputc( '\n', stderr );
}
} // namespace xrsim
+896
View File
@@ -0,0 +1,896 @@
#include "Compositor.h"
#include "Common.h"
#include "Config.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <thread>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_vulkan.h"
namespace xrsim
{
namespace
{
constexpr uint32_t kDisplayWidth = 1280;
constexpr uint32_t kDisplayHeight = 1280;
constexpr VkFormat kDisplayFormat = VK_FORMAT_R8G8B8A8_UNORM;
constexpr uint32_t kMinImageCount = 2;
constexpr float kLookSpeed = 2.5f; // metres/second
void TransitionImage(
VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout,
VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, VkAccessFlags srcAccess,
VkAccessFlags dstAccess, uint32_t layerCount
)
{
VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, layerCount };
barrier.srcAccessMask = srcAccess;
barrier.dstAccessMask = dstAccess;
vkCmdPipelineBarrier( cmd, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier );
}
void FrameRender( ImGui_ImplVulkanH_Window * wd, VkDevice device, VkQueue queue )
{
VkSemaphore imageAcquired = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore;
VkSemaphore renderComplete = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
vkAcquireNextImageKHR(
device, wd->Swapchain, UINT64_MAX, imageAcquired, VK_NULL_HANDLE, &wd->FrameIndex
);
ImGui_ImplVulkanH_Frame * fd = &wd->Frames[wd->FrameIndex];
vkWaitForFences( device, 1, &fd->Fence, VK_TRUE, UINT64_MAX );
vkResetFences( device, 1, &fd->Fence );
vkResetCommandPool( device, fd->CommandPool, 0 );
VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer( fd->CommandBuffer, &beginInfo );
VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
renderPassInfo.renderPass = wd->RenderPass;
renderPassInfo.framebuffer = fd->Framebuffer;
renderPassInfo.renderArea.extent.width = static_cast<uint32_t>( wd->Width );
renderPassInfo.renderArea.extent.height = static_cast<uint32_t>( wd->Height );
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &wd->ClearValue;
vkCmdBeginRenderPass( fd->CommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE );
ImGui_ImplVulkan_RenderDrawData( ImGui::GetDrawData(), fd->CommandBuffer );
vkCmdEndRenderPass( fd->CommandBuffer );
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &imageAcquired;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &fd->CommandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderComplete;
vkEndCommandBuffer( fd->CommandBuffer );
vkQueueSubmit( queue, 1, &submitInfo, fd->Fence );
}
void FramePresent( ImGui_ImplVulkanH_Window * wd, VkQueue queue )
{
VkSemaphore renderComplete = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &renderComplete;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &wd->Swapchain;
presentInfo.pImageIndices = &wd->FrameIndex;
vkQueuePresentKHR( queue, &presentInfo );
wd->SemaphoreIndex = ( wd->SemaphoreIndex + 1 ) % wd->SemaphoreCount;
}
class GlfwImGuiCompositor : public Compositor
{
public:
bool Initialize( RuntimeInstance * instance, RuntimeSession * session ) override;
void Shutdown() override;
void Present( RuntimeSession * session, const XrCompositionLayerProjection * layer ) override;
void Pump( RuntimeSession * session ) override;
private:
struct PendingEye
{
VkImage image = VK_NULL_HANDLE;
uint32_t arrayLayer = 0;
uint32_t width = 0;
uint32_t height = 0;
};
void UpdateTracking();
void DrawUi();
void EnsureDisplayImages( uint32_t width, uint32_t height );
void DestroyDisplayImages();
void CopyEyes( RuntimeSession * session, const PendingEye * eyes, uint32_t eyeCount );
void PumpAndRender();
std::thread::id windowThreadId_;
std::mutex pendingMutex_;
PendingEye pendingEyes_[2];
uint32_t pendingEyeCount_ = 0;
bool pendingValid_ = false;
RuntimeInstance * instance_ = nullptr;
RuntimeSession * session_ = nullptr;
GLFWwindow * window_ = nullptr;
bool glfwOwned_ = false;
bool contextCreated_ = false;
bool glfwBackendCreated_ = false;
bool vulkanBackendCreated_ = false;
bool swapchainCreated_ = false;
ImGui_ImplVulkanH_Window imguiWindow_{};
VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE;
VkSampler sampler_ = VK_NULL_HANDLE;
// Per-eye display targets sampled by ImGui.
VkImage displayImages_[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE };
VkDeviceMemory displayMemory_[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE };
VkImageView displayViews_[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE };
VkDescriptorSet displayTextures_[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE };
uint32_t displayWidth_ = 0;
uint32_t displayHeight_ = 0;
bool displayInitialized_ = false;
bool haveEyes_ = false;
VkCommandPool copyPool_ = VK_NULL_HANDLE;
VkCommandBuffer copyCmd_ = VK_NULL_HANDLE;
VkFence copyFence_ = VK_NULL_HANDLE;
};
bool GlfwImGuiCompositor::Initialize( RuntimeInstance * instance, RuntimeSession * session )
{
if ( std::getenv( "XRSIM_NO_WINDOW" ) != nullptr )
{
return false;
}
if ( instance == nullptr || session == nullptr || session->vkInstance == VK_NULL_HANDLE ||
session->vkDevice == VK_NULL_HANDLE || session->vkPhysicalDevice == VK_NULL_HANDLE )
{
return false;
}
instance_ = instance;
session_ = session;
windowThreadId_ = std::this_thread::get_id();
if ( glfwInit() != GLFW_TRUE )
{
Log( "glfwInit failed" );
return false;
}
glfwOwned_ = true;
const SimulatorConfig & config = GetConfig();
glfwWindowHint( GLFW_CLIENT_API, GLFW_NO_API );
window_ = glfwCreateWindow(
config.windowWidth, config.windowHeight, config.windowTitle.c_str(), nullptr, nullptr
);
if ( window_ == nullptr )
{
Log( "glfwCreateWindow failed" );
Shutdown();
return false;
}
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkResult surfaceResult =
glfwCreateWindowSurface( session->vkInstance, window_, nullptr, &surface );
if ( surfaceResult != VK_SUCCESS )
{
Log( "glfwCreateWindowSurface failed (%d)", (int)surfaceResult );
Shutdown();
return false;
}
// Verify the app's queue family can render and present to our surface.
VkBool32 presentSupported = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(
session->vkPhysicalDevice, session->queueFamilyIndex, surface, &presentSupported
);
uint32_t familyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties( session->vkPhysicalDevice, &familyCount, nullptr );
std::vector<VkQueueFamilyProperties> families( familyCount );
vkGetPhysicalDeviceQueueFamilyProperties(
session->vkPhysicalDevice, &familyCount, families.data()
);
const bool graphicsSupported =
( session->queueFamilyIndex < familyCount ) &&
( families[session->queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT ) != 0;
if ( presentSupported == VK_FALSE || !graphicsSupported )
{
Log(
"Queue family %u cannot both render and present to the simulator window; disabling "
"compositor",
session->queueFamilyIndex
);
Shutdown();
return false;
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
contextCreated_ = true;
ImGuiIO & io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.IniFilename = "openxr_simulator_imgui.ini";
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForVulkan( window_, true );
glfwBackendCreated_ = true;
const VkFormat requestFormats[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM };
// Requested in priority order; first supported wins. "mailbox"/"immediate" do not cap the
// frame rate, "fifo" syncs to the monitor refresh.
VkPresentModeKHR requestModes[3];
int requestModeCount = 0;
if ( config.presentMode == PresentMode::Fifo )
{
requestModes[requestModeCount++] = VK_PRESENT_MODE_FIFO_KHR;
requestModes[requestModeCount++] = VK_PRESENT_MODE_MAILBOX_KHR;
}
else if ( config.presentMode == PresentMode::Immediate )
{
requestModes[requestModeCount++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
requestModes[requestModeCount++] = VK_PRESENT_MODE_MAILBOX_KHR;
requestModes[requestModeCount++] = VK_PRESENT_MODE_FIFO_KHR;
}
else
{
requestModes[requestModeCount++] = VK_PRESENT_MODE_MAILBOX_KHR;
requestModes[requestModeCount++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
requestModes[requestModeCount++] = VK_PRESENT_MODE_FIFO_KHR;
}
imguiWindow_.Surface = surface;
imguiWindow_.SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(
session->vkPhysicalDevice, surface, requestFormats, IM_ARRAYSIZE( requestFormats ),
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
);
imguiWindow_.PresentMode = ImGui_ImplVulkanH_SelectPresentMode(
session->vkPhysicalDevice, surface, requestModes, requestModeCount
);
imguiWindow_.ClearEnable = true;
imguiWindow_.ClearValue = { { { 0.06f, 0.06f, 0.08f, 1.0f } } };
ImGui_ImplVulkanH_CreateOrResizeWindow(
session->vkInstance, session->vkPhysicalDevice, session->vkDevice, &imguiWindow_,
session->queueFamilyIndex, nullptr, config.windowWidth, config.windowHeight, kMinImageCount
);
swapchainCreated_ = true;
const char * presentModeName = "FIFO (vsync)";
if ( imguiWindow_.PresentMode == VK_PRESENT_MODE_MAILBOX_KHR )
{
presentModeName = "MAILBOX";
}
else if ( imguiWindow_.PresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR )
{
presentModeName = "IMMEDIATE";
}
Log(
"Compositor window present mode: %s (requested %s)", presentModeName,
ToString( config.presentMode )
);
VkDescriptorPoolSize poolSize{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 32 };
VkDescriptorPoolCreateInfo poolInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
poolInfo.maxSets = 32;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
if ( vkCreateDescriptorPool( session->vkDevice, &poolInfo, nullptr, &descriptorPool_ ) !=
VK_SUCCESS )
{
Log( "Failed to create ImGui descriptor pool" );
Shutdown();
return false;
}
ImGui_ImplVulkan_InitInfo initInfo{};
initInfo.Instance = session->vkInstance;
initInfo.PhysicalDevice = session->vkPhysicalDevice;
initInfo.Device = session->vkDevice;
initInfo.QueueFamily = session->queueFamilyIndex;
initInfo.Queue = session->queue;
initInfo.DescriptorPool = descriptorPool_;
initInfo.RenderPass = imguiWindow_.RenderPass;
initInfo.MinImageCount = kMinImageCount;
initInfo.ImageCount = imguiWindow_.ImageCount;
initInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
if ( !ImGui_ImplVulkan_Init( &initInfo ) )
{
Log( "ImGui_ImplVulkan_Init failed" );
Shutdown();
return false;
}
vulkanBackendCreated_ = true;
VkSamplerCreateInfo samplerInfo{ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.maxLod = 1.0f;
vkCreateSampler( session->vkDevice, &samplerInfo, nullptr, &sampler_ );
VkCommandPoolCreateInfo copyPoolInfo{ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
copyPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
copyPoolInfo.queueFamilyIndex = session->queueFamilyIndex;
vkCreateCommandPool( session->vkDevice, &copyPoolInfo, nullptr, &copyPool_ );
VkCommandBufferAllocateInfo cmdInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
cmdInfo.commandPool = copyPool_;
cmdInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmdInfo.commandBufferCount = 1;
vkAllocateCommandBuffers( session->vkDevice, &cmdInfo, &copyCmd_ );
VkFenceCreateInfo fenceInfo{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
vkCreateFence( session->vkDevice, &fenceInfo, nullptr, &copyFence_ );
EnsureDisplayImages( kDisplayWidth, kDisplayHeight );
Log( "Compositor initialized (GLFW + ImGui docking)" );
return true;
}
void GlfwImGuiCompositor::DestroyDisplayImages()
{
if ( session_ == nullptr )
{
return;
}
vkDeviceWaitIdle( session_->vkDevice );
for ( int i = 0; i < 2; ++i )
{
if ( displayTextures_[i] != VK_NULL_HANDLE )
{
ImGui_ImplVulkan_RemoveTexture( displayTextures_[i] );
displayTextures_[i] = VK_NULL_HANDLE;
}
if ( displayViews_[i] != VK_NULL_HANDLE )
{
vkDestroyImageView( session_->vkDevice, displayViews_[i], nullptr );
displayViews_[i] = VK_NULL_HANDLE;
}
if ( displayImages_[i] != VK_NULL_HANDLE )
{
vkDestroyImage( session_->vkDevice, displayImages_[i], nullptr );
displayImages_[i] = VK_NULL_HANDLE;
}
if ( displayMemory_[i] != VK_NULL_HANDLE )
{
vkFreeMemory( session_->vkDevice, displayMemory_[i], nullptr );
displayMemory_[i] = VK_NULL_HANDLE;
}
}
displayInitialized_ = false;
haveEyes_ = false;
}
void GlfwImGuiCompositor::EnsureDisplayImages( uint32_t width, uint32_t height )
{
if ( displayImages_[0] != VK_NULL_HANDLE && displayWidth_ == width &&
displayHeight_ == height )
{
return;
}
DestroyDisplayImages();
displayWidth_ = width;
displayHeight_ = height;
VkImageCreateInfo imageInfo{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = kDisplayFormat;
imageInfo.extent = { width, height, 1 };
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
for ( int i = 0; i < 2; ++i )
{
vkCreateImage( session_->vkDevice, &imageInfo, nullptr, &displayImages_[i] );
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements( session_->vkDevice, displayImages_[i], &requirements );
VkPhysicalDeviceMemoryProperties memoryProperties{};
vkGetPhysicalDeviceMemoryProperties( session_->vkPhysicalDevice, &memoryProperties );
uint32_t memoryType = UINT32_MAX;
for ( uint32_t t = 0; t < memoryProperties.memoryTypeCount; ++t )
{
if ( ( requirements.memoryTypeBits & ( 1u << t ) ) &&
( memoryProperties.memoryTypes[t].propertyFlags &
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ) )
{
memoryType = t;
break;
}
}
VkMemoryAllocateInfo allocateInfo{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
allocateInfo.allocationSize = requirements.size;
allocateInfo.memoryTypeIndex = memoryType;
vkAllocateMemory( session_->vkDevice, &allocateInfo, nullptr, &displayMemory_[i] );
vkBindImageMemory(
session_->vkDevice, displayImages_[i], displayMemory_[i], 0
);
VkImageViewCreateInfo viewInfo{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
viewInfo.image = displayImages_[i];
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = kDisplayFormat;
viewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
vkCreateImageView( session_->vkDevice, &viewInfo, nullptr, &displayViews_[i] );
displayTextures_[i] = ImGui_ImplVulkan_AddTexture(
sampler_, displayViews_[i], VK_IMAGE_LAYOUT_GENERAL
);
}
displayInitialized_ = false;
}
void GlfwImGuiCompositor::CopyEyes(
RuntimeSession * session, const PendingEye * eyes, uint32_t eyeCount
)
{
if ( eyeCount == 0 )
{
return;
}
// The application may still be executing its frame on this queue.
vkQueueWaitIdle( session->queue );
vkWaitForFences( session->vkDevice, 1, &copyFence_, VK_TRUE, UINT64_MAX );
vkResetFences( session->vkDevice, 1, &copyFence_ );
vkResetCommandBuffer( copyCmd_, 0 );
VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer( copyCmd_, &beginInfo );
for ( uint32_t eye = 0; eye < eyeCount; ++eye )
{
const VkImage xrImage = eyes[eye].image;
const uint32_t arrayLayer = eyes[eye].arrayLayer;
const uint32_t width = eyes[eye].width;
const uint32_t height = eyes[eye].height;
if ( xrImage == VK_NULL_HANDLE )
{
continue;
}
EnsureDisplayImages( width, height );
TransitionImage(
copyCmd_, xrImage, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT, 2
);
if ( !displayInitialized_ )
{
TransitionImage(
copyCmd_, displayImages_[eye], VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, 1
);
}
else
{
TransitionImage(
copyCmd_, displayImages_[eye], VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, 1
);
}
VkImageCopy region{};
region.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, arrayLayer, 1 };
region.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
region.extent = { width, height, 1 };
vkCmdCopyImage(
copyCmd_, xrImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, displayImages_[eye],
VK_IMAGE_LAYOUT_GENERAL, 1, &region
);
TransitionImage(
copyCmd_, displayImages_[eye], VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT, 1
);
TransitionImage(
copyCmd_, xrImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 2
);
}
vkEndCommandBuffer( copyCmd_ );
VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &copyCmd_;
vkQueueSubmit( session->queue, 1, &submitInfo, copyFence_ );
displayInitialized_ = true;
haveEyes_ = true;
}
void GlfwImGuiCompositor::UpdateTracking()
{
ImGuiIO & io = ImGui::GetIO();
TrackingState & tracking = GetTracking();
if ( !io.WantCaptureMouse && ImGui::IsMouseDown( ImGuiMouseButton_Right ) )
{
tracking.yaw -= io.MouseDelta.x * 0.003f;
tracking.pitch -= io.MouseDelta.y * 0.003f;
tracking.pitch = std::max( -1.5f, std::min( 1.5f, tracking.pitch ) );
}
const float dt = std::min( io.DeltaTime, 0.1f );
const float speed = kLookSpeed * dt;
const float forwardX = -std::sin( tracking.yaw );
const float forwardZ = -std::cos( tracking.yaw );
const float rightX = std::cos( tracking.yaw );
const float rightZ = -std::sin( tracking.yaw );
if ( glfwGetKey( window_, GLFW_KEY_W ) == GLFW_PRESS )
{
tracking.posX += forwardX * speed;
tracking.posZ += forwardZ * speed;
}
if ( glfwGetKey( window_, GLFW_KEY_S ) == GLFW_PRESS )
{
tracking.posX -= forwardX * speed;
tracking.posZ -= forwardZ * speed;
}
if ( glfwGetKey( window_, GLFW_KEY_D ) == GLFW_PRESS )
{
tracking.posX += rightX * speed;
tracking.posZ += rightZ * speed;
}
if ( glfwGetKey( window_, GLFW_KEY_A ) == GLFW_PRESS )
{
tracking.posX -= rightX * speed;
tracking.posZ -= rightZ * speed;
}
if ( glfwGetKey( window_, GLFW_KEY_E ) == GLFW_PRESS )
{
tracking.posY += speed;
}
if ( glfwGetKey( window_, GLFW_KEY_Q ) == GLFW_PRESS )
{
tracking.posY -= speed;
}
// ---- Emulated controllers -------------------------------------
InputState & input = GetInputState();
const XrQuaternionf headOrientation = GetHeadOrientation();
const XrVector3f headPosition = GetHeadPosition();
auto keyDown = [&]( int key ) { return glfwGetKey( window_, key ) == GLFW_PRESS; };
auto axis = [&]( int positive, int negative )
{ return ( keyDown( positive ) ? 1.0f : 0.0f ) - ( keyDown( negative ) ? 1.0f : 0.0f ); };
auto handPose = [&]( float localX, float localY, float localZ )
{
const XrVector3f offset =
RotateByQuaternion( headOrientation, XrVector3f{ localX, localY, localZ } );
XrPosef pose{};
pose.orientation = headOrientation;
pose.position = XrVector3f{ headPosition.x + offset.x, headPosition.y + offset.y,
headPosition.z + offset.z };
return pose;
};
input.hands[0].active = true;
input.hands[1].active = true;
{
ControllerState & left = input.hands[0];
left.gripPose = handPose( 0.18f, -0.30f, -0.35f );
left.aimPose = left.gripPose;
left.trigger = keyDown( GLFW_KEY_F ) ? 1.0f : 0.0f;
left.grip = keyDown( GLFW_KEY_G ) ? 1.0f : 0.0f;
left.thumbstick = XrVector2f{ axis( GLFW_KEY_L, GLFW_KEY_J ), axis( GLFW_KEY_I, GLFW_KEY_K ) };
left.x = keyDown( GLFW_KEY_C );
left.y = keyDown( GLFW_KEY_V );
left.menu = keyDown( GLFW_KEY_TAB );
left.stickClick = keyDown( GLFW_KEY_R );
left.touch = left.trigger > 0.0f || left.grip > 0.0f;
}
{
ControllerState & right = input.hands[1];
right.gripPose = handPose( -0.18f, -0.30f, -0.35f );
right.aimPose = right.gripPose;
right.trigger = keyDown( GLFW_KEY_H ) ? 1.0f : 0.0f;
right.grip = keyDown( GLFW_KEY_U ) ? 1.0f : 0.0f;
right.thumbstick = XrVector2f{ axis( GLFW_KEY_RIGHT, GLFW_KEY_LEFT ),
axis( GLFW_KEY_UP, GLFW_KEY_DOWN ) };
right.a = keyDown( GLFW_KEY_N );
right.b = keyDown( GLFW_KEY_M );
right.stickClick = keyDown( GLFW_KEY_O );
right.touch = right.trigger > 0.0f || right.grip > 0.0f;
}
}
void GlfwImGuiCompositor::DrawUi()
{
ImGui::DockSpaceOverViewport();
TrackingState & tracking = GetTracking();
ImGui::Begin( "Controls" );
ImGui::TextUnformatted( "Head: right-drag look, WASD move, Q/E down/up" );
ImGui::TextUnformatted( "Left hand: F trigger, G grip, IJKL stick, R stick-click, C/V buttons" );
ImGui::TextUnformatted( "Right hand: H trigger, U grip, arrows stick, O stick-click, N/M buttons" );
ImGui::TextUnformatted( "Menu: Tab" );
ImGui::Separator();
ImGui::SliderFloat( "Yaw", &tracking.yaw, -3.14f, 3.14f );
ImGui::SliderFloat( "Pitch", &tracking.pitch, -1.5f, 1.5f );
ImGui::SliderFloat( "IPD", &tracking.ipd, 0.04f, 0.09f, "%.3f m" );
ImGui::DragFloat3( "Position", &tracking.posX, 0.01f );
if ( ImGui::Button( "Reset" ) )
{
tracking = TrackingState{};
}
const SimulatorConfig & cfg = GetConfig();
ImGui::Text(
"Headset: %s %ux%u @ %u Hz", cfg.systemName.c_str(), cfg.eyeWidth, cfg.eyeHeight,
cfg.refreshRateHz
);
ImGui::Text( "Present mode: %s", ToString( cfg.presentMode ) );
ImGui::Separator();
ImGui::Text( "%.1f FPS", ImGui::GetIO().Framerate );
ImGui::Text( "Queue: %u Device: %p", session_->queueFamilyIndex, (void *)session_->vkDevice );
ImGui::End();
const SimulatorConfig & config = GetConfig();
auto drawEye = [&]( const char * name, int index )
{
ImGui::Begin( name );
if ( haveEyes_ )
{
ImGui::Image(
(ImTextureID)(uint64_t)displayTextures_[index], ImGui::GetContentRegionAvail()
);
}
else
{
ImGui::TextUnformatted( "waiting for frames..." );
}
ImGui::End();
};
switch ( config.displayMode )
{
case DisplayMode::SingleEyeLeft: drawEye( "Eye L", 0 ); break;
case DisplayMode::SingleEyeRight: drawEye( "Eye R", 1 ); break;
default:
drawEye( "Eye L", 0 );
drawEye( "Eye R", 1 );
break;
}
}
void GlfwImGuiCompositor::PumpAndRender()
{
glfwPollEvents();
int frameWidth = 0, frameHeight = 0;
glfwGetFramebufferSize( window_, &frameWidth, &frameHeight );
if ( frameWidth <= 0 || frameHeight <= 0 )
{
return;
}
if ( frameWidth != imguiWindow_.Width || frameHeight != imguiWindow_.Height )
{
ImGui_ImplVulkan_SetMinImageCount( kMinImageCount );
ImGui_ImplVulkanH_CreateOrResizeWindow(
session_->vkInstance, session_->vkPhysicalDevice, session_->vkDevice, &imguiWindow_,
session_->queueFamilyIndex, nullptr, frameWidth, frameHeight, kMinImageCount
);
imguiWindow_.FrameIndex = 0;
}
ImGui_ImplVulkan_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
UpdateTracking();
DrawUi();
ImGui::Render();
FrameRender( &imguiWindow_, session_->vkDevice, session_->queue );
FramePresent( &imguiWindow_, session_->queue );
}
void GlfwImGuiCompositor::Present(
RuntimeSession * session, const XrCompositionLayerProjection * layer
)
{
if ( window_ == nullptr )
{
return;
}
// Called from a background thread: only record the submitted images here.
if ( layer != nullptr && layer->viewCount > 0 )
{
const uint32_t viewCount = layer->viewCount > 2 ? 2 : layer->viewCount;
PendingEye resolved[2];
uint32_t resolvedCount = 0;
for ( uint32_t eye = 0; eye < viewCount; ++eye )
{
const XrSwapchainSubImage & sub = layer->views[eye].subImage;
VkImage image = VK_NULL_HANDLE;
uint32_t arrayLayer = 0, width = 0, height = 0;
if ( ResolveSubImage(
sub.swapchain, sub, &image, &arrayLayer, &width, &height
) )
{
resolved[resolvedCount++] = PendingEye{ image, arrayLayer, width, height };
}
}
if ( resolvedCount > 0 )
{
std::lock_guard<std::mutex> lock( pendingMutex_ );
for ( uint32_t i = 0; i < resolvedCount; ++i )
{
pendingEyes_[i] = resolved[i];
}
pendingEyeCount_ = resolvedCount;
pendingValid_ = true;
}
}
// If the caller happens to be the window thread, present immediately.
if ( std::this_thread::get_id() == windowThreadId_ )
{
Pump( session );
}
}
void GlfwImGuiCompositor::Pump( RuntimeSession * session )
{
if ( window_ == nullptr || std::this_thread::get_id() != windowThreadId_ )
{
return;
}
PendingEye eyes[2];
uint32_t count = 0;
{
std::lock_guard<std::mutex> lock( pendingMutex_ );
if ( pendingValid_ )
{
for ( uint32_t i = 0; i < pendingEyeCount_; ++i )
{
eyes[i] = pendingEyes_[i];
}
count = pendingEyeCount_;
pendingValid_ = false;
pendingEyeCount_ = 0;
}
}
if ( count > 0 )
{
CopyEyes( session, eyes, count );
}
PumpAndRender();
}
void GlfwImGuiCompositor::Shutdown()
{
const bool hasDevice =
session_ != nullptr && session_->vkDevice != VK_NULL_HANDLE;
if ( hasDevice )
{
vkDeviceWaitIdle( session_->vkDevice );
if ( vulkanBackendCreated_ )
{
DestroyDisplayImages();
}
if ( copyFence_ != VK_NULL_HANDLE )
{
vkDestroyFence( session_->vkDevice, copyFence_, nullptr );
copyFence_ = VK_NULL_HANDLE;
}
if ( copyPool_ != VK_NULL_HANDLE )
{
vkDestroyCommandPool( session_->vkDevice, copyPool_, nullptr );
copyPool_ = VK_NULL_HANDLE;
}
if ( sampler_ != VK_NULL_HANDLE )
{
vkDestroySampler( session_->vkDevice, sampler_, nullptr );
sampler_ = VK_NULL_HANDLE;
}
if ( vulkanBackendCreated_ )
{
ImGui_ImplVulkan_Shutdown();
vulkanBackendCreated_ = false;
}
if ( descriptorPool_ != VK_NULL_HANDLE )
{
vkDestroyDescriptorPool( session_->vkDevice, descriptorPool_, nullptr );
descriptorPool_ = VK_NULL_HANDLE;
}
if ( swapchainCreated_ )
{
ImGui_ImplVulkanH_DestroyWindow(
session_->vkInstance, session_->vkDevice, &imguiWindow_, nullptr
);
swapchainCreated_ = false;
}
}
if ( glfwBackendCreated_ )
{
ImGui_ImplGlfw_Shutdown();
glfwBackendCreated_ = false;
}
if ( contextCreated_ )
{
ImGui::DestroyContext();
contextCreated_ = false;
}
if ( window_ != nullptr )
{
glfwDestroyWindow( window_ );
window_ = nullptr;
}
if ( glfwOwned_ )
{
glfwTerminate();
glfwOwned_ = false;
}
session_ = nullptr;
instance_ = nullptr;
}
} // namespace
std::unique_ptr<Compositor> CreateCompositor()
{
return std::make_unique<GlfwImGuiCompositor>();
}
} // namespace xrsim
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "Runtime.h"
#include <memory>
namespace xrsim
{
/**
* @brief Displays the eye images submitted by the application.
*
* The default implementation is a GLFW + Dear ImGui (docking) window. A no-op implementation
* is created when the compositor is disabled at build time or when XRSIM_NO_WINDOW is set.
*/
class Compositor
{
public:
virtual ~Compositor() = default;
virtual bool Initialize( RuntimeInstance * instance, RuntimeSession * session ) = 0;
virtual void Shutdown() = 0;
/**
* @brief Records one submitted projection layer.
*
* May be called from any thread (the engine calls xrEndFrame on a background thread), so
* this must not touch the window / GLFW / ImGui. All window work happens in Pump().
*/
virtual void Present(
RuntimeSession * session, const XrCompositionLayerProjection * layer
) = 0;
/**
* @brief Displays the most recently submitted frame.
*
* Must be called on the thread that created the window (the simulator pumps this from
* xrWaitFrame, which the engine calls on its main thread). No-op on other threads.
*/
virtual void Pump( RuntimeSession * session ) = 0;
};
std::unique_ptr<Compositor> CreateCompositor();
} // namespace xrsim
+21
View File
@@ -0,0 +1,21 @@
#include "Compositor.h"
namespace xrsim
{
namespace
{
class NullCompositor : public Compositor
{
public:
bool Initialize( RuntimeInstance *, RuntimeSession * ) override { return true; }
void Shutdown() override {}
void Present( RuntimeSession *, const XrCompositionLayerProjection * ) override {}
void Pump( RuntimeSession * ) override {}
};
} // namespace
std::unique_ptr<Compositor> CreateCompositor()
{
return std::make_unique<NullCompositor>();
}
} // namespace xrsim
+193
View File
@@ -0,0 +1,193 @@
#include "Config.h"
#include <cstdlib>
#include <fstream>
#include <nlohmann/json.hpp>
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#endif
namespace xrsim
{
using nlohmann::json;
namespace
{
std::string ModuleDirectory()
{
#ifdef _WIN32
HMODULE module = nullptr;
GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>( &ModuleDirectory ), &module
);
char buffer[MAX_PATH] = {};
DWORD length = GetModuleFileNameA( module, buffer, MAX_PATH );
std::string path( buffer, length );
const auto separator = path.find_last_of( "\\/" );
if ( separator != std::string::npos )
{
path.resize( separator + 1 );
}
else
{
path.clear();
}
return path;
#else
return std::string();
#endif
}
PresentMode ParsePresentMode( const std::string & value )
{
if ( value == "fifo" || value == "vsync" )
{
return PresentMode::Fifo;
}
if ( value == "immediate" )
{
return PresentMode::Immediate;
}
return PresentMode::Mailbox;
}
DisplayMode ParseDisplayMode( const std::string & value )
{
if ( value == "singleEyeLeft" )
{
return DisplayMode::SingleEyeLeft;
}
if ( value == "singleEyeRight" )
{
return DisplayMode::SingleEyeRight;
}
return DisplayMode::SideBySide;
}
} // namespace
const char * ToString( PresentMode mode )
{
switch ( mode )
{
case PresentMode::Fifo: return "fifo";
case PresentMode::Immediate: return "immediate";
default: return "mailbox";
}
}
const char * ToString( DisplayMode mode )
{
switch ( mode )
{
case DisplayMode::SingleEyeLeft: return "singleEyeLeft";
case DisplayMode::SingleEyeRight: return "singleEyeRight";
default: return "sideBySide";
}
}
std::string SimulatorConfig::ResolvePath()
{
if ( const char * env = std::getenv( "XRSIM_CONFIG" ) )
{
if ( env[0] != '\0' )
{
return env;
}
}
return ModuleDirectory() + "openxr_simulator_config.json";
}
SimulatorConfig SimulatorConfig::Load()
{
return LoadFrom( ResolvePath() );
}
SimulatorConfig SimulatorConfig::LoadFrom( const std::string & path )
{
SimulatorConfig config;
std::ifstream file( path );
if ( !file )
{
return config;
}
json root;
try
{
file >> root;
}
catch ( const std::exception & )
{
return config;
}
config.systemName = root.value( "systemName", config.systemName );
config.vendorId = root.value( "vendorId", config.vendorId );
config.eyeWidth = root.value( "eyeWidth", config.eyeWidth );
config.eyeHeight = root.value( "eyeHeight", config.eyeHeight );
config.refreshRateHz = root.value( "refreshRateHz", config.refreshRateHz );
config.fovLeftDeg = root.value( "fovLeftDeg", config.fovLeftDeg );
config.fovRightDeg = root.value( "fovRightDeg", config.fovRightDeg );
config.fovUpDeg = root.value( "fovUpDeg", config.fovUpDeg );
config.fovDownDeg = root.value( "fovDownDeg", config.fovDownDeg );
config.ipdMeters = root.value( "ipdMeters", config.ipdMeters );
config.swapchainImageCount = root.value( "swapchainImageCount", config.swapchainImageCount );
config.windowTitle = root.value( "windowTitle", config.windowTitle );
config.windowWidth = root.value( "windowWidth", config.windowWidth );
config.windowHeight = root.value( "windowHeight", config.windowHeight );
config.presentMode = ParsePresentMode( root.value( "presentMode", std::string( "mailbox" ) ) );
config.displayMode =
ParseDisplayMode( root.value( "displayMode", std::string( "sideBySide" ) ) );
return config;
}
bool SimulatorConfig::Save( const std::string & path ) const
{
json root;
root["systemName"] = systemName;
root["vendorId"] = vendorId;
root["eyeWidth"] = eyeWidth;
root["eyeHeight"] = eyeHeight;
root["refreshRateHz"] = refreshRateHz;
root["fovLeftDeg"] = fovLeftDeg;
root["fovRightDeg"] = fovRightDeg;
root["fovUpDeg"] = fovUpDeg;
root["fovDownDeg"] = fovDownDeg;
root["ipdMeters"] = ipdMeters;
root["swapchainImageCount"] = swapchainImageCount;
root["windowTitle"] = windowTitle;
root["windowWidth"] = windowWidth;
root["windowHeight"] = windowHeight;
root["presentMode"] = ToString( presentMode );
root["displayMode"] = ToString( displayMode );
std::ofstream file( path );
if ( !file )
{
return false;
}
file << root.dump( 4 ) << std::endl;
return static_cast<bool>( file );
}
SimulatorConfig & GetConfig()
{
static SimulatorConfig config = SimulatorConfig::Load();
return config;
}
} // namespace xrsim
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
#include <string>
namespace xrsim
{
enum class PresentMode
{
Mailbox, // uncapped, no tearing
Immediate, // uncapped, may tear
Fifo, // vsync (caps to monitor refresh)
};
enum class DisplayMode
{
SideBySide,
SingleEyeLeft,
SingleEyeRight,
};
const char * ToString( PresentMode mode );
const char * ToString( DisplayMode mode );
/**
* @brief Runtime configuration, read from a JSON file by the runtime DLL and written by the
* configurator application.
*/
struct SimulatorConfig
{
// Headset identity.
std::string systemName = "OpenXR Simulator";
uint32_t vendorId = 0;
// Per-eye render target and display timing.
uint32_t eyeWidth = 1280;
uint32_t eyeHeight = 1280;
uint32_t refreshRateHz = 90;
// Field of view, per half-angle in degrees (left/right equal => symmetric).
float fovLeftDeg = 45.0f;
float fovRightDeg = 45.0f;
float fovUpDeg = 45.0f;
float fovDownDeg = 45.0f;
float ipdMeters = 0.064f;
uint32_t swapchainImageCount = 3;
// Compositor window.
std::string windowTitle = "OpenXR Simulator";
int windowWidth = 1600;
int windowHeight = 900;
PresentMode presentMode = PresentMode::Mailbox;
DisplayMode displayMode = DisplayMode::SideBySide;
/** @brief Loads the config from the resolved path (or defaults if absent). */
static SimulatorConfig Load();
/** @brief Loads the config from an explicit path (or defaults if absent/invalid). */
static SimulatorConfig LoadFrom( const std::string & path );
/** @brief Resolves the config path (XRSIM_CONFIG env, else <module-dir>/openxr_simulator_config.json). */
static std::string ResolvePath();
/** @brief Writes the config to a path (creates parent-less file). */
bool Save( const std::string & path ) const;
};
/** @brief Process-wide configuration, loaded once on first use. */
SimulatorConfig & GetConfig();
} // namespace xrsim
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <array>
#include <cstdint>
namespace xrsim
{
/**
* @brief Built-in emulated headset presets.
*
* Resolution is the per-eye panel resolution (what the runtime advertises as
* recommendedImageRect*). Field of view is stored as per-direction half-angles in degrees and is
* an approximation of the rendered FoV; the runtime converts it to tangents for XrFovf. IPD is a
* typical/default value in metres. Values are approximate and tunable in the configurator.
*/
struct HeadsetPreset
{
const char * name;
uint32_t eyeWidth;
uint32_t eyeHeight;
uint32_t refreshRateHz;
float fovLeftDeg;
float fovRightDeg;
float fovUpDeg;
float fovDownDeg;
float ipdMeters;
};
inline constexpr std::array<HeadsetPreset, 20> kHeadsetPresets = { {
// --- Generic ---
{ "Generic 1280", 1280, 1280, 90, 45.0f, 45.0f, 45.0f, 45.0f, 0.064f },
// --- Meta / Oculus (Quest) ---
{ "Meta Quest 2", 1832, 1920, 90, 52.0f, 52.0f, 49.0f, 49.0f, 0.063f },
{ "Meta Quest 3", 2064, 2208, 90, 54.0f, 54.0f, 49.0f, 49.0f, 0.063f },
{ "Meta Quest 3S", 1832, 1920, 90, 52.0f, 52.0f, 49.0f, 49.0f, 0.063f },
{ "Meta Quest Pro", 1800, 1920, 90, 53.0f, 53.0f, 48.0f, 48.0f, 0.063f },
// --- Valve ---
{ "Valve Index", 1440, 1600, 120, 54.0f, 54.0f, 55.0f, 55.0f, 0.064f },
{ "Valve Steam Frame", 2160, 2160, 90, 52.0f, 52.0f, 48.0f, 48.0f, 0.063f },
// --- HTC ---
{ "HTC Vive", 1080, 1200, 90, 50.0f, 50.0f, 50.0f, 50.0f, 0.064f },
{ "HTC Vive Pro 2", 2448, 2448, 90, 60.0f, 60.0f, 58.0f, 58.0f, 0.064f },
{ "HTC Vive Focus 3", 2448, 2448, 90, 54.0f, 54.0f, 54.0f, 54.0f, 0.064f },
// --- HP ---
{ "HP Reverb G2", 2160, 2160, 90, 49.0f, 49.0f, 45.0f, 45.0f, 0.0635f },
// --- ByteDance Pico ---
{ "Pico 4", 2160, 2160, 90, 52.0f, 52.0f, 51.0f, 51.0f, 0.063f },
{ "Pico 4 Ultra", 2160, 2160, 90, 52.0f, 52.0f, 51.0f, 51.0f, 0.063f },
{ "Pico Neo 3", 1832, 1920, 90, 49.0f, 49.0f, 49.0f, 49.0f, 0.0635f },
// --- Varjo ---
{ "Varjo Aero", 2880, 2720, 90, 50.0f, 50.0f, 48.0f, 48.0f, 0.063f },
{ "Varjo XR-4", 3840, 3744, 90, 60.0f, 60.0f, 52.0f, 52.0f, 0.063f },
// --- Other PC VR ---
{ "Pimax Crystal", 2880, 2880, 90, 60.0f, 60.0f, 50.0f, 50.0f, 0.064f },
{ "Bigscreen Beyond 2", 2560, 2560, 90, 47.0f, 47.0f, 45.0f, 45.0f, 0.063f },
{ "PlayStation VR2", 2000, 2040, 90, 55.0f, 55.0f, 50.0f, 50.0f, 0.063f },
// --- Android XR ---
{ "Samsung Galaxy XR", 3552, 3840, 90, 54.0f, 54.0f, 50.0f, 50.0f, 0.063f },
} };
} // namespace xrsim
+219
View File
@@ -0,0 +1,219 @@
#include "Common.h"
#include "Runtime.h"
#include "openxr/openxr_loader_negotiation.h"
#include <cstring>
namespace
{
template <typename T>
bool Assign( PFN_xrVoidFunction * out, T function )
{
*out = reinterpret_cast<PFN_xrVoidFunction>( function );
return true;
}
bool Fail( PFN_xrVoidFunction * out )
{
*out = nullptr;
return false;
}
} // namespace
namespace xrsim
{
XrResult XRAPI_CALL GetInstanceProcAddr(
XrInstance /*instance*/, const char * name, PFN_xrVoidFunction * function
)
{
if ( function == nullptr || name == nullptr )
{
return XR_ERROR_VALIDATION_FAILURE;
}
// Global functions (requested with XR_NULL_HANDLE).
if ( strcmp( name, "xrGetInstanceProcAddr" ) == 0 )
return Assign( function, &GetInstanceProcAddr ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateApiLayerProperties" ) == 0 )
return Assign( function, &EnumerateApiLayerProperties ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateInstanceExtensionProperties" ) == 0 )
return Assign( function, &EnumerateInstanceExtensionProperties )
? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateInstance" ) == 0 )
return Assign( function, &CreateInstance ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
// Instance functions.
if ( strcmp( name, "xrDestroyInstance" ) == 0 )
return Assign( function, &DestroyInstance ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetInstanceProperties" ) == 0 )
return Assign( function, &GetInstanceProperties ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetSystem" ) == 0 )
return Assign( function, &GetSystem ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetSystemProperties" ) == 0 )
return Assign( function, &GetSystemProperties ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateViewConfigurations" ) == 0 )
return Assign( function, &EnumerateViewConfigurations ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateViewConfigurationViews" ) == 0 )
return Assign( function, &EnumerateViewConfigurationViews ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateEnvironmentBlendModes" ) == 0 )
return Assign( function, &EnumerateEnvironmentBlendModes ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateSwapchainFormats" ) == 0 )
return Assign( function, &EnumerateSwapchainFormats ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
// XR_KHR_vulkan_enable2.
if ( strcmp( name, "xrGetVulkanGraphicsRequirements2KHR" ) == 0 )
return Assign( function, &GetVulkanGraphicsRequirements2KHR ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateVulkanInstanceKHR" ) == 0 )
return Assign( function, &CreateVulkanInstanceKHR ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetVulkanGraphicsDevice2KHR" ) == 0 )
return Assign( function, &GetVulkanGraphicsDevice2KHR ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateVulkanDeviceKHR" ) == 0 )
return Assign( function, &CreateVulkanDeviceKHR ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
// Session.
if ( strcmp( name, "xrCreateSession" ) == 0 )
return Assign( function, &CreateSession ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrDestroySession" ) == 0 )
return Assign( function, &DestroySession ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrBeginSession" ) == 0 )
return Assign( function, &BeginSession ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEndSession" ) == 0 )
return Assign( function, &EndSession ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrPollEvent" ) == 0 )
return Assign( function, &PollEvent ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateReferenceSpace" ) == 0 )
return Assign( function, &CreateReferenceSpace ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrDestroySpace" ) == 0 )
return Assign( function, &DestroySpace ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
// Swapchain.
if ( strcmp( name, "xrCreateSwapchain" ) == 0 )
return Assign( function, &CreateSwapchain ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrDestroySwapchain" ) == 0 )
return Assign( function, &DestroySwapchain ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEnumerateSwapchainImages" ) == 0 )
return Assign( function, &EnumerateSwapchainImages ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrAcquireSwapchainImage" ) == 0 )
return Assign( function, &AcquireSwapchainImage ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrWaitSwapchainImage" ) == 0 )
return Assign( function, &WaitSwapchainImage ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrReleaseSwapchainImage" ) == 0 )
return Assign( function, &ReleaseSwapchainImage ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
// Frame loop.
if ( strcmp( name, "xrWaitFrame" ) == 0 )
return Assign( function, &WaitFrame ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrBeginFrame" ) == 0 )
return Assign( function, &BeginFrame ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrEndFrame" ) == 0 )
return Assign( function, &EndFrame ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrLocateViews" ) == 0 )
return Assign( function, &LocateViews ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
// Paths.
if ( strcmp( name, "xrStringToPath" ) == 0 )
return Assign( function, &StringToPath ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrPathToString" ) == 0 )
return Assign( function, &PathToString ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
// Actions.
if ( strcmp( name, "xrCreateActionSet" ) == 0 )
return Assign( function, &CreateActionSet ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrDestroyActionSet" ) == 0 )
return Assign( function, &DestroyActionSet ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateAction" ) == 0 )
return Assign( function, &CreateAction ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrDestroyAction" ) == 0 )
return Assign( function, &DestroyAction ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrSuggestInteractionProfileBindings" ) == 0 )
return Assign( function, &SuggestInteractionProfileBindings ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrAttachSessionActionSets" ) == 0 )
return Assign( function, &AttachSessionActionSets ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetCurrentInteractionProfile" ) == 0 )
return Assign( function, &GetCurrentInteractionProfile ) ? XR_SUCCESS
: XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrCreateActionSpace" ) == 0 )
return Assign( function, &CreateActionSpace ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrLocateSpace" ) == 0 )
return Assign( function, &LocateSpace ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrSyncActions" ) == 0 )
return Assign( function, &SyncActions ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetActionStateBoolean" ) == 0 )
return Assign( function, &GetActionStateBoolean ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetActionStateFloat" ) == 0 )
return Assign( function, &GetActionStateFloat ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetActionStateVector2f" ) == 0 )
return Assign( function, &GetActionStateVector2f ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrGetActionStatePose" ) == 0 )
return Assign( function, &GetActionStatePose ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrApplyHapticFeedback" ) == 0 )
return Assign( function, &ApplyHapticFeedback ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
if ( strcmp( name, "xrStopHapticFeedback" ) == 0 )
return Assign( function, &StopHapticFeedback ) ? XR_SUCCESS : XR_ERROR_RUNTIME_FAILURE;
// The loader probes for every function it knows about when an instance is created, so
// missing functions are expected and intentionally not logged here.
return Fail( function ) ? XR_SUCCESS : XR_ERROR_FUNCTION_UNSUPPORTED;
}
} // namespace xrsim
// ---------------------------------------------------------------------------
// Loader negotiation entry point.
// ---------------------------------------------------------------------------
extern "C"
{
#ifdef _WIN32
__declspec( dllexport )
#endif
XrResult XRAPI_CALL xrNegotiateLoaderRuntimeInterface(
const XrNegotiateLoaderInfo * loaderInfo,
XrNegotiateRuntimeRequest * runtimeRequest
)
{
if ( loaderInfo == nullptr || runtimeRequest == nullptr )
{
return XR_ERROR_INITIALIZATION_FAILED;
}
if ( loaderInfo->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO ||
loaderInfo->structVersion != XR_LOADER_INFO_STRUCT_VERSION ||
loaderInfo->structSize != sizeof( XrNegotiateLoaderInfo ) )
{
return XR_ERROR_INITIALIZATION_FAILED;
}
// Our runtime only implements interface version 1.
if ( loaderInfo->minInterfaceVersion > XR_CURRENT_LOADER_RUNTIME_VERSION ||
loaderInfo->maxInterfaceVersion < XR_CURRENT_LOADER_RUNTIME_VERSION )
{
return XR_ERROR_INITIALIZATION_FAILED;
}
runtimeRequest->structType = XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST;
runtimeRequest->structVersion = XR_RUNTIME_INFO_STRUCT_VERSION;
runtimeRequest->structSize = sizeof( XrNegotiateRuntimeRequest );
runtimeRequest->runtimeInterfaceVersion = XR_CURRENT_LOADER_RUNTIME_VERSION;
runtimeRequest->runtimeApiVersion = XR_MAKE_VERSION( 1, 0, 28 );
runtimeRequest->getInstanceProcAddr = xrsim::GetInstanceProcAddr;
xrsim::Log( "Negotiated loader/runtime interface (api 1.0.28)" );
return XR_SUCCESS;
}
}
+1737
View File
File diff suppressed because it is too large Load Diff
+392
View File
@@ -0,0 +1,392 @@
#pragma once
// Order matters: windows.h and vulkan.h must be visible before
// openxr_platform.h when the corresponding XR_USE_* macros are defined.
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
# include <unknwn.h> // IUnknown, used by XR_MSFT_* Win32 platform structs
#endif
#include <vulkan/vulkan.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace xrsim
{
class Compositor;
struct RuntimeInstance;
struct RuntimeSession;
struct RuntimeSwapchain;
struct RuntimeSpace;
// -----------------------------------------------------------------------
// Simulation state
// -----------------------------------------------------------------------
struct TrackingState
{
float yaw = 0.0f; // radians, positive = look left
float pitch = 0.0f; // radians, positive = look up
float posX = 0.0f;
float posY = 0.0f;
float posZ = 0.0f;
float ipd = 0.064f; // interpupillary distance in metres
};
TrackingState & GetTracking();
/**
* @brief Emulated state of one controller.
*/
struct ControllerState
{
bool active = false;
XrPosef gripPose{ { 0.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f } };
XrPosef aimPose{ { 0.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f } };
float trigger = 0.0f;
float grip = 0.0f;
XrVector2f thumbstick{ 0.0f, 0.0f };
bool menu = false;
bool a = false;
bool b = false;
bool x = false;
bool y = false;
bool stickClick = false;
bool touch = false;
};
/**
* @brief Emulated input state for the simulator (index 0 = left, 1 = right).
*/
struct InputState
{
ControllerState hands[2];
};
InputState & GetInputState();
/** @brief Head orientation derived from TrackingState (identity at rest). */
XrQuaternionf GetHeadOrientation();
/** @brief Head position derived from TrackingState. */
XrVector3f GetHeadPosition();
/** @brief Rotates a vector by a quaternion. */
XrVector3f RotateByQuaternion( const XrQuaternionf & q, const XrVector3f & v );
/** @brief Interns a path string to its stable XrPath (and remembers the string). */
XrPath HashPath( const char * path );
// -----------------------------------------------------------------------
// Runtime objects
// -----------------------------------------------------------------------
struct RuntimeInstance
{
XrInstance handle = XR_NULL_HANDLE;
std::string applicationName;
std::string engineName;
std::vector<std::string> enabledExtensions;
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
VkInstance vkInstance = VK_NULL_HANDLE;
VkPhysicalDevice vkPhysicalDevice = VK_NULL_HANDLE;
VkDevice vkDevice = VK_NULL_HANDLE;
std::unique_ptr<Compositor> compositor;
// Event queue (session-state changes).
std::deque<XrEventDataBuffer> events;
bool valid = false;
};
struct RuntimeSwapchain
{
XrSwapchain handle = XR_NULL_HANDLE;
RuntimeSession * session = nullptr;
XrSwapchainCreateInfo createInfo{};
uint32_t width = 0;
uint32_t height = 0;
uint32_t arraySize = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
std::vector<VkImage> images;
std::vector<VkDeviceMemory> memories;
uint32_t nextImageIndex = 0;
int32_t acquiredIndex = -1;
int32_t lastReleasedIndex = 0;
bool valid = false;
};
struct RuntimeSpace
{
XrSpace handle = XR_NULL_HANDLE;
RuntimeSession * session = nullptr;
XrReferenceSpaceType refType = XR_REFERENCE_SPACE_TYPE_LOCAL;
bool actionSpace = false;
XrAction action = XR_NULL_HANDLE;
XrPath subactionPath = XR_NULL_PATH;
XrPosef poseInActionSpace{};
bool valid = false;
};
struct RuntimeSession
{
XrSession handle = XR_NULL_HANDLE;
RuntimeInstance * instance = nullptr;
XrSystemId systemId = XR_NULL_SYSTEM_ID;
VkInstance vkInstance = VK_NULL_HANDLE;
VkPhysicalDevice vkPhysicalDevice = VK_NULL_HANDLE;
VkDevice vkDevice = VK_NULL_HANDLE;
uint32_t queueFamilyIndex = 0;
uint32_t queueIndex = 0;
VkQueue queue = VK_NULL_HANDLE;
VkCommandPool commandPool = VK_NULL_HANDLE;
XrSessionState state = XR_SESSION_STATE_UNKNOWN;
bool running = false;
// Frame timing.
int64_t nextDisplayTime = 0;
int64_t framePeriodNs = 0;
XrTime predictedDisplayTime = 0;
bool frameBegun = false;
// Swapchains created for this session.
std::vector<RuntimeSwapchain *> swapchains;
bool valid = false;
};
RuntimeInstance * GetInstance( XrInstance handle );
RuntimeSession * GetSession( XrSession handle );
RuntimeSwapchain * GetSwapchain( XrSwapchain handle );
RuntimeSpace * GetSpace( XrSpace handle );
/**
* @brief Resolves a swapchain sub-image reference to a Vulkan image + layer.
*/
bool ResolveSubImage(
XrSwapchain swapchain, const XrSwapchainSubImage & subImage, VkImage * outImage,
uint32_t * outArrayLayer, uint32_t * outWidth, uint32_t * outHeight
);
/** @brief Pushes a session-state-changed event onto the instance's queue. */
void PushSessionStateEvent( XrInstance instance, XrSession session, XrSessionState state );
/**
* @brief The single dispatch entry point handed to the loader.
*/
XrResult XRAPI_CALL GetInstanceProcAddr(
XrInstance instance, const char * name, PFN_xrVoidFunction * function
);
// ---- global functions -------------------------------------------------
XrResult XRAPI_CALL EnumerateApiLayerProperties(
uint32_t propertyCapacityInput, uint32_t * propertyCountOutput,
XrApiLayerProperties * properties
);
XrResult XRAPI_CALL EnumerateInstanceExtensionProperties(
const char * layerName, uint32_t propertyCapacityInput, uint32_t * propertyCountOutput,
XrExtensionProperties * properties
);
XrResult XRAPI_CALL CreateInstance(
const XrInstanceCreateInfo * createInfo, XrInstance * instance
);
// ---- instance functions -----------------------------------------------
XrResult XRAPI_CALL DestroyInstance( XrInstance instance );
XrResult XRAPI_CALL GetInstanceProperties(
XrInstance instance, XrInstanceProperties * properties
);
XrResult XRAPI_CALL GetSystem(
XrInstance instance, const XrSystemGetInfo * getInfo, XrSystemId * systemId
);
XrResult XRAPI_CALL GetSystemProperties(
XrInstance instance, XrSystemId systemId, XrSystemProperties * properties
);
XrResult XRAPI_CALL EnumerateViewConfigurations(
XrInstance instance, XrSystemId systemId, uint32_t viewConfigurationTypeCapacityInput,
uint32_t * viewConfigurationTypeCountOutput, XrViewConfigurationType * viewConfigurationTypes
);
XrResult XRAPI_CALL EnumerateViewConfigurationViews(
XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType,
uint32_t viewCapacityInput, uint32_t * viewCountOutput, XrViewConfigurationView * views
);
XrResult XRAPI_CALL EnumerateEnvironmentBlendModes(
XrInstance instance, XrSystemId systemId, XrViewConfigurationType viewConfigurationType,
uint32_t environmentBlendModeCapacityInput, uint32_t * environmentBlendModeCountOutput,
XrEnvironmentBlendMode * environmentBlendModes
);
XrResult XRAPI_CALL EnumerateSwapchainFormats(
XrSession session, uint32_t formatCapacityInput, uint32_t * formatCountOutput,
int64_t * formats
);
// ---- XR_KHR_vulkan_enable2 --------------------------------------------
XrResult XRAPI_CALL GetVulkanGraphicsRequirements2KHR(
XrInstance instance, XrSystemId systemId,
XrGraphicsRequirementsVulkanKHR * graphicsRequirements
);
XrResult XRAPI_CALL CreateVulkanInstanceKHR(
XrInstance instance, const XrVulkanInstanceCreateInfoKHR * createInfo,
VkInstance * vulkanInstance, VkResult * vulkanResult
);
XrResult XRAPI_CALL GetVulkanGraphicsDevice2KHR(
XrInstance instance, const XrVulkanGraphicsDeviceGetInfoKHR * getInfo,
VkPhysicalDevice * vulkanPhysicalDevice
);
XrResult XRAPI_CALL CreateVulkanDeviceKHR(
XrInstance instance, const XrVulkanDeviceCreateInfoKHR * createInfo, VkDevice * vulkanDevice,
VkResult * vulkanResult
);
// ---- session ----------------------------------------------------------
XrResult XRAPI_CALL CreateSession(
XrInstance instance, const XrSessionCreateInfo * createInfo, XrSession * session
);
XrResult XRAPI_CALL DestroySession( XrSession session );
XrResult XRAPI_CALL BeginSession( XrSession session, const XrSessionBeginInfo * beginInfo );
XrResult XRAPI_CALL EndSession( XrSession session );
XrResult XRAPI_CALL PollEvent( XrInstance instance, XrEventDataBuffer * eventData );
XrResult XRAPI_CALL CreateReferenceSpace(
XrSession session, const XrReferenceSpaceCreateInfo * createInfo, XrSpace * space
);
XrResult XRAPI_CALL DestroySpace( XrSpace space );
// ---- swapchain --------------------------------------------------------
XrResult XRAPI_CALL CreateSwapchain(
XrSession session, const XrSwapchainCreateInfo * createInfo, XrSwapchain * swapchain
);
XrResult XRAPI_CALL DestroySwapchain( XrSwapchain swapchain );
XrResult XRAPI_CALL EnumerateSwapchainImages(
XrSwapchain swapchain, uint32_t imageCapacityInput, uint32_t * imageCountOutput,
XrSwapchainImageBaseHeader * images
);
XrResult XRAPI_CALL AcquireSwapchainImage(
XrSwapchain swapchain, const XrSwapchainImageAcquireInfo * acquireInfo, uint32_t * index
);
XrResult XRAPI_CALL WaitSwapchainImage(
XrSwapchain swapchain, const XrSwapchainImageWaitInfo * waitInfo
);
XrResult XRAPI_CALL ReleaseSwapchainImage(
XrSwapchain swapchain, const XrSwapchainImageReleaseInfo * releaseInfo
);
// ---- frame loop -------------------------------------------------------
XrResult XRAPI_CALL WaitFrame(
XrSession session, const XrFrameWaitInfo * frameWaitInfo, XrFrameState * frameState
);
XrResult XRAPI_CALL BeginFrame( XrSession session, const XrFrameBeginInfo * frameBeginInfo );
XrResult XRAPI_CALL EndFrame( XrSession session, const XrFrameEndInfo * frameEndInfo );
XrResult XRAPI_CALL LocateViews(
XrSession session, const XrViewLocateInfo * viewLocateInfo, XrViewState * viewState,
uint32_t viewCapacityInput, uint32_t * viewCountOutput, XrView * views
);
// ---- paths ------------------------------------------------------------
XrResult XRAPI_CALL StringToPath( XrInstance instance, const char * pathString, XrPath * path );
XrResult XRAPI_CALL PathToString(
XrInstance instance, XrPath path, uint32_t bufferCapacityInput, uint32_t * bufferCountOutput,
char * buffer
);
// ---- actions ----------------------------------------------------------
XrResult XRAPI_CALL CreateActionSet(
XrInstance instance, const XrActionSetCreateInfo * createInfo, XrActionSet * actionSet
);
XrResult XRAPI_CALL DestroyActionSet( XrActionSet actionSet );
XrResult XRAPI_CALL CreateAction(
XrActionSet actionSet, const XrActionCreateInfo * createInfo, XrAction * action
);
XrResult XRAPI_CALL DestroyAction( XrAction action );
XrResult XRAPI_CALL SuggestInteractionProfileBindings(
XrInstance instance, const XrInteractionProfileSuggestedBinding * suggestedBindings
);
XrResult XRAPI_CALL AttachSessionActionSets(
XrSession session, const XrSessionActionSetsAttachInfo * attachInfo
);
XrResult XRAPI_CALL GetCurrentInteractionProfile(
XrSession session, XrPath topLevelUserPath, XrInteractionProfileState * interactionProfile
);
XrResult XRAPI_CALL CreateActionSpace(
XrSession session, const XrActionSpaceCreateInfo * createInfo, XrSpace * space
);
XrResult XRAPI_CALL LocateSpace(
XrSpace space, XrSpace baseSpace, XrTime time, XrSpaceLocation * location
);
XrResult XRAPI_CALL SyncActions( XrSession session, const XrActionsSyncInfo * syncInfo );
XrResult XRAPI_CALL GetActionStateBoolean(
XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateBoolean * state
);
XrResult XRAPI_CALL GetActionStateFloat(
XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateFloat * state
);
XrResult XRAPI_CALL GetActionStateVector2f(
XrSession session, const XrActionStateGetInfo * getInfo, XrActionStateVector2f * state
);
XrResult XRAPI_CALL GetActionStatePose(
XrSession session, const XrActionStateGetInfo * getInfo, XrActionStatePose * state
);
XrResult XRAPI_CALL ApplyHapticFeedback(
XrSession session, const XrHapticActionInfo * hapticActionInfo,
const XrHapticBaseHeader * hapticFeedback
);
XrResult XRAPI_CALL StopHapticFeedback(
XrSession session, const XrHapticActionInfo * hapticActionInfo
);
} // namespace xrsim
+6135
View File
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) 2017-2023, The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG, Inc.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
//
// Initial Author: Mark Young <marky@lunarg.com>
//
#pragma once
#include <openxr/openxr.h>
#ifdef __cplusplus
extern "C" {
#endif
// Forward declare.
typedef struct XrApiLayerCreateInfo XrApiLayerCreateInfo;
// Function pointer prototype for the xrCreateApiLayerInstance function used in place of xrCreateInstance.
// This function allows us to pass special API layer information to each layer during the process of creating an Instance.
typedef XrResult(XRAPI_PTR *PFN_xrCreateApiLayerInstance)(const XrInstanceCreateInfo *info,
const XrApiLayerCreateInfo *apiLayerInfo, XrInstance *instance);
// Loader/API Layer Interface versions
// 1 - First version, introduces negotiation structure and functions
#define XR_CURRENT_LOADER_API_LAYER_VERSION 1
// Loader/Runtime Interface versions
// 1 - First version, introduces negotiation structure and functions
#define XR_CURRENT_LOADER_RUNTIME_VERSION 1
// Version negotiation values
typedef enum XrLoaderInterfaceStructs {
XR_LOADER_INTERFACE_STRUCT_UNINTIALIZED = 0,
XR_LOADER_INTERFACE_STRUCT_LOADER_INFO,
XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST,
XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST,
XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO,
XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO,
} XrLoaderInterfaceStructs;
#define XR_LOADER_INFO_STRUCT_VERSION 1
typedef struct XrNegotiateLoaderInfo {
XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_LOADER_INFO
uint32_t structVersion; // XR_LOADER_INFO_STRUCT_VERSION
size_t structSize; // sizeof(XrNegotiateLoaderInfo)
uint32_t minInterfaceVersion;
uint32_t maxInterfaceVersion;
XrVersion minApiVersion;
XrVersion maxApiVersion;
} XrNegotiateLoaderInfo;
#define XR_API_LAYER_INFO_STRUCT_VERSION 1
typedef struct XrNegotiateApiLayerRequest {
XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST
uint32_t structVersion; // XR_API_LAYER_INFO_STRUCT_VERSION
size_t structSize; // sizeof(XrNegotiateApiLayerRequest)
uint32_t layerInterfaceVersion; // CURRENT_LOADER_API_LAYER_VERSION
XrVersion layerApiVersion;
PFN_xrGetInstanceProcAddr getInstanceProcAddr;
PFN_xrCreateApiLayerInstance createApiLayerInstance;
} XrNegotiateApiLayerRequest;
#define XR_RUNTIME_INFO_STRUCT_VERSION 1
typedef struct XrNegotiateRuntimeRequest {
XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST
uint32_t structVersion; // XR_RUNTIME_INFO_STRUCT_VERSION
size_t structSize; // sizeof(XrNegotiateRuntimeRequest)
uint32_t runtimeInterfaceVersion; // CURRENT_LOADER_RUNTIME_VERSION
XrVersion runtimeApiVersion;
PFN_xrGetInstanceProcAddr getInstanceProcAddr;
} XrNegotiateRuntimeRequest;
// Function used to negotiate an interface betewen the loader and an API layer. Each library exposing one or
// more API layers needs to expose at least this function.
typedef XrResult(XRAPI_PTR *PFN_xrNegotiateLoaderApiLayerInterface)(const XrNegotiateLoaderInfo *loaderInfo,
const char *apiLayerName,
XrNegotiateApiLayerRequest *apiLayerRequest);
// Function used to negotiate an interface betewen the loader and a runtime. Each runtime should expose
// at least this function.
typedef XrResult(XRAPI_PTR *PFN_xrNegotiateLoaderRuntimeInterface)(const XrNegotiateLoaderInfo *loaderInfo,
XrNegotiateRuntimeRequest *runtimeRequest);
// Forward declare.
typedef struct XrApiLayerNextInfo XrApiLayerNextInfo;
#define XR_API_LAYER_NEXT_INFO_STRUCT_VERSION 1
struct XrApiLayerNextInfo {
XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO
uint32_t structVersion; // XR_API_LAYER_NEXT_INFO_STRUCT_VERSION
size_t structSize; // sizeof(XrApiLayerNextInfo)
char layerName[XR_MAX_API_LAYER_NAME_SIZE]; // Name of API layer which should receive this info
PFN_xrGetInstanceProcAddr nextGetInstanceProcAddr; // Pointer to next API layer's xrGetInstanceProcAddr
PFN_xrCreateApiLayerInstance nextCreateApiLayerInstance; // Pointer to next API layer's xrCreateApiLayerInstance
XrApiLayerNextInfo *next; // Pointer to the next API layer info in the sequence
};
#define XR_API_LAYER_MAX_SETTINGS_PATH_SIZE 512
#define XR_API_LAYER_CREATE_INFO_STRUCT_VERSION 1
typedef struct XrApiLayerCreateInfo {
XrLoaderInterfaceStructs structType; // XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO
uint32_t structVersion; // XR_API_LAYER_CREATE_INFO_STRUCT_VERSION
size_t structSize; // sizeof(XrApiLayerCreateInfo)
void *loaderInstance; // Pointer to the LoaderInstance class
char settings_file_location[XR_API_LAYER_MAX_SETTINGS_PATH_SIZE]; // Location to the found settings file (or empty '\0')
XrApiLayerNextInfo *nextInfo; // Pointer to the next API layer's Info
} XrApiLayerCreateInfo;
#ifdef __cplusplus
} // extern "C"
#endif
+714
View File
@@ -0,0 +1,714 @@
#ifndef OPENXR_PLATFORM_H_
#define OPENXR_PLATFORM_H_ 1
/*
** Copyright 2017-2023 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
** This header is generated from the Khronos OpenXR XML API Registry.
**
*/
#include "openxr.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_KHR_android_thread_settings 1
#define XR_KHR_android_thread_settings_SPEC_VERSION 5
#define XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME "XR_KHR_android_thread_settings"
typedef enum XrAndroidThreadTypeKHR {
XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR = 1,
XR_ANDROID_THREAD_TYPE_APPLICATION_WORKER_KHR = 2,
XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR = 3,
XR_ANDROID_THREAD_TYPE_RENDERER_WORKER_KHR = 4,
XR_ANDROID_THREAD_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF
} XrAndroidThreadTypeKHR;
typedef XrResult (XRAPI_PTR *PFN_xrSetAndroidApplicationThreadKHR)(XrSession session, XrAndroidThreadTypeKHR threadType, uint32_t threadId);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrSetAndroidApplicationThreadKHR(
XrSession session,
XrAndroidThreadTypeKHR threadType,
uint32_t threadId);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_KHR_android_surface_swapchain 1
#define XR_KHR_android_surface_swapchain_SPEC_VERSION 4
#define XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME "XR_KHR_android_surface_swapchain"
typedef XrResult (XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo* info, XrSwapchain* swapchain, jobject* surface);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrCreateSwapchainAndroidSurfaceKHR(
XrSession session,
const XrSwapchainCreateInfo* info,
XrSwapchain* swapchain,
jobject* surface);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_KHR_android_create_instance 1
#define XR_KHR_android_create_instance_SPEC_VERSION 3
#define XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME "XR_KHR_android_create_instance"
// XrInstanceCreateInfoAndroidKHR extends XrInstanceCreateInfo
typedef struct XrInstanceCreateInfoAndroidKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
void* XR_MAY_ALIAS applicationVM;
void* XR_MAY_ALIAS applicationActivity;
} XrInstanceCreateInfoAndroidKHR;
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_KHR_vulkan_swapchain_format_list 1
#define XR_KHR_vulkan_swapchain_format_list_SPEC_VERSION 4
#define XR_KHR_VULKAN_SWAPCHAIN_FORMAT_LIST_EXTENSION_NAME "XR_KHR_vulkan_swapchain_format_list"
typedef struct XrVulkanSwapchainFormatListCreateInfoKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
uint32_t viewFormatCount;
const VkFormat* viewFormats;
} XrVulkanSwapchainFormatListCreateInfoKHR;
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef XR_USE_GRAPHICS_API_OPENGL
#define XR_KHR_opengl_enable 1
#define XR_KHR_opengl_enable_SPEC_VERSION 10
#define XR_KHR_OPENGL_ENABLE_EXTENSION_NAME "XR_KHR_opengl_enable"
#ifdef XR_USE_PLATFORM_WIN32
// XrGraphicsBindingOpenGLWin32KHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingOpenGLWin32KHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
HDC hDC;
HGLRC hGLRC;
} XrGraphicsBindingOpenGLWin32KHR;
#endif // XR_USE_PLATFORM_WIN32
#ifdef XR_USE_PLATFORM_XLIB
// XrGraphicsBindingOpenGLXlibKHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingOpenGLXlibKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
Display* xDisplay;
uint32_t visualid;
GLXFBConfig glxFBConfig;
GLXDrawable glxDrawable;
GLXContext glxContext;
} XrGraphicsBindingOpenGLXlibKHR;
#endif // XR_USE_PLATFORM_XLIB
#ifdef XR_USE_PLATFORM_XCB
// XrGraphicsBindingOpenGLXcbKHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingOpenGLXcbKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
xcb_connection_t* connection;
uint32_t screenNumber;
xcb_glx_fbconfig_t fbconfigid;
xcb_visualid_t visualid;
xcb_glx_drawable_t glxDrawable;
xcb_glx_context_t glxContext;
} XrGraphicsBindingOpenGLXcbKHR;
#endif // XR_USE_PLATFORM_XCB
#ifdef XR_USE_PLATFORM_WAYLAND
// XrGraphicsBindingOpenGLWaylandKHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingOpenGLWaylandKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
struct wl_display* display;
} XrGraphicsBindingOpenGLWaylandKHR;
#endif // XR_USE_PLATFORM_WAYLAND
typedef struct XrSwapchainImageOpenGLKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
uint32_t image;
} XrSwapchainImageOpenGLKHR;
typedef struct XrGraphicsRequirementsOpenGLKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
XrVersion minApiVersionSupported;
XrVersion maxApiVersionSupported;
} XrGraphicsRequirementsOpenGLKHR;
typedef XrResult (XRAPI_PTR *PFN_xrGetOpenGLGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsOpenGLKHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetOpenGLGraphicsRequirementsKHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsOpenGLKHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_OPENGL */
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
#define XR_KHR_opengl_es_enable 1
#define XR_KHR_opengl_es_enable_SPEC_VERSION 8
#define XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME "XR_KHR_opengl_es_enable"
#ifdef XR_USE_PLATFORM_ANDROID
// XrGraphicsBindingOpenGLESAndroidKHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingOpenGLESAndroidKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
EGLDisplay display;
EGLConfig config;
EGLContext context;
} XrGraphicsBindingOpenGLESAndroidKHR;
#endif // XR_USE_PLATFORM_ANDROID
typedef struct XrSwapchainImageOpenGLESKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
uint32_t image;
} XrSwapchainImageOpenGLESKHR;
typedef struct XrGraphicsRequirementsOpenGLESKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
XrVersion minApiVersionSupported;
XrVersion maxApiVersionSupported;
} XrGraphicsRequirementsOpenGLESKHR;
typedef XrResult (XRAPI_PTR *PFN_xrGetOpenGLESGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsOpenGLESKHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetOpenGLESGraphicsRequirementsKHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsOpenGLESKHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_OPENGL_ES */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_KHR_vulkan_enable 1
#define XR_KHR_vulkan_enable_SPEC_VERSION 8
#define XR_KHR_VULKAN_ENABLE_EXTENSION_NAME "XR_KHR_vulkan_enable"
// XrGraphicsBindingVulkanKHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingVulkanKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
VkInstance instance;
VkPhysicalDevice physicalDevice;
VkDevice device;
uint32_t queueFamilyIndex;
uint32_t queueIndex;
} XrGraphicsBindingVulkanKHR;
typedef struct XrSwapchainImageVulkanKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
VkImage image;
} XrSwapchainImageVulkanKHR;
typedef struct XrGraphicsRequirementsVulkanKHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
XrVersion minApiVersionSupported;
XrVersion maxApiVersionSupported;
} XrGraphicsRequirementsVulkanKHR;
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanInstanceExtensionsKHR)(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer);
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanDeviceExtensionsKHR)(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t* bufferCountOutput, char* buffer);
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsDeviceKHR)(XrInstance instance, XrSystemId systemId, VkInstance vkInstance, VkPhysicalDevice* vkPhysicalDevice);
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsVulkanKHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanInstanceExtensionsKHR(
XrInstance instance,
XrSystemId systemId,
uint32_t bufferCapacityInput,
uint32_t* bufferCountOutput,
char* buffer);
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanDeviceExtensionsKHR(
XrInstance instance,
XrSystemId systemId,
uint32_t bufferCapacityInput,
uint32_t* bufferCountOutput,
char* buffer);
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsDeviceKHR(
XrInstance instance,
XrSystemId systemId,
VkInstance vkInstance,
VkPhysicalDevice* vkPhysicalDevice);
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsRequirementsKHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsVulkanKHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef XR_USE_GRAPHICS_API_D3D11
#define XR_KHR_D3D11_enable 1
#define XR_KHR_D3D11_enable_SPEC_VERSION 9
#define XR_KHR_D3D11_ENABLE_EXTENSION_NAME "XR_KHR_D3D11_enable"
// XrGraphicsBindingD3D11KHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingD3D11KHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
ID3D11Device* device;
} XrGraphicsBindingD3D11KHR;
typedef struct XrSwapchainImageD3D11KHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
ID3D11Texture2D* texture;
} XrSwapchainImageD3D11KHR;
typedef struct XrGraphicsRequirementsD3D11KHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
LUID adapterLuid;
D3D_FEATURE_LEVEL minFeatureLevel;
} XrGraphicsRequirementsD3D11KHR;
typedef XrResult (XRAPI_PTR *PFN_xrGetD3D11GraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D11KHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetD3D11GraphicsRequirementsKHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsD3D11KHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_D3D11 */
#ifdef XR_USE_GRAPHICS_API_D3D12
#define XR_KHR_D3D12_enable 1
#define XR_KHR_D3D12_enable_SPEC_VERSION 9
#define XR_KHR_D3D12_ENABLE_EXTENSION_NAME "XR_KHR_D3D12_enable"
// XrGraphicsBindingD3D12KHR extends XrSessionCreateInfo
typedef struct XrGraphicsBindingD3D12KHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
ID3D12Device* device;
ID3D12CommandQueue* queue;
} XrGraphicsBindingD3D12KHR;
typedef struct XrSwapchainImageD3D12KHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
ID3D12Resource* texture;
} XrSwapchainImageD3D12KHR;
typedef struct XrGraphicsRequirementsD3D12KHR {
XrStructureType type;
void* XR_MAY_ALIAS next;
LUID adapterLuid;
D3D_FEATURE_LEVEL minFeatureLevel;
} XrGraphicsRequirementsD3D12KHR;
typedef XrResult (XRAPI_PTR *PFN_xrGetD3D12GraphicsRequirementsKHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D12KHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetD3D12GraphicsRequirementsKHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsD3D12KHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_D3D12 */
#ifdef XR_USE_PLATFORM_WIN32
#define XR_KHR_win32_convert_performance_counter_time 1
#define XR_KHR_win32_convert_performance_counter_time_SPEC_VERSION 1
#define XR_KHR_WIN32_CONVERT_PERFORMANCE_COUNTER_TIME_EXTENSION_NAME "XR_KHR_win32_convert_performance_counter_time"
typedef XrResult (XRAPI_PTR *PFN_xrConvertWin32PerformanceCounterToTimeKHR)(XrInstance instance, const LARGE_INTEGER* performanceCounter, XrTime* time);
typedef XrResult (XRAPI_PTR *PFN_xrConvertTimeToWin32PerformanceCounterKHR)(XrInstance instance, XrTime time, LARGE_INTEGER* performanceCounter);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrConvertWin32PerformanceCounterToTimeKHR(
XrInstance instance,
const LARGE_INTEGER* performanceCounter,
XrTime* time);
XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimeToWin32PerformanceCounterKHR(
XrInstance instance,
XrTime time,
LARGE_INTEGER* performanceCounter);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_WIN32 */
#ifdef XR_USE_TIMESPEC
#define XR_KHR_convert_timespec_time 1
#define XR_KHR_convert_timespec_time_SPEC_VERSION 1
#define XR_KHR_CONVERT_TIMESPEC_TIME_EXTENSION_NAME "XR_KHR_convert_timespec_time"
typedef XrResult (XRAPI_PTR *PFN_xrConvertTimespecTimeToTimeKHR)(XrInstance instance, const struct timespec* timespecTime, XrTime* time);
typedef XrResult (XRAPI_PTR *PFN_xrConvertTimeToTimespecTimeKHR)(XrInstance instance, XrTime time, struct timespec* timespecTime);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimespecTimeToTimeKHR(
XrInstance instance,
const struct timespec* timespecTime,
XrTime* time);
XRAPI_ATTR XrResult XRAPI_CALL xrConvertTimeToTimespecTimeKHR(
XrInstance instance,
XrTime time,
struct timespec* timespecTime);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_TIMESPEC */
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_KHR_loader_init_android 1
#define XR_KHR_loader_init_android_SPEC_VERSION 1
#define XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME "XR_KHR_loader_init_android"
typedef struct XrLoaderInitInfoAndroidKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
void* XR_MAY_ALIAS applicationVM;
void* XR_MAY_ALIAS applicationContext;
} XrLoaderInitInfoAndroidKHR;
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_KHR_vulkan_enable2 1
#define XR_KHR_vulkan_enable2_SPEC_VERSION 2
#define XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME "XR_KHR_vulkan_enable2"
typedef XrFlags64 XrVulkanInstanceCreateFlagsKHR;
// Flag bits for XrVulkanInstanceCreateFlagsKHR
typedef XrFlags64 XrVulkanDeviceCreateFlagsKHR;
// Flag bits for XrVulkanDeviceCreateFlagsKHR
typedef struct XrVulkanInstanceCreateInfoKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
XrSystemId systemId;
XrVulkanInstanceCreateFlagsKHR createFlags;
PFN_vkGetInstanceProcAddr pfnGetInstanceProcAddr;
const VkInstanceCreateInfo* vulkanCreateInfo;
const VkAllocationCallbacks* vulkanAllocator;
} XrVulkanInstanceCreateInfoKHR;
typedef struct XrVulkanDeviceCreateInfoKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
XrSystemId systemId;
XrVulkanDeviceCreateFlagsKHR createFlags;
PFN_vkGetInstanceProcAddr pfnGetInstanceProcAddr;
VkPhysicalDevice vulkanPhysicalDevice;
const VkDeviceCreateInfo* vulkanCreateInfo;
const VkAllocationCallbacks* vulkanAllocator;
} XrVulkanDeviceCreateInfoKHR;
typedef XrGraphicsBindingVulkanKHR XrGraphicsBindingVulkan2KHR;
typedef struct XrVulkanGraphicsDeviceGetInfoKHR {
XrStructureType type;
const void* XR_MAY_ALIAS next;
XrSystemId systemId;
VkInstance vulkanInstance;
} XrVulkanGraphicsDeviceGetInfoKHR;
typedef XrSwapchainImageVulkanKHR XrSwapchainImageVulkan2KHR;
typedef XrGraphicsRequirementsVulkanKHR XrGraphicsRequirementsVulkan2KHR;
typedef XrResult (XRAPI_PTR *PFN_xrCreateVulkanInstanceKHR)(XrInstance instance, const XrVulkanInstanceCreateInfoKHR* createInfo, VkInstance* vulkanInstance, VkResult* vulkanResult);
typedef XrResult (XRAPI_PTR *PFN_xrCreateVulkanDeviceKHR)(XrInstance instance, const XrVulkanDeviceCreateInfoKHR* createInfo, VkDevice* vulkanDevice, VkResult* vulkanResult);
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsDevice2KHR)(XrInstance instance, const XrVulkanGraphicsDeviceGetInfoKHR* getInfo, VkPhysicalDevice* vulkanPhysicalDevice);
typedef XrResult (XRAPI_PTR *PFN_xrGetVulkanGraphicsRequirements2KHR)(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsVulkanKHR* graphicsRequirements);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrCreateVulkanInstanceKHR(
XrInstance instance,
const XrVulkanInstanceCreateInfoKHR* createInfo,
VkInstance* vulkanInstance,
VkResult* vulkanResult);
XRAPI_ATTR XrResult XRAPI_CALL xrCreateVulkanDeviceKHR(
XrInstance instance,
const XrVulkanDeviceCreateInfoKHR* createInfo,
VkDevice* vulkanDevice,
VkResult* vulkanResult);
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsDevice2KHR(
XrInstance instance,
const XrVulkanGraphicsDeviceGetInfoKHR* getInfo,
VkPhysicalDevice* vulkanPhysicalDevice);
XRAPI_ATTR XrResult XRAPI_CALL xrGetVulkanGraphicsRequirements2KHR(
XrInstance instance,
XrSystemId systemId,
XrGraphicsRequirementsVulkanKHR* graphicsRequirements);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef XR_USE_PLATFORM_EGL
#define XR_MNDX_egl_enable 1
#define XR_MNDX_egl_enable_SPEC_VERSION 1
#define XR_MNDX_EGL_ENABLE_EXTENSION_NAME "XR_MNDX_egl_enable"
// XrGraphicsBindingEGLMNDX extends XrSessionCreateInfo
typedef struct XrGraphicsBindingEGLMNDX {
XrStructureType type;
const void* XR_MAY_ALIAS next;
PFNEGLGETPROCADDRESSPROC getProcAddress;
EGLDisplay display;
EGLConfig config;
EGLContext context;
} XrGraphicsBindingEGLMNDX;
#endif /* XR_USE_PLATFORM_EGL */
#ifdef XR_USE_PLATFORM_WIN32
#define XR_MSFT_perception_anchor_interop 1
#define XR_MSFT_perception_anchor_interop_SPEC_VERSION 1
#define XR_MSFT_PERCEPTION_ANCHOR_INTEROP_EXTENSION_NAME "XR_MSFT_perception_anchor_interop"
typedef XrResult (XRAPI_PTR *PFN_xrCreateSpatialAnchorFromPerceptionAnchorMSFT)(XrSession session, IUnknown* perceptionAnchor, XrSpatialAnchorMSFT* anchor);
typedef XrResult (XRAPI_PTR *PFN_xrTryGetPerceptionAnchorFromSpatialAnchorMSFT)(XrSession session, XrSpatialAnchorMSFT anchor, IUnknown** perceptionAnchor);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpatialAnchorFromPerceptionAnchorMSFT(
XrSession session,
IUnknown* perceptionAnchor,
XrSpatialAnchorMSFT* anchor);
XRAPI_ATTR XrResult XRAPI_CALL xrTryGetPerceptionAnchorFromSpatialAnchorMSFT(
XrSession session,
XrSpatialAnchorMSFT anchor,
IUnknown** perceptionAnchor);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_WIN32 */
#ifdef XR_USE_PLATFORM_WIN32
#define XR_MSFT_holographic_window_attachment 1
#define XR_MSFT_holographic_window_attachment_SPEC_VERSION 1
#define XR_MSFT_HOLOGRAPHIC_WINDOW_ATTACHMENT_EXTENSION_NAME "XR_MSFT_holographic_window_attachment"
#ifdef XR_USE_PLATFORM_WIN32
// XrHolographicWindowAttachmentMSFT extends XrSessionCreateInfo
typedef struct XrHolographicWindowAttachmentMSFT {
XrStructureType type;
const void* XR_MAY_ALIAS next;
IUnknown* holographicSpace;
IUnknown* coreWindow;
} XrHolographicWindowAttachmentMSFT;
#endif // XR_USE_PLATFORM_WIN32
#endif /* XR_USE_PLATFORM_WIN32 */
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_FB_android_surface_swapchain_create 1
#define XR_FB_android_surface_swapchain_create_SPEC_VERSION 1
#define XR_FB_ANDROID_SURFACE_SWAPCHAIN_CREATE_EXTENSION_NAME "XR_FB_android_surface_swapchain_create"
typedef XrFlags64 XrAndroidSurfaceSwapchainFlagsFB;
// Flag bits for XrAndroidSurfaceSwapchainFlagsFB
static const XrAndroidSurfaceSwapchainFlagsFB XR_ANDROID_SURFACE_SWAPCHAIN_SYNCHRONOUS_BIT_FB = 0x00000001;
static const XrAndroidSurfaceSwapchainFlagsFB XR_ANDROID_SURFACE_SWAPCHAIN_USE_TIMESTAMPS_BIT_FB = 0x00000002;
#ifdef XR_USE_PLATFORM_ANDROID
// XrAndroidSurfaceSwapchainCreateInfoFB extends XrSwapchainCreateInfo
typedef struct XrAndroidSurfaceSwapchainCreateInfoFB {
XrStructureType type;
const void* XR_MAY_ALIAS next;
XrAndroidSurfaceSwapchainFlagsFB createFlags;
} XrAndroidSurfaceSwapchainCreateInfoFB;
#endif // XR_USE_PLATFORM_ANDROID
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_PLATFORM_ML
#define XR_ML_compat 1
#define XR_ML_compat_SPEC_VERSION 1
#define XR_ML_COMPAT_EXTENSION_NAME "XR_ML_compat"
typedef struct XrCoordinateSpaceCreateInfoML {
XrStructureType type;
const void* XR_MAY_ALIAS next;
MLCoordinateFrameUID cfuid;
XrPosef poseInCoordinateSpace;
} XrCoordinateSpaceCreateInfoML;
typedef XrResult (XRAPI_PTR *PFN_xrCreateSpaceFromCoordinateFrameUIDML)(XrSession session, const XrCoordinateSpaceCreateInfoML *createInfo, XrSpace* space);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrCreateSpaceFromCoordinateFrameUIDML(
XrSession session,
const XrCoordinateSpaceCreateInfoML * createInfo,
XrSpace* space);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_ML */
#ifdef XR_USE_PLATFORM_WIN32
#define XR_OCULUS_audio_device_guid 1
#define XR_OCULUS_audio_device_guid_SPEC_VERSION 1
#define XR_OCULUS_AUDIO_DEVICE_GUID_EXTENSION_NAME "XR_OCULUS_audio_device_guid"
#define XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS 128
typedef XrResult (XRAPI_PTR *PFN_xrGetAudioOutputDeviceGuidOculus)(XrInstance instance, wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]);
typedef XrResult (XRAPI_PTR *PFN_xrGetAudioInputDeviceGuidOculus)(XrInstance instance, wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]);
#ifndef XR_NO_PROTOTYPES
#ifdef XR_EXTENSION_PROTOTYPES
XRAPI_ATTR XrResult XRAPI_CALL xrGetAudioOutputDeviceGuidOculus(
XrInstance instance,
wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]);
XRAPI_ATTR XrResult XRAPI_CALL xrGetAudioInputDeviceGuidOculus(
XrInstance instance,
wchar_t buffer[XR_MAX_AUDIO_DEVICE_STR_SIZE_OCULUS]);
#endif /* XR_EXTENSION_PROTOTYPES */
#endif /* !XR_NO_PROTOTYPES */
#endif /* XR_USE_PLATFORM_WIN32 */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_FB_foveation_vulkan 1
#define XR_FB_foveation_vulkan_SPEC_VERSION 1
#define XR_FB_FOVEATION_VULKAN_EXTENSION_NAME "XR_FB_foveation_vulkan"
// XrSwapchainImageFoveationVulkanFB extends XrSwapchainImageVulkanKHR
typedef struct XrSwapchainImageFoveationVulkanFB {
XrStructureType type;
void* XR_MAY_ALIAS next;
VkImage image;
uint32_t width;
uint32_t height;
} XrSwapchainImageFoveationVulkanFB;
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef XR_USE_PLATFORM_ANDROID
#define XR_FB_swapchain_update_state_android_surface 1
#define XR_FB_swapchain_update_state_android_surface_SPEC_VERSION 1
#define XR_FB_SWAPCHAIN_UPDATE_STATE_ANDROID_SURFACE_EXTENSION_NAME "XR_FB_swapchain_update_state_android_surface"
#ifdef XR_USE_PLATFORM_ANDROID
typedef struct XrSwapchainStateAndroidSurfaceDimensionsFB {
XrStructureType type;
void* XR_MAY_ALIAS next;
uint32_t width;
uint32_t height;
} XrSwapchainStateAndroidSurfaceDimensionsFB;
#endif // XR_USE_PLATFORM_ANDROID
#endif /* XR_USE_PLATFORM_ANDROID */
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
#define XR_FB_swapchain_update_state_opengl_es 1
#define XR_FB_swapchain_update_state_opengl_es_SPEC_VERSION 1
#define XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME "XR_FB_swapchain_update_state_opengl_es"
#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
typedef struct XrSwapchainStateSamplerOpenGLESFB {
XrStructureType type;
void* XR_MAY_ALIAS next;
EGLenum minFilter;
EGLenum magFilter;
EGLenum wrapModeS;
EGLenum wrapModeT;
EGLenum swizzleRed;
EGLenum swizzleGreen;
EGLenum swizzleBlue;
EGLenum swizzleAlpha;
float maxAnisotropy;
XrColor4f borderColor;
} XrSwapchainStateSamplerOpenGLESFB;
#endif // XR_USE_GRAPHICS_API_OPENGL_ES
#endif /* XR_USE_GRAPHICS_API_OPENGL_ES */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_FB_swapchain_update_state_vulkan 1
#define XR_FB_swapchain_update_state_vulkan_SPEC_VERSION 1
#define XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME "XR_FB_swapchain_update_state_vulkan"
#ifdef XR_USE_GRAPHICS_API_VULKAN
typedef struct XrSwapchainStateSamplerVulkanFB {
XrStructureType type;
void* XR_MAY_ALIAS next;
VkFilter minFilter;
VkFilter magFilter;
VkSamplerMipmapMode mipmapMode;
VkSamplerAddressMode wrapModeS;
VkSamplerAddressMode wrapModeT;
VkComponentSwizzle swizzleRed;
VkComponentSwizzle swizzleGreen;
VkComponentSwizzle swizzleBlue;
VkComponentSwizzle swizzleAlpha;
float maxAnisotropy;
XrColor4f borderColor;
} XrSwapchainStateSamplerVulkanFB;
#endif // XR_USE_GRAPHICS_API_VULKAN
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef XR_USE_GRAPHICS_API_VULKAN
#define XR_META_vulkan_swapchain_create_info 1
#define XR_META_vulkan_swapchain_create_info_SPEC_VERSION 1
#define XR_META_VULKAN_SWAPCHAIN_CREATE_INFO_EXTENSION_NAME "XR_META_vulkan_swapchain_create_info"
// XrVulkanSwapchainCreateInfoMETA extends XrSwapchainCreateInfo
typedef struct XrVulkanSwapchainCreateInfoMETA {
XrStructureType type;
const void* XR_MAY_ALIAS next;
VkImageCreateFlags additionalCreateFlags;
VkImageUsageFlags additionalUsageFlags;
} XrVulkanSwapchainCreateInfoMETA;
#endif /* XR_USE_GRAPHICS_API_VULKAN */
#ifdef __cplusplus
}
#endif
#endif
+114
View File
@@ -0,0 +1,114 @@
/*
** Copyright (c) 2017-2023, The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#ifndef OPENXR_PLATFORM_DEFINES_H_
#define OPENXR_PLATFORM_DEFINES_H_ 1
#ifdef __cplusplus
extern "C" {
#endif
/* Platform-specific calling convention macros.
*
* Platforms should define these so that OpenXR clients call OpenXR functions
* with the same calling conventions that the OpenXR implementation expects.
*
* XRAPI_ATTR - Placed before the return type in function declarations.
* Useful for C++11 and GCC/Clang-style function attribute syntax.
* XRAPI_CALL - Placed after the return type in function declarations.
* Useful for MSVC-style calling convention syntax.
* XRAPI_PTR - Placed between the '(' and '*' in function pointer types.
*
* Function declaration: XRAPI_ATTR void XRAPI_CALL xrFunction(void);
* Function pointer type: typedef void (XRAPI_PTR *PFN_xrFunction)(void);
*/
#if defined(_WIN32)
#define XRAPI_ATTR
// On Windows, functions use the stdcall convention
#define XRAPI_CALL __stdcall
#define XRAPI_PTR XRAPI_CALL
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7
#error "API not supported for the 'armeabi' NDK ABI"
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)
// On Android 32-bit ARM targets, functions use the "hardfloat"
// calling convention, i.e. float parameters are passed in registers. This
// is true even if the rest of the application passes floats on the stack,
// as it does by default when compiling for the armeabi-v7a NDK ABI.
#define XRAPI_ATTR __attribute__((pcs("aapcs-vfp")))
#define XRAPI_CALL
#define XRAPI_PTR XRAPI_ATTR
#else
// On other platforms, use the default calling convention
#define XRAPI_ATTR
#define XRAPI_CALL
#define XRAPI_PTR
#endif
#include <stddef.h>
#if !defined(XR_NO_STDINT_H)
#if defined(_MSC_VER) && (_MSC_VER < 1600)
typedef signed __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef signed __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef signed __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef signed __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif
#endif // !defined( XR_NO_STDINT_H )
// XR_PTR_SIZE (in bytes)
#if (defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__))
#define XR_PTR_SIZE 8
#else
#define XR_PTR_SIZE 4
#endif
// Needed so we can use clang __has_feature portably.
#if !defined(XR_COMPILER_HAS_FEATURE)
#if defined(__clang__)
#define XR_COMPILER_HAS_FEATURE(x) __has_feature(x)
#else
#define XR_COMPILER_HAS_FEATURE(x) 0
#endif
#endif
// Identifies if the current compiler has C++11 support enabled.
// Does not by itself identify if any given C++11 feature is present.
#if !defined(XR_CPP11_ENABLED) && defined(__cplusplus)
#if defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__)
#define XR_CPP11_ENABLED 1
#elif defined(_MSC_VER) && (_MSC_VER >= 1600)
#define XR_CPP11_ENABLED 1
#elif (__cplusplus >= 201103L) // 201103 is the first C++11 version.
#define XR_CPP11_ENABLED 1
#endif
#endif
// Identifies if the current compiler supports C++11 nullptr.
#if !defined(XR_CPP_NULLPTR_SUPPORTED)
#if defined(XR_CPP11_ENABLED) && \
((defined(__clang__) && XR_COMPILER_HAS_FEATURE(cxx_nullptr)) || \
(defined(__GNUC__) && (((__GNUC__ * 1000) + __GNUC_MINOR__) >= 4006)) || \
(defined(_MSC_VER) && (_MSC_VER >= 1600)) || \
(defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403)))
#define XR_CPP_NULLPTR_SUPPORTED 1
#endif
#endif
#if !defined(XR_CPP_NULLPTR_SUPPORTED)
#define XR_CPP_NULLPTR_SUPPORTED 0
#endif // !defined(XR_CPP_NULLPTR_SUPPORTED)
#ifdef __cplusplus
}
#endif
#endif
+254
View File
@@ -0,0 +1,254 @@
// OpenXR Simulator configurator.
//
// A small GLFW + Dear ImGui tool that edits the runtime's JSON config file. The runtime DLL reads
// the same file (resolved via XRSIM_CONFIG, else <dll-dir>/openxr_simulator_config.json).
#define GLFW_INCLUDE_NONE
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#endif
#include <GL/gl.h>
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "Config.h"
#include "HeadsetPresets.h"
#include <cstring>
#include <string>
namespace
{
int PresentModeIndex( xrsim::PresentMode mode )
{
switch ( mode )
{
case xrsim::PresentMode::Mailbox: return 0;
case xrsim::PresentMode::Immediate: return 1;
case xrsim::PresentMode::Fifo: return 2;
}
return 0;
}
int DisplayModeIndex( xrsim::DisplayMode mode )
{
switch ( mode )
{
case xrsim::DisplayMode::SideBySide: return 0;
case xrsim::DisplayMode::SingleEyeLeft: return 1;
case xrsim::DisplayMode::SingleEyeRight: return 2;
}
return 0;
}
} // namespace
int main( int, char ** )
{
if ( glfwInit() != GLFW_TRUE )
{
return 1;
}
const char * glslVersion = "#version 130";
glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 0 );
GLFWwindow * window =
glfwCreateWindow( 920, 720, "OpenXR Simulator Configurator", nullptr, nullptr );
if ( window == nullptr )
{
glfwTerminate();
return 1;
}
glfwMakeContextCurrent( window );
glfwSwapInterval( 1 );
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL( window, true );
ImGui_ImplOpenGL3_Init( glslVersion );
std::string configPath = xrsim::SimulatorConfig::ResolvePath();
xrsim::SimulatorConfig config = xrsim::SimulatorConfig::LoadFrom( configPath );
std::string status;
while ( glfwWindowShouldClose( window ) == 0 )
{
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
const ImGuiViewport * viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos( viewport->WorkPos );
ImGui::SetNextWindowSize( viewport->WorkSize );
ImGui::Begin(
"OpenXR Simulator Config", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
);
ImGui::TextWrapped(
"These settings are read by openxr_simulator.dll at runtime startup. Restart the "
"application after saving."
);
ImGui::Separator();
static char pathBuffer[1024] = {};
if ( pathBuffer[0] == '\0' )
{
std::strncpy( pathBuffer, configPath.c_str(), sizeof( pathBuffer ) - 1 );
}
if ( ImGui::InputText( "Config path", pathBuffer, sizeof( pathBuffer ) ) )
{
configPath = pathBuffer;
}
if ( ImGui::Button( "Reload" ) )
{
config = xrsim::SimulatorConfig::LoadFrom( configPath );
status = "Reloaded.";
}
ImGui::SameLine();
if ( ImGui::Button( "Save" ) )
{
status = config.Save( configPath ) ? "Saved." : "Failed to save!";
}
ImGui::SameLine();
if ( ImGui::Button( "Reset to defaults" ) )
{
config = xrsim::SimulatorConfig{};
status = "Reset to defaults (not saved yet).";
}
if ( !status.empty() )
{
ImGui::SameLine();
ImGui::TextColored( ImVec4( 0.5f, 0.9f, 0.5f, 1.0f ), "%s", status.c_str() );
}
ImGui::Separator();
{
constexpr int presetCount = static_cast<int>( xrsim::kHeadsetPresets.size() );
static const char * presetItems[presetCount + 1] = {};
if ( presetItems[0] == nullptr )
{
presetItems[0] = "Custom";
for ( int i = 0; i < presetCount; ++i )
{
presetItems[i + 1] = xrsim::kHeadsetPresets[i].name;
}
}
static int presetIndex = 0;
if ( ImGui::Combo( "Headset preset", &presetIndex, presetItems, presetCount + 1 ) &&
presetIndex > 0 )
{
const xrsim::HeadsetPreset & preset = xrsim::kHeadsetPresets[presetIndex - 1];
config.systemName = preset.name;
config.eyeWidth = preset.eyeWidth;
config.eyeHeight = preset.eyeHeight;
config.refreshRateHz = preset.refreshRateHz;
config.fovLeftDeg = preset.fovLeftDeg;
config.fovRightDeg = preset.fovRightDeg;
config.fovUpDeg = preset.fovUpDeg;
config.fovDownDeg = preset.fovDownDeg;
config.ipdMeters = preset.ipdMeters;
status = std::string( "Applied preset: " ) + preset.name;
}
}
ImGui::Separator();
if ( ImGui::CollapsingHeader( "Headset", ImGuiTreeNodeFlags_DefaultOpen ) )
{
char nameBuffer[256];
std::strncpy( nameBuffer, config.systemName.c_str(), sizeof( nameBuffer ) - 1 );
nameBuffer[sizeof( nameBuffer ) - 1] = '\0';
if ( ImGui::InputText( "System name", nameBuffer, sizeof( nameBuffer ) ) )
{
config.systemName = nameBuffer;
}
ImGui::InputScalar( "Vendor id", ImGuiDataType_U32, &config.vendorId );
}
if ( ImGui::CollapsingHeader( "Display", ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui::InputScalar( "Eye width", ImGuiDataType_U32, &config.eyeWidth );
ImGui::InputScalar( "Eye height", ImGuiDataType_U32, &config.eyeHeight );
ImGui::InputScalar( "Refresh rate (Hz)", ImGuiDataType_U32, &config.refreshRateHz );
ImGui::SliderFloat( "IPD (m)", &config.ipdMeters, 0.04f, 0.09f, "%.3f" );
ImGui::InputScalar( "Swapchain images", ImGuiDataType_U32, &config.swapchainImageCount );
}
if ( ImGui::CollapsingHeader( "Field of view (half-angle degrees)", ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui::SliderFloat( "Left", &config.fovLeftDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Right", &config.fovRightDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Up", &config.fovUpDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Down", &config.fovDownDeg, 20.0f, 80.0f, "%.1f" );
if ( ImGui::Button( "Symmetric" ) )
{
config.fovRightDeg = config.fovLeftDeg;
config.fovUpDeg = config.fovLeftDeg;
config.fovDownDeg = config.fovLeftDeg;
}
}
if ( ImGui::CollapsingHeader( "Compositor window", ImGuiTreeNodeFlags_DefaultOpen ) )
{
char titleBuffer[256];
std::strncpy( titleBuffer, config.windowTitle.c_str(), sizeof( titleBuffer ) - 1 );
titleBuffer[sizeof( titleBuffer ) - 1] = '\0';
if ( ImGui::InputText( "Window title", titleBuffer, sizeof( titleBuffer ) ) )
{
config.windowTitle = titleBuffer;
}
ImGui::InputInt( "Window width", &config.windowWidth );
ImGui::InputInt( "Window height", &config.windowHeight );
int presentIndex = PresentModeIndex( config.presentMode );
const char * presentItems[] = { "Mailbox (uncapped)", "Immediate (uncapped, tearing)",
"FIFO (vsync)" };
if ( ImGui::Combo( "Present mode", &presentIndex, presentItems, IM_ARRAYSIZE( presentItems ) ) )
{
config.presentMode = static_cast<xrsim::PresentMode>( presentIndex );
}
int displayIndex = DisplayModeIndex( config.displayMode );
const char * displayItems[] = { "Side by side", "Single eye (left)", "Single eye (right)" };
if ( ImGui::Combo( "Display mode", &displayIndex, displayItems, IM_ARRAYSIZE( displayItems ) ) )
{
config.displayMode = static_cast<xrsim::DisplayMode>( displayIndex );
}
}
ImGui::Separator();
ImGui::TextDisabled(
"Note: 'Mailbox' avoids capping the application to the monitor refresh rate."
);
ImGui::End();
ImGui::Render();
int displayWidth = 0, displayHeight = 0;
glfwGetFramebufferSize( window, &displayWidth, &displayHeight );
glViewport( 0, 0, displayWidth, displayHeight );
glClearColor( 0.10f, 0.10f, 0.12f, 1.0f );
glClear( GL_COLOR_BUFFER_BIT );
ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() );
glfwSwapBuffers( window );
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow( window );
glfwTerminate();
return 0;
}