move graphics initialization to instance
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
Ok((
|
||||
wgpu_device,
|
||||
wgpu_queue,
|
||||
wgpu_adapter,
|
||||
wgpu_instance,
|
||||
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)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,8 +52,14 @@ impl GraphicsBackend {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum XrError {
|
||||
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}")]
|
||||
@@ -73,12 +78,45 @@ pub enum XrError {
|
||||
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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod oxr;
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub mod webxr;
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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, &[])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user