openxr implementation complete

This commit is contained in:
awtterpip
2024-01-31 20:12:14 -06:00
parent 4678013e1c
commit 96ebb1e7e6
9 changed files with 428 additions and 134 deletions

View File

@@ -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"

View File

@@ -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<T: ActionInputTrait<Pose> + 'static> From<T> for Action<Pose> {
}
}
impl<T: ActionInputTrait<Vec2> + 'static> From<T> for Action<Vec2> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<bool> + 'static> From<T> for Action<bool> {
fn from(value: T) -> Self {
Self(Rc::new(value))

View File

@@ -79,7 +79,7 @@ impl dyn InputTrait {
/// Represents input actions, such as bools, floats, and poses
pub trait ActionInputTrait<A> {
fn get(&self) -> A;
fn get(&self) -> Result<A>;
}
/// Represents haptic actions.

View File

@@ -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<openxr::Haptic>),
Pose(openxr::Action<openxr::Posef>),
Float(openxr::Action<f32>),
@@ -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<Bindings>,
map: HashMap<String, UntypedOXrAction>,
}
pub struct OXrSession {
@@ -100,11 +102,11 @@ pub struct OXrSession {
)>,
>,
pub(crate) bindings: Rc<Mutex<BindingState>>,
pub(crate) frame_state: Mutex<openxr::FrameState>,
pub(crate) frame_state: Rc<Mutex<openxr::FrameState>>,
pub(crate) views: Mutex<[openxr::View; 2]>,
pub(crate) frame_waiter: Mutex<openxr::FrameWaiter>,
pub(crate) swapchain: graphics::Swapchain,
pub(crate) stage: openxr::Space,
pub(crate) stage: Rc<openxr::Space>,
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<Input> {
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::<Vec<_>>())?;
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<openxr::AnyGraphics>,
action_set: openxr::ActionSet,
bindings: Rc<Mutex<BindingState>>,
}
impl OXrInput {
fn create_action<A: openxr::ActionTy>(
&self,
name: &str,
handed: bool,
) -> openxr::Result<openxr::Action<A>> {
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::<A>(name, name, &[left_path, right_path])
} else {
self.action_set.create_action(name, name, &[])
}
}
stage: Rc<openxr::Space>,
frame_state: Rc<Mutex<openxr::FrameState>>,
}
impl InputTrait for OXrInput {
@@ -287,7 +298,21 @@ impl InputTrait for OXrInput {
name: &str,
path: path::UntypedActionPath,
) -> Result<Action<Haptic>> {
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::<openxr::Haptic>(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<Action<Pose>> {
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::<openxr::Posef>(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<Action<f32>> {
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::<f32>(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<Action<bool>> {
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::<bool>(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<Action<glam::Vec2>> {
todo!()
) -> Result<Action<Vec2>> {
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::<Vector2f>(name, name, &[])?;
entry.insert(UntypedOXrAction::Vec2(action.clone()));
action
}
};
Ok(OXrAction::<Vector2f, Vec2>(action, self.inner_session.clone(), PhantomData).into())
}
}
pub struct OXrHaptics(
openxr::Action<openxr::Haptic>,
openxr::Session<openxr::AnyGraphics>,
);
impl HapticTrait for OXrHaptics {}
pub struct OXrPoseAction {
pose_space: openxr::Space,
stage: Rc<openxr::Space>,
frame_state: Rc<Mutex<openxr::FrameState>>,
}
impl ActionInputTrait<Pose> for OXrPoseAction {
fn get(&self) -> Result<Pose> {
let pose = self
.pose_space
.locate(
&self.stage,
self.frame_state.lock().unwrap().predicted_display_time,
)?
.pose;
Ok(pose.into())
}
}
pub struct OXrAction<A: openxr::ActionInput, T = ()>(
openxr::Action<A>,
openxr::Session<openxr::AnyGraphics>,
PhantomData<T>,
);
impl<A: ActionType + openxr::ActionInput> ActionInputTrait<A> for OXrAction<A> {
fn get(&self) -> Result<A> {
Ok(self.0.state(&self.1, openxr::Path::NULL)?.current_state)
}
}
impl ActionInputTrait<Vec2> for OXrAction<Vector2f, Vec2> {
fn get(&self) -> Result<Vec2> {
let Vector2f { x, y } = self.0.state(&self.1, openxr::Path::NULL)?.current_state;
Ok(Vec2 { x, y })
}
}

View File

@@ -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,

View File

@@ -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<openxr::sys::Result> for XrError {
fn from(_: openxr::sys::Result) -> Self {
@@ -10,17 +14,13 @@ impl From<openxr::sys::Result> for XrError {
}
impl From<Posef> 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<Posef> 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
}
}

View File

@@ -195,6 +195,8 @@ impl InputTrait for WebXrInput {
}
}
pub struct OXrActionInput(openxr::Action);
pub struct WebXrHaptics(web_sys::GamepadHapticActuator, ActionPath);
impl ActionTrait for WebXrHaptics {

View File

@@ -1,20 +1,18 @@
use std::marker::PhantomData;
use glam::Vec2;
use crate::prelude::ActionType;
#[derive(Clone, Copy)]
pub struct ActionPath<P: InputComponent> {
pub(crate) input: InputId,
pub(crate) comp: PathComponent,
pub(crate) hand: Option<Hand>,
_data: PhantomData<P>,
}
#[derive(Clone, Copy)]
pub struct UntypedActionPath {
pub(crate) input: InputId,
pub(crate) comp: PathComponent,
pub(crate) hand: Option<Hand>,
}
impl<P: InputComponent> From<ActionPath<P>> for UntypedActionPath {
@@ -24,11 +22,11 @@ impl<P: InputComponent> From<ActionPath<P>> for UntypedActionPath {
}
impl<P: InputComponent> ActionPath<P> {
const fn new(input: InputId, comp: PathComponent, hand: Option<Hand>) -> Self {
const fn new(input: InputId, comp: PathComponent) -> Self {
Self {
input,
comp,
hand,
// hand,
_data: PhantomData,
}
}
@@ -37,21 +35,18 @@ impl<P: InputComponent> ActionPath<P> {
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<LEFT>;
)*
}
pub mod right {
const RIGHT: bool = false;
$(
pub type $inner_handed = super::$inner_handed<RIGHT>;
#[$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<const HAND: bool>;
)*
}
pub mod hand_left {
use super::*;
$(
$(
#[$inner_handed_meta]
)*
pub type $inner_handed = private::$inner_handed<LEFT>;
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<RIGHT>;
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<const HAND: bool>;
impl $inner_handed<true> {
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<false> {
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,
}
}
}

View File

@@ -15,7 +15,9 @@ pub struct SessionCreateInfo {
pub texture_format: wgpu::TextureFormat,
}
pub struct Bindings {}
pub enum Bindings {
OculusTouch,
}
pub struct Haptic;