mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-03-23 10:12:47 +08:00
test: Add testgpu_spinning_cube_xr (#14943)
Co-authored-by: Ethan Lee <flibitijibibo@gmail.com>
This commit is contained in:
320
docs/README-xr.md
Normal file
320
docs/README-xr.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# OpenXR / VR Development with SDL
|
||||
|
||||
This document covers how to build OpenXR (VR/AR) applications using SDL's GPU API with OpenXR integration.
|
||||
|
||||
## Overview
|
||||
|
||||
SDL3 provides OpenXR integration through the GPU API, allowing you to render to VR/AR headsets using a unified interface across multiple graphics backends (Vulkan, D3D12, Metal).
|
||||
|
||||
**Key features:**
|
||||
- Automatic OpenXR instance and session management
|
||||
- Swapchain creation and image acquisition
|
||||
- Support for multi-pass stereo rendering
|
||||
- Works with desktop VR runtimes (SteamVR, Oculus, Windows Mixed Reality) and standalone headsets (Meta Quest, Pico)
|
||||
|
||||
## Desktop Development
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **OpenXR Loader** (`openxr_loader.dll` / `libopenxr_loader.so`)
|
||||
- On Windows: Usually installed with VR runtime software (Oculus, SteamVR)
|
||||
- On Linux: Install via package manager (e.g., `libopenxr-loader1` on Ubuntu)
|
||||
- Can also use `SDL_HINT_OPENXR_LIBRARY` to specify a custom loader path
|
||||
|
||||
2. **OpenXR Runtime**
|
||||
- At least one OpenXR runtime must be installed and active
|
||||
- Examples: SteamVR, Oculus Desktop, Monado (Linux)
|
||||
|
||||
3. **VR Headset**
|
||||
- Connected and recognized by the runtime
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```c
|
||||
#include <openxr/openxr.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_openxr.h>
|
||||
|
||||
// These will be populated by SDL
|
||||
XrInstance xr_instance = XR_NULL_HANDLE;
|
||||
XrSystemId xr_system_id = 0;
|
||||
|
||||
// Create GPU device with XR enabled
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, true);
|
||||
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_INSTANCE_POINTER, &xr_instance);
|
||||
SDL_SetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_SYSTEM_ID_POINTER, &xr_system_id);
|
||||
|
||||
// Optional: Override app name/version (defaults to SDL_SetAppMetadata values if not set)
|
||||
SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_NAME_STRING, "My VR App");
|
||||
SDL_SetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_VERSION_NUMBER, 1);
|
||||
|
||||
SDL_GPUDevice *device = SDL_CreateGPUDeviceWithProperties(props);
|
||||
SDL_DestroyProperties(props);
|
||||
|
||||
// xr_instance and xr_system_id are now populated by SDL
|
||||
```
|
||||
|
||||
See `test/testgpu_spinning_cube_xr.c` for a complete example.
|
||||
|
||||
---
|
||||
|
||||
## Android Development
|
||||
|
||||
Building OpenXR applications for Android standalone headsets (Meta Quest, Pico, etc.) requires additional manifest configuration beyond standard Android apps.
|
||||
|
||||
### Android Manifest Requirements
|
||||
|
||||
The manifest requirements fall into three categories:
|
||||
|
||||
1. **OpenXR Standard (Khronos)** - Required for all OpenXR apps
|
||||
2. **Platform-Specific** - Required for specific headset platforms
|
||||
3. **Optional Features** - Enable additional capabilities
|
||||
|
||||
---
|
||||
|
||||
### OpenXR Standard Requirements (All Platforms)
|
||||
|
||||
These are required by the Khronos OpenXR specification for Android:
|
||||
|
||||
#### Permissions
|
||||
|
||||
```xml
|
||||
<!-- OpenXR runtime broker communication -->
|
||||
<uses-permission android:name="org.khronos.openxr.permission.OPENXR" />
|
||||
<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM" />
|
||||
```
|
||||
|
||||
#### Queries (Android 11+)
|
||||
|
||||
Required for the app to discover OpenXR runtimes:
|
||||
|
||||
```xml
|
||||
<queries>
|
||||
<provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
|
||||
<intent>
|
||||
<action android:name="org.khronos.openxr.OpenXRRuntimeService" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.khronos.openxr.OpenXRApiLayerService" />
|
||||
</intent>
|
||||
</queries>
|
||||
```
|
||||
|
||||
#### Hardware Features
|
||||
|
||||
```xml
|
||||
<!-- VR head tracking (standard OpenXR requirement) -->
|
||||
<uses-feature android:name="android.hardware.vr.headtracking"
|
||||
android:required="true"
|
||||
android:version="1" />
|
||||
|
||||
<!-- Touchscreen not required for VR -->
|
||||
<uses-feature android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Graphics requirements -->
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.vulkan.level"
|
||||
android:required="true"
|
||||
android:version="1" />
|
||||
<uses-feature android:name="android.hardware.vulkan.version"
|
||||
android:required="true"
|
||||
android:version="0x00401000" />
|
||||
```
|
||||
|
||||
#### Intent Category
|
||||
|
||||
```xml
|
||||
<activity ...>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<!-- Khronos OpenXR immersive app category -->
|
||||
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Meta Quest Requirements
|
||||
|
||||
These are **required** for apps to run properly on Meta Quest devices. Without these, your app may launch in "pancake" 2D mode instead of VR.
|
||||
|
||||
#### VR Intent Category (Critical!)
|
||||
|
||||
```xml
|
||||
<activity ...>
|
||||
<intent-filter>
|
||||
...
|
||||
<!-- CRITICAL: Without this, app launches in 2D mode on Quest! -->
|
||||
<category android:name="com.oculus.intent.category.VR" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
```
|
||||
|
||||
#### Supported Devices
|
||||
|
||||
```xml
|
||||
<application ...>
|
||||
<!-- Required: Specifies which Quest devices are supported -->
|
||||
<meta-data android:name="com.oculus.supportedDevices"
|
||||
android:value="quest|quest2|questpro|quest3|quest3s" />
|
||||
</application>
|
||||
```
|
||||
|
||||
#### Focus Handling (Recommended)
|
||||
|
||||
```xml
|
||||
<application ...>
|
||||
<!-- Properly handles when user opens the Quest system menu -->
|
||||
<meta-data android:name="com.oculus.vr.focusaware"
|
||||
android:value="true" />
|
||||
</application>
|
||||
```
|
||||
|
||||
#### Hand Tracking (Optional)
|
||||
|
||||
```xml
|
||||
<!-- Feature declaration -->
|
||||
<uses-feature android:name="oculus.software.handtracking"
|
||||
android:required="false" />
|
||||
|
||||
<application ...>
|
||||
<!-- V2.0 allows app to launch without controllers -->
|
||||
<meta-data android:name="com.oculus.handtracking.version"
|
||||
android:value="V2.0" />
|
||||
<meta-data android:name="com.oculus.handtracking.frequency"
|
||||
android:value="HIGH" />
|
||||
</application>
|
||||
```
|
||||
|
||||
#### VR Splash Screen (Optional)
|
||||
|
||||
```xml
|
||||
<application ...>
|
||||
<meta-data android:name="com.oculus.ossplash"
|
||||
android:value="true" />
|
||||
<meta-data android:name="com.oculus.ossplash.colorspace"
|
||||
android:value="QUEST_SRGB_NONGAMMA" />
|
||||
<meta-data android:name="com.oculus.ossplash.background"
|
||||
android:resource="@drawable/vr_splash" />
|
||||
</application>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pico Requirements
|
||||
|
||||
For Pico Neo, Pico 4, and other Pico headsets:
|
||||
|
||||
#### VR Intent Category
|
||||
|
||||
```xml
|
||||
<activity ...>
|
||||
<intent-filter>
|
||||
...
|
||||
<!-- Pico VR category -->
|
||||
<category android:name="com.picovr.intent.category.VR" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
```
|
||||
|
||||
#### Supported Devices (Optional)
|
||||
|
||||
```xml
|
||||
<application ...>
|
||||
<!-- Pico device support -->
|
||||
<meta-data android:name="pvr.app.type"
|
||||
android:value="vr" />
|
||||
</application>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### HTC Vive Focus / VIVE XR Elite
|
||||
|
||||
```xml
|
||||
<activity ...>
|
||||
<intent-filter>
|
||||
...
|
||||
<!-- HTC Vive category -->
|
||||
<category android:name="com.htc.intent.category.VRAPP" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Declaration | Purpose | Scope |
|
||||
|-------------|---------|-------|
|
||||
| `org.khronos.openxr.permission.OPENXR` | Runtime communication | All OpenXR |
|
||||
| `android.hardware.vr.headtracking` | Marks app as VR | All OpenXR |
|
||||
| `org.khronos.openxr.intent.category.IMMERSIVE_HMD` | Khronos standard VR category | All OpenXR |
|
||||
| `com.oculus.intent.category.VR` | Launch in VR mode | Meta Quest |
|
||||
| `com.oculus.supportedDevices` | Device compatibility | Meta Quest |
|
||||
| `com.oculus.vr.focusaware` | System menu handling | Meta Quest |
|
||||
| `com.picovr.intent.category.VR` | Launch in VR mode | Pico |
|
||||
| `com.htc.intent.category.VRAPP` | Launch in VR mode | HTC Vive |
|
||||
|
||||
---
|
||||
|
||||
## Example Manifest
|
||||
|
||||
SDL provides an example XR manifest template at:
|
||||
`test/android/cmake/AndroidManifest.xr.xml.cmake`
|
||||
|
||||
This template includes:
|
||||
- All Khronos OpenXR requirements
|
||||
- Meta Quest support (configurable via `SDL_ANDROID_XR_META_SUPPORT` CMake option)
|
||||
- Proper intent filters for VR launching
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### App launches in 2D "pancake" mode
|
||||
|
||||
**Cause:** Missing platform-specific VR intent category.
|
||||
|
||||
**Solution:** Add the appropriate category for your target platform:
|
||||
- Meta Quest: `com.oculus.intent.category.VR`
|
||||
- Pico: `com.picovr.intent.category.VR`
|
||||
- HTC: `com.htc.intent.category.VRAPP`
|
||||
|
||||
### "No OpenXR runtime found" error
|
||||
|
||||
**Cause:** The OpenXR loader can't find a runtime.
|
||||
|
||||
**Solutions:**
|
||||
- **Desktop:** Ensure VR software (SteamVR, Oculus) is installed and running
|
||||
- **Android:** Ensure your manifest has the correct `<queries>` block for runtime discovery
|
||||
- **Linux:** Install `libopenxr-loader1` and configure the active runtime
|
||||
|
||||
### OpenXR loader not found
|
||||
|
||||
**Cause:** `openxr_loader.dll` / `libopenxr_loader.so` is not in the library path.
|
||||
|
||||
**Solutions:**
|
||||
- Install the Khronos OpenXR SDK
|
||||
- On Windows, VR runtimes typically install this, but may not add it to PATH
|
||||
- Use `SDL_HINT_OPENXR_LIBRARY` to specify the loader path explicitly
|
||||
|
||||
### Vulkan validation errors on shutdown
|
||||
|
||||
**Cause:** GPU resources destroyed while still in use.
|
||||
|
||||
**Solution:** Call `SDL_WaitForGPUIdle(device)` before releasing any GPU resources or destroying the device.
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Khronos OpenXR Specification](https://www.khronos.org/openxr/)
|
||||
- [Meta Quest Developer Documentation](https://developer.oculus.com/documentation/)
|
||||
- [Pico Developer Documentation](https://developer.pico-interactive.com/)
|
||||
- [SDL GPU API Documentation](https://wiki.libsdl.org/)
|
||||
- Example code: `test/testgpu_spinning_cube_xr.c`
|
||||
@@ -382,6 +382,8 @@ add_sdl_test_executable(testgpu_simple_clear SOURCES testgpu_simple_clear.c)
|
||||
add_sdl_test_executable(testgpu_spinning_cube SOURCES testgpu_spinning_cube.c ${icon_png_header} DEPENDS generate-icon_png_header)
|
||||
add_sdl_test_executable(testgpurender_effects MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_effects.c)
|
||||
add_sdl_test_executable(testgpurender_msdf MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testgpurender_msdf.c)
|
||||
add_sdl_test_executable(testgpu_spinning_cube_xr SOURCES testgpu_spinning_cube_xr.c)
|
||||
|
||||
if(ANDROID)
|
||||
target_link_libraries(testgles PRIVATE GLESv1_CM)
|
||||
elseif(IOS OR TVOS)
|
||||
@@ -750,7 +752,35 @@ if(ANDROID AND TARGET SDL3::Jar)
|
||||
configure_file(android/cmake/SDLTestActivity.java.cmake "${JAVA_PACKAGE_DIR}/SDLTestActivity.java" @ONLY)
|
||||
configure_file(android/cmake/res/values/strings.xml.cmake android/res/values/strings-${TEST}.xml @ONLY)
|
||||
configure_file(android/cmake/res/xml/shortcuts.xml.cmake "${GENERATED_RES_FOLDER}/xml/shortcuts.xml" @ONLY)
|
||||
configure_file(android/cmake/AndroidManifest.xml.cmake "${generated_manifest_path}" @ONLY)
|
||||
# Use XR-specific manifest for XR tests, standard manifest for others
|
||||
if("${TEST}" MATCHES "_xr$")
|
||||
# Meta Quest-specific manifest sections (enabled by default, set to empty to disable)
|
||||
# These are ignored by non-Meta runtimes but required for proper Quest integration
|
||||
if(NOT DEFINED SDL_ANDROID_XR_META_SUPPORT OR SDL_ANDROID_XR_META_SUPPORT)
|
||||
set(ANDROID_XR_META_FEATURES
|
||||
" <!-- Meta Quest hand tracking support -->
|
||||
<uses-feature android:name=\"oculus.software.handtracking\" android:required=\"false\" />
|
||||
")
|
||||
set(ANDROID_XR_META_METADATA
|
||||
" <!-- Meta Quest supported devices -->
|
||||
<meta-data android:name=\"com.oculus.supportedDevices\" android:value=\"quest|quest2|questpro|quest3|quest3s\" />
|
||||
<meta-data android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />
|
||||
<!-- Hand tracking support level (V2 allows launching without controllers) -->
|
||||
<meta-data android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />
|
||||
<meta-data android:name=\"com.oculus.handtracking.frequency\" android:value=\"HIGH\" />
|
||||
")
|
||||
set(ANDROID_XR_META_INTENT_CATEGORY
|
||||
" <!-- VR intent category for Meta Quest -->
|
||||
<category android:name=\"com.oculus.intent.category.VR\" />")
|
||||
else()
|
||||
set(ANDROID_XR_META_FEATURES "")
|
||||
set(ANDROID_XR_META_METADATA "")
|
||||
set(ANDROID_XR_META_INTENT_CATEGORY "")
|
||||
endif()
|
||||
configure_file(android/cmake/AndroidManifest.xr.xml.cmake "${generated_manifest_path}" @ONLY)
|
||||
else()
|
||||
configure_file(android/cmake/AndroidManifest.xml.cmake "${generated_manifest_path}" @ONLY)
|
||||
endif()
|
||||
file(GENERATE
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/android/${TEST}-$<CONFIG>/res/values/strings.xml"
|
||||
INPUT "${CMAKE_CURRENT_BINARY_DIR}/android/res/values/strings-${TEST}.xml"
|
||||
|
||||
94
test/android/cmake/AndroidManifest.xr.xml.cmake
Normal file
94
test/android/cmake/AndroidManifest.xr.xml.cmake
Normal file
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="@ANDROID_MANIFEST_PACKAGE@"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- OpenGL ES 3.2 for Vulkan fallback on XR devices -->
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
|
||||
<!-- Vulkan requirements -->
|
||||
<uses-feature android:name="android.hardware.vulkan.level" android:required="true" android:version="1" />
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:required="true" android:version="0x00401000" />
|
||||
|
||||
<!-- VR Head Tracking (standard OpenXR requirement) -->
|
||||
<uses-feature android:name="android.hardware.vr.headtracking" android:required="true" android:version="1" />
|
||||
|
||||
<!-- Touchscreen not required for VR -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
@ANDROID_XR_META_FEATURES@
|
||||
<!-- Game controller support -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Allow access to the vibrator (for controller haptics) -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- OpenXR permissions (for runtime broker communication) -->
|
||||
<uses-permission android:name="org.khronos.openxr.permission.OPENXR" />
|
||||
<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM" />
|
||||
|
||||
<!-- OpenXR runtime/layer queries -->
|
||||
<queries>
|
||||
<provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
|
||||
<intent>
|
||||
<action android:name="org.khronos.openxr.OpenXRRuntimeService" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="org.khronos.openxr.OpenXRApiLayerService" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/sdl-test"
|
||||
android:roundIcon="@mipmap/sdl-test_round"
|
||||
android:label="@string/label"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:hardwareAccelerated="true">
|
||||
|
||||
@ANDROID_XR_META_METADATA@
|
||||
<activity
|
||||
android:name="@ANDROID_MANIFEST_PACKAGE@.SDLTestActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/label"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||
android:excludeFromRecents="false"
|
||||
android:resizeableActivity="false"
|
||||
tools:ignore="NonResizeableActivity">
|
||||
|
||||
<!-- Standard launcher intent -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@ANDROID_XR_META_INTENT_CATEGORY@
|
||||
<!-- Khronos OpenXR category (for broader compatibility) -->
|
||||
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="@ANDROID_MANIFEST_PACKAGE@.SDLEntryTestActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/label">
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
983
test/testgpu_spinning_cube_xr.c
Normal file
983
test/testgpu_spinning_cube_xr.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user