From 96ebb1e7e6320b1739a785908020a6459ce17433 Mon Sep 17 00:00:00 2001 From: awtterpip Date: Wed, 31 Jan 2024 20:12:14 -0600 Subject: [PATCH] openxr implementation complete --- xr_api/Cargo.toml | 1 + xr_api/src/api.rs | 8 + xr_api/src/api_traits.rs | 2 +- xr_api/src/backend/oxr.rs | 198 ++++++++++++++--- xr_api/src/backend/oxr/graphics/vulkan.rs | 13 +- xr_api/src/backend/oxr/utils.rs | 80 ++++++- xr_api/src/backend/webxr.rs | 2 + xr_api/src/path.rs | 254 +++++++++++++++------- xr_api/src/types.rs | 4 +- 9 files changed, 428 insertions(+), 134 deletions(-) diff --git a/xr_api/Cargo.toml b/xr_api/Cargo.toml index c8544ca..c803779 100644 --- a/xr_api/Cargo.toml +++ b/xr_api/Cargo.toml @@ -11,6 +11,7 @@ linked = ["openxr/linked"] ash = "0.37.3" futures = "0.3.29" glam = "0.24.1" +hashbrown = "0.14" paste = "1.0.14" thiserror = "1.0.51" tracing = "0.1.40" diff --git a/xr_api/src/api.rs b/xr_api/src/api.rs index d5046e7..51f63f8 100644 --- a/xr_api/src/api.rs +++ b/xr_api/src/api.rs @@ -1,6 +1,8 @@ use std::ops::Deref; use std::rc::Rc; +use glam::Vec2; + use crate::prelude::*; /// Entry point to the API @@ -105,6 +107,12 @@ impl + 'static> From for Action { } } +impl + 'static> From for Action { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + impl + 'static> From for Action { fn from(value: T) -> Self { Self(Rc::new(value)) diff --git a/xr_api/src/api_traits.rs b/xr_api/src/api_traits.rs index f8affd5..80695f1 100644 --- a/xr_api/src/api_traits.rs +++ b/xr_api/src/api_traits.rs @@ -79,7 +79,7 @@ impl dyn InputTrait { /// Represents input actions, such as bools, floats, and poses pub trait ActionInputTrait { - fn get(&self) -> A; + fn get(&self) -> Result; } /// Represents haptic actions. diff --git a/xr_api/src/backend/oxr.rs b/xr_api/src/backend/oxr.rs index e81cbcf..5bc8361 100644 --- a/xr_api/src/backend/oxr.rs +++ b/xr_api/src/backend/oxr.rs @@ -1,10 +1,11 @@ mod graphics; mod utils; -use std::{rc::Rc, sync::Mutex}; +use std::{marker::PhantomData, rc::Rc, sync::Mutex}; -use glam::{Mat4, UVec2}; -use openxr::EnvironmentBlendMode; +use glam::{Mat4, UVec2, Vec2}; +use hashbrown::{hash_map, HashMap}; +use openxr::{EnvironmentBlendMode, Vector2f}; use tracing::{info, info_span, warn}; use crate::{backend::oxr::graphics::VIEW_TYPE, prelude::*}; @@ -70,7 +71,7 @@ impl InstanceTrait for OXrInstance { } } -enum UntypedOXrAction { +pub(crate) enum UntypedOXrAction { Haptics(openxr::Action), Pose(openxr::Action), Float(openxr::Action), @@ -79,9 +80,10 @@ enum UntypedOXrAction { } #[derive(Default)] -struct BindingState { - sets_attached: bool, - bindings: Vec<(UntypedOXrAction, openxr::Path)>, +pub(crate) struct BindingState { + sessions_attached: bool, + bindings: Option, + map: HashMap, } pub struct OXrSession { @@ -100,11 +102,11 @@ pub struct OXrSession { )>, >, pub(crate) bindings: Rc>, - pub(crate) frame_state: Mutex, + pub(crate) frame_state: Rc>, pub(crate) views: Mutex<[openxr::View; 2]>, pub(crate) frame_waiter: Mutex, pub(crate) swapchain: graphics::Swapchain, - pub(crate) stage: openxr::Space, + pub(crate) stage: Rc, pub(crate) head: openxr::Space, pub(crate) resolution: UVec2, pub(crate) blend_mode: EnvironmentBlendMode, @@ -128,14 +130,38 @@ impl SessionTrait for OXrSession { } fn create_input(&self, bindings: Bindings) -> Result { + let mut binding_state = self.bindings.lock().unwrap(); + if binding_state.bindings.is_none() { + binding_state.bindings = Some(bindings); + } else { + Err(XrError::Placeholder)? + } + let action_set = self .inner_instance .create_action_set("xr_input", "XR Input", 0)?; self.action_sets.lock().unwrap().push(action_set.clone()); - todo!() + Ok(OXrInput { + inner_session: self.session.clone().into_any_graphics(), + action_set, + bindings: self.bindings.clone(), + stage: self.stage.clone(), + frame_state: self.frame_state.clone(), + } + .into()) } fn begin_frame(&self) -> Result<(View, View)> { + { + let mut bindings = self.bindings.lock().unwrap(); + if !bindings.sessions_attached { + let _span = info_span!("xr_attach_actions"); + let action_sets = self.action_sets.lock().unwrap(); + self.session + .attach_action_sets(&action_sets.iter().collect::>())?; + bindings.sessions_attached = true; + } + } { let _span = info_span!("xr_poll_events"); while let Some(event) = self @@ -259,26 +285,11 @@ impl SessionTrait for OXrSession { } pub struct OXrInput { - inner_instance: openxr::Instance, + inner_session: openxr::Session, action_set: openxr::ActionSet, bindings: Rc>, -} - -impl OXrInput { - fn create_action( - &self, - name: &str, - handed: bool, - ) -> openxr::Result> { - if handed { - let left_path = self.inner_instance.string_to_path("/user/hand/left")?; - let right_path = self.inner_instance.string_to_path("/user/hand/right")?; - self.action_set - .create_action::(name, name, &[left_path, right_path]) - } else { - self.action_set.create_action(name, name, &[]) - } - } + stage: Rc, + frame_state: Rc>, } impl InputTrait for OXrInput { @@ -287,7 +298,21 @@ impl InputTrait for OXrInput { name: &str, path: path::UntypedActionPath, ) -> Result> { - todo!() + let mut bindings = self.bindings.lock().unwrap(); + let action = match bindings.map.entry(path.into_xr_path()) { + hash_map::Entry::Occupied(entry) => match entry.get() { + UntypedOXrAction::Haptics(action) => action.clone(), + _ => Err(XrError::Placeholder)?, + }, + hash_map::Entry::Vacant(entry) => { + let action = self + .action_set + .create_action::(name, name, &[])?; + entry.insert(UntypedOXrAction::Haptics(action.clone())); + action + } + }; + Ok(OXrHaptics(action, self.inner_session.clone()).into()) } fn create_action_pose( @@ -295,7 +320,30 @@ impl InputTrait for OXrInput { name: &str, path: path::UntypedActionPath, ) -> Result> { - todo!() + let mut bindings = self.bindings.lock().unwrap(); + let action = match bindings.map.entry(path.into_xr_path()) { + hash_map::Entry::Occupied(entry) => match entry.get() { + UntypedOXrAction::Pose(action) => action.clone(), + _ => Err(XrError::Placeholder)?, + }, + hash_map::Entry::Vacant(entry) => { + let action = self + .action_set + .create_action::(name, name, &[])?; + entry.insert(UntypedOXrAction::Pose(action.clone())); + action + } + }; + Ok(OXrPoseAction { + pose_space: action.create_space( + self.inner_session.clone(), + openxr::Path::NULL, + openxr::Posef::IDENTITY, + )?, + stage: self.stage.clone(), + frame_state: self.frame_state.clone(), + } + .into()) } fn create_action_float( @@ -303,7 +351,19 @@ impl InputTrait for OXrInput { name: &str, path: path::UntypedActionPath, ) -> Result> { - todo!() + let mut bindings = self.bindings.lock().unwrap(); + let action = match bindings.map.entry(path.into_xr_path()) { + hash_map::Entry::Occupied(entry) => match entry.get() { + UntypedOXrAction::Float(action) => action.clone(), + _ => Err(XrError::Placeholder)?, + }, + hash_map::Entry::Vacant(entry) => { + let action = self.action_set.create_action::(name, name, &[])?; + entry.insert(UntypedOXrAction::Float(action.clone())); + action + } + }; + Ok(OXrAction(action, self.inner_session.clone(), PhantomData).into()) } fn create_action_bool( @@ -311,15 +371,85 @@ impl InputTrait for OXrInput { name: &str, path: path::UntypedActionPath, ) -> Result> { - todo!() + let mut bindings = self.bindings.lock().unwrap(); + let action = match bindings.map.entry(path.into_xr_path()) { + hash_map::Entry::Occupied(entry) => match entry.get() { + UntypedOXrAction::Bool(action) => action.clone(), + _ => Err(XrError::Placeholder)?, + }, + hash_map::Entry::Vacant(entry) => { + let action = self.action_set.create_action::(name, name, &[])?; + entry.insert(UntypedOXrAction::Bool(action.clone())); + action + } + }; + Ok(OXrAction(action, self.inner_session.clone(), PhantomData).into()) } fn create_action_vec2( &self, name: &str, path: path::UntypedActionPath, - ) -> Result> { - todo!() + ) -> Result> { + let mut bindings = self.bindings.lock().unwrap(); + let action = match bindings.map.entry(path.into_xr_path()) { + hash_map::Entry::Occupied(entry) => match entry.get() { + UntypedOXrAction::Vec2(action) => action.clone(), + _ => Err(XrError::Placeholder)?, + }, + hash_map::Entry::Vacant(entry) => { + let action = self.action_set.create_action::(name, name, &[])?; + entry.insert(UntypedOXrAction::Vec2(action.clone())); + action + } + }; + Ok(OXrAction::(action, self.inner_session.clone(), PhantomData).into()) + } +} + +pub struct OXrHaptics( + openxr::Action, + openxr::Session, +); + +impl HapticTrait for OXrHaptics {} + +pub struct OXrPoseAction { + pose_space: openxr::Space, + stage: Rc, + frame_state: Rc>, +} + +impl ActionInputTrait for OXrPoseAction { + fn get(&self) -> Result { + let pose = self + .pose_space + .locate( + &self.stage, + self.frame_state.lock().unwrap().predicted_display_time, + )? + .pose; + + Ok(pose.into()) + } +} + +pub struct OXrAction( + openxr::Action, + openxr::Session, + PhantomData, +); + +impl ActionInputTrait for OXrAction { + fn get(&self) -> Result { + Ok(self.0.state(&self.1, openxr::Path::NULL)?.current_state) + } +} + +impl ActionInputTrait for OXrAction { + fn get(&self) -> Result { + let Vector2f { x, y } = self.0.state(&self.1, openxr::Path::NULL)?.current_state; + Ok(Vec2 { x, y }) } } diff --git a/xr_api/src/backend/oxr/graphics/vulkan.rs b/xr_api/src/backend/oxr/graphics/vulkan.rs index 8a0f613..dcf749d 100644 --- a/xr_api/src/backend/oxr/graphics/vulkan.rs +++ b/xr_api/src/backend/oxr/graphics/vulkan.rs @@ -1,4 +1,5 @@ use std::ffi::{c_void, CString}; +use std::rc::Rc; use std::sync::Mutex; use super::{Swapchain, SwapchainInner, VIEW_TYPE}; @@ -306,15 +307,19 @@ pub fn init_oxr_graphics( buffers, image_index: Mutex::new(0), }), - frame_state: Mutex::new(openxr::FrameState { + frame_state: Rc::new(Mutex::new(openxr::FrameState { predicted_display_time: openxr::Time::from_nanos(1), predicted_display_period: openxr::Duration::from_nanos(1), should_render: true, - }), + })), blend_mode, frame_waiter: Mutex::new(frame_wait), - stage: session - .create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?, + stage: Rc::new( + session.create_reference_space( + openxr::ReferenceSpaceType::STAGE, + openxr::Posef::IDENTITY, + )?, + ), head: session .create_reference_space(openxr::ReferenceSpaceType::VIEW, openxr::Posef::IDENTITY)?, format: swapchain_format, diff --git a/xr_api/src/backend/oxr/utils.rs b/xr_api/src/backend/oxr/utils.rs index a3a6698..793d40f 100644 --- a/xr_api/src/backend/oxr/utils.rs +++ b/xr_api/src/backend/oxr/utils.rs @@ -1,7 +1,11 @@ use glam::Quat; use openxr::Posef; -use crate::{error::XrError, prelude::Pose}; +use crate::{ + error::XrError, + path::{input, Handed, InputId, PathComponent, UntypedActionPath}, + prelude::Pose, +}; impl From for XrError { fn from(_: openxr::sys::Result) -> Self { @@ -10,17 +14,13 @@ impl From for XrError { } impl From for Pose { - fn from(value: Posef) -> Self { - let translation = { - let openxr::Vector3f { x, y, z } = value.position; - [x, y, z].into() - }; - + fn from(pose: Posef) -> Self { + // with enough sign errors anything is possible let rotation = { - let openxr::Quaternionf { x, y, z, w } = value.orientation; - - Quat::from_xyzw(x, y, z, w) + let o = pose.orientation; + Quat::from_rotation_x(180.0f32.to_radians()) * glam::quat(o.w, o.z, o.y, o.x) }; + let translation = glam::vec3(-pose.position.x, pose.position.y, -pose.position.z); Pose { translation, @@ -28,3 +28,63 @@ impl From for Pose { } } } + +impl UntypedActionPath { + pub(crate) fn into_xr_path(self) -> String { + let dev_path; + let sub_path; + let comp_path = match self.comp { + PathComponent::Click => "/click", + PathComponent::Touch => "/touch", + PathComponent::Value => "/value", + PathComponent::X => "/x", + PathComponent::Y => "/y", + PathComponent::Pose => "/pose", + PathComponent::Haptic => "/haptic", + }; + match self.input { + InputId::Left(hand) => { + dev_path = "/user/hand/left"; + sub_path = match hand { + Handed::PrimaryButton => "/input/x", + Handed::SecondaryButton => "/input/y", + Handed::Select => "/input/select", + Handed::Menu => "/input/menu", + Handed::Thumbstick => "/input/thumbstick", + Handed::Trigger => "/input/trigger", + Handed::Grip if matches!(self.comp, PathComponent::Pose) => "/input/grip", + Handed::Grip => "/input/squeeze", + Handed::Output => "/output", + }; + } + InputId::Right(hand) => { + dev_path = "/user/hand/right"; + sub_path = match hand { + Handed::PrimaryButton => "/input/a", + Handed::SecondaryButton => "/input/b", + Handed::Select => "/input/select", + Handed::Menu => "/input/menu", + Handed::Thumbstick => "/input/thumbstick", + Handed::Trigger => "/input/trigger", + Handed::Grip if matches!(self.comp, PathComponent::Pose) => "/input/grip", + Handed::Grip => "/input/squeeze", + Handed::Output => "/output", + }; + } + InputId::Head(head) => { + use input::head::Head; + dev_path = "/user/head"; + sub_path = match head { + Head::VolumeUp => "/input/volume_up", + Head::VolumeDown => "/input/volume_down", + Head::MuteMic => "/input/mute_mic", + }; + } + }; + + let mut path = dev_path.to_owned(); + path.push_str(sub_path); + path.push_str(comp_path); + path + } +} diff --git a/xr_api/src/backend/webxr.rs b/xr_api/src/backend/webxr.rs index cfa6f71..b5ea43e 100644 --- a/xr_api/src/backend/webxr.rs +++ b/xr_api/src/backend/webxr.rs @@ -195,6 +195,8 @@ impl InputTrait for WebXrInput { } } +pub struct OXrActionInput(openxr::Action); + pub struct WebXrHaptics(web_sys::GamepadHapticActuator, ActionPath); impl ActionTrait for WebXrHaptics { diff --git a/xr_api/src/path.rs b/xr_api/src/path.rs index 516891d..970d152 100644 --- a/xr_api/src/path.rs +++ b/xr_api/src/path.rs @@ -1,20 +1,18 @@ use std::marker::PhantomData; -use glam::Vec2; - use crate::prelude::ActionType; +#[derive(Clone, Copy)] pub struct ActionPath { pub(crate) input: InputId, pub(crate) comp: PathComponent, - pub(crate) hand: Option, _data: PhantomData

, } +#[derive(Clone, Copy)] pub struct UntypedActionPath { pub(crate) input: InputId, pub(crate) comp: PathComponent, - pub(crate) hand: Option, } impl From> for UntypedActionPath { @@ -24,11 +22,11 @@ impl From> for UntypedActionPath { } impl ActionPath

{ - const fn new(input: InputId, comp: PathComponent, hand: Option) -> Self { + const fn new(input: InputId, comp: PathComponent) -> Self { Self { input, comp, - hand, + // hand, _data: PhantomData, } } @@ -37,21 +35,18 @@ impl ActionPath

{ UntypedActionPath { input: self.input, comp: self.comp, - hand: self.hand, + // hand: self.hand, } } } -pub(crate) enum Hand { - Left, - Right, -} - +#[derive(Clone, Copy)] pub(crate) enum PathComponent { Click, Touch, Value, - Axes, + X, + Y, Pose, Haptic, } @@ -74,10 +69,16 @@ impl Value { const COMP: PathComponent = PathComponent::Value; } -pub struct Axes; +pub struct X; -impl Axes { - const COMP: PathComponent = PathComponent::Axes; +impl X { + const COMP: PathComponent = PathComponent::X; +} + +pub struct Y; + +impl Y { + const COMP: PathComponent = PathComponent::Y; } pub struct Pose; @@ -105,11 +106,15 @@ impl InputComponent for Touch { } impl InputComponent for Value { - type PathType = bool; + type PathType = f32; } -impl InputComponent for Axes { - type PathType = Vec2; +impl InputComponent for X { + type PathType = f32; +} + +impl InputComponent for Y { + type PathType = f32; } impl InputComponent for Pose { @@ -129,85 +134,151 @@ macro_rules! input_ids { $(#[$inner_handed_meta:meta])* $inner_handed:ident { $( - $comp_name_handed:ident, - )* + $(#[$comp_name_handed_meta:meta])* + $comp_name_handed:ident + ),* + $(,)? } )* } $( - $(#[$inner_meta:meta])* - $inner:ident { + $(#[$dev_path_meta:meta])* + $dev_path:ident { $( - $comp_name:ident, + $(#[$inner_meta:meta])* + $inner:ident { + $( + $(#[$comp_name_meta:meta])* + $comp_name:ident + ),* + $(,)? + } )* } )* ) => { - $( - #[$id_meta] - )* paste::paste! { - pub(crate) enum $id { - $( - $inner, - )* - $( - [<$inner_handed Left>], - [<$inner_handed Right>], - )* - } - } - - pub mod left { const LEFT: bool = true; - $( - pub type $inner_handed = super::$inner_handed; - )* - } - - pub mod right { const RIGHT: bool = false; + $( - pub type $inner_handed = super::$inner_handed; + #[$id_meta] )* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub(crate) enum $id { + Left(Handed), + Right(Handed), + $( + $( + #[$dev_path_meta] + )* + [<$dev_path:camel>](input::$dev_path::[<$dev_path:camel>]), + )* + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub(crate) enum Handed { + $( + $( + #[$inner_handed_meta] + )* + $inner_handed, + )* + } + + pub mod input { + use super::*; + + pub(crate) mod private { + $( + $( + #[$inner_handed_meta] + )* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct $inner_handed; + )* + } + + pub mod hand_left { + use super::*; + + $( + $( + #[$inner_handed_meta] + )* + pub type $inner_handed = private::$inner_handed; + + impl $inner_handed { + $( + $( + #[$comp_name_handed_meta] + )* + pub const [<$comp_name_handed:snake:upper>]: ActionPath<$comp_name_handed> = ActionPath::<$comp_name_handed>::new($id::Left(Handed::$inner_handed), $comp_name_handed::COMP); + )* + } + )* + } + + pub mod hand_right { + use super::*; + + $( + $( + #[$inner_handed_meta] + )* + pub type $inner_handed = private::$inner_handed; + + impl $inner_handed { + $( + $( + #[$comp_name_handed_meta] + )* + pub const [<$comp_name_handed:snake:upper>]: ActionPath<$comp_name_handed> = ActionPath::<$comp_name_handed>::new($id::Right(Handed::$inner_handed), $comp_name_handed::COMP); + )* + } + )* + } + + $( + $( + #[$dev_path_meta] + )* + pub mod $dev_path { + use super::*; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub(crate) enum [<$dev_path:camel>] { + $( + $( + #[$inner_meta] + )* + $inner, + )* + } + + $( + $( + #[$inner_meta] + )* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct $inner; + + $( + #[$inner_meta] + )* + impl $inner { + $( + $( + #[$comp_name_meta] + )* + pub const [<$comp_name:snake:upper>]: ActionPath<$comp_name> = ActionPath::<$comp_name>::new($id::[<$dev_path:camel>]([<$dev_path:camel>]::$inner), $comp_name::COMP); + )* + } + )* + } + )* + } } - - $( - $( - #[$inner_handed_meta] - )* - pub struct $inner_handed; - impl $inner_handed { - paste::paste! { - $( - pub const [<$comp_name_handed:snake:upper>]: ActionPath<$comp_name_handed> = ActionPath::<$comp_name_handed>::new($id::[<$inner_handed Left>], $comp_name_handed::COMP, Some(Hand::Left)); - )* - } - } - impl $inner_handed { - paste::paste! { - $( - pub const [<$comp_name_handed:snake:upper>]: ActionPath<$comp_name_handed> = ActionPath::<$comp_name_handed>::new($id::[<$inner_handed Right>], $comp_name_handed::COMP, Some(Hand::Right)); - )* - } - } - - )* - - $( - $( - #[$inner_meta] - )* - pub struct $inner; - - impl $inner { - paste::paste! { - $( - pub const [<$comp_name:snake:upper>]: ActionPath<$comp_name> = ActionPath::<$comp_name>::new($id::$inner, $comp_name::COMP, None); - )* - } - } - )* }; } @@ -229,7 +300,8 @@ input_ids! { Click, } Thumbstick { - Axes, + X, + Y, Click, Touch, } @@ -242,5 +314,19 @@ input_ids! { Value, Pose, } + Output { + Haptic, + } + } + head { + VolumeUp { + Click, + } + VolumeDown { + Click, + } + MuteMic { + Click, + } } } diff --git a/xr_api/src/types.rs b/xr_api/src/types.rs index f3c7301..395091b 100644 --- a/xr_api/src/types.rs +++ b/xr_api/src/types.rs @@ -15,7 +15,9 @@ pub struct SessionCreateInfo { pub texture_format: wgpu::TextureFormat, } -pub struct Bindings {} +pub enum Bindings { + OculusTouch, +} pub struct Haptic;