Revert "move to single crate for webxr and openxr"

This reverts commit 5e769ef52b.
This commit is contained in:
awtterpip
2024-04-18 16:30:18 -05:00
parent 5e769ef52b
commit d98b9a4455
25 changed files with 747 additions and 576 deletions

View File

@@ -1,114 +1,98 @@
[package] [package]
name = "bevy_xr" name = "bevy_oxr"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "Community crate for XR in Bevy" description = "Community crate for OpenXR in Bevy"
repository = "https://github.com/awtterpip/bevy_oxr" repository = "https://github.com/awtterpip/bevy_oxr"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
[features] [features]
default = ["vulkan"] default = ["linked", "vulkan"]
linked = ["openxr/linked"]
vulkan = ["dep:ash"] vulkan = ["dep:ash"]
[dependencies] [dependencies]
bevy_openxr.path = "./crates/bevy_openxr"
bevy_xr.path = "./crates/bevy_xr"
anyhow = "1.0.79"
async-std = "1.12.0"
bevy = "0.13.0" bevy = "0.13.0"
paste = "1.0.14"
thiserror = "1.0.57"
wgpu = "0.19.3" wgpu = "0.19.3"
wgpu-hal = "0.19.3" wgpu-hal = "0.19.3"
thiserror = "1.0.57" winit = "0.28.7"
bevy_xr_api.path = "bevy_xr_api"
[target.'cfg(target_family = "unix")'.dependencies]
openxr = { version = "0.17.1", features = ["mint"] }
[target.'cfg(target_family = "windows")'.dependencies]
openxr = { version = "0.17.1", features = ["mint", "static"] }
[target.'cfg(not(target_family = "wasm"))'.dependencies] [target.'cfg(not(target_family = "wasm"))'.dependencies]
ash = { version = "0.37.3", optional = true } ash = { version = "0.37.3", optional = true }
[target.'cfg(target_family = "unix")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
openxr = { version = "0.18.0", features = ["mint"] } js-sys = "0.3"
wasm-bindgen = "0.2.91"
[target.'cfg(target_family = "windows")'.dependencies] glow = "0.12.1"
openxr = { version = "0.18.0", features = ["mint", "static"] } web-sys = { version = "0.3.67", features = [
# STANDARD
'console',
# [dependencies] 'Document',
# bevy_openxr.path = "./crates/bevy_openxr" 'Element',
# bevy_xr.path = "./crates/bevy_xr" 'Headers',
# anyhow = "1.0.79" 'Navigator',
# async-std = "1.12.0" 'Window',
# bevy = "0.13.0" # IO
# paste = "1.0.14" # 'Url',
# thiserror = "1.0.57" # WEBGL
# wgpu = "0.19.3" 'Gpu',
# wgpu-hal = "0.19.3" 'HtmlCanvasElement',
# winit = "0.28.7" 'WebGl2RenderingContext',
'WebGlFramebuffer',
# [target.'cfg(target_family = "unix")'.dependencies] 'GamepadHapticActuator',
# openxr = { version = "0.17.1", features = ["mint"] } ## XR
'DomPointReadOnly',
# [target.'cfg(target_family = "windows")'.dependencies] 'XrWebGlLayer',
# openxr = { version = "0.17.1", features = ["mint", "static"] } 'XrBoundedReferenceSpace',
'XrEye',
# [target.'cfg(not(target_family = "wasm"))'.dependencies] 'XrFrame',
# ash = { version = "0.37.3", optional = true } 'XrHandedness',
'XrInputSource',
# [target.'cfg(target_family = "wasm")'.dependencies] 'XrInputSourceArray',
# js-sys = "0.3" 'XrInputSourceEvent',
# wasm-bindgen = "0.2.91" 'XrInputSourceEventInit',
# glow = "0.12.1" 'XrInputSourcesChangeEvent',
# web-sys = { version = "0.3.67", features = [ 'XrJointPose',
# # STANDARD 'XrJointSpace',
# 'console', 'XrPose',
# 'Document', 'XrReferenceSpace',
# 'Element', 'XrReferenceSpaceEvent',
# 'Headers', 'XrReferenceSpaceEventInit',
# 'Navigator', 'XrReferenceSpaceType',
# 'Window', 'XrRenderState',
# # IO 'XrRenderStateInit',
# # 'Url', 'XrRigidTransform',
# # WEBGL 'XrSession',
# 'Gpu', 'XrSessionEvent',
# 'HtmlCanvasElement', 'XrSessionEventInit',
# 'WebGl2RenderingContext', 'XrSessionInit',
# 'WebGlFramebuffer', 'XrSessionMode',
# 'GamepadHapticActuator', 'XrSpace',
# ## XR 'XrTargetRayMode',
# 'DomPointReadOnly', 'XrView',
# 'XrWebGlLayer', 'XrViewerPose',
# 'XrBoundedReferenceSpace', 'XrViewport',
# 'XrEye', 'XrVisibilityState',
# 'XrFrame', 'XrWebGlLayer',
# 'XrHandedness', 'XrWebGlLayerInit',
# 'XrInputSource', 'XrSystem',
# 'XrInputSourceArray', ] }
# 'XrInputSourceEvent', wasm-bindgen-futures = "0.4"
# '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"
[workspace] [workspace]
members = ["bevy_xr_api"] members = ["crates/*"]
[workspace.dependencies] [workspace.dependencies]
bevy = "0.13.0" bevy = "0.13.0"

View File

@@ -0,0 +1,25 @@
[package]
name = "bevy_openxr"
version = "0.1.0"
edition = "2021"
[features]
default = ["vulkan"]
vulkan = ["dep:ash"]
[dependencies]
thiserror = "1.0.57"
wgpu = "0.19.3"
wgpu-hal = "0.19.3"
bevy_xr.path = "../bevy_xr"
bevy.workspace = true
ash = { version = "0.37.3", optional = true }
[target.'cfg(target_family = "unix")'.dependencies]
openxr = { version = "0.18.0", features = ["mint"] }
[target.'cfg(target_family = "windows")'.dependencies]
openxr = { version = "0.18.0", features = ["mint", "static"] }

View File

@@ -0,0 +1,79 @@
//! A simple 3D scene with light shining over a cube sitting on a plane.
use std::any::TypeId;
use bevy::prelude::*;
use bevy_openxr::{
actions::{create_action_sets, ActionApp},
add_xr_plugins, resources::{TypedAction, XrActions, XrInstance},
};
use bevy_xr::actions::{Action, ActionState};
use openxr::Binding;
#[derive(Action)]
#[action(action_type = bool, name = "jump")]
pub struct Jump;
fn main() {
App::new()
.add_plugins(add_xr_plugins(DefaultPlugins))
.add_systems(Startup, setup.after(create_action_sets))
.add_systems(Update, read_action_state)
.register_action::<Jump>()
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
actions: Res<XrActions>,
instance: Res<XrInstance>,
) {
let TypedAction::Bool(action) = actions.get(&TypeId::of::<Jump>()).unwrap() else {
unreachable!()
};
instance.suggest_interaction_profile_bindings(instance.string_to_path("/interaction_profiles/oculus/touch_controller").unwrap(), &[
Binding::new(action, instance.string_to_path("/user/hand/right/input/a/click").unwrap())
]).unwrap();
// circular base
commands.spawn(PbrBundle {
mesh: meshes.add(Circle::new(4.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(Color::rgb_u8(124, 144, 255)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// // camera
// commands.spawn(XrCameraBundle {
// transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
// camera: Camera {
// target: RenderTarget::TextureView(ManualTextureViewHandle(XR_TEXTURE_INDEX + 1)),
// ..default()
// },
// ..default()
// });
}
fn read_action_state(
state: Res<ActionState<Jump>>
) {
info!("{}", state.pressed())
}

View File

@@ -0,0 +1,205 @@
use std::any::TypeId;
use std::marker::PhantomData;
use crate::init::XrPreUpdateSet;
use crate::resources::*;
use crate::types::*;
use bevy::app::{App, Plugin, PreUpdate, Startup};
use bevy::ecs::schedule::common_conditions::resource_added;
use bevy::ecs::schedule::IntoSystemConfigs;
use bevy::ecs::system::{Commands, Res, ResMut};
use bevy::input::InputSystem;
use bevy::log::error;
use bevy::math::{vec2, Vec2};
use bevy::utils::hashbrown::HashMap;
use bevy_xr::actions::ActionPlugin;
use bevy_xr::actions::{Action, ActionList, ActionState};
use bevy_xr::session::session_available;
use bevy_xr::session::session_running;
pub struct XrActionPlugin;
impl Plugin for XrActionPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, create_action_sets.run_if(session_available))
.add_systems(
PreUpdate,
sync_actions.run_if(session_running).before(InputSystem),
)
.add_systems(
PreUpdate,
attach_action_sets
.after(XrPreUpdateSet::HandleEvents)
.run_if(resource_added::<XrSession>),
);
}
}
pub fn create_action_sets(
instance: Res<XrInstance>,
action_list: Res<ActionList>,
mut commands: Commands,
) {
let (action_set, actions) =
initialize_action_sets(&instance, &action_list).expect("Failed to initialize action set");
commands.insert_resource(action_set);
commands.insert_resource(actions);
}
pub fn attach_action_sets(mut action_set: ResMut<XrActionSet>, session: Res<XrSession>) {
session
.attach_action_sets(&[&action_set])
.expect("Failed to attach action sets");
action_set.attach();
}
pub fn sync_actions(session: Res<XrSession>, action_set: Res<XrActionSet>) {
session
.sync_actions(&[openxr::ActiveActionSet::new(&action_set)])
.expect("Failed to sync actions");
}
fn initialize_action_sets(
instance: &XrInstance,
action_info: &ActionList,
) -> Result<(XrActionSet, XrActions)> {
let action_set = instance.create_action_set("actions", "actions", 0)?;
let mut actions = HashMap::new();
for action_info in action_info.0.iter() {
use bevy_xr::actions::ActionType::*;
let action = match action_info.action_type {
Bool => TypedAction::Bool(action_set.create_action(
action_info.name,
action_info.pretty_name,
&[],
)?),
Float => TypedAction::Float(action_set.create_action(
action_info.name,
action_info.pretty_name,
&[],
)?),
Vector => TypedAction::Vector(action_set.create_action(
action_info.name,
action_info.pretty_name,
&[],
)?),
};
actions.insert(action_info.type_id, action);
}
Ok((XrActionSet::new(action_set), XrActions(actions)))
}
pub struct XrActionUpdatePlugin<A: Action>(PhantomData<A>);
impl<A> Plugin for XrActionUpdatePlugin<A>
where
A: Action,
A::ActionType: XrActionTy,
{
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, update_action_state::<A>.in_set(InputSystem).run_if(session_running));
}
}
impl<A: Action> Default for XrActionUpdatePlugin<A> {
fn default() -> Self {
Self(Default::default())
}
}
pub trait XrActionTy: Sized {
fn get_action_state(
action: &TypedAction,
session: &XrSession,
subaction_path: Option<openxr::Path>,
) -> Option<Self>;
}
impl XrActionTy for bool {
fn get_action_state(
action: &TypedAction,
session: &XrSession,
subaction_path: Option<openxr::Path>,
) -> Option<Self> {
match action {
TypedAction::Bool(action) => action
.state(session, subaction_path.unwrap_or(openxr::Path::NULL))
.ok()
.map(|state| state.current_state),
_ => None,
}
}
}
impl XrActionTy for f32 {
fn get_action_state(
action: &TypedAction,
session: &XrSession,
subaction_path: Option<openxr::Path>,
) -> Option<Self> {
match action {
TypedAction::Float(action) => action
.state(session, subaction_path.unwrap_or(openxr::Path::NULL))
.ok()
.map(|state| state.current_state),
_ => None,
}
}
}
impl XrActionTy for Vec2 {
fn get_action_state(
action: &TypedAction,
session: &XrSession,
subaction_path: Option<openxr::Path>,
) -> Option<Self> {
match action {
TypedAction::Vector(action) => action
.state(session, subaction_path.unwrap_or(openxr::Path::NULL))
.ok()
.map(|state| vec2(state.current_state.x, state.current_state.y)),
_ => None,
}
}
}
pub fn update_action_state<A>(
mut action_state: ResMut<ActionState<A>>,
session: Res<XrSession>,
actions: Res<XrActions>,
) where
A: Action,
A::ActionType: XrActionTy,
{
if let Some(action) = actions.get(&TypeId::of::<A>()) {
if let Some(state) = A::ActionType::get_action_state(action, &session, None) {
action_state.set(state);
} else {
error!(
"Failed to update value for action '{}'",
std::any::type_name::<A>()
);
}
}
}
pub trait ActionApp {
fn register_action<A>(&mut self) -> &mut Self
where
A: Action,
A::ActionType: XrActionTy;
}
impl ActionApp for App {
fn register_action<A>(&mut self) -> &mut Self
where
A: Action,
A::ActionType: XrActionTy,
{
self.add_plugins((
ActionPlugin::<A>::default(),
XrActionUpdatePlugin::<A>::default(),
))
}
}

View File

@@ -1,12 +1,10 @@
use crate::graphics::GraphicsBackend;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use super::graphics::GraphicsBackend;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum OXrError { pub enum XrError {
#[error("OpenXR error: {0}")] #[error("OpenXR error: {0}")]
OpenXrError(#[from] openxr::sys::Result), OpenXrError(#[from] openxr::sys::Result),
#[error("OpenXR loading error: {0}")] #[error("OpenXR loading error: {0}")]
@@ -19,6 +17,10 @@ pub enum OXrError {
WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError), WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError),
#[error("Unsupported texture format: {0:?}")] #[error("Unsupported texture format: {0:?}")]
UnsupportedTextureFormat(wgpu::TextureFormat), 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")] #[error("Graphics backend '{0:?}' is not available")]
UnavailableBackend(GraphicsBackend), UnavailableBackend(GraphicsBackend),
#[error("No compatible backend available")] #[error("No compatible backend available")]
@@ -43,55 +45,9 @@ pub enum OXrError {
}, },
#[error("Failed to create CString: {0}")] #[error("Failed to create CString: {0}")]
NulError(#[from] std::ffi::NulError), NulError(#[from] std::ffi::NulError),
#[error("Graphics init error: {0}")]
InitError(InitError),
} }
pub use init_error::InitError; impl From<Vec<Cow<'static, str>>> for XrError {
/// This module is needed because thiserror does not allow conditional compilation within enums for some reason,
/// so graphics api specific errors are implemented here.
mod init_error {
use super::OXrError;
use std::fmt;
#[derive(Debug)]
pub enum InitError {
#[cfg(feature = "vulkan")]
VulkanError(ash::vk::Result),
#[cfg(feature = "vulkan")]
VulkanLoadingError(ash::LoadingError),
}
impl fmt::Display for InitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "vulkan")]
InitError::VulkanError(error) => write!(f, "Vulkan error: {}", error),
#[cfg(feature = "vulkan")]
InitError::VulkanLoadingError(error) => {
write!(f, "Vulkan loading error: {}", error)
}
}
}
}
#[cfg(feature = "vulkan")]
impl From<ash::vk::Result> for OXrError {
fn from(value: ash::vk::Result) -> Self {
Self::InitError(InitError::VulkanError(value))
}
}
#[cfg(feature = "vulkan")]
impl From<ash::LoadingError> for OXrError {
fn from(value: ash::LoadingError) -> Self {
Self::InitError(InitError::VulkanLoadingError(value))
}
}
}
impl From<Vec<Cow<'static, str>>> for OXrError {
fn from(value: Vec<Cow<'static, str>>) -> Self { fn from(value: Vec<Cow<'static, str>>) -> Self {
Self::UnavailableExtensions(UnavailableExts(value)) Self::UnavailableExtensions(UnavailableExts(value))
} }

View File

@@ -2,8 +2,8 @@ use bevy::prelude::{Deref, DerefMut};
use openxr::ExtensionSet; use openxr::ExtensionSet;
#[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut)] #[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut)]
pub struct OXrExtensions(ExtensionSet); pub struct XrExtensions(ExtensionSet);
impl OXrExtensions { impl XrExtensions {
pub fn raw_mut(&mut self) -> &mut ExtensionSet { pub fn raw_mut(&mut self) -> &mut ExtensionSet {
&mut self.0 &mut self.0
} }
@@ -27,21 +27,21 @@ impl OXrExtensions {
self self
} }
/// returns true if all of the extensions enabled are also available in `available_exts` /// returns true if all of the extensions enabled are also available in `available_exts`
pub fn is_available(&self, available_exts: &OXrExtensions) -> bool { pub fn is_available(&self, available_exts: &XrExtensions) -> bool {
self.clone() & available_exts.clone() == *self self.clone() & available_exts.clone() == *self
} }
} }
impl From<ExtensionSet> for OXrExtensions { impl From<ExtensionSet> for XrExtensions {
fn from(value: ExtensionSet) -> Self { fn from(value: ExtensionSet) -> Self {
Self(value) Self(value)
} }
} }
impl From<OXrExtensions> for ExtensionSet { impl From<XrExtensions> for ExtensionSet {
fn from(val: OXrExtensions) -> Self { fn from(val: XrExtensions) -> Self {
val.0 val.0
} }
} }
impl Default for OXrExtensions { impl Default for XrExtensions {
fn default() -> Self { fn default() -> Self {
let exts = ExtensionSet::default(); let exts = ExtensionSet::default();
//exts.ext_hand_tracking = true; //exts.ext_hand_tracking = true;
@@ -165,7 +165,7 @@ macro_rules! impl_ext {
) => { ) => {
$( $(
$macro! { $macro! {
OXrExtensions; XrExtensions;
almalence_digital_lens_control, almalence_digital_lens_control,
bd_controller_interaction, bd_controller_interaction,
epic_view_configuration_fov, epic_view_configuration_fov,

View File

@@ -5,51 +5,32 @@ use std::any::TypeId;
use bevy::math::UVec2; use bevy::math::UVec2;
use crate::oxr::types::{AppInfo, OXrExtensions, Result, WgpuGraphics}; use crate::extensions::XrExtensions;
use crate::types::*;
/// This is an extension trait to the [`Graphics`](openxr::Graphics) trait and is how the graphics API should be interacted with.
pub unsafe trait GraphicsExt: openxr::Graphics { pub unsafe trait GraphicsExt: openxr::Graphics {
/// Wrap the graphics specific type into the [GraphicsWrap] enum /// Wrap the graphics specific type into the [GraphicsWrap] enum
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T>; fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T>;
/// Returns all of the required openxr extensions to use this graphics API.
fn required_exts() -> OXrExtensions;
/// Convert from wgpu format to the graphics format /// Convert from wgpu format to the graphics format
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>; fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>;
/// Convert from the graphics format to wgpu format /// Convert from the graphics format to wgpu format
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>; fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
/// Convert an API specific swapchain image to a [`Texture`](wgpu::Texture). /// Initialize graphics for this backend
/// fn init_graphics(
/// # Safety app_info: &AppInfo,
/// instance: &openxr::Instance,
/// The `image` argument must be a valid handle. system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
/// Convert a swapchain function
unsafe fn to_wgpu_img( unsafe fn to_wgpu_img(
image: Self::SwapchainImage, image: Self::SwapchainImage,
device: &wgpu::Device, device: &wgpu::Device,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
resolution: UVec2, resolution: UVec2,
) -> Result<wgpu::Texture>; ) -> Result<wgpu::Texture>;
/// Initialize graphics for this backend and return a [`WgpuGraphics`] for bevy and an API specific [Self::SessionCreateInfo] for openxr fn required_exts() -> XrExtensions;
fn init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
} }
/// A type that can be used in [`GraphicsWrap`].
///
/// # Example
///
/// ```
/// pub struct XrSession(GraphicsWrap<XrSession>);
///
/// impl GraphicsType for XrSession {
/// type Inner<G: GraphicsExt> = openxr::Session<G>;
/// }
///
/// ```
///
/// In this example, `GraphicsWrap<XrSession>` is now an enum with variants for each graphics API. The enum can be matched to get the graphics specific inner type.
pub trait GraphicsType { pub trait GraphicsType {
type Inner<G: GraphicsExt>; type Inner<G: GraphicsExt>;
} }
@@ -58,13 +39,12 @@ impl GraphicsType for () {
type Inner<G: GraphicsExt> = (); type Inner<G: GraphicsExt> = ();
} }
/// This is a special variant of [GraphicsWrap] using the unit struct as the inner type. This is to simply represent a graphics backend without storing data.
pub type GraphicsBackend = GraphicsWrap<()>; pub type GraphicsBackend = GraphicsWrap<()>;
impl GraphicsBackend { impl GraphicsBackend {
const ALL: &'static [Self] = &[Self::Vulkan(())]; const ALL: &'static [Self] = &[Self::Vulkan(())];
pub fn available_backends(exts: &OXrExtensions) -> Vec<Self> { pub fn available_backends(exts: &XrExtensions) -> Vec<Self> {
Self::ALL Self::ALL
.iter() .iter()
.copied() .copied()
@@ -72,11 +52,11 @@ impl GraphicsBackend {
.collect() .collect()
} }
pub fn is_available(&self, exts: &OXrExtensions) -> bool { pub fn is_available(&self, exts: &XrExtensions) -> bool {
self.required_exts().is_available(exts) self.required_exts().is_available(exts)
} }
pub fn required_exts(&self) -> OXrExtensions { pub fn required_exts(&self) -> XrExtensions {
graphics_match!( graphics_match!(
self; self;
_ => Api::required_exts() _ => Api::required_exts()
@@ -84,7 +64,6 @@ impl GraphicsBackend {
} }
} }
/// This struct is for creating agnostic objects for OpenXR graphics API specific structs.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphicsWrap<T: GraphicsType> { pub enum GraphicsWrap<T: GraphicsType> {
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
@@ -118,33 +97,6 @@ impl<T: GraphicsType> GraphicsWrap<T> {
} }
} }
/// This macro can be used to quickly run the same code for every variant of [GraphicsWrap].
/// The first argument should be an expression that returns either a reference or owned value of [GraphicsWrap].
/// The second argument should be a match arm with the pattern for the inner type.
///
/// # Example
///
/// ```
/// pub struct OXrFrameStream(GraphicsWrap<XrFrameStream>);
///
/// impl GraphicsType for OXrFrameStream {
/// // Here is the inner type
/// type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
/// }
///
/// fn begin(frame_stream: &mut XrFrameStream) {
/// graphics_match! {
/// // get the inner 'GraphicsWrap' struct
/// &mut frame_stream.0;
/// // now we can handle every match arm with a single arm
/// // important: the first pattern here represents the inner type of `GraphicsWrap`
/// // it is already destructured for you.
/// stream => stream.begin();
/// }
/// }
/// ```
///
/// Additionally, if you want the type that implements `GraphicsExt` in the scope of the match body, you can access that type through the type alias `Api`.
macro_rules! graphics_match { macro_rules! graphics_match {
( (
$field:expr; $field:expr;
@@ -152,7 +104,7 @@ macro_rules! graphics_match {
) => { ) => {
match $field { match $field {
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
$crate::oxr::graphics::GraphicsWrap::Vulkan($var) => { $crate::graphics::GraphicsWrap::Vulkan($var) => {
#[allow(unused)] #[allow(unused)]
type Api = openxr::Vulkan; type Api = openxr::Vulkan;
graphics_match!(@arm_impl Vulkan; $expr $(=> $($return)*)?) graphics_match!(@arm_impl Vulkan; $expr $(=> $($return)*)?)
@@ -165,7 +117,7 @@ macro_rules! graphics_match {
$variant:ident; $variant:ident;
$expr:expr => $wrap_ty:ty $expr:expr => $wrap_ty:ty
) => { ) => {
$crate::oxr::graphics::GraphicsWrap::<$wrap_ty>::$variant($expr) GraphicsWrap::<$wrap_ty>::$variant($expr)
}; };
( (

View File

@@ -7,9 +7,11 @@ use openxr::Version;
use wgpu_hal::api::Vulkan; use wgpu_hal::api::Vulkan;
use wgpu_hal::Api; use wgpu_hal::Api;
use super::{GraphicsExt, GraphicsType, GraphicsWrap}; use crate::error::XrError;
use crate::oxr::error::OXrError; use crate::extensions::XrExtensions;
use crate::oxr::types::{AppInfo, OXrExtensions, Result, WgpuGraphics}; use crate::types::*;
use super::GraphicsExt;
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
const VK_TARGET_VERSION: Version = Version::new(1, 2, 0); const VK_TARGET_VERSION: Version = Version::new(1, 2, 0);
@@ -24,74 +26,14 @@ const VK_TARGET_VERSION_ASH: u32 = ash::vk::make_api_version(
); );
unsafe impl GraphicsExt for openxr::Vulkan { unsafe impl GraphicsExt for openxr::Vulkan {
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T> {
GraphicsWrap::Vulkan(item)
}
fn required_exts() -> OXrExtensions {
let mut extensions = openxr::ExtensionSet::default();
extensions.khr_vulkan_enable2 = true;
extensions.into()
}
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> { fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> {
wgpu_to_vulkan(format).map(|f| f.as_raw() as _) wgpu_to_vulkan(format).map(|f| f.as_raw() as _)
} }
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> { fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _)) vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
} }
unsafe fn to_wgpu_img(
color_image: Self::SwapchainImage,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<wgpu::Texture> {
let color_image = ash::vk::Image::from_raw(color_image);
let wgpu_hal_texture = unsafe {
<wgpu_hal::vulkan::Api as wgpu_hal::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: 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 {
device.create_texture_from_hal::<wgpu_hal::vulkan::Api>(
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: format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
},
)
};
Ok(texture)
}
fn init_graphics( fn init_graphics(
app_info: &AppInfo, app_info: &AppInfo,
instance: &openxr::Instance, instance: &openxr::Instance,
@@ -106,7 +48,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
reqs.min_api_version_supported, reqs.min_api_version_supported,
reqs.max_api_version_supported.major() + 1 reqs.max_api_version_supported.major() + 1
); );
return Err(OXrError::FailedGraphicsRequirements); return Err(XrError::FailedGraphicsRequirements);
}; };
let vk_entry = unsafe { ash::Entry::load() }?; let vk_entry = unsafe { ash::Entry::load() }?;
let flags = wgpu::InstanceFlags::empty(); let flags = wgpu::InstanceFlags::empty();
@@ -165,7 +107,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
VK_TARGET_VERSION.minor(), VK_TARGET_VERSION.minor(),
VK_TARGET_VERSION.patch() VK_TARGET_VERSION.patch()
); );
return Err(OXrError::FailedGraphicsRequirements); return Err(XrError::FailedGraphicsRequirements);
} }
let wgpu_vk_instance = unsafe { let wgpu_vk_instance = unsafe {
@@ -189,7 +131,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
let Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else { let Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else {
error!("WGPU failed to provide an adapter"); error!("WGPU failed to provide an adapter");
return Err(OXrError::FailedGraphicsRequirements); return Err(XrError::FailedGraphicsRequirements);
}; };
let enabled_extensions = wgpu_exposed_adapter let enabled_extensions = wgpu_exposed_adapter
@@ -292,6 +234,66 @@ unsafe impl GraphicsExt for openxr::Vulkan {
}, },
)) ))
} }
unsafe fn to_wgpu_img(
color_image: Self::SwapchainImage,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<wgpu::Texture> {
let color_image = ash::vk::Image::from_raw(color_image);
let wgpu_hal_texture = unsafe {
<wgpu_hal::vulkan::Api as wgpu_hal::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: 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 {
device.create_texture_from_hal::<wgpu_hal::vulkan::Api>(
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: format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
},
)
};
Ok(texture)
}
fn required_exts() -> XrExtensions {
let mut extensions = openxr::ExtensionSet::default();
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> { fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {

View File

@@ -1,51 +1,41 @@
use bevy::math::uvec2;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResourcePlugin; use bevy::render::extract_resource::ExtractResourcePlugin;
use bevy::render::renderer::RenderAdapter; use bevy::render::renderer::{
use bevy::render::renderer::RenderAdapterInfo; RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
use bevy::render::renderer::RenderDevice; };
use bevy::render::renderer::RenderInstance;
use bevy::render::renderer::RenderQueue;
use bevy::render::settings::RenderCreation; use bevy::render::settings::RenderCreation;
use bevy::render::MainWorld; use bevy::render::{MainWorld, Render, RenderApp, RenderPlugin, RenderSet};
use bevy::render::Render;
use bevy::render::RenderApp;
use bevy::render::RenderPlugin;
use bevy::render::RenderSet;
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy::winit::UpdateMode; use bevy::winit::{UpdateMode, WinitSettings};
use bevy::winit::WinitSettings; use bevy_xr::session::{
use bevy_xr_api::session::handle_session; handle_session, session_available, session_running, status_equals, BeginXrSession,
use bevy_xr_api::session::session_available; CreateXrSession, DestroyXrSession, EndXrSession, XrSharedStatus, XrStatus,
use bevy_xr_api::session::session_running; };
use bevy_xr_api::session::status_equals;
use bevy_xr_api::session::BeginXrSession;
use bevy_xr_api::session::CreateXrSession;
use bevy_xr_api::session::DestroyXrSession;
use bevy_xr_api::session::EndXrSession;
use bevy_xr_api::session::XrSharedStatus;
use bevy_xr_api::session::XrStatus;
use crate::oxr::error::OXrError; use crate::graphics::*;
use crate::oxr::graphics::*; use crate::resources::*;
use crate::oxr::resources::*; use crate::types::*;
use crate::oxr::types::*;
pub fn session_started(started: Option<Res<OXrSessionStarted>>) -> bool { pub fn session_started(started: Option<Res<XrSessionStarted>>) -> bool {
started.is_some_and(|started| started.get()) started.is_some_and(|started| started.get())
} }
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum OXrPreUpdateSet { pub enum XrPreUpdateSet {
PollEvents, PollEvents,
HandleEvents, HandleEvents,
} }
pub struct OXrInitPlugin { #[derive(Component)]
pub struct XrTrackingRoot;
pub struct XrInitPlugin {
/// Information about the app this is being used to build. /// Information about the app this is being used to build.
pub app_info: AppInfo, pub app_info: AppInfo,
/// Extensions wanted for this session. /// Extensions wanted for this session.
// TODO!() This should be changed to take a simpler list of features wanted that this crate supports. i.e. hand tracking // TODO!() This should be changed to take a simpler list of features wanted that this crate supports. i.e. hand tracking
pub exts: OXrExtensions, pub exts: XrExtensions,
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode. /// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
pub blend_modes: Option<Vec<EnvironmentBlendMode>>, pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
/// List of backends the openxr session can use. If [None], pick the first available backend. /// List of backends the openxr session can use. If [None], pick the first available backend.
@@ -58,10 +48,7 @@ pub struct OXrInitPlugin {
pub synchronous_pipeline_compilation: bool, pub synchronous_pipeline_compilation: bool,
} }
#[derive(Component)] impl Plugin for XrInitPlugin {
pub struct OXrTrackingRoot;
impl Plugin for OXrInitPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
match self.init_xr() { match self.init_xr() {
Ok(( Ok((
@@ -83,9 +70,9 @@ impl Plugin for OXrInitPlugin {
), ),
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation, synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
}, },
ExtractResourcePlugin::<OXrCleanupSession>::default(), ExtractResourcePlugin::<XrCleanupSession>::default(),
ExtractResourcePlugin::<OXrTime>::default(), ExtractResourcePlugin::<XrTime>::default(),
ExtractResourcePlugin::<OXrRootTransform>::default(), ExtractResourcePlugin::<XrRootTransform>::default(),
)) ))
.add_systems(First, reset_per_frame_resources) .add_systems(First, reset_per_frame_resources)
.add_systems( .add_systems(
@@ -93,7 +80,7 @@ impl Plugin for OXrInitPlugin {
( (
poll_events poll_events
.run_if(session_available) .run_if(session_available)
.in_set(OXrPreUpdateSet::PollEvents), .in_set(XrPreUpdateSet::PollEvents),
( (
(create_xr_session, apply_deferred) (create_xr_session, apply_deferred)
.chain() .chain()
@@ -109,7 +96,7 @@ impl Plugin for OXrInitPlugin {
.run_if(on_event::<DestroyXrSession>()) .run_if(on_event::<DestroyXrSession>())
.run_if(status_equals(XrStatus::Exiting)), .run_if(status_equals(XrStatus::Exiting)),
) )
.in_set(OXrPreUpdateSet::HandleEvents), .in_set(XrPreUpdateSet::HandleEvents),
), ),
) )
.add_systems( .add_systems(
@@ -123,24 +110,24 @@ impl Plugin for OXrInitPlugin {
focused_mode: UpdateMode::Continuous, focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous,
}) })
.init_resource::<OXrCleanupSession>() .init_resource::<XrCleanupSession>()
.init_resource::<OXrRootTransform>() .init_resource::<XrRootTransform>()
.insert_non_send_resource(session_create_info); .insert_non_send_resource(session_create_info);
app.world app.world
.spawn((TransformBundle::default(), OXrTrackingRoot)); .spawn((TransformBundle::default(), XrTrackingRoot));
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.insert_resource(instance) .insert_resource(instance)
.insert_resource(system_id) .insert_resource(system_id)
.insert_resource(status) .insert_resource(status)
.init_resource::<OXrRootTransform>() .init_resource::<XrRootTransform>()
.init_resource::<OXrCleanupSession>() .init_resource::<XrCleanupSession>()
.add_systems( .add_systems(
Render, Render,
destroy_xr_session_render destroy_xr_session_render
.run_if(resource_equals(OXrCleanupSession(true))) .run_if(resource_equals(XrCleanupSession(true)))
.after(RenderSet::ExtractCommands), .after(RenderSet::ExtractCommands),
) )
.add_systems( .add_systems(
@@ -164,12 +151,12 @@ impl Plugin for OXrInitPlugin {
app.configure_sets( app.configure_sets(
PreUpdate, PreUpdate,
( (
OXrPreUpdateSet::PollEvents.before(handle_session), XrPreUpdateSet::PollEvents.before(handle_session),
OXrPreUpdateSet::HandleEvents.after(handle_session), XrPreUpdateSet::HandleEvents.after(handle_session),
), ),
); );
let session_started = OXrSessionStarted::default(); let session_started = XrSessionStarted::default();
app.insert_resource(session_started.clone()); app.insert_resource(session_started.clone());
@@ -180,24 +167,24 @@ impl Plugin for OXrInitPlugin {
} }
pub fn update_root_transform( pub fn update_root_transform(
mut root_transform: ResMut<OXrRootTransform>, mut root_transform: ResMut<XrRootTransform>,
root: Query<&GlobalTransform, With<OXrTrackingRoot>>, root: Query<&GlobalTransform, With<XrTrackingRoot>>,
) { ) {
let transform = root.single(); let transform = root.single();
root_transform.0 = *transform; root_transform.0 = *transform;
} }
fn xr_entry() -> Result<OXrEntry> { fn xr_entry() -> Result<XrEntry> {
#[cfg(windows)] #[cfg(windows)]
let entry = openxr::Entry::linked(); let entry = openxr::Entry::linked();
#[cfg(not(windows))] #[cfg(not(windows))]
let entry = unsafe { openxr::Entry::load()? }; let entry = unsafe { openxr::Entry::load()? };
Ok(OXrEntry(entry)) Ok(XrEntry(entry))
} }
impl OXrInitPlugin { impl XrInitPlugin {
fn init_xr(&self) -> Result<(OXrInstance, OXrSystemId, WgpuGraphics, SessionConfigInfo)> { fn init_xr(&self) -> Result<(XrInstance, XrSystemId, WgpuGraphics, XrSessionCreateInfo)> {
let entry = xr_entry()?; let entry = xr_entry()?;
let available_exts = entry.enumerate_extensions()?; let available_exts = entry.enumerate_extensions()?;
@@ -224,7 +211,7 @@ impl OXrInitPlugin {
} else { } else {
available_backends.first().copied() available_backends.first().copied()
} }
.ok_or(OXrError::NoAvailableBackend)?; .ok_or(XrError::NoAvailableBackend)?;
let exts = self.exts.clone() & available_exts; let exts = self.exts.clone() & available_exts;
@@ -256,7 +243,7 @@ impl OXrInitPlugin {
let (graphics, graphics_info) = instance.init_graphics(system_id)?; let (graphics, graphics_info) = instance.init_graphics(system_id)?;
let session_create_info = SessionConfigInfo { let session_create_info = XrSessionCreateInfo {
blend_modes: self.blend_modes.clone(), blend_modes: self.blend_modes.clone(),
formats: self.formats.clone(), formats: self.formats.clone(),
resolutions: self.resolutions.clone(), resolutions: self.resolutions.clone(),
@@ -265,7 +252,7 @@ impl OXrInitPlugin {
Ok(( Ok((
instance, instance,
OXrSystemId(system_id), XrSystemId(system_id),
graphics, graphics,
session_create_info, session_create_info,
)) ))
@@ -274,22 +261,22 @@ impl OXrInitPlugin {
fn init_xr_session( fn init_xr_session(
device: &wgpu::Device, device: &wgpu::Device,
instance: &OXrInstance, instance: &XrInstance,
system_id: openxr::SystemId, system_id: openxr::SystemId,
SessionConfigInfo { XrSessionCreateInfo {
blend_modes, blend_modes,
formats, formats,
resolutions, resolutions,
graphics_info, graphics_info,
}: SessionConfigInfo, }: XrSessionCreateInfo,
) -> Result<( ) -> Result<(
OXrSession, XrSession,
OXrFrameWaiter, XrFrameWaiter,
OXrFrameStream, XrFrameStream,
OXrSwapchain, XrSwapchain,
OXrSwapchainImages, XrSwapchainImages,
OXrGraphicsInfo, XrGraphicsInfo,
OXrStage, XrStage,
)> { )> {
let (session, frame_waiter, frame_stream) = let (session, frame_waiter, frame_stream) =
unsafe { instance.create_session(system_id, graphics_info)? }; unsafe { instance.create_session(system_id, graphics_info)? };
@@ -297,7 +284,7 @@ fn init_xr_session(
// TODO!() support other view configurations // TODO!() support other view configurations
let available_view_configurations = instance.enumerate_view_configurations(system_id)?; let available_view_configurations = instance.enumerate_view_configurations(system_id)?;
if !available_view_configurations.contains(&openxr::ViewConfigurationType::PRIMARY_STEREO) { if !available_view_configurations.contains(&openxr::ViewConfigurationType::PRIMARY_STEREO) {
return Err(OXrError::NoAvailableViewConfiguration); return Err(XrError::NoAvailableViewConfiguration);
} }
let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO; let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO;
@@ -333,7 +320,7 @@ fn init_xr_session(
} else { } else {
if let Some(config) = view_configuration_views.first() { if let Some(config) = view_configuration_views.first() {
Some(( Some((
UVec2::new( uvec2(
config.recommended_image_rect_width, config.recommended_image_rect_width,
config.recommended_image_rect_height, config.recommended_image_rect_height,
), ),
@@ -343,7 +330,7 @@ fn init_xr_session(
None None
} }
} }
.ok_or(OXrError::NoAvailableViewConfiguration)?; .ok_or(XrError::NoAvailableViewConfiguration)?;
let available_formats = session.enumerate_swapchain_formats()?; let available_formats = session.enumerate_swapchain_formats()?;
@@ -358,7 +345,7 @@ fn init_xr_session(
} else { } else {
available_formats.first().copied() available_formats.first().copied()
} }
.ok_or(OXrError::NoAvailableFormat)?; .ok_or(XrError::NoAvailableFormat)?;
let swapchain = session.create_swapchain(SwapchainCreateInfo { let swapchain = session.create_swapchain(SwapchainCreateInfo {
create_flags: SwapchainCreateFlags::EMPTY, create_flags: SwapchainCreateFlags::EMPTY,
@@ -391,15 +378,15 @@ fn init_xr_session(
} else { } else {
available_blend_modes.first().copied() available_blend_modes.first().copied()
} }
.ok_or(OXrError::NoAvailableBackend)?; .ok_or(XrError::NoAvailableBackend)?;
let stage = OXrStage( let stage = XrStage(
session session
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)? .create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
.into(), .into(),
); );
let graphics_info = OXrGraphicsInfo { let graphics_info = XrGraphicsInfo {
blend_mode, blend_mode,
resolution, resolution,
format, format,
@@ -419,19 +406,19 @@ fn init_xr_session(
/// This is used solely to transport resources from the main world to the render world. /// This is used solely to transport resources from the main world to the render world.
#[derive(Resource)] #[derive(Resource)]
struct XrRenderResources { struct XrRenderResources {
session: OXrSession, session: XrSession,
frame_stream: OXrFrameStream, frame_stream: XrFrameStream,
swapchain: OXrSwapchain, swapchain: XrSwapchain,
images: OXrSwapchainImages, images: XrSwapchainImages,
graphics_info: OXrGraphicsInfo, graphics_info: XrGraphicsInfo,
stage: OXrStage, stage: XrStage,
} }
pub fn create_xr_session( pub fn create_xr_session(
device: Res<RenderDevice>, device: Res<RenderDevice>,
instance: Res<OXrInstance>, instance: Res<XrInstance>,
create_info: NonSend<SessionConfigInfo>, create_info: NonSend<XrSessionCreateInfo>,
system_id: Res<OXrSystemId>, system_id: Res<XrSystemId>,
mut commands: Commands, mut commands: Commands,
) { ) {
match init_xr_session( match init_xr_session(
@@ -459,7 +446,7 @@ pub fn create_xr_session(
} }
} }
pub fn begin_xr_session(session: Res<OXrSession>, session_started: Res<OXrSessionStarted>) { pub fn begin_xr_session(session: Res<XrSession>, session_started: Res<XrSessionStarted>) {
let _span = info_span!("xr_begin_session"); let _span = info_span!("xr_begin_session");
session session
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO) .begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
@@ -467,7 +454,7 @@ pub fn begin_xr_session(session: Res<OXrSession>, session_started: Res<OXrSessio
session_started.set(true); session_started.set(true);
} }
pub fn end_xr_session(session: Res<OXrSession>, session_started: Res<OXrSessionStarted>) { pub fn end_xr_session(session: Res<XrSession>, session_started: Res<XrSessionStarted>) {
let _span = info_span!("xr_end_session"); let _span = info_span!("xr_end_session");
session.end().expect("Failed to end session"); session.end().expect("Failed to end session");
session_started.set(false); session_started.set(false);
@@ -496,7 +483,7 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
} }
/// Polls any OpenXR events and handles them accordingly /// Polls any OpenXR events and handles them accordingly
pub fn poll_events(instance: Res<OXrInstance>, status: Res<XrSharedStatus>) { pub fn poll_events(instance: Res<XrInstance>, status: Res<XrSharedStatus>) {
let _span = info_span!("xr_poll_events"); let _span = info_span!("xr_poll_events");
let mut buffer = Default::default(); let mut buffer = Default::default();
while let Some(event) = instance while let Some(event) = instance
@@ -532,24 +519,24 @@ pub fn poll_events(instance: Res<OXrInstance>, status: Res<XrSharedStatus>) {
} }
} }
pub fn reset_per_frame_resources(mut cleanup: ResMut<OXrCleanupSession>) { pub fn reset_per_frame_resources(mut cleanup: ResMut<XrCleanupSession>) {
**cleanup = false; **cleanup = false;
} }
pub fn destroy_xr_session(mut commands: Commands) { pub fn destroy_xr_session(mut commands: Commands) {
commands.remove_resource::<OXrSession>(); commands.remove_resource::<XrSession>();
commands.remove_resource::<OXrFrameWaiter>(); commands.remove_resource::<XrFrameWaiter>();
commands.remove_resource::<OXrSwapchainImages>(); commands.remove_resource::<XrSwapchainImages>();
commands.remove_resource::<OXrGraphicsInfo>(); commands.remove_resource::<XrGraphicsInfo>();
commands.remove_resource::<OXrStage>(); commands.remove_resource::<XrStage>();
commands.insert_resource(OXrCleanupSession(true)); commands.insert_resource(XrCleanupSession(true));
} }
pub fn destroy_xr_session_render(world: &mut World) { pub fn destroy_xr_session_render(world: &mut World) {
world.remove_resource::<OXrSwapchain>(); world.remove_resource::<XrSwapchain>();
world.remove_resource::<OXrFrameStream>(); world.remove_resource::<XrFrameStream>();
world.remove_resource::<OXrStage>(); world.remove_resource::<XrStage>();
world.remove_resource::<OXrSwapchainImages>(); world.remove_resource::<XrSwapchainImages>();
world.remove_resource::<OXrGraphicsInfo>(); world.remove_resource::<XrGraphicsInfo>();
world.remove_resource::<OXrSession>(); world.remove_resource::<XrSession>();
} }

View File

@@ -2,13 +2,13 @@ use std::mem;
use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space}; use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space};
use crate::oxr::graphics::graphics_match; use crate::graphics::graphics_match;
use crate::oxr::resources::OXrSwapchain; use crate::resources::XrSwapchain;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SwapchainSubImage<'a> { pub struct SwapchainSubImage<'a> {
inner: sys::SwapchainSubImage, inner: sys::SwapchainSubImage,
swapchain: Option<&'a OXrSwapchain>, swapchain: Option<&'a XrSwapchain>,
} }
impl<'a> SwapchainSubImage<'a> { impl<'a> SwapchainSubImage<'a> {
@@ -30,7 +30,7 @@ impl<'a> SwapchainSubImage<'a> {
&self.inner &self.inner
} }
#[inline] #[inline]
pub fn swapchain(mut self, value: &'a OXrSwapchain) -> Self { pub fn swapchain(mut self, value: &'a XrSwapchain) -> Self {
graphics_match!( graphics_match!(
&value.0; &value.0;
swap => self.inner.swapchain = swap.as_raw() swap => self.inner.swapchain = swap.as_raw()
@@ -59,7 +59,7 @@ impl<'a> Default for SwapchainSubImage<'a> {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct CompositionLayerProjectionView<'a> { pub struct CompositionLayerProjectionView<'a> {
inner: sys::CompositionLayerProjectionView, inner: sys::CompositionLayerProjectionView,
swapchain: Option<&'a OXrSwapchain>, swapchain: Option<&'a XrSwapchain>,
} }
impl<'a> CompositionLayerProjectionView<'a> { impl<'a> CompositionLayerProjectionView<'a> {
@@ -104,13 +104,13 @@ impl<'a> Default for CompositionLayerProjectionView<'a> {
} }
} }
pub unsafe trait CompositionLayer<'a> { pub unsafe trait CompositionLayer<'a> {
fn swapchain(&self) -> Option<&'a OXrSwapchain>; fn swapchain(&self) -> Option<&'a XrSwapchain>;
fn header(&self) -> &'a sys::CompositionLayerBaseHeader; fn header(&self) -> &'a sys::CompositionLayerBaseHeader;
} }
#[derive(Clone)] #[derive(Clone)]
pub struct CompositionLayerProjection<'a> { pub struct CompositionLayerProjection<'a> {
inner: sys::CompositionLayerProjection, inner: sys::CompositionLayerProjection,
swapchain: Option<&'a OXrSwapchain>, swapchain: Option<&'a XrSwapchain>,
views: Vec<sys::CompositionLayerProjectionView>, views: Vec<sys::CompositionLayerProjectionView>,
} }
impl<'a> CompositionLayerProjection<'a> { impl<'a> CompositionLayerProjection<'a> {
@@ -154,7 +154,7 @@ impl<'a> CompositionLayerProjection<'a> {
} }
} }
unsafe impl<'a> CompositionLayer<'a> for CompositionLayerProjection<'a> { unsafe impl<'a> CompositionLayer<'a> for CompositionLayerProjection<'a> {
fn swapchain(&self) -> Option<&'a OXrSwapchain> { fn swapchain(&self) -> Option<&'a XrSwapchain> {
self.swapchain self.swapchain
} }

View File

@@ -1,5 +1,18 @@
use actions::XrActionPlugin;
use bevy::{
app::{PluginGroup, PluginGroupBuilder},
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
utils::default,
window::{PresentMode, Window, WindowPlugin},
};
use bevy_xr::camera::XrCameraPlugin;
use init::XrInitPlugin;
use render::XrRenderPlugin;
pub mod actions;
pub mod camera;
pub mod error; pub mod error;
mod exts; pub mod extensions;
pub mod graphics; pub mod graphics;
pub mod init; pub mod init;
pub mod layer_builder; pub mod layer_builder;
@@ -7,25 +20,13 @@ pub mod render;
pub mod resources; pub mod resources;
pub mod types; pub mod types;
// use actions::XrActionPlugin;
use bevy::{
app::{PluginGroup, PluginGroupBuilder},
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
utils::default,
window::{PresentMode, Window, WindowPlugin},
};
use bevy_xr_api::camera::XrCameraPlugin;
use bevy_xr_api::session::XrSessionPlugin;
use init::OXrInitPlugin;
use render::XrRenderPlugin;
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder { pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
plugins plugins
.build() .build()
.disable::<RenderPlugin>() .disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>() .disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(XrSessionPlugin) .add_before::<RenderPlugin, _>(bevy_xr::session::XrSessionPlugin)
.add_before::<RenderPlugin, _>(OXrInitPlugin { .add_before::<RenderPlugin, _>(XrInitPlugin {
app_info: default(), app_info: default(),
exts: default(), exts: default(),
blend_modes: default(), blend_modes: default(),
@@ -36,7 +37,7 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
}) })
.add(XrRenderPlugin) .add(XrRenderPlugin)
.add(XrCameraPlugin) .add(XrCameraPlugin)
// .add(XrActionPlugin) .add(XrActionPlugin)
.set(WindowPlugin { .set(WindowPlugin {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
primary_window: Some(Window { primary_window: Some(Window {

View File

@@ -9,28 +9,28 @@ use bevy::{
}, },
transform::TransformSystem, transform::TransformSystem,
}; };
use bevy_xr_api::camera::{XrCamera, XrCameraBundle, XrProjection}; use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection};
use openxr::{CompositionLayerFlags, ViewStateFlags}; use openxr::{CompositionLayerFlags, ViewStateFlags};
use crate::oxr::init::{session_started, OXrPreUpdateSet}; use crate::init::{session_started, XrPreUpdateSet};
use crate::oxr::layer_builder::*; use crate::layer_builder::*;
use crate::oxr::resources::*; use crate::resources::*;
pub struct XrRenderPlugin; pub struct XrRenderPlugin;
impl Plugin for XrRenderPlugin { impl Plugin for XrRenderPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins((ExtractResourcePlugin::<OXrViews>::default(),)) app.add_plugins((ExtractResourcePlugin::<XrViews>::default(),))
.add_systems( .add_systems(
PreUpdate, PreUpdate,
( (
init_views.run_if(resource_added::<OXrGraphicsInfo>), init_views.run_if(resource_added::<XrGraphicsInfo>),
wait_frame.run_if(session_started), wait_frame.run_if(session_started),
locate_views.run_if(session_started), locate_views.run_if(session_started),
update_views.run_if(session_started), update_views.run_if(session_started),
) )
.chain() .chain()
.after(OXrPreUpdateSet::HandleEvents), .after(XrPreUpdateSet::HandleEvents),
) )
.add_systems( .add_systems(
PostUpdate, PostUpdate,
@@ -65,9 +65,9 @@ pub const XR_TEXTURE_INDEX: u32 = 3383858418;
// TODO: have cameras initialized externally and then recieved by this function. // TODO: have cameras initialized externally and then recieved by this function.
/// This is needed to properly initialize the texture views so that bevy will set them to the correct resolution despite them being updated in the render world. /// This is needed to properly initialize the texture views so that bevy will set them to the correct resolution despite them being updated in the render world.
pub fn init_views( pub fn init_views(
graphics_info: Res<OXrGraphicsInfo>, graphics_info: Res<XrGraphicsInfo>,
mut manual_texture_views: ResMut<ManualTextureViews>, mut manual_texture_views: ResMut<ManualTextureViews>,
swapchain_images: Res<OXrSwapchainImages>, swapchain_images: Res<XrSwapchainImages>,
mut commands: Commands, mut commands: Commands,
) { ) {
let _span = info_span!("xr_init_views"); let _span = info_span!("xr_init_views");
@@ -93,22 +93,22 @@ pub fn init_views(
)); ));
views.push(default()); views.push(default());
} }
commands.insert_resource(OXrViews(views)); commands.insert_resource(XrViews(views));
} }
pub fn wait_frame(mut frame_waiter: ResMut<OXrFrameWaiter>, mut commands: Commands) { pub fn wait_frame(mut frame_waiter: ResMut<XrFrameWaiter>, mut commands: Commands) {
let _span = info_span!("xr_wait_frame"); let _span = info_span!("xr_wait_frame");
let state = frame_waiter.wait().expect("Failed to wait frame"); let state = frame_waiter.wait().expect("Failed to wait frame");
// Here we insert the predicted display time for when this frame will be displayed. // Here we insert the predicted display time for when this frame will be displayed.
// TODO: don't add predicted_display_period if pipelined rendering plugin not enabled // TODO: don't add predicted_display_period if pipelined rendering plugin not enabled
commands.insert_resource(OXrTime(state.predicted_display_time)); commands.insert_resource(XrTime(state.predicted_display_time));
} }
pub fn locate_views( pub fn locate_views(
session: Res<OXrSession>, session: Res<XrSession>,
stage: Res<OXrStage>, stage: Res<XrStage>,
time: Res<OXrTime>, time: Res<XrTime>,
mut openxr_views: ResMut<OXrViews>, mut openxr_views: ResMut<XrViews>,
) { ) {
let _span = info_span!("xr_locate_views"); let _span = info_span!("xr_locate_views");
let (flags, xr_views) = session let (flags, xr_views) = session
@@ -125,7 +125,7 @@ pub fn locate_views(
flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID, flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID,
flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID, flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID,
) { ) {
(true, true) => *openxr_views = OXrViews(xr_views), (true, true) => *openxr_views = XrViews(xr_views),
(true, false) => { (true, false) => {
for (i, view) in openxr_views.iter_mut().enumerate() { for (i, view) in openxr_views.iter_mut().enumerate() {
view.pose.orientation = xr_views[i].pose.orientation; view.pose.orientation = xr_views[i].pose.orientation;
@@ -142,7 +142,7 @@ pub fn locate_views(
pub fn update_views( pub fn update_views(
mut query: Query<(&mut Transform, &mut XrProjection, &XrCamera)>, mut query: Query<(&mut Transform, &mut XrProjection, &XrCamera)>,
views: ResMut<OXrViews>, views: ResMut<XrViews>,
) { ) {
for (mut transform, mut projection, camera) in query.iter_mut() { for (mut transform, mut projection, camera) in query.iter_mut() {
let Some(view) = views.get(camera.0 as usize) else { let Some(view) = views.get(camera.0 as usize) else {
@@ -162,8 +162,8 @@ pub fn update_views(
} }
pub fn update_views_render_world( pub fn update_views_render_world(
views: Res<OXrViews>, views: Res<XrViews>,
root: Res<OXrRootTransform>, root: Res<XrRootTransform>,
mut query: Query<(&mut ExtractedView, &XrCamera)>, mut query: Query<(&mut ExtractedView, &XrCamera)>,
) { ) {
for (mut extracted_view, camera) in query.iter_mut() { for (mut extracted_view, camera) in query.iter_mut() {
@@ -280,10 +280,10 @@ fn calculate_projection(near_z: f32, fov: openxr::Fovf) -> Mat4 {
/// # Safety /// # Safety
/// Images inserted into texture views here should not be written to until [`wait_image`] is ran /// Images inserted into texture views here should not be written to until [`wait_image`] is ran
pub fn insert_texture_views( pub fn insert_texture_views(
swapchain_images: Res<OXrSwapchainImages>, swapchain_images: Res<XrSwapchainImages>,
mut swapchain: ResMut<OXrSwapchain>, mut swapchain: ResMut<XrSwapchain>,
mut manual_texture_views: ResMut<ManualTextureViews>, mut manual_texture_views: ResMut<ManualTextureViews>,
graphics_info: Res<OXrGraphicsInfo>, graphics_info: Res<XrGraphicsInfo>,
) { ) {
let _span = info_span!("xr_insert_texture_views"); let _span = info_span!("xr_insert_texture_views");
let index = swapchain.acquire_image().expect("Failed to acquire image"); let index = swapchain.acquire_image().expect("Failed to acquire image");
@@ -294,7 +294,7 @@ pub fn insert_texture_views(
} }
} }
pub fn wait_image(mut swapchain: ResMut<OXrSwapchain>) { pub fn wait_image(mut swapchain: ResMut<XrSwapchain>) {
swapchain swapchain
.wait_image(openxr::Duration::INFINITE) .wait_image(openxr::Duration::INFINITE)
.expect("Failed to wait image"); .expect("Failed to wait image");
@@ -303,7 +303,7 @@ pub fn wait_image(mut swapchain: ResMut<OXrSwapchain>) {
pub fn add_texture_view( pub fn add_texture_view(
manual_texture_views: &mut ManualTextureViews, manual_texture_views: &mut ManualTextureViews,
texture: &wgpu::Texture, texture: &wgpu::Texture,
info: &OXrGraphicsInfo, info: &XrGraphicsInfo,
index: u32, index: u32,
) -> ManualTextureViewHandle { ) -> ManualTextureViewHandle {
let view = texture.create_view(&wgpu::TextureViewDescriptor { let view = texture.create_view(&wgpu::TextureViewDescriptor {
@@ -322,17 +322,17 @@ pub fn add_texture_view(
handle handle
} }
pub fn begin_frame(mut frame_stream: ResMut<OXrFrameStream>) { pub fn begin_frame(mut frame_stream: ResMut<XrFrameStream>) {
frame_stream.begin().expect("Failed to begin frame") frame_stream.begin().expect("Failed to begin frame")
} }
pub fn end_frame( pub fn end_frame(
mut frame_stream: ResMut<OXrFrameStream>, mut frame_stream: ResMut<XrFrameStream>,
mut swapchain: ResMut<OXrSwapchain>, mut swapchain: ResMut<XrSwapchain>,
stage: Res<OXrStage>, stage: Res<XrStage>,
display_time: Res<OXrTime>, display_time: Res<XrTime>,
graphics_info: Res<OXrGraphicsInfo>, graphics_info: Res<XrGraphicsInfo>,
openxr_views: Res<OXrViews>, openxr_views: Res<XrViews>,
) { ) {
let _span = info_span!("xr_end_frame"); let _span = info_span!("xr_end_frame");
swapchain.release_image().unwrap(); swapchain.release_image().unwrap();

View File

@@ -1,36 +1,35 @@
use std::any::TypeId;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use crate::error::XrError;
use crate::graphics::*;
use crate::layer_builder::CompositionLayer;
use crate::types::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResource; use bevy::render::extract_resource::ExtractResource;
use bevy::utils::HashMap;
use openxr::AnyGraphics; use openxr::AnyGraphics;
use crate::oxr::error::OXrError;
use crate::oxr::graphics::*;
use crate::oxr::layer_builder::CompositionLayer;
use crate::oxr::types::*;
/// Wrapper around the entry point to the OpenXR API
#[derive(Deref, Clone)] #[derive(Deref, Clone)]
pub struct OXrEntry(pub openxr::Entry); pub struct XrEntry(pub openxr::Entry);
impl OXrEntry { impl XrEntry {
/// Enumerate available extensions for this OpenXR runtime. pub fn enumerate_extensions(&self) -> Result<XrExtensions> {
pub fn enumerate_extensions(&self) -> Result<OXrExtensions> {
Ok(self.0.enumerate_extensions().map(Into::into)?) Ok(self.0.enumerate_extensions().map(Into::into)?)
} }
pub fn create_instance( pub fn create_instance(
&self, &self,
app_info: AppInfo, app_info: AppInfo,
exts: OXrExtensions, exts: XrExtensions,
layers: &[&str], layers: &[&str],
backend: GraphicsBackend, backend: GraphicsBackend,
) -> Result<OXrInstance> { ) -> Result<XrInstance> {
let available_exts = self.enumerate_extensions()?; let available_exts = self.enumerate_extensions()?;
if !backend.is_available(&available_exts) { if !backend.is_available(&available_exts) {
return Err(OXrError::UnavailableBackend(backend)); return Err(XrError::UnavailableBackend(backend));
} }
let required_exts = exts | backend.required_exts(); let required_exts = exts | backend.required_exts();
@@ -46,7 +45,7 @@ impl OXrEntry {
layers, layers,
)?; )?;
Ok(OXrInstance(instance, backend, app_info)) Ok(XrInstance(instance, backend, app_info))
} }
pub fn available_backends(&self) -> Result<Vec<GraphicsBackend>> { pub fn available_backends(&self) -> Result<Vec<GraphicsBackend>> {
@@ -56,47 +55,39 @@ impl OXrEntry {
} }
} }
/// Wrapper around [openxr::Instance] with additional data for safety.
#[derive(Resource, Deref, Clone)] #[derive(Resource, Deref, Clone)]
pub struct OXrInstance( pub struct XrInstance(
#[deref] pub openxr::Instance, #[deref] pub openxr::Instance,
pub(crate) GraphicsBackend, pub(crate) GraphicsBackend,
pub(crate) AppInfo, pub(crate) AppInfo,
); );
impl OXrInstance { impl XrInstance {
pub fn into_inner(self) -> openxr::Instance {
self.0
}
/// Initialize graphics. This is used to create [WgpuGraphics] for the bevy app and to get the [SessionCreateInfo] to make an XR session.
pub fn init_graphics( pub fn init_graphics(
&self, &self,
system_id: openxr::SystemId, system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, SessionCreateInfo)> { ) -> Result<(WgpuGraphics, XrSessionGraphicsInfo)> {
graphics_match!( graphics_match!(
self.1; self.1;
_ => { _ => {
let (graphics, session_info) = Api::init_graphics(&self.2, &self, system_id)?; let (graphics, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
Ok((graphics, SessionCreateInfo(Api::wrap(session_info)))) Ok((graphics, XrSessionGraphicsInfo(Api::wrap(session_info))))
} }
) )
} }
/// Creates an [OXrSession]
///
/// # Safety /// # Safety
/// ///
/// `info` must contain valid handles for the graphics api /// `info` must contain valid handles for the graphics api
pub unsafe fn create_session( pub unsafe fn create_session(
&self, &self,
system_id: openxr::SystemId, system_id: openxr::SystemId,
info: SessionCreateInfo, info: XrSessionGraphicsInfo,
) -> Result<(OXrSession, OXrFrameWaiter, OXrFrameStream)> { ) -> Result<(XrSession, XrFrameWaiter, XrFrameStream)> {
if !info.0.using_graphics_of_val(&self.1) { if !info.0.using_graphics_of_val(&self.1) {
return Err(OXrError::GraphicsBackendMismatch { return Err(XrError::GraphicsBackendMismatch {
item: std::any::type_name::<SessionCreateInfo>(), item: std::any::type_name::<XrSessionGraphicsInfo>(),
backend: info.0.graphics_name(), backend: info.0.graphics_name(),
expected_backend: self.1.graphics_name(), expected_backend: self.1.graphics_name(),
}); });
@@ -105,61 +96,59 @@ impl OXrInstance {
info.0; info.0;
info => { info => {
let (session, frame_waiter, frame_stream) = self.0.create_session::<Api>(system_id, &info)?; let (session, frame_waiter, frame_stream) = self.0.create_session::<Api>(system_id, &info)?;
Ok((session.into(), OXrFrameWaiter(frame_waiter), OXrFrameStream(Api::wrap(frame_stream)))) Ok((session.into(), XrFrameWaiter(frame_waiter), XrFrameStream(Api::wrap(frame_stream))))
} }
) )
} }
} }
/// Graphics agnostic wrapper around [openxr::Session] #[derive(Clone)]
pub struct XrSessionGraphicsInfo(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for XrSessionGraphicsInfo {
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
}
#[derive(Resource, Deref, Clone)] #[derive(Resource, Deref, Clone)]
pub struct OXrSession( pub struct XrSession(
#[deref] pub openxr::Session<AnyGraphics>, #[deref] pub(crate) openxr::Session<AnyGraphics>,
pub GraphicsWrap<Self>, pub(crate) GraphicsWrap<Self>,
); );
impl GraphicsType for OXrSession { impl GraphicsType for XrSession {
type Inner<G: GraphicsExt> = openxr::Session<G>; type Inner<G: GraphicsExt> = openxr::Session<G>;
} }
impl<G: GraphicsExt> From<openxr::Session<G>> for OXrSession { impl<G: GraphicsExt> From<openxr::Session<G>> for XrSession {
fn from(session: openxr::Session<G>) -> Self { fn from(value: openxr::Session<G>) -> Self {
Self::new(session) Self(value.clone().into_any_graphics(), G::wrap(value))
} }
} }
impl OXrSession { impl XrSession {
pub fn new<G: GraphicsExt>(session: openxr::Session<G>) -> Self {
Self(session.clone().into_any_graphics(), G::wrap(session))
}
/// Enumerate all available swapchain formats.
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> { pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
graphics_match!( graphics_match!(
&self.1; &self.1;
session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::into_wgpu_format).collect()) session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::to_wgpu_format).collect())
) )
} }
/// Creates an [OXrSwapchain]. pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<XrSwapchain> {
pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<OXrSwapchain> { Ok(XrSwapchain(graphics_match!(
Ok(OXrSwapchain(graphics_match!(
&self.1; &self.1;
session => session.create_swapchain(&info.try_into()?)? => OXrSwapchain session => session.create_swapchain(&info.try_into()?)? => XrSwapchain
))) )))
} }
} }
/// Graphics agnostic wrapper around [openxr::FrameStream]
#[derive(Resource)] #[derive(Resource)]
pub struct OXrFrameStream(pub GraphicsWrap<Self>); pub struct XrFrameStream(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for OXrFrameStream { impl GraphicsType for XrFrameStream {
type Inner<G: GraphicsExt> = openxr::FrameStream<G>; type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
} }
impl OXrFrameStream { impl XrFrameStream {
/// Indicate that graphics device work is beginning.
pub fn begin(&mut self) -> openxr::Result<()> { pub fn begin(&mut self) -> openxr::Result<()> {
graphics_match!( graphics_match!(
&mut self.0; &mut self.0;
@@ -167,10 +156,6 @@ impl OXrFrameStream {
) )
} }
/// Indicate that all graphics work for the frame has been submitted
///
/// `layers` is an array of references to any type of composition layer,
/// e.g. [`CompositionLayerProjection`](crate::oxr::layer_builder::CompositionLayerProjection)
pub fn end( pub fn end(
&mut self, &mut self,
display_time: openxr::Time, display_time: openxr::Time,
@@ -202,20 +187,17 @@ impl OXrFrameStream {
} }
} }
/// Handle for waiting to render a frame. Check [`FrameWaiter`](openxr::FrameWaiter) for available methods.
#[derive(Resource, Deref, DerefMut)] #[derive(Resource, Deref, DerefMut)]
pub struct OXrFrameWaiter(pub openxr::FrameWaiter); pub struct XrFrameWaiter(pub openxr::FrameWaiter);
/// Graphics agnostic wrapper around [openxr::Swapchain]
#[derive(Resource)] #[derive(Resource)]
pub struct OXrSwapchain(pub GraphicsWrap<Self>); pub struct XrSwapchain(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for OXrSwapchain { impl GraphicsType for XrSwapchain {
type Inner<G: GraphicsExt> = openxr::Swapchain<G>; type Inner<G: GraphicsExt> = openxr::Swapchain<G>;
} }
impl OXrSwapchain { impl XrSwapchain {
/// Determine the index of the next image to render to in the swapchain image array
pub fn acquire_image(&mut self) -> Result<u32> { pub fn acquire_image(&mut self) -> Result<u32> {
graphics_match!( graphics_match!(
&mut self.0; &mut self.0;
@@ -223,7 +205,6 @@ impl OXrSwapchain {
) )
} }
/// Wait for the compositor to finish reading from the oldest unwaited acquired image
pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> { pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> {
graphics_match!( graphics_match!(
&mut self.0; &mut self.0;
@@ -231,7 +212,6 @@ impl OXrSwapchain {
) )
} }
/// Release the oldest acquired image
pub fn release_image(&mut self) -> Result<()> { pub fn release_image(&mut self) -> Result<()> {
graphics_match!( graphics_match!(
&mut self.0; &mut self.0;
@@ -239,13 +219,12 @@ impl OXrSwapchain {
) )
} }
/// Enumerates swapchain images and converts them to wgpu [`Texture`](wgpu::Texture)s.
pub fn enumerate_images( pub fn enumerate_images(
&self, &self,
device: &wgpu::Device, device: &wgpu::Device,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
resolution: UVec2, resolution: UVec2,
) -> Result<OXrSwapchainImages> { ) -> Result<XrSwapchainImages> {
graphics_match!( graphics_match!(
&self.0; &self.0;
swap => { swap => {
@@ -255,39 +234,43 @@ impl OXrSwapchain {
images.push(Api::to_wgpu_img(image, device, format, resolution)?); images.push(Api::to_wgpu_img(image, device, format, resolution)?);
} }
} }
Ok(OXrSwapchainImages(images.into())) Ok(XrSwapchainImages(images.into()))
} }
) )
} }
} }
/// Stores the generated swapchain images.
#[derive(Debug, Deref, Resource, Clone)]
pub struct OXrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
/// Thread safe wrapper around [openxr::Space] representing the stage.
#[derive(Deref, Clone, Resource)] #[derive(Deref, Clone, Resource)]
pub struct OXrStage(pub Arc<openxr::Space>); pub struct XrStage(pub Arc<openxr::Space>);
/// Stores the latest generated [OXrViews] #[derive(Debug, Deref, Resource, Clone)]
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)] pub struct XrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
pub struct OXrViews(pub Vec<openxr::View>);
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
pub struct XrTime(pub openxr::Time);
#[derive(Copy, Clone, Eq, PartialEq, Resource)]
pub struct XrSwapchainInfo {
pub format: wgpu::TextureFormat,
pub resolution: UVec2,
}
/// Wrapper around [openxr::SystemId] to allow it to be stored as a resource.
#[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)] #[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)]
pub struct OXrSystemId(pub openxr::SystemId); pub struct XrSystemId(pub openxr::SystemId);
/// Resource storing graphics info for the currently running session.
#[derive(Clone, Copy, Resource)] #[derive(Clone, Copy, Resource)]
pub struct OXrGraphicsInfo { pub struct XrGraphicsInfo {
pub blend_mode: EnvironmentBlendMode, pub blend_mode: EnvironmentBlendMode,
pub resolution: UVec2, pub resolution: UVec2,
pub format: wgpu::TextureFormat, pub format: wgpu::TextureFormat,
} }
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)]
pub struct XrViews(pub Vec<openxr::View>);
#[derive(Clone)] #[derive(Clone)]
/// This is used to store information from startup that is needed to create the session after the instance has been created. /// This is used to store information from startup that is needed to create the session after the instance has been created.
pub struct SessionConfigInfo { pub struct XrSessionCreateInfo {
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode. /// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
pub blend_modes: Option<Vec<EnvironmentBlendMode>>, pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
/// List of formats the openxr session can use. If [None], pick the first available format /// List of formats the openxr session can use. If [None], pick the first available format
@@ -295,13 +278,13 @@ pub struct SessionConfigInfo {
/// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution. /// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution.
pub resolutions: Option<Vec<UVec2>>, pub resolutions: Option<Vec<UVec2>>,
/// Graphics info used to create a session. /// Graphics info used to create a session.
pub graphics_info: SessionCreateInfo, pub graphics_info: XrSessionGraphicsInfo,
} }
#[derive(Resource, Clone, Default)] #[derive(Resource, Clone, Default)]
pub struct OXrSessionStarted(Arc<AtomicBool>); pub struct XrSessionStarted(Arc<AtomicBool>);
impl OXrSessionStarted { impl XrSessionStarted {
pub fn set(&self, val: bool) { pub fn set(&self, val: bool) {
self.0.store(val, Ordering::SeqCst); self.0.store(val, Ordering::SeqCst);
} }
@@ -311,14 +294,36 @@ impl OXrSessionStarted {
} }
} }
/// The calculated display time for the app. Passed through the pipeline.
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
pub struct OXrTime(pub openxr::Time);
/// The root transform's global position for late latching in the render world.
#[derive(ExtractResource, Resource, Clone, Copy, Default)] #[derive(ExtractResource, Resource, Clone, Copy, Default)]
pub struct OXrRootTransform(pub GlobalTransform); pub struct XrRootTransform(pub GlobalTransform);
#[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)] #[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)]
/// This is inserted into the world to signify if the session should be cleaned up. /// This is inserted into the world to signify if the session should be cleaned up.
pub struct OXrCleanupSession(pub bool); pub struct XrCleanupSession(pub bool);
#[derive(Resource, Clone, Deref)]
pub struct XrActionSet(#[deref] pub openxr::ActionSet, bool);
impl XrActionSet {
pub fn new(action_set: openxr::ActionSet) -> Self {
Self(action_set, false)
}
pub fn attach(&mut self) {
self.1 = true;
}
pub fn is_attached(&self) -> bool {
self.1
}
}
#[derive(Clone)]
pub enum TypedAction {
Bool(openxr::Action<bool>),
Float(openxr::Action<f32>),
Vector(openxr::Action<openxr::Vector2f>),
}
#[derive(Resource, Clone, Deref)]
pub struct XrActions(pub HashMap<TypeId, TypedAction>);

View File

@@ -1,15 +1,15 @@
use std::borrow::Cow; use std::borrow::Cow;
use super::error::OXrError; pub use crate::error::XrError;
use super::graphics::{GraphicsExt, GraphicsType, GraphicsWrap}; pub use crate::extensions::XrExtensions;
use crate::graphics::GraphicsExt;
pub use crate::oxr::exts::OXrExtensions; pub use openxr::{
ApiLayerProperties, EnvironmentBlendMode, SwapchainCreateFlags, SwapchainUsageFlags,
};
pub use openxr::{EnvironmentBlendMode, SwapchainCreateFlags, SwapchainUsageFlags}; pub type Result<T> = std::result::Result<T, XrError>;
pub type Result<T> = std::result::Result<T, OXrError>;
/// A container for all required graphics objects needed for a bevy app.
pub struct WgpuGraphics( pub struct WgpuGraphics(
pub wgpu::Device, pub wgpu::Device,
pub wgpu::Queue, pub wgpu::Queue,
@@ -18,13 +18,11 @@ pub struct WgpuGraphics(
pub wgpu::Instance, pub wgpu::Instance,
); );
/// A version number that can be stored inside of a u32
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Version(pub u8, pub u8, pub u16); pub struct Version(pub u8, pub u8, pub u16);
impl Version { impl Version {
/// Bevy's version number pub const BEVY: Self = Self(0, 12, 1);
pub const BEVY: Self = Self(0, 13, 0);
pub const fn to_u32(self) -> u32 { pub const fn to_u32(self) -> u32 {
let major = (self.0 as u32) << 24; let major = (self.0 as u32) << 24;
@@ -33,29 +31,21 @@ impl Version {
} }
} }
/// Info needed about an app for OpenXR
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct AppInfo { pub struct AppInfo {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
pub version: Version, pub version: Version,
} }
impl AppInfo {
/// The default app info for a generic bevy app
pub const BEVY: Self = Self {
name: Cow::Borrowed("Bevy"),
version: Version::BEVY,
};
}
impl Default for AppInfo { impl Default for AppInfo {
fn default() -> Self { fn default() -> Self {
Self::BEVY Self {
name: "Bevy".into(),
version: Version::BEVY,
}
} }
} }
/// Info needed to create a swapchain.
/// This is an API agnostic version of [openxr::SwapchainCreateInfo] used for some of this library's functions
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SwapchainCreateInfo { pub struct SwapchainCreateInfo {
pub create_flags: SwapchainCreateFlags, pub create_flags: SwapchainCreateFlags,
@@ -70,14 +60,14 @@ pub struct SwapchainCreateInfo {
} }
impl<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInfo<G> { impl<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInfo<G> {
type Error = OXrError; type Error = XrError;
fn try_from(value: SwapchainCreateInfo) -> Result<Self> { fn try_from(value: SwapchainCreateInfo) -> Result<Self> {
Ok(openxr::SwapchainCreateInfo { Ok(openxr::SwapchainCreateInfo {
create_flags: value.create_flags, create_flags: value.create_flags,
usage_flags: value.usage_flags, usage_flags: value.usage_flags,
format: G::from_wgpu_format(value.format) format: G::from_wgpu_format(value.format)
.ok_or(OXrError::UnsupportedTextureFormat(value.format))?, .ok_or(XrError::UnsupportedTextureFormat(value.format))?,
sample_count: value.sample_count, sample_count: value.sample_count,
width: value.width, width: value.width,
height: value.height, height: value.height,
@@ -87,12 +77,3 @@ impl<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInf
}) })
} }
} }
/// Info needed to create a session. Mostly contains graphics info.
/// This is an API agnostic version of [openxr::Graphics::SessionCreateInfo] used for some of this library's functions
#[derive(Clone)]
pub struct SessionCreateInfo(pub GraphicsWrap<Self>);
impl GraphicsType for SessionCreateInfo {
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
}

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "bevy_xr_api" name = "bevy_xr"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View File

@@ -1,11 +1,10 @@
//! A simple 3D scene with light shining over a cube sitting on a plane. //! A simple 3D scene with light shining over a cube sitting on a plane.
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xr::add_xr_plugins;
fn main() { fn main() {
App::new() App::new()
.add_plugins(add_xr_plugins(DefaultPlugins)) .add_plugins(DefaultPlugins)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.run(); .run();
} }

View File

@@ -1,7 +1,2 @@
#[cfg(not(target_family = "wasm"))] pub use bevy_openxr;
pub mod oxr; pub use bevy_xr;
#[cfg(target_family = "wasm")]
pub mod webxr;
#[cfg(not(target_family = "wasm"))]
pub use oxr::add_xr_plugins;