move graphics initialization to instance

This commit is contained in:
awtterpip
2024-02-27 22:45:12 -06:00
parent bda328f434
commit ca007e8937
21 changed files with 244 additions and 2358 deletions

View File

@@ -7,33 +7,66 @@ pub use resources::*;
pub use types::*;
use bevy::app::{App, Plugin};
use bevy::log::error;
pub fn xr_entry() -> Result<XrEntry> {
#[cfg(windows)]
let entry = openxr::Entry::linked();
#[cfg(not(windows))]
let entry = unsafe { openxr::Entry::load()? };
Ok(entry.into())
Ok(XrEntry(entry))
}
fn init_xr() -> Result<()> {
let entry = xr_entry()?;
let instance = entry.create_instance(
AppInfo::default(),
XrExtensions::default(),
GraphicsBackend::Vulkan(()),
)?;
let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
instance.create_session(system_id)?;
Ok(())
pub struct XrInitPlugin {
/// Information about the app this is being used to build.
pub app_info: AppInfo,
/// Extensions wanted for this session.
// This should preferably be changed into a simpler list of features wanted that this crate supports. i.e. hand tracking
pub exts: XrExtensions,
/// List of backends the openxr session can use. If [None], pick the first available backend.
pub backends: Option<Vec<GraphicsBackend>>,
}
pub struct XrInitPlugin;
impl Plugin for XrInitPlugin {
fn build(&self, app: &mut App) {
let entry = xr_entry();
init_xr(self, app).unwrap();
todo!()
}
}
fn init_xr(config: &XrInitPlugin, _app: &mut App) -> Result<()> {
let entry = xr_entry()?;
let available_exts = entry.enumerate_extensions()?;
for ext in available_exts.unavailable_exts(&config.exts) {
error!(
"Extension \"{ext}\" not available in the current openxr runtime. Disabling extension."
);
}
let available_backends = GraphicsBackend::available_backends(&available_exts);
// Backend selection
let backend = if let Some(wanted_backends) = &config.backends {
let mut backend = None;
for wanted_backend in wanted_backends {
if available_backends.contains(wanted_backend) {
backend = Some(*wanted_backend);
break;
}
}
backend
} else {
available_backends.first().copied()
}
.ok_or(XrError::NoAvailableBackend)?;
let exts = config.exts.clone() & available_exts;
let instance = entry.create_instance(config.app_info.clone(), exts, backend)?;
let _system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
//instance.create_session(system_id)?;
Ok(())
}

View File

@@ -49,6 +49,40 @@ impl Default for XrExtensions {
}
}
macro_rules! unavailable_exts {
(
$exts:ty;
$(
$(
#[$meta:meta]
)*
$ident:ident
),*
$(,)?
) => {
impl $exts {
/// Returns any extensions needed by `required_exts` that aren't available in `self`
pub(crate) fn unavailable_exts(&self, required_exts: &Self) -> Vec<std::borrow::Cow<'static, str>> {
let mut exts = vec![];
$(
$(
#[$meta]
)*
if required_exts.0.$ident && !self.0.$ident {
exts.push(std::borrow::Cow::Borrowed(stringify!($ident)))
}
)*
for ext in required_exts.0.other.iter() {
if !self.0.other.contains(ext) {
exts.push(std::borrow::Cow::Owned(ext.clone()))
}
}
exts
}
}
};
}
macro_rules! bitor {
(
$exts:ty;
@@ -254,4 +288,4 @@ macro_rules! impl_ext {
};
}
impl_ext!(bitor, bitand);
impl_ext!(bitor, bitand, unavailable_exts);

View File

@@ -1,15 +1,21 @@
pub mod vulkan;
use std::any::TypeId;
use bevy::math::UVec2;
use crate::openxr::resources::*;
use crate::openxr::types::{AppInfo, Result, XrError};
use crate::types::BlendMode;
pub trait GraphicsExt: openxr::Graphics {
pub unsafe trait GraphicsExt: openxr::Graphics {
/// Wrap the graphics specific type into the [GraphicsWrap] enum
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T>;
/// Convert from wgpu format to the graphics format
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>;
/// Convert from the graphics format to wgpu format
fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
fn create_session(
/// Initialize graphics for this backend
fn init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
@@ -18,10 +24,9 @@ pub trait GraphicsExt: openxr::Graphics {
wgpu::Queue,
wgpu::Adapter,
wgpu::Instance,
XrSession,
XrFrameWaiter,
XrFrameStream,
Self::SessionCreateInfo,
)>;
/// Convert a swapchain function
unsafe fn to_wgpu_img(
image: Self::SwapchainImage,
device: &wgpu::Device,
@@ -45,16 +50,32 @@ impl<T: GraphicsType> GraphicsWrap<T> {
)
}
fn graphics_type(&self) -> std::any::TypeId {
fn graphics_type(&self) -> TypeId {
graphics_match!(
self;
_ => std::any::TypeId::of::<Api>()
_ => TypeId::of::<Api>()
)
}
/// Checks if this struct is using the wanted graphics api.
pub fn using_graphics<G: GraphicsExt + 'static>(&self) -> bool {
self.graphics_type() == std::any::TypeId::of::<G>()
self.graphics_type() == TypeId::of::<G>()
}
/// Checks if the two values are both using the same graphics backend
pub fn using_graphics_of_val<V: GraphicsType>(&self, other: &GraphicsWrap<V>) -> bool {
self.graphics_type() == other.graphics_type()
}
pub fn as_type<G: GraphicsExt>(&self) -> Result<&T::Inner<G>> {
// graphics_match!(
// self;
// inner => if TypeId::of::<Api> == TypeId::of::<G> {
// return Ok(inner)
// }
// );
return Err(XrError::FailedGraphicsRequirements);
}
}

View File

@@ -8,7 +8,6 @@ use wgpu_hal::api::Vulkan;
use wgpu_hal::Api;
use crate::openxr::extensions::XrExtensions;
use crate::openxr::resources::*;
use crate::openxr::types::Result;
use super::{AppInfo, GraphicsExt, XrError};
@@ -25,7 +24,7 @@ const VK_TARGET_VERSION_ASH: u32 = ash::vk::make_api_version(
VK_TARGET_VERSION.patch() as u32,
);
impl GraphicsExt for openxr::Vulkan {
unsafe impl GraphicsExt for openxr::Vulkan {
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> {
wgpu_to_vulkan(format).map(|f| f.as_raw() as _)
}
@@ -34,7 +33,7 @@ impl GraphicsExt for openxr::Vulkan {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
}
fn create_session(
fn init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
@@ -43,9 +42,7 @@ impl GraphicsExt for openxr::Vulkan {
wgpu::Queue,
wgpu::Adapter,
wgpu::Instance,
XrSession,
XrFrameWaiter,
XrFrameStream,
Self::SessionCreateInfo,
)> {
let reqs = instance.graphics_requirements::<openxr::Vulkan>(system_id)?;
if VK_TARGET_VERSION < reqs.min_api_version_supported
@@ -228,30 +225,18 @@ impl GraphicsExt for openxr::Vulkan {
)
}?;
let (session, frame_wait, frame_stream) = unsafe {
instance.create_session::<openxr::Vulkan>(
system_id,
&openxr::vulkan::SessionCreateInfo {
instance: vk_instance_ptr,
physical_device: vk_physical_device_ptr,
device: vk_device_ptr,
queue_family_index,
queue_index: 0,
},
)
}?;
Ok((
wgpu_device,
wgpu_queue,
wgpu_adapter,
wgpu_instance,
XrSession(
session.clone().into_any_graphics(),
super::GraphicsWrap::Vulkan(session),
),
XrFrameWaiter(frame_wait),
XrFrameStream(super::GraphicsWrap::Vulkan(frame_stream)),
openxr::vulkan::SessionCreateInfo {
instance: vk_instance_ptr,
physical_device: vk_physical_device_ptr,
device: vk_device_ptr,
queue_family_index,
queue_index: 0,
},
))
}
@@ -310,6 +295,10 @@ impl GraphicsExt for openxr::Vulkan {
extensions.khr_vulkan_enable2 = true;
extensions.into()
}
fn wrap<T: super::GraphicsType>(item: T::Inner<Self>) -> super::GraphicsWrap<T> {
super::GraphicsWrap::Vulkan(item)
}
}
fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {

View File

@@ -1,12 +1,14 @@
use bevy::prelude::*;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderInstance, RenderQueue};
use bevy::render::settings::RenderCreation;
use openxr::AnyGraphics;
use super::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap};
use super::types::*;
#[derive(Deref, Clone)]
pub struct XrEntry(openxr::Entry);
pub struct XrEntry(pub openxr::Entry);
impl XrEntry {
pub fn enumerate_extensions(&self) -> Result<XrExtensions> {
@@ -48,37 +50,57 @@ impl XrEntry {
}
}
impl From<openxr::Entry> for XrEntry {
fn from(value: openxr::Entry) -> Self {
Self(value)
}
}
#[derive(Resource, Deref, Clone)]
pub struct XrInstance(
#[deref] pub(crate) openxr::Instance,
#[deref] pub openxr::Instance,
pub(crate) GraphicsBackend,
pub(crate) AppInfo,
);
impl XrInstance {
pub fn create_session(
pub fn init_graphics(
&self,
system_id: openxr::SystemId,
) -> Result<(
wgpu::Device,
wgpu::Queue,
wgpu::Adapter,
wgpu::Instance,
XrSession,
XrFrameWaiter,
XrFrameStream,
)> {
) -> Result<(RenderCreation, SessionCreateInfo)> {
graphics_match!(
self.1;
_ => Api::create_session(&self.2, &self.0, system_id)
_ => {
let (device, queue, adapter, instance, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
Ok((RenderCreation::manual(device.into(), RenderQueue(queue.into()), RenderAdapterInfo(adapter.get_info()), RenderAdapter(adapter.into()), RenderInstance(instance.into())), SessionCreateInfo(Api::wrap(session_info))))
}
)
}
/// # Safety
///
/// `info` must contain valid handles for the graphics api
pub unsafe fn create_session(
&self,
system_id: openxr::SystemId,
info: SessionCreateInfo,
) -> Result<(XrSession, XrFrameWaiter, XrFrameStream)> {
if !info.0.using_graphics_of_val(&self.1) {
return Err(XrError::GraphicsBackendMismatch {
item: std::any::type_name::<SessionCreateInfo>(),
backend: info.0.graphics_name(),
expected_backend: self.1.graphics_name(),
});
}
graphics_match!(
info.0;
info => {
let (session, frame_waiter, frame_stream) = unsafe { self.0.create_session::<Api>(system_id, &info)? };
Ok((session.into(), XrFrameWaiter(frame_waiter), XrFrameStream(Api::wrap(frame_stream))))
}
)
}
}
pub struct SessionCreateInfo(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for SessionCreateInfo {
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
}
impl GraphicsType for XrSession {
@@ -91,6 +113,12 @@ pub struct XrSession(
pub(crate) GraphicsWrap<Self>,
);
impl<G: GraphicsExt> From<openxr::Session<G>> for XrSession {
fn from(value: openxr::Session<G>) -> Self {
Self(value.clone().into_any_graphics(), G::wrap(value))
}
}
impl XrSession {
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
graphics_match!(
@@ -107,6 +135,7 @@ impl XrSession {
}
}
#[derive(Resource)]
pub struct XrFrameStream(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for XrFrameStream {
@@ -135,8 +164,8 @@ impl XrFrameStream {
for (i, layer) in layers.into_iter().enumerate() {
if let Some(swapchain) = layer.swapchain() {
if !swapchain.0.using_graphics::<Api>() {
warn!(
"composition layer {i} is using graphics api '{}', expected graphics api '{}'. Excluding layer from frame submission.",
error!(
"Composition layer {i} is using graphics api '{}', expected graphics api '{}'. Excluding layer from frame submission.",
swapchain.0.graphics_name(),
std::any::type_name::<Api>(),
);
@@ -152,9 +181,10 @@ impl XrFrameStream {
}
}
#[derive(Deref, DerefMut)]
#[derive(Resource, Deref, DerefMut)]
pub struct XrFrameWaiter(pub openxr::FrameWaiter);
#[derive(Resource)]
pub struct XrSwapchain(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for XrSwapchain {

View File

@@ -1,11 +1,10 @@
use std::borrow::Cow;
use thiserror::Error;
use super::graphics::{graphics_match, GraphicsExt, GraphicsWrap};
pub use super::extensions::XrExtensions;
pub use openxr::{
Extent2Di, Offset2Di, Rect2Di, SwapchainCreateFlags, SwapchainUsageFlags, SystemId,
Extent2Di, Graphics, Offset2Di, Rect2Di, SwapchainCreateFlags, SwapchainUsageFlags, SystemId,
};
pub type Result<T> = std::result::Result<T, XrError>;
@@ -53,32 +52,71 @@ impl GraphicsBackend {
}
}
#[derive(Error, Debug)]
pub enum XrError {
#[error("OpenXR error: {0}")]
OpenXrError(#[from] openxr::sys::Result),
#[error("OpenXR loading error: {0}")]
OpenXrLoadingError(#[from] openxr::LoadError),
#[error("WGPU instance error: {0}")]
WgpuInstanceError(#[from] wgpu_hal::InstanceError),
#[error("WGPU device error: {0}")]
WgpuDeviceError(#[from] wgpu_hal::DeviceError),
#[error("WGPU request device error: {0}")]
WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError),
#[error("Unsupported texture format: {0:?}")]
UnsupportedTextureFormat(wgpu::TextureFormat),
#[error("Vulkan error: {0}")]
VulkanError(#[from] ash::vk::Result),
#[error("Vulkan loading error: {0}")]
VulkanLoadingError(#[from] ash::LoadingError),
#[error("Graphics backend '{0:?}' is not available")]
UnavailableBackend(GraphicsBackend),
#[error("Could not meet graphics requirements for platform. See console for details")]
FailedGraphicsRequirements,
#[error("Failed to create CString: {0}")]
NulError(#[from] std::ffi::NulError),
mod error {
use super::GraphicsBackend;
use std::borrow::Cow;
use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum XrError {
#[error("OpenXR error: {0}")]
OpenXrError(#[from] openxr::sys::Result),
#[error("OpenXR loading error: {0}")]
OpenXrLoadingError(#[from] openxr::LoadError),
#[error("WGPU instance error: {0}")]
WgpuInstanceError(#[from] wgpu_hal::InstanceError),
#[error("WGPU device error: {0}")]
WgpuDeviceError(#[from] wgpu_hal::DeviceError),
#[error("WGPU request device error: {0}")]
WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError),
#[error("Unsupported texture format: {0:?}")]
UnsupportedTextureFormat(wgpu::TextureFormat),
#[error("Vulkan error: {0}")]
VulkanError(#[from] ash::vk::Result),
#[error("Vulkan loading error: {0}")]
VulkanLoadingError(#[from] ash::LoadingError),
#[error("Graphics backend '{0:?}' is not available")]
UnavailableBackend(GraphicsBackend),
#[error("No available backend")]
NoAvailableBackend,
#[error("OpenXR runtime does not support these extensions: {0}")]
UnavailableExtensions(UnavailableExts),
#[error("Could not meet graphics requirements for platform. See console for details")]
FailedGraphicsRequirements,
#[error(
"Tried to use item {item} with backend {backend}. Expected backend {expected_backend}"
)]
GraphicsBackendMismatch {
item: &'static str,
backend: &'static str,
expected_backend: &'static str,
},
#[error("Failed to create CString: {0}")]
NulError(#[from] std::ffi::NulError),
}
impl From<Vec<Cow<'static, str>>> for XrError {
fn from(value: Vec<Cow<'static, str>>) -> Self {
Self::UnavailableExtensions(UnavailableExts(value))
}
}
#[derive(Debug)]
pub struct UnavailableExts(Vec<Cow<'static, str>>);
impl fmt::Display for UnavailableExts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for s in &self.0 {
write!(f, "\t{s}")?;
}
Ok(())
}
}
}
pub use error::XrError;
#[derive(Debug, Copy, Clone)]
pub struct SwapchainCreateInfo {
pub create_flags: SwapchainCreateFlags,

View File

@@ -5,7 +5,7 @@ use bevy::render::camera::{RenderTarget, Viewport};
use crate::types::Pose;
pub(crate) const XR_TEXTURE_VIEW_INDEX: u32 = 1208214591;
pub const XR_TEXTURE_VIEW_INDEX: u32 = 1208214591;
#[derive(Debug, Clone)]
pub struct XrView {

View File

@@ -1,78 +0,0 @@
[package]
name = "xr_api"
version = "0.1.0"
edition = "2021"
[features]
default = ["linked"]
linked = ["openxr/linked"]
[dependencies]
futures = "0.3.29"
glam = "0.24.1"
hashbrown = "0.14"
paste = "1.0.14"
thiserror = "1.0.51"
tracing = "0.1.40"
wgpu = "0.17.1"
wgpu-hal = "0.17.1"
[target.'cfg(not(target_family = "wasm"))'.dependencies]
openxr = "0.17.1"
ash = "0.37.3"
[target.'cfg(target_family = "wasm")'.dependencies]
wasm-bindgen = "0.2.87"
web-sys = { version = "0.3.61", features = [
# STANDARD
'console',
'Document',
'Element',
'Headers',
'Navigator',
'Window',
# IO
# 'Url',
# WEBGL
'Gpu',
'HtmlCanvasElement',
'WebGl2RenderingContext',
'WebGlFramebuffer',
## XR
'DomPointReadOnly',
'XrWebGlLayer',
'XrBoundedReferenceSpace',
'XrEye',
'XrFrame',
'XrHandedness',
'XrInputSource',
'XrInputSourceArray',
'XrInputSourceEvent',
'XrInputSourceEventInit',
'XrInputSourcesChangeEvent',
'XrJointPose',
'XrJointSpace',
'XrPose',
'XrReferenceSpace',
'XrReferenceSpaceEvent',
'XrReferenceSpaceEventInit',
'XrReferenceSpaceType',
'XrRenderState',
'XrRenderStateInit',
'XrRigidTransform',
'XrSession',
'XrSessionEvent',
'XrSessionEventInit',
'XrSessionInit',
'XrSessionMode',
'XrSpace',
'XrTargetRayMode',
'XrView',
'XrViewerPose',
'XrViewport',
'XrVisibilityState',
'XrWebGlLayer',
'XrWebGlLayerInit',
'XrSystem',
] }
wasm-bindgen-futures = "0.4"

View File

@@ -1,117 +0,0 @@
use std::ops::Deref;
use std::rc::Rc;
use glam::Vec2;
use crate::prelude::*;
/// Entry point to the API
///
/// To see methods available for this struct, refer to [EntryTrait]
#[derive(Clone)]
pub struct Entry(Rc<dyn EntryTrait>);
impl Entry {
/// Constructs a new Xr entry
pub fn new() -> Self {
todo!()
}
}
/// Represents an intent to start a session with requested extensions.
///
/// To see methods available for this struct, refer to [InstanceTrait]
#[derive(Clone)]
pub struct Instance(Rc<dyn InstanceTrait>);
/// Represents a running XR application.
///
/// To see methods available for this struct, refer to [SessionTrait]
#[derive(Clone)]
pub struct Session(Rc<dyn SessionTrait>);
/// A view of one eye. Used to retrieve render data such as texture views and projection matrices.
///
/// To see methods available for this struct, refer to [ViewTrait]
#[derive(Clone)]
pub struct View(Rc<dyn ViewTrait>);
/// Represents all XR input sources.
///
/// To see methods available for this struct, refer to [InputTrait]
#[derive(Clone)]
pub struct Input(Rc<dyn InputTrait>);
/// Represents an XR Action. Can be used to retrieve input values or trigger output devices such as haptics.
///
/// The methods available to this struct are dependent upon the action type. For input values, use `.get()` to retrieve the values.
/// For haptics, please refer to [HapticTrait]
#[derive(Clone)]
pub struct Action<A: ActionType>(Rc<A::Inner>);
macro_rules! impl_api {
($($t:ty, $trait:ident; )*) => {
$(
impl std::ops::Deref for $t {
type Target = dyn $trait;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<T: $trait + 'static> From<T> for $t {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
)*
};
}
impl_api! {
Entry, EntryTrait;
Instance, InstanceTrait;
Session, SessionTrait;
View, ViewTrait;
Input, InputTrait;
}
impl<A: ActionType> Deref for Action<A> {
type Target = A::Inner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: HapticTrait + 'static> From<T> for Action<Haptic> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<f32> + 'static> From<T> for Action<f32> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<Pose> + 'static> From<T> for Action<Pose> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
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

@@ -1,117 +0,0 @@
use glam::{UVec2, Vec2, Vec3A};
use wgpu::{Adapter, AdapterInfo, Device, Queue, TextureView};
use crate::prelude::*;
use crate::path::{InputComponent, UntypedActionPath};
pub trait EntryTrait {
/// Return currently available extensions
fn available_extensions(&self) -> Result<ExtensionSet>;
/// Create an [Instance] with the enabled extensions.
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance>;
}
pub trait InstanceTrait {
/// Returns the [Entry] used to create this.
fn entry(&self) -> Entry;
/// Returns an [ExtensionSet] listing all enabled extensions.
fn enabled_extensions(&self) -> ExtensionSet;
/// Creates a [Session] with the requested properties
fn create_session(&self, info: SessionCreateInfo) -> Result<Session>;
}
pub trait SessionTrait {
/// Returns the [Instance] used to create this.
fn instance(&self) -> &Instance;
/// Get render resources compatible with this session.
fn get_render_resources(&self)
-> Option<(Device, Queue, AdapterInfo, Adapter, wgpu::Instance)>;
/// Returns the position of the headset.
fn headset_location(&self) -> Result<Pose>;
/// Request input modules with the specified bindings.
fn create_input(&self, bindings: Bindings) -> Result<Input>;
/// Wait until a frame is ready to render to.
fn wait_frame(&self) -> Result<FrameData>;
/// Begin rendering work for the frame.
fn begin_frame(&self) -> Result<()>;
/// Locate the views of each eye.
fn locate_views(&self) -> Result<(View, View)>;
/// Submits rendering work for this frame.
fn end_frame(&self, data: FrameData) -> Result<()>;
/// Gets the resolution of a single eye.
fn resolution(&self) -> UVec2;
/// Gets the texture format for the session.
fn format(&self) -> wgpu::TextureFormat;
}
pub trait ViewTrait {
/// Returns the [TextureView] used to render this view.
fn texture_view(&self) -> Option<TextureView>;
/// Returns the [Pose] representing the current position of this view.
fn pose(&self) -> Pose;
/// Returns the projection matrix for the current view.
fn projection_matrix(&self, near: f32, far: f32) -> glam::Mat4;
/// Gets the fov of the camera.
fn fov(&self) -> Fov;
/// Gets the resolution for this view.
fn resolution(&self) -> UVec2;
/// Gets the texture format for the view.
fn format(&self) -> wgpu::TextureFormat;
}
pub trait InputTrait {
/// Get the haptic action at the specified path.
fn create_action_haptics(&self, path: UntypedActionPath) -> Result<Action<Haptic>>;
/// Get the pose action at the specified path.
fn create_action_pose(&self, path: UntypedActionPath) -> Result<Action<Pose>>;
/// Get the float action at the specified path.
fn create_action_float(&self, path: UntypedActionPath) -> Result<Action<f32>>;
/// Get the boolean action at the specified path.
fn create_action_bool(&self, path: UntypedActionPath) -> Result<Action<bool>>;
/// Get the Vec2 action at the specified path.
fn create_action_vec2(&self, path: UntypedActionPath) -> Result<Action<Vec2>>;
}
// This impl is moved outside of the trait to ensure that InputTrait stays object safe.
impl dyn InputTrait {
/// Get the action at the specified path.
pub fn create_action<P: InputComponent>(
&self,
path: ActionPath<P>,
) -> Result<Action<P::PathType>> {
P::PathType::get(self, path.untyped())
}
}
/// Represents input actions, such as bools, floats, and poses
pub trait ActionInputTrait<A> {
fn get(&self) -> Result<A>;
}
/// Represents haptic actions.
pub trait HapticTrait {}
impl<T: InstanceTrait> EntryTrait for T {
fn available_extensions(&self) -> Result<ExtensionSet> {
self.entry().available_extensions()
}
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
self.entry().create_instance(exts)
}
}
impl<T: SessionTrait> InstanceTrait for T {
fn entry(&self) -> Entry {
self.instance().entry()
}
fn enabled_extensions(&self) -> ExtensionSet {
self.instance().enabled_extensions()
}
fn create_session(&self, info: SessionCreateInfo) -> Result<Session> {
self.instance().create_session(info)
}
}

View File

@@ -1,4 +0,0 @@
#[cfg(not(target_family = "wasm"))]
pub mod oxr;
#[cfg(target_family = "wasm")]
pub mod webxr;

View File

@@ -1,617 +0,0 @@
mod graphics;
mod utils;
use utils::UntypedOXrAction;
use std::{marker::PhantomData, rc::Rc, sync::Mutex};
use glam::{Mat4, UVec2, Vec2, Vec3A};
use hashbrown::{hash_map, HashMap};
use openxr::{EnvironmentBlendMode, Vector2f};
use tracing::{info, info_span, warn};
use crate::{backend::oxr::graphics::VIEW_TYPE, path, prelude::*};
pub struct OXrEntry(openxr::Entry);
impl OXrEntry {
pub fn new() -> Self {
#[cfg(feature = "linked")]
return OXrEntry(openxr::Entry::linked());
#[cfg(not(feature = "linked"))]
return OXrEntry(unsafe { openxr::Entry::load().expect("Failed to load OpenXR runtime") });
}
}
impl Into<openxr::ExtensionSet> for ExtensionSet {
fn into(self) -> openxr::ExtensionSet {
let mut set = openxr::ExtensionSet::default();
set.khr_vulkan_enable2 = self.vulkan;
set
}
}
impl EntryTrait for OXrEntry {
fn available_extensions(&self) -> Result<ExtensionSet> {
// self.0.enumerate_extensions();
Ok(ExtensionSet::default())
}
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
#[allow(unused_mut)]
let mut enabled_extensions: openxr::ExtensionSet = exts.into();
#[cfg(target_os = "android")]
{
enabled_extensions.khr_android_create_instance = true;
}
let xr_instance = self.0.create_instance(
&openxr::ApplicationInfo {
application_name: "bevy",
..Default::default()
},
&enabled_extensions,
&[],
)?;
Ok(OXrInstance(xr_instance, exts).into())
}
}
#[derive(Clone)]
pub struct OXrInstance(openxr::Instance, ExtensionSet);
impl InstanceTrait for OXrInstance {
fn entry(&self) -> Entry {
OXrEntry(self.0.entry().clone()).into()
}
fn enabled_extensions(&self) -> ExtensionSet {
self.1
}
fn create_session(&self, info: SessionCreateInfo) -> Result<Session> {
graphics::init_oxr_graphics(self.clone(), self.1, info.texture_format).map(Into::into)
}
}
#[derive(Default)]
pub(crate) struct BindingState {
sessions_attached: bool,
bindings: Option<Bindings>,
map: HashMap<String, UntypedOXrAction>,
}
pub struct OXrSession {
pub(crate) instance: Instance,
// this could definitely be done better
pub(crate) inner_instance: openxr::Instance,
pub(crate) session: openxr::Session<openxr::AnyGraphics>,
pub(crate) action_sets: Mutex<Vec<openxr::ActionSet>>,
pub(crate) render_resources: Mutex<
Option<(
wgpu::Device,
wgpu::Queue,
wgpu::AdapterInfo,
wgpu::Adapter,
wgpu::Instance,
)>,
>,
pub(crate) bindings: Rc<Mutex<BindingState>>,
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: Rc<openxr::Space>,
pub(crate) head: openxr::Space,
pub(crate) resolution: UVec2,
pub(crate) blend_mode: EnvironmentBlendMode,
pub(crate) format: wgpu::TextureFormat,
}
impl SessionTrait for OXrSession {
fn instance(&self) -> &Instance {
&self.instance
}
fn get_render_resources(
&self,
) -> Option<(
wgpu::Device,
wgpu::Queue,
wgpu::AdapterInfo,
wgpu::Adapter,
wgpu::Instance,
)> {
std::mem::take(&mut self.render_resources.lock().unwrap())
}
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());
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 wait_frame(&self) -> Result<FrameData> {
{
let mut bindings = self.bindings.lock().unwrap();
if !bindings.sessions_attached {
if let Some(interaction_profile) = bindings.bindings {
let _span = info_span!("xr_attach_actions");
let controller_bindings: Vec<openxr::Binding> = bindings
.map
.iter()
.filter_map(|(path, action)| {
let path = match self.inner_instance.string_to_path(path) {
Ok(path) => path,
Err(e) => {
warn!("{e}");
return None;
}
};
Some(match action {
UntypedOXrAction::Haptics(action) => {
openxr::Binding::new(action, path)
}
UntypedOXrAction::Pose(action) => {
openxr::Binding::new(action, path)
}
UntypedOXrAction::Float(action) => {
openxr::Binding::new(action, path)
}
UntypedOXrAction::Bool(action) => {
openxr::Binding::new(action, path)
}
UntypedOXrAction::Vec2(action) => {
openxr::Binding::new(action, path)
}
})
})
.collect();
self.inner_instance.suggest_interaction_profile_bindings(
self.inner_instance
.string_to_path(interaction_profile.get_interaction_profile())?,
&controller_bindings,
)?;
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
.inner_instance
.poll_event(&mut Default::default())
.unwrap()
{
use openxr::Event::*;
match event {
SessionStateChanged(e) => {
// Session state change is where we can begin and end sessions, as well as
// find quit messages!
info!("entered XR state {:?}", e.state());
match e.state() {
openxr::SessionState::READY => {
self.session.begin(VIEW_TYPE).unwrap();
}
openxr::SessionState::STOPPING => {
self.session.end().unwrap();
}
openxr::SessionState::EXITING | openxr::SessionState::LOSS_PENDING => {
return Err(XrError::Placeholder);
}
_ => {}
}
}
InstanceLossPending(_) => return Err(XrError::Placeholder),
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
}
}
}
{
let _span = info_span!("xr_wait_frame").entered();
*self.frame_state.lock().unwrap() = match self.frame_waiter.lock().unwrap().wait() {
Ok(a) => a,
Err(e) => {
warn!("error: {}", e);
return Err(XrError::Placeholder);
}
};
}
Ok(FrameData)
}
fn begin_frame(&self) -> Result<()> {
{
let _span = info_span!("xr_begin_frame").entered();
self.swapchain.begin().unwrap()
}
{
let _span = info_span!("xr_acquire_image").entered();
self.swapchain.acquire_image().unwrap()
}
{
let _span = info_span!("xr_wait_image").entered();
self.swapchain.wait_image().unwrap();
}
{
let action_sets = self.action_sets.lock().unwrap();
self.session
.sync_actions(&action_sets.iter().map(Into::into).collect::<Vec<_>>())?;
}
Ok(())
}
fn locate_views(&self) -> Result<(View, View)> {
let views = {
let _span = info_span!("xr_locate_views").entered();
self.session
.locate_views(
VIEW_TYPE,
self.frame_state.lock().unwrap().predicted_display_time,
&self.stage,
)
.unwrap()
.1
};
*self.views.lock().unwrap() = [views[0].clone(), views[1].clone()];
{
let _span = info_span!("xr_update_manual_texture_views").entered();
let (left, right) = self.swapchain.get_render_views();
let left = OXrView {
texture: Mutex::new(Some(left)),
view: views[0],
resolution: self.resolution,
format: self.format,
};
let right = OXrView {
texture: Mutex::new(Some(right)),
view: views[1],
resolution: self.resolution,
format: self.format,
};
Ok((left.into(), right.into()))
}
}
fn end_frame(&self, data: FrameData) -> Result<()> {
{
let _span = info_span!("xr_release_image").entered();
self.swapchain.release_image().unwrap();
}
{
let _span = info_span!("xr_end_frame").entered();
let frame_state = self.frame_state.lock().unwrap().clone();
let result = self.swapchain.end(
frame_state.predicted_display_time,
&*self.views.lock().unwrap(),
&self.stage,
self.resolution,
self.blend_mode,
frame_state.should_render,
// passthrough_layer.map(|p| p.into_inner()),
);
match result {
Ok(_) => {}
Err(e) => warn!("error: {}", e),
}
}
Ok(())
}
fn resolution(&self) -> UVec2 {
self.resolution
}
fn format(&self) -> wgpu::TextureFormat {
self.format
}
fn headset_location(&self) -> Result<Pose> {
let location = self.head.locate(
&self.stage,
self.frame_state.lock().unwrap().predicted_display_time,
)?;
Ok(location.pose.into())
}
}
pub struct OXrInput {
inner_session: openxr::Session<openxr::AnyGraphics>,
action_set: openxr::ActionSet,
bindings: Rc<Mutex<BindingState>>,
stage: Rc<openxr::Space>,
frame_state: Rc<Mutex<openxr::FrameState>>,
}
impl InputTrait for OXrInput {
fn create_action_haptics(&self, path: path::UntypedActionPath) -> Result<Action<Haptic>> {
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 name = &path.into_name();
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(&self, path: path::UntypedActionPath) -> Result<Action<Pose>> {
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 name = &path.into_name();
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(&self, path: path::UntypedActionPath) -> Result<Action<f32>> {
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 name = &path.into_name();
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(&self, path: path::UntypedActionPath) -> Result<Action<bool>> {
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 name = &path.into_name();
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, path: path::UntypedActionPath) -> 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 name = &path.into_name();
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 })
}
}
pub struct OXrView {
texture: Mutex<Option<wgpu::TextureView>>,
view: openxr::View,
resolution: UVec2,
format: wgpu::TextureFormat,
}
impl ViewTrait for OXrView {
fn texture_view(&self) -> Option<wgpu::TextureView> {
std::mem::take(&mut *self.texture.lock().unwrap())
}
fn pose(&self) -> Pose {
self.view.pose.clone().into()
}
fn projection_matrix(&self, near: f32, far: f32) -> glam::Mat4 {
// symmetric perspective for debugging
// let x_fov = (self.fov.angle_left.abs() + self.fov.angle_right.abs());
// let y_fov = (self.fov.angle_up.abs() + self.fov.angle_down.abs());
// return Mat4::perspective_infinite_reverse_rh(y_fov, x_fov / y_fov, self.near);
let fov = self.view.fov;
let is_vulkan_api = false; // FIXME wgpu probably abstracts this
let tan_angle_left = fov.angle_left.tan();
let tan_angle_right = fov.angle_right.tan();
let tan_angle_down = fov.angle_down.tan();
let tan_angle_up = fov.angle_up.tan();
let tan_angle_width = tan_angle_right - tan_angle_left;
// Set to tanAngleDown - tanAngleUp for a clip space with positive Y
// down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with
// positive Y up (OpenGL / D3D / Metal).
// const float tanAngleHeight =
// graphicsApi == GRAPHICS_VULKAN ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown);
let tan_angle_height = if is_vulkan_api {
tan_angle_down - tan_angle_up
} else {
tan_angle_up - tan_angle_down
};
// Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
// Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
// const float offsetZ =
// (graphicsApi == GRAPHICS_OPENGL || graphicsApi == GRAPHICS_OPENGL_ES) ? nearZ : 0;
// FIXME handle enum of graphics apis
let offset_z = 0.;
let mut cols: [f32; 16] = [0.0; 16];
if far <= near {
// place the far plane at infinity
cols[0] = 2. / tan_angle_width;
cols[4] = 0.;
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
cols[12] = 0.;
cols[1] = 0.;
cols[5] = 2. / tan_angle_height;
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
cols[13] = 0.;
cols[2] = 0.;
cols[6] = 0.;
cols[10] = -1.;
cols[14] = -(near + offset_z);
cols[3] = 0.;
cols[7] = 0.;
cols[11] = -1.;
cols[15] = 0.;
// bevy uses the _reverse_ infinite projection
// https://dev.theomader.com/depth-precision/
let z_reversal = Mat4::from_cols_array_2d(&[
[1f32, 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., -1., 0.],
[0., 0., 1., 1.],
]);
return z_reversal * Mat4::from_cols_array(&cols);
} else {
// normal projection
cols[0] = 2. / tan_angle_width;
cols[4] = 0.;
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
cols[12] = 0.;
cols[1] = 0.;
cols[5] = 2. / tan_angle_height;
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
cols[13] = 0.;
cols[2] = 0.;
cols[6] = 0.;
cols[10] = -(far + offset_z) / (far - near);
cols[14] = -(far * (near + offset_z)) / (far - near);
cols[3] = 0.;
cols[7] = 0.;
cols[11] = -1.;
cols[15] = 0.;
}
Mat4::from_cols_array(&cols)
}
fn resolution(&self) -> UVec2 {
self.resolution
}
fn format(&self) -> wgpu::TextureFormat {
self.format
}
fn fov(&self) -> Fov {
self.view.fov.into()
}
}

View File

@@ -1,183 +0,0 @@
mod vulkan;
use std::sync::Mutex;
use glam::UVec2;
use tracing::warn;
use super::{OXrInstance, OXrSession};
use crate::error::{Result, XrError};
use crate::types::ExtensionSet;
pub const VIEW_TYPE: openxr::ViewConfigurationType = openxr::ViewConfigurationType::PRIMARY_STEREO;
pub fn init_oxr_graphics(
instance: OXrInstance,
extensions: ExtensionSet,
format: wgpu::TextureFormat,
) -> Result<OXrSession> {
if extensions.vulkan {
return vulkan::init_oxr_graphics(instance, format, String::new());
}
Err(XrError::Placeholder)
}
pub enum Swapchain {
Vulkan(SwapchainInner<openxr::Vulkan>),
}
impl Swapchain {
pub(crate) fn begin(&self) -> Result<()> {
Ok(match self {
Swapchain::Vulkan(swapchain) => swapchain.begin()?,
})
}
pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
match self {
Swapchain::Vulkan(swapchain) => swapchain.get_render_views(),
}
}
pub(crate) fn acquire_image(&self) -> Result<()> {
Ok(match self {
Swapchain::Vulkan(swapchain) => swapchain.acquire_image()?,
})
}
pub(crate) fn wait_image(&self) -> Result<()> {
Ok(match self {
Swapchain::Vulkan(swapchain) => swapchain.wait_image()?,
})
}
pub(crate) fn release_image(&self) -> Result<()> {
Ok(match self {
Swapchain::Vulkan(swapchain) => swapchain.release_image()?,
})
}
pub(crate) fn end(
&self,
predicted_display_time: openxr::Time,
views: &[openxr::View],
stage: &openxr::Space,
resolution: UVec2,
environment_blend_mode: openxr::EnvironmentBlendMode,
should_render: bool,
) -> Result<()> {
Ok(match self {
Swapchain::Vulkan(swapchain) => swapchain.end(
predicted_display_time,
views,
stage,
resolution,
environment_blend_mode,
should_render,
)?,
})
}
}
pub struct SwapchainInner<G: openxr::Graphics> {
stream: Mutex<openxr::FrameStream<G>>,
swapchain: Mutex<openxr::Swapchain<G>>,
buffers: Vec<wgpu::Texture>,
image_index: Mutex<usize>,
}
impl<G: openxr::Graphics> SwapchainInner<G> {
fn begin(&self) -> openxr::Result<()> {
self.stream.lock().unwrap().begin()
}
fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
let texture = &self.buffers[*self.image_index.lock().unwrap()];
(
texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
..Default::default()
}),
texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
base_array_layer: 1,
..Default::default()
}),
)
}
fn acquire_image(&self) -> openxr::Result<()> {
let image_index = self.swapchain.lock().unwrap().acquire_image()?;
*self.image_index.lock().unwrap() = image_index as _;
Ok(())
}
fn wait_image(&self) -> openxr::Result<()> {
self.swapchain
.lock()
.unwrap()
.wait_image(openxr::Duration::INFINITE)
}
fn release_image(&self) -> openxr::Result<()> {
self.swapchain.lock().unwrap().release_image()
}
fn end(
&self,
predicted_display_time: openxr::Time,
views: &[openxr::View],
stage: &openxr::Space,
resolution: UVec2,
environment_blend_mode: openxr::EnvironmentBlendMode,
should_render: bool,
) -> openxr::Result<()> {
let rect = openxr::Rect2Di {
offset: openxr::Offset2Di { x: 0, y: 0 },
extent: openxr::Extent2Di {
width: resolution.x as _,
height: resolution.y as _,
},
};
let swapchain = self.swapchain.lock().unwrap();
if views.len() == 0 {
warn!("views are len of 0");
return Ok(());
}
let mut stream = self.stream.lock().unwrap();
if should_render {
stream.end(
predicted_display_time,
environment_blend_mode,
&[&openxr::CompositionLayerProjection::new()
.space(stage)
.views(&[
openxr::CompositionLayerProjectionView::new()
.pose(views[0].pose)
.fov(views[0].fov)
.sub_image(
openxr::SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(0)
.image_rect(rect),
),
openxr::CompositionLayerProjectionView::new()
.pose(views[1].pose)
.fov(views[1].fov)
.sub_image(
openxr::SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(1)
.image_rect(rect),
),
])],
)
} else {
stream.end(predicted_display_time, environment_blend_mode, &[])
}
}
}

View File

@@ -1,461 +0,0 @@
use std::ffi::{c_void, CString};
use std::rc::Rc;
use std::sync::Mutex;
use super::{Swapchain, SwapchainInner, VIEW_TYPE};
use crate::backend::oxr::{OXrInstance, OXrSession};
use crate::error::{Result, XrError};
use ash::vk::{self, Handle};
use glam::uvec2;
pub fn init_oxr_graphics(
instance: OXrInstance,
swapchain_format: wgpu::TextureFormat,
xr_app_name: String,
) -> Result<OXrSession> {
use wgpu_hal::{api::Vulkan as V, Api};
let xr_instance = &instance.0;
let xr_system_id = xr_instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
// TODO! allow selecting specific blend mode.
let blend_mode = openxr::EnvironmentBlendMode::OPAQUE;
#[cfg(not(target_os = "android"))]
let vk_target_version = vk::make_api_version(0, 1, 2, 0);
#[cfg(not(target_os = "android"))]
let vk_target_version_xr = openxr::Version::new(1, 2, 0);
#[cfg(target_os = "android")]
let vk_target_version = vk::make_api_version(0, 1, 1, 0);
#[cfg(target_os = "android")]
let vk_target_version_xr = openxr::Version::new(1, 1, 0);
let reqs = xr_instance.graphics_requirements::<openxr::Vulkan>(xr_system_id)?;
if vk_target_version_xr < reqs.min_api_version_supported
|| vk_target_version_xr.major() > reqs.max_api_version_supported.major()
{
return Err(XrError::Placeholder);
}
let vk_entry = unsafe { ash::Entry::load() }.map_err(|_| XrError::Placeholder)?;
let flags = wgpu_hal::InstanceFlags::empty();
let extensions = <V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)
.map_err(|_| XrError::Placeholder)?;
let device_extensions = vec![
ash::extensions::khr::Swapchain::name(),
ash::extensions::khr::DrawIndirectCount::name(),
#[cfg(target_os = "android")]
ash::extensions::khr::TimelineSemaphore::name(),
];
let vk_instance = unsafe {
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
let app_name = CString::new(xr_app_name).map_err(|_| XrError::Placeholder)?;
let vk_app_info = vk::ApplicationInfo::builder()
.application_name(&app_name)
.application_version(1)
.engine_name(&app_name)
.engine_version(1)
.api_version(vk_target_version);
let vk_instance = xr_instance
.create_vulkan_instance(
xr_system_id,
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
&vk::InstanceCreateInfo::builder()
.application_info(&vk_app_info)
.enabled_extension_names(&extensions_cchar) as *const _
as *const _,
)
.unwrap()
.map_err(vk::Result::from_raw)
.unwrap();
ash::Instance::load(
vk_entry.static_fn(),
vk::Instance::from_raw(vk_instance as _),
)
};
let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void;
let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe {
xr_instance.vulkan_graphics_device(xr_system_id, vk_instance.handle().as_raw() as _)? as _
});
let vk_physical_device_ptr = vk_physical_device.as_raw() as *const c_void;
let vk_device_properties =
unsafe { vk_instance.get_physical_device_properties(vk_physical_device) };
if vk_device_properties.api_version < vk_target_version {
unsafe { vk_instance.destroy_instance(None) }
panic!("Vulkan physical device doesn't support version 1.1");
}
let wgpu_vk_instance = unsafe {
<V as Api>::Instance::from_raw(
vk_entry.clone(),
vk_instance.clone(),
vk_target_version,
0,
None,
extensions,
flags,
false,
Some(Box::new(())),
)
.map_err(|_| XrError::Placeholder)?
};
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::MULTIVIEW
| wgpu::Features::MULTI_DRAW_INDIRECT_COUNT
| wgpu::Features::MULTI_DRAW_INDIRECT;
let wgpu_exposed_adapter = wgpu_vk_instance
.expose_adapter(vk_physical_device)
.ok_or(XrError::Placeholder)?;
let enabled_extensions = wgpu_exposed_adapter
.adapter
.required_device_extensions(wgpu_features);
let (wgpu_open_device, vk_device_ptr, queue_family_index) = {
let extensions_cchar: Vec<_> = device_extensions.iter().map(|s| s.as_ptr()).collect();
let mut enabled_phd_features = wgpu_exposed_adapter
.adapter
.physical_device_features(&enabled_extensions, wgpu_features);
let family_index = 0;
let family_info = vk::DeviceQueueCreateInfo::builder()
.queue_family_index(family_index)
.queue_priorities(&[1.0])
.build();
let family_infos = [family_info];
let info = enabled_phd_features
.add_to_device_create_builder(
vk::DeviceCreateInfo::builder()
.queue_create_infos(&family_infos)
.push_next(&mut vk::PhysicalDeviceMultiviewFeatures {
multiview: vk::TRUE,
..Default::default()
}),
)
.enabled_extension_names(&extensions_cchar)
.build();
let vk_device = unsafe {
let vk_device = xr_instance
.create_vulkan_device(
xr_system_id,
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
vk_physical_device.as_raw() as _,
&info as *const _ as *const _,
)?
.map_err(|_| XrError::Placeholder)?;
ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _))
};
let vk_device_ptr = vk_device.handle().as_raw() as *const c_void;
let wgpu_open_device = unsafe {
wgpu_exposed_adapter.adapter.device_from_raw(
vk_device,
true,
&enabled_extensions,
wgpu_features,
family_info.queue_family_index,
0,
)
}
.map_err(|_| XrError::Placeholder)?;
(
wgpu_open_device,
vk_device_ptr,
family_info.queue_family_index,
)
};
let wgpu_instance =
unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Vulkan>(wgpu_vk_instance) };
let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) };
let (wgpu_device, wgpu_queue) = unsafe {
wgpu_adapter.create_device_from_hal(
wgpu_open_device,
&wgpu::DeviceDescriptor {
label: None,
features: wgpu_features,
limits: wgpu::Limits {
max_bind_groups: 8,
max_storage_buffer_binding_size: wgpu_adapter
.limits()
.max_storage_buffer_binding_size,
max_push_constant_size: 4,
..Default::default()
},
},
None,
)
}
.map_err(|_| XrError::Placeholder)?;
let (session, frame_wait, frame_stream) = unsafe {
xr_instance.create_session::<openxr::Vulkan>(
xr_system_id,
&openxr::vulkan::SessionCreateInfo {
instance: vk_instance_ptr,
physical_device: vk_physical_device_ptr,
device: vk_device_ptr,
queue_family_index,
queue_index: 0,
},
)
}?;
let views = xr_instance.enumerate_view_configuration_views(xr_system_id, VIEW_TYPE)?;
let resolution = uvec2(
views[0].recommended_image_rect_width,
views[0].recommended_image_rect_height,
);
let swapchain = session
.create_swapchain(&openxr::SwapchainCreateInfo {
create_flags: openxr::SwapchainCreateFlags::EMPTY,
usage_flags: openxr::SwapchainUsageFlags::COLOR_ATTACHMENT
| openxr::SwapchainUsageFlags::SAMPLED,
format: wgpu_to_vulkan(swapchain_format)
.ok_or(XrError::Placeholder)?
.as_raw() as _,
// The Vulkan graphics pipeline we create is not set up for multisampling,
// so we hardcode this to 1. If we used a proper multisampling setup, we
// could set this to `views[0].recommended_swapchain_sample_count`.
sample_count: 1,
width: resolution.x,
height: resolution.y,
face_count: 1,
array_size: 2,
mip_count: 1,
})
.unwrap();
let images = swapchain.enumerate_images().unwrap();
let buffers: Vec<_> = images
.into_iter()
.map(|color_image| {
let color_image = vk::Image::from_raw(color_image);
let wgpu_hal_texture = unsafe {
<V as Api>::Device::texture_from_raw(
color_image,
&wgpu_hal::TextureDescriptor {
label: Some("VR Swapchain"),
size: wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: swapchain_format,
usage: wgpu_hal::TextureUses::COLOR_TARGET
| wgpu_hal::TextureUses::COPY_DST,
memory_flags: wgpu_hal::MemoryFlags::empty(),
view_formats: vec![],
},
None,
)
};
let texture = unsafe {
wgpu_device.create_texture_from_hal::<V>(
wgpu_hal_texture,
&wgpu::TextureDescriptor {
label: Some("VR Swapchain"),
size: wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: swapchain_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
},
)
};
texture
})
.collect();
Ok(OXrSession {
inner_instance: instance.0.clone(),
instance: instance.into(),
session: session.clone().into_any_graphics(),
render_resources: Mutex::new(Some((
wgpu_device,
wgpu_queue,
wgpu_adapter.get_info(),
wgpu_adapter,
wgpu_instance,
))),
resolution,
views: Mutex::new([openxr::View::default(), openxr::View::default()]),
swapchain: Swapchain::Vulkan(SwapchainInner {
stream: Mutex::new(frame_stream),
swapchain: Mutex::new(swapchain),
buffers,
image_index: Mutex::new(0),
}),
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: 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,
action_sets: Default::default(),
bindings: Default::default(),
})
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<vk::Format> {
// Copied with minor modification from:
// https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151
// license: MIT OR Apache-2.0
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
Some(match format {
Tf::R8Unorm => F::R8_UNORM,
Tf::R8Snorm => F::R8_SNORM,
Tf::R8Uint => F::R8_UINT,
Tf::R8Sint => F::R8_SINT,
Tf::R16Uint => F::R16_UINT,
Tf::R16Sint => F::R16_SINT,
Tf::R16Unorm => F::R16_UNORM,
Tf::R16Snorm => F::R16_SNORM,
Tf::R16Float => F::R16_SFLOAT,
Tf::Rg8Unorm => F::R8G8_UNORM,
Tf::Rg8Snorm => F::R8G8_SNORM,
Tf::Rg8Uint => F::R8G8_UINT,
Tf::Rg8Sint => F::R8G8_SINT,
Tf::Rg16Unorm => F::R16G16_UNORM,
Tf::Rg16Snorm => F::R16G16_SNORM,
Tf::R32Uint => F::R32_UINT,
Tf::R32Sint => F::R32_SINT,
Tf::R32Float => F::R32_SFLOAT,
Tf::Rg16Uint => F::R16G16_UINT,
Tf::Rg16Sint => F::R16G16_SINT,
Tf::Rg16Float => F::R16G16_SFLOAT,
Tf::Rgba8Unorm => F::R8G8B8A8_UNORM,
Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB,
Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB,
Tf::Rgba8Snorm => F::R8G8B8A8_SNORM,
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
// Tf::Rgb10a2Uint => F::A2B10G10R10_UINT_PACK32,
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
Tf::Rg32Uint => F::R32G32_UINT,
Tf::Rg32Sint => F::R32G32_SINT,
Tf::Rg32Float => F::R32G32_SFLOAT,
Tf::Rgba16Uint => F::R16G16B16A16_UINT,
Tf::Rgba16Sint => F::R16G16B16A16_SINT,
Tf::Rgba16Unorm => F::R16G16B16A16_UNORM,
Tf::Rgba16Snorm => F::R16G16B16A16_SNORM,
Tf::Rgba16Float => F::R16G16B16A16_SFLOAT,
Tf::Rgba32Uint => F::R32G32B32A32_UINT,
Tf::Rgba32Sint => F::R32G32B32A32_SINT,
Tf::Rgba32Float => F::R32G32B32A32_SFLOAT,
Tf::Depth32Float => F::D32_SFLOAT,
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
Tf::Depth16Unorm => F::D16_UNORM,
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK,
Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK,
Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK,
Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK,
Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK,
Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK,
Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK,
Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK,
Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK,
Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK,
Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK,
Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK,
Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK,
Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK,
Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK,
Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK,
Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK,
Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK,
Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK,
Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK,
Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK,
Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK,
Tf::Astc { block, channel } => match channel {
AstcChannel::Unorm => match block {
AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK,
},
AstcChannel::UnormSrgb => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK,
},
AstcChannel::Hdr => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT,
AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT,
AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT,
AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT,
},
},
})
}

View File

@@ -1,208 +0,0 @@
use glam::Quat;
use openxr::{Action, Fovf, Posef};
use crate::{
error::XrError,
path::{input, Handed, InputId, PathComponent, UntypedActionPath},
prelude::Pose,
};
use super::{Bindings, Fov};
impl From<openxr::sys::Result> for XrError {
fn from(_: openxr::sys::Result) -> Self {
XrError::Placeholder
}
}
impl From<Posef> for Pose {
fn from(pose: Posef) -> Self {
// with enough sign errors anything is possible
let rotation = {
let o = pose.orientation;
Quat::from_xyzw(o.x, o.y, o.z, o.w)
};
let translation = glam::vec3(pose.position.x, pose.position.y, pose.position.z);
Pose {
translation,
rotation,
}
}
}
impl From<Fovf> for Fov {
fn from(fov: Fovf) -> Self {
let Fovf {
angle_left,
angle_right,
angle_down,
angle_up,
} = fov;
Self {
angle_down,
angle_left,
angle_right,
angle_up,
}
}
}
macro_rules! untyped_oxr_actions {
(
$id:ident {
$(
$inner:ident($inner_ty:ty)
),*
$(,)?
}
) => {
pub(crate) enum $id {
$(
$inner($inner_ty),
)*
}
$(
impl TryInto<$inner_ty> for $id {
type Error = ();
fn try_into(self) -> std::prelude::v1::Result<$inner_ty, Self::Error> {
match self {
Self::$inner(action) => Ok(action),
_ => Err(()),
}
}
}
impl From<$inner_ty> for $id {
fn from(value: $inner_ty) -> Self {
Self::$inner(value)
}
}
)*
};
}
untyped_oxr_actions! {
UntypedOXrAction {
Haptics(Action<openxr::Haptic>),
Pose(Action<openxr::Posef>),
Float(Action<f32>),
Bool(Action<bool>),
Vec2(Action<openxr::Vector2f>),
}
}
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
}
pub(crate) fn into_name(&self) -> String {
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 => "",
};
let dev_path = match self.input {
InputId::Left(hand) => match hand {
Handed::PrimaryButton => "left_primary_button",
Handed::SecondaryButton => "left_secondary_button",
Handed::Select => "left_select",
Handed::Menu => "left_menu",
Handed::Thumbstick => "left_thumbstick",
Handed::Trigger => "left_trigger",
Handed::Grip => "left_grip",
Handed::Output => "left_output",
},
InputId::Right(hand) => match hand {
Handed::PrimaryButton => "right_primary_button",
Handed::SecondaryButton => "right_secondary_button",
Handed::Select => "right_select",
Handed::Menu => "right_menu",
Handed::Thumbstick => "right_thumbstick",
Handed::Trigger => "right_trigger",
Handed::Grip => "right_grip",
Handed::Output => "right_output",
},
InputId::Head(head) => {
use input::head::Head;
match head {
Head::VolumeUp => "volume_up",
Head::VolumeDown => "volume_down",
Head::MuteMic => "mute_mic",
}
}
};
let mut path = dev_path.to_string();
path.push_str(comp_path);
path
}
}
impl Bindings {
pub(crate) fn get_interaction_profile(&self) -> &'static str {
match self {
Bindings::OculusTouch => "/interaction_profiles/oculus/touch_controller",
}
}
}

View File

@@ -1 +0,0 @@

View File

@@ -1,17 +0,0 @@
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::js_sys::Promise;
use wasm_bindgen_futures::JsFuture;
pub trait PromiseRes {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue>;
}
impl PromiseRes for Promise {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue> {
resolve_promise(self)
}
}
pub fn resolve_promise<T: From<JsValue>>(promise: Promise) -> Result<T, JsValue> {
futures::executor::block_on(async move { JsFuture::from(promise).await.map(Into::into) })
}

View File

@@ -1,16 +0,0 @@
use std::fmt::Display;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, XrError>;
#[derive(Error, Debug)]
pub enum XrError {
Placeholder,
}
impl Display for XrError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}

View File

@@ -1,23 +0,0 @@
//! Abstracted API over WebXR and OpenXR
//!
//! This crate is intended to be used as a common API for cross platform projects. It was primarily
//! made for use in Bevy, but can be used elsewhere.
//!
//! To get started, create an [Entry] with [Entry](Entry#method.new)
mod api;
pub mod api_traits;
pub mod backend;
pub mod error;
pub mod path;
pub mod types;
pub mod prelude {
pub use super::api::*;
pub use super::api_traits::*;
pub use super::error::*;
pub use super::path::{input, ActionPath};
pub use super::types::*;
}
pub use api::*;

View File

@@ -1,332 +0,0 @@
use std::marker::PhantomData;
use crate::prelude::ActionType;
#[derive(Clone, Copy)]
pub struct ActionPath<P: InputComponent> {
pub(crate) input: InputId,
pub(crate) comp: PathComponent,
_data: PhantomData<P>,
}
#[derive(Clone, Copy)]
pub struct UntypedActionPath {
pub(crate) input: InputId,
pub(crate) comp: PathComponent,
}
impl<P: InputComponent> From<ActionPath<P>> for UntypedActionPath {
fn from(value: ActionPath<P>) -> Self {
value.untyped()
}
}
impl<P: InputComponent> ActionPath<P> {
const fn new(input: InputId, comp: PathComponent) -> Self {
Self {
input,
comp,
// hand,
_data: PhantomData,
}
}
pub fn untyped(self) -> UntypedActionPath {
UntypedActionPath {
input: self.input,
comp: self.comp,
// hand: self.hand,
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum PathComponent {
Click,
Touch,
Value,
X,
Y,
Pose,
Haptic,
}
pub struct Click;
impl Click {
const COMP: PathComponent = PathComponent::Click;
}
pub struct Touch;
impl Touch {
const COMP: PathComponent = PathComponent::Touch;
}
pub struct Value;
impl Value {
const COMP: PathComponent = PathComponent::Value;
}
pub struct X;
impl X {
const COMP: PathComponent = PathComponent::X;
}
pub struct Y;
impl Y {
const COMP: PathComponent = PathComponent::Y;
}
pub struct Pose;
impl Pose {
const COMP: PathComponent = PathComponent::Pose;
}
pub struct Haptic;
impl Haptic {
const COMP: PathComponent = PathComponent::Haptic;
}
pub trait InputComponent {
type PathType: ActionType;
}
impl InputComponent for Click {
type PathType = bool;
}
impl InputComponent for Touch {
type PathType = bool;
}
impl InputComponent for Value {
type PathType = f32;
}
impl InputComponent for X {
type PathType = f32;
}
impl InputComponent for Y {
type PathType = f32;
}
impl InputComponent for Pose {
type PathType = crate::types::Pose;
}
impl InputComponent for Haptic {
type PathType = crate::types::Haptic;
}
macro_rules! input_ids {
(
$(#[$id_meta:meta])*
$id:ident;
handed {
$(
$(#[$inner_handed_meta:meta])*
$inner_handed:ident {
$(
$(#[$comp_name_handed_meta:meta])*
$comp_name_handed:ident
),*
$(,)?
}
)*
}
$(
$(#[$dev_path_meta:meta])*
$dev_path:ident {
$(
$(#[$inner_meta:meta])*
$inner:ident {
$(
$(#[$comp_name_meta:meta])*
$comp_name:ident
),*
$(,)?
}
)*
}
)*
) => {
paste::paste! {
const LEFT: bool = true;
const RIGHT: bool = false;
$(
#[$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);
)*
}
)*
}
)*
}
}
};
}
input_ids! {
InputId;
handed {
PrimaryButton {
Click,
Touch,
}
SecondaryButton {
Click,
Touch,
}
Select {
Click,
}
Menu {
Click,
}
Thumbstick {
X,
Y,
Click,
Touch,
}
Trigger {
Touch,
Click,
}
Grip {
Click,
Value,
Pose,
}
Output {
Haptic,
}
}
head {
VolumeUp {
Click,
}
VolumeDown {
Click,
}
MuteMic {
Click,
}
}
}

View File

@@ -1,85 +0,0 @@
use glam::{Quat, Vec2, Vec3};
use crate::api::Action;
use crate::api_traits::{ActionInputTrait, HapticTrait, InputTrait};
use crate::error::Result;
use crate::path::UntypedActionPath;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ExtensionSet {
pub vulkan: bool,
}
pub struct FrameData;
pub struct SessionCreateInfo {
/// preferred texture format
pub texture_format: wgpu::TextureFormat,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Bindings {
OculusTouch,
}
pub struct Haptic;
#[derive(Clone, Copy, Debug, Default)]
pub struct Pose {
pub translation: Vec3,
pub rotation: Quat,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Fov {
pub angle_left: f32,
pub angle_right: f32,
pub angle_down: f32,
pub angle_up: f32,
}
pub trait ActionType: Sized {
type Inner: ?Sized;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>>;
}
impl ActionType for Haptic {
type Inner = dyn HapticTrait;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>> {
input.create_action_haptics(path)
}
}
impl ActionType for Pose {
type Inner = dyn ActionInputTrait<Pose>;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>> {
input.create_action_pose(path)
}
}
impl ActionType for f32 {
type Inner = dyn ActionInputTrait<f32>;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>> {
input.create_action_float(path)
}
}
impl ActionType for bool {
type Inner = dyn ActionInputTrait<bool>;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>> {
input.create_action_bool(path)
}
}
impl ActionType for Vec2 {
type Inner = dyn ActionInputTrait<Vec2>;
fn get(input: &dyn InputTrait, path: UntypedActionPath) -> Result<Action<Self>> {
input.create_action_vec2(path)
}
}