OpenXRS

repository·master·Indexed 43 days ago

https://github.com/ralith/openxrs

Rust bindings for the OpenXR virtual and augmented reality runtime. It consists of two crates: 'openxr', providing high-level safe abstractions, and 'openxr-sys', providing low-level unsafe bindings generated from the Khronos XML registry. The library supports multiple loader integration methods via cargo features (static, loaded, linked) and includes specific support for Android platforms, such as Oculus Quest and Quest 2.

Tokens
12.7K
Snippets
52
Records
65
Agent score
56%

What's inside OpenXRS

  1. Overview of OpenXRS

    master

    OpenXRS provides Rust bindings for the OpenXR virtual and augmented reality runtime. The project is split into two main crates:

    1. openxr: High-level, ergonomic, and safe bindings. It provides abstractions for common tasks while offering 'escape hatches' to the raw API for specialized requirements.
    2. openxr-sys: Low-level, unsafe bindings that provide faithful access to the raw OpenXR API. It improves upon standard bindgen output by using Rust naming conventions, strongly typed enums/bitmasks, and helpful Debug implementations, while remaining almost entirely generated from the Khronos XML registry.
  2. Overview of OpenXR examples

    master

    The openxr crate includes several examples demonstrating different capabilities of the OpenXR runtime:

    • hello: A basic example that retrieves and displays information about the OpenXR runtime and the connected XR headset.
    • vulkan: A desktop Vulkan example that displays a head-locked gradient spanning both eyes. It also reports controller positions to the terminal.
    • vulkan-android: An Android-compatible version of the vulkan example designed for devices like Oculus Quest and Quest 2.
  3. Update the OpenXR SDK and regenerate the sys crate

    master

    If you need to update the underlying OpenXR SDK to a specific commit hash or tag, follow these steps:

    1. Update the submodule: Navigate to the SDK directory and check out the desired version.
    2. Regenerate bindings: Run the generator tool to update the sys crate bindings.
    3. Verify: Run the project's formatting, build, and test suite to ensure the new SDK version is compatible with the existing code.
    # 1. Update the OpenXR-SDK submodule
    cd openxrs/sys/OpenXR-SDK
    git up <tag/xxx or hash>
    cd ../..
    
    # 2. Regenerate the sys crate
    cd generator
    cargo run --bin generator
    cd ..
    
    # 3. Verify the changes
    cargo fmt && cargo build && cargo test
  4. Run the `vulkan-android` example on Oculus Quest/Quest 2

    master

    To run the Vulkan example on Android devices (specifically Oculus Quest and Quest 2), you must set up the Android toolchain and specific shared libraries. The vulkan-android example uses the same source as the vulkan example but is configured for the Android runtime.

    Prerequisites

    1. Install cargo-apk.
    2. Download Android SDK version 30 and set the $ANDROID_HOME environment variable to its path.
    3. Download Android NDK version 23 and set the $ANDROID_NDK_ROOT environment variable to its path.

    Library Setup

    Before running, you must manually place specific libraries into the project structure:

    1. Copy the arm64-v8a folder from ${ANDROID_NDK_ROOT}/sources/cxx-stl/llvm-libc++/libs/ into openxr/examples/libs/.
    2. Obtain libopenxr_loader.so from the Oculus OpenXR Mobile SDK and place it in openxr/examples/libs/arm64-v8a.

    Execution

    Run the following command from the openxr directory:

    cd openxr
    cargo apk run --example vulkan-android
  5. Clone the openxr-sys repository with submodules

    master

    To ensure all necessary submodules (including the OpenXR SDK) are present, clone the repository using the --recurse-submodules flag.

    If you have already cloned the repository without submodules, initialize and update them by running the following command from the repository root:

    git submodule update --init
  6. Use HandTracker for skeletal hand tracking

    master

    The HandTracker struct provides access to skeletal hand tracking data via the XR_EXT_hand_tracking extension. It is used to track the position and velocity of individual hand joints.

    To use hand tracking, you must ensure the XR_EXT_hand_tracking extension is present in your session. The tracker is initialized for a specific hand (left or right) and provides access to a fixed number of joints defined by HAND_JOINT_COUNT.

    Key types exported for hand tracking:

    • Hand: Represents the hand being tracked (e.g., HandEXT).
    • HandJoint: Represents a specific joint in the hand.
    • HandJointLocation: Contains the spatial data for a joint.
    • HandJointVelocity: Contains the velocity data for a joint.
    • HandJointLocations: A type alias for an array of HandJointLocation for all joints.
    • HandJointVelocities: A type alias for an array of HandJointVelocity for all joints.
    use openxr as xr;
    
    // Note: The actual creation of a HandTracker is currently internal (`pub(crate) fn create`), 
    // but it requires a valid Session and a Hand type.
    // Once obtained, you can access the raw handle via `.as_raw()`.
  7. The `Instance` struct in OpenXR

    master

    The Instance is the root object that mediates an application's interaction with the OpenXR runtime. It is constructed from an Entry and serves as the primary handle for querying system properties, managing sessions, and interacting with input archetypes. It is Clone and manages its own lifecycle via an internal Arc and Drop implementation that calls destroy_instance on the underlying handle.

    // An Instance is typically obtained via an Entry (not shown in this file)
    let instance = instance_from_entry(entry);
  8. How FrameStream manages frame presentation

    master

    A FrameStream is a secondary interface to an OpenXR Session object that exposes only frame timing and synchronization operations (begin, end, and end_secondary). It is designed to provide a safe, synchronized interface for the presentation loop by using &mut self receivers, which statically guarantees that calls are made in the correct order as required by the OpenXR specification.

    A typical presentation loop involves:

    1. Waiting for the next frame via a FrameWaiter.
    2. Acquiring an image from a Swapchain.
    3. Calling frame_stream.begin() to indicate graphics work is starting.
    4. Performing rendering/drawing.
    5. Calling frame_stream.end() with the predicted display time and composition layers to submit the frame.
    6. Releasing the swapchain image.
    // Typical presentation loop body
    let state = frame_waiter.wait().unwrap();
    let image = swapchain.acquire_image().unwrap();
    swapchain.wait_image(openxr::Duration::INFINITE).unwrap();
    
    frame_stream.begin().unwrap();
    
    if state.should_render {
        // draw scene...
    }
    
    // ... locate views and set matrices ...
    
    swapchain.release_image().unwrap();
    frame_stream
        .end(
            state.predicted_display_time,
            openxr::EnvironmentBlendMode::OPAQUE,
            &[&openxr::CompositionLayerProjection::new()...],
        )
        .unwrap();
  9. Manage render targets with Swapchain

    master

    The Swapchain<G> struct manages a set of images used for rendering to a XR session, where G is a type implementing the Graphics trait. It handles the lifecycle of swapchain images, including acquiring the next image for rendering and synchronizing with the compositor.

    Image Lifecycle Workflow

    To render to a swapchain, you must follow a specific sequence of calls to ensure proper synchronization with the compositor:

    1. acquire_image(): Determines the index of the next image in the array to render to.
    2. wait_image(timeout: Duration): Waits for the compositor to finish reading from the oldest unwaited acquired image. This must be called after acquire_image and before release_image.
    3. release_image(): Releases the oldest acquired image. This must be called after wait_image and before the next acquire_image cycle.

    Note: Calling wait_image before release_image has been called in the current cycle will trigger an assertion error.

    // Conceptual workflow
    let image_index = swapchain.acquire_image()?;
    
    // ... perform rendering to image_index ...
    
    swapchain.wait_image(Duration::from_millis(100))?;
    swapchain.release_image()?;
  10. Configure PassthroughLayerFB creation

    master

    When creating a PassthroughLayerFB, you must provide specific configuration parameters:

    • session: The active OpenXR Session.
    • passthrough: A reference to the Passthrough feature instance.
    • flags: PassthroughFlagsFB defining layer behavior.
    • purpose: PassthroughLayerPurposeFB indicating the intended use of the layer.
  11. Configure `openxr` Cargo features

    master

    The openxr crate supports several cargo features to control how the OpenXR loader is integrated into your application:

    • static: Builds the Khronos OpenXR loader directly into your binary. This is the easiest setup method but requires a working C++ compiler and CMake. Access it via Entry::linked().
    • loaded: Allows you to manually identify and load an OpenXR implementation at runtime. Use this if the standard Khronos loader cannot be used in your environment.
    • linked: Attempts to link against an OpenXR loader already present in your build environment (e.g., on desktop Linux). This avoids bundling a built-in loader.
    • mint: Enables conversions to and from mint types (a common math library interface in Rust) where applicable.
  12. Use AndroidPlatformInfo for Android OpenXR

    master

    On Android, OpenXR requires specific platform information to interface with the Android lifecycle and the Java VM. You must use AndroidPlatformInfo when calling Entry::linked, Entry::load, Entry::load_from, or Entry::create_instance.

    Safety Warning: The arguments provided to AndroidPlatformInfo::new must be valid pointers:

    • java_vm: A valid pointer to the JNI JavaVM, cast to *mut std::ffi::c_void.
    • activity: A valid JNI reference to the android.app.Activity that drives the session lifecycle, cast to *mut std::ffi::c_void.

    AndroidPlatformInfo handles the initialization of the XR_KHR_loader_init_android extension and the XR_KHR_android_create_instance extension internally.

    // Safety: Ensure java_vm and activity are valid JNI pointers
    let platform_info = unsafe { AndroidPlatformInfo::new(java_vm_ptr, activity_ptr) };
    
    // Use platform_info to create the entry or instance
    let entry = unsafe { Entry::load(&platform_info)? };