Initial commit: OpenXR simulator runtime, compositor, configurator, docs
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
out/
|
||||
build/
|
||||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
openxr_simulator_imgui.ini
|
||||
imgui.ini
|
||||
*.dll
|
||||
*.lib
|
||||
*.exp
|
||||
*.pdb
|
||||
*.ilk
|
||||
+144
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
@@ -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`.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"file_format_version": "1.0.0",
|
||||
"runtime": {
|
||||
"library_path": "./openxr_simulator.dll",
|
||||
"name": "@SIM_RUNTIME_NAME@"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ©PoolInfo, nullptr, ©Pool_ );
|
||||
|
||||
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, ©Cmd_ );
|
||||
|
||||
VkFenceCreateInfo fenceInfo{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
vkCreateFence( session->vkDevice, &fenceInfo, nullptr, ©Fence_ );
|
||||
|
||||
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, ©Fence_, VK_TRUE, UINT64_MAX );
|
||||
vkResetFences( session->vkDevice, 1, ©Fence_ );
|
||||
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, ®ion
|
||||
);
|
||||
|
||||
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 = ©Cmd_;
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+392
@@ -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
|
||||
Vendored
+6135
File diff suppressed because it is too large
Load Diff
+114
@@ -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
|
||||
Vendored
+714
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user