diff --git a/crates/bevy_openxr/examples/sessions.rs b/crates/bevy_openxr/examples/sessions.rs new file mode 100644 index 0000000..3404eec --- /dev/null +++ b/crates/bevy_openxr/examples/sessions.rs @@ -0,0 +1,62 @@ +//! A simple 3D scene with light shining over a cube sitting on a plane. + +use bevy::prelude::*; +use bevy_openxr::add_xr_plugins; +use bevy_xr::session::{ XrStatus}; + +fn main() { + App::new() + .add_plugins(add_xr_plugins(DefaultPlugins)) + .add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin) + .add_systems(Startup, setup) + .add_systems(Update, handle_input) + .insert_resource(AmbientLight::default()) + .run(); +} + +fn handle_input( + keys: Res>, + mut end: EventWriter, + mut _destroy: EventWriter, + mut _begin: EventWriter, + mut create: EventWriter, + state: Res, +) { + if keys.just_pressed(KeyCode::KeyE) { + info!("sending end"); + end.send_default(); + } + if keys.just_pressed(KeyCode::KeyC) { + info!("sending create"); + create.send_default(); + } + if keys.just_pressed(KeyCode::KeyI) { + info!("current state: {:?}", *state); + } +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // 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() + }); + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} diff --git a/crates/bevy_openxr/src/openxr/action_binding.rs b/crates/bevy_openxr/src/openxr/action_binding.rs index a5de40b..2d5af6d 100644 --- a/crates/bevy_openxr/src/openxr/action_binding.rs +++ b/crates/bevy_openxr/src/openxr/action_binding.rs @@ -5,19 +5,23 @@ use bevy::ecs::schedule::ScheduleLabel; use bevy::ecs::system::RunSystemOnce; use bevy::prelude::*; use bevy::utils::HashMap; -use bevy_xr::session::{status_changed_to, XrStatus}; use openxr::sys::ActionSuggestedBinding; -use crate::resources::OxrInstance; +use crate::{resources::OxrInstance, session::OxrSessionStatusEvent}; impl Plugin for OxrActionBindingPlugin { fn build(&self, app: &mut App) { app.add_schedule(Schedule::new(OxrSendActionBindings)); app.add_event::(); app.add_systems( - Update, - run_action_binding_sugestion - .run_if(status_changed_to(XrStatus::Ready).and_then(run_once())), + PostUpdate, + run_action_binding_sugestion.run_if( + |mut session_state: EventReader| { + session_state + .read() + .any(|s| *s == OxrSessionStatusEvent::Created) + }, + ), ); } } diff --git a/crates/bevy_openxr/src/openxr/action_set_attaching.rs b/crates/bevy_openxr/src/openxr/action_set_attaching.rs index d8a04f8..9eeedc8 100644 --- a/crates/bevy_openxr/src/openxr/action_set_attaching.rs +++ b/crates/bevy_openxr/src/openxr/action_set_attaching.rs @@ -1,4 +1,4 @@ -use crate::resources::OxrSession; +use crate::session::{OxrSession, OxrSessionStatusEvent}; use bevy::prelude::*; use bevy_xr::session::status_changed_to; @@ -7,7 +7,11 @@ impl Plugin for OxrActionAttachingPlugin { app.add_event::(); app.add_systems( PostUpdate, - attach_sets.run_if(status_changed_to(bevy_xr::session::XrStatus::Ready)), + attach_sets.run_if(|mut session_status_event: EventReader| { + session_status_event + .read() + .any(|s| *s == OxrSessionStatusEvent::Created) + }), ); } } diff --git a/crates/bevy_openxr/src/openxr/action_set_syncing.rs b/crates/bevy_openxr/src/openxr/action_set_syncing.rs index 8bb5baf..ff57380 100644 --- a/crates/bevy_openxr/src/openxr/action_set_syncing.rs +++ b/crates/bevy_openxr/src/openxr/action_set_syncing.rs @@ -1,4 +1,4 @@ -use crate::resources::OxrSession; +use crate::session::OxrSession; use bevy::prelude::*; use bevy_xr::session::session_running; diff --git a/crates/bevy_openxr/src/openxr/features/handtracking.rs b/crates/bevy_openxr/src/openxr/features/handtracking.rs index 0e92576..c60750c 100644 --- a/crates/bevy_openxr/src/openxr/features/handtracking.rs +++ b/crates/bevy_openxr/src/openxr/features/handtracking.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use bevy_xr::hands::{LeftHand, RightHand}; use bevy_xr::{ hands::{HandBone, HandBoneRadius}, - session::{session_running, status_changed_to, XrStatus}, + session::{session_running, XrSessionCreated, XrSessionExiting}, }; use openxr::SpaceLocationFlags; @@ -10,7 +10,8 @@ use crate::resources::Pipelined; use crate::{ init::OxrTrackingRoot, reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace}, - resources::{OxrFrameState, OxrSession}, + resources::OxrFrameState, + session::OxrSession, }; pub struct HandTrackingPlugin { @@ -28,14 +29,8 @@ impl Plugin for HandTrackingPlugin { fn build(&self, app: &mut App) { app.add_systems(PreUpdate, locate_hands.run_if(session_running)); if self.default_hands { - app.add_systems( - PreUpdate, - clean_up_default_hands.run_if(status_changed_to(XrStatus::Exiting)), - ); - app.add_systems( - PostUpdate, - spawn_default_hands.run_if(status_changed_to(XrStatus::Ready)), - ); + app.add_systems(XrSessionExiting, clean_up_default_hands); + app.add_systems(XrSessionCreated, spawn_default_hands); } } } @@ -45,7 +40,7 @@ fn spawn_default_hands( session: Res, root: Query>, ) { - info!("spawning hands"); + debug!("spawning default hands"); let Ok(root) = root.get_single() else { error!("unable to get tracking root, skipping hand creation"); return; @@ -77,6 +72,7 @@ fn spawn_default_hands( for bone in HandBone::get_all_bones() { let bone_left = cmds .spawn(( + DefaultHandBone, SpatialBundle::default(), bone, HandBoneRadius(0.0), @@ -85,6 +81,7 @@ fn spawn_default_hands( .id(); let bone_right = cmds .spawn(( + DefaultHandBone, SpatialBundle::default(), bone, HandBoneRadius(0.0), @@ -113,15 +110,16 @@ fn spawn_default_hands( #[derive(Component)] struct DefaultHandTracker; #[derive(Component)] -struct DefaultHandBones; +struct DefaultHandBone; #[allow(clippy::type_complexity)] fn clean_up_default_hands( mut cmds: Commands, - query: Query, With)>>, + query: Query, With)>>, ) { for e in &query { - cmds.entity(e).despawn(); + debug!("removing default hand entity"); + cmds.entity(e).despawn_recursive(); } } diff --git a/crates/bevy_openxr/src/openxr/features/passthrough.rs b/crates/bevy_openxr/src/openxr/features/passthrough.rs index f8fcea8..9dcf898 100644 --- a/crates/bevy_openxr/src/openxr/features/passthrough.rs +++ b/crates/bevy_openxr/src/openxr/features/passthrough.rs @@ -7,6 +7,7 @@ use openxr::PassthroughCapabilityFlagsFB; use crate::layer_builder::PassthroughLayer; use crate::resources::*; +use crate::session::OxrSession; use crate::types::*; pub struct OxrPassthroughPlugin; diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index d1da5bd..8fada5f 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -1,5 +1,6 @@ use bevy::app::MainScheduleOrder; use bevy::ecs::schedule::ScheduleLabel; +use bevy::ecs::system::RunSystemOnce; use bevy::prelude::*; use bevy::render::extract_resource::ExtractResourcePlugin; use bevy::render::renderer::RenderAdapter; @@ -22,12 +23,15 @@ use bevy_xr::session::BeginXrSession; use bevy_xr::session::CreateXrSession; use bevy_xr::session::DestroyXrSession; use bevy_xr::session::EndXrSession; +use bevy_xr::session::XrSessionExiting; use bevy_xr::session::XrStatus; use bevy_xr::session::XrStatusChanged; use crate::error::OxrError; use crate::graphics::*; use crate::resources::*; +use crate::session::OxrSession; +use crate::session::OxrSessionStatusEvent; use crate::types::*; pub fn session_started(started: Option>) -> bool { @@ -45,9 +49,6 @@ pub struct OxrLast; #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] pub struct OxrHandleEvents; -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] -pub struct OxrSessionCreated; - pub struct OxrInitPlugin { /// Information about the app this is being used to build. pub app_info: AppInfo, @@ -112,7 +113,6 @@ impl Plugin for OxrInitPlugin { ExtractResourcePlugin::::default(), )) .init_schedule(OxrLast) - .init_schedule(OxrSessionCreated) .add_systems( OxrLast, ( @@ -126,7 +126,7 @@ impl Plugin for OxrInitPlugin { .run_if(status_equals(XrStatus::Ready)), end_xr_session .run_if(on_event::()) - .run_if(status_equals(XrStatus::Stopping)), + .run_if(status_equals(XrStatus::Running)), destroy_xr_session .run_if(on_event::()) .run_if(status_equals(XrStatus::Exiting)), @@ -134,6 +134,7 @@ impl Plugin for OxrInitPlugin { .chain() .in_set(OxrHandleEvents), ) + .add_systems(XrSessionExiting, destroy_xr_session) .add_systems( PostUpdate, update_root_transform.after(TransformSystem::TransformPropagate), @@ -163,7 +164,7 @@ impl Plugin for OxrInitPlugin { Render, destroy_xr_session_render .run_if(resource_equals(OxrCleanupSession(true))) - .after(RenderSet::ExtractCommands), + .after(RenderSet::Cleanup), ) .add_systems( ExtractSchedule, @@ -276,51 +277,6 @@ impl OxrInitPlugin { } } -pub fn reset_per_frame_resources(mut cleanup: ResMut) { - **cleanup = false; -} - -/// Polls any OpenXR events and handles them accordingly -pub fn poll_events( - instance: Res, - mut status: ResMut, - mut changed_event: EventWriter, -) { - let _span = info_span!("xr_poll_events"); - let mut buffer = Default::default(); - while let Some(event) = instance - .poll_event(&mut buffer) - .expect("Failed to poll event") - { - use openxr::Event::*; - match event { - SessionStateChanged(state) => { - use openxr::SessionState; - - let state = state.state(); - - info!("entered XR state {:?}", state); - - let new_status = match state { - SessionState::IDLE => XrStatus::Idle, - SessionState::READY => XrStatus::Ready, - SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { - XrStatus::Running - } - SessionState::STOPPING => XrStatus::Stopping, - SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting, - _ => unreachable!(), - }; - changed_event.send(XrStatusChanged(new_status)); - *status = new_status; - } - InstanceLossPending(_) => {} - EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()), - _ => {} - } - } -} - fn init_xr_session( device: &wgpu::Device, instance: &OxrInstance, @@ -455,35 +411,6 @@ fn init_xr_session( )) } -pub fn create_xr_session(world: &mut World) { - let device = world.resource::(); - let instance = world.resource::(); - let create_info = world.non_send_resource::(); - let system_id = world.resource::(); - match init_xr_session( - device.wgpu_device(), - &instance, - **system_id, - create_info.clone(), - ) { - Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info)) => { - world.insert_resource(session.clone()); - world.insert_resource(frame_waiter); - world.insert_resource(images.clone()); - world.insert_resource(graphics_info.clone()); - world.insert_resource(OxrRenderResources { - session, - frame_stream, - swapchain, - images, - graphics_info, - }); - world.run_schedule(OxrSessionCreated); - } - Err(e) => error!("Failed to initialize XrSession: {e}"), - } -} - pub fn begin_xr_session(session: Res, mut session_started: ResMut) { let _span = info_span!("xr_begin_session"); session @@ -494,7 +421,7 @@ pub fn begin_xr_session(session: Res, mut session_started: ResMut, mut session_started: ResMut) { let _span = info_span!("xr_end_session"); - session.end().expect("Failed to end session"); + session.request_exit().expect("Failed to end session"); session_started.0 = false; } @@ -508,6 +435,36 @@ struct OxrRenderResources { graphics_info: OxrGraphicsInfo, } +pub fn create_xr_session( + device: Res, + instance: Res, + create_info: NonSend, + system_id: Res, + mut commands: Commands, +) { + match init_xr_session( + device.wgpu_device(), + &instance, + **system_id, + create_info.clone(), + ) { + Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info)) => { + commands.insert_resource(session.clone()); + commands.insert_resource(frame_waiter); + commands.insert_resource(images.clone()); + commands.insert_resource(graphics_info.clone()); + commands.insert_resource(OxrRenderResources { + session, + frame_stream, + swapchain, + images, + graphics_info, + }); + } + Err(e) => error!("Failed to initialize XrSession: {e}"), + } +} + /// This system transfers important render resources from the main world to the render world when a session is created. pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut) { let Some(OxrRenderResources { @@ -528,12 +485,64 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut(); - world.remove_resource::(); - world.remove_resource::(); - world.remove_resource::(); - world.insert_resource(OxrCleanupSession(true)); +/// Polls any OpenXR events and handles them accordingly +pub fn poll_events( + instance: Res, + mut status: ResMut, + mut changed_event: EventWriter, + mut session_status_events: EventWriter, +) { + let _span = info_span!("xr_poll_events"); + let mut buffer = Default::default(); + while let Some(event) = instance + .poll_event(&mut buffer) + .expect("Failed to poll event") + { + use openxr::Event::*; + match event { + SessionStateChanged(state) => { + use openxr::SessionState; + + let state = state.state(); + + info!("entered XR state {:?}", state); + + let new_status = match state { + SessionState::IDLE => { + if *status == XrStatus::Available { + session_status_events.send(OxrSessionStatusEvent::Created); + } + XrStatus::Idle + } + SessionState::READY => XrStatus::Ready, + SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { + XrStatus::Running + } + SessionState::STOPPING => XrStatus::Stopping, + SessionState::EXITING | SessionState::LOSS_PENDING => { + session_status_events.send(OxrSessionStatusEvent::AboutToBeDestroyed); + XrStatus::Exiting + } + _ => unreachable!(), + }; + changed_event.send(XrStatusChanged(new_status)); + *status = new_status; + } + InstanceLossPending(_) => {} + EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()), + _ => {} + } + } +} + +pub fn reset_per_frame_resources(mut cleanup: ResMut) { + **cleanup = false; +} + +pub fn destroy_xr_session(mut commands: Commands) { + commands.remove_resource::(); + commands.remove_resource::(); + commands.remove_resource::(); } pub fn destroy_xr_session_render(world: &mut World) { @@ -541,5 +550,9 @@ pub fn destroy_xr_session_render(world: &mut World) { world.remove_resource::(); world.remove_resource::(); world.remove_resource::(); + world.run_system_once(apply_deferred); + world.run_schedule(XrSessionExiting); + world.run_system_once(apply_deferred); world.remove_resource::(); + world.insert_resource(OxrSessionStarted(false)); } diff --git a/crates/bevy_openxr/src/openxr/mod.rs b/crates/bevy_openxr/src/openxr/mod.rs index b4428e2..6615370 100644 --- a/crates/bevy_openxr/src/openxr/mod.rs +++ b/crates/bevy_openxr/src/openxr/mod.rs @@ -13,6 +13,7 @@ use render::OxrRenderPlugin; use self::{ features::{handtracking::HandTrackingPlugin, passthrough::OxrPassthroughPlugin}, reference_space::OxrReferenceSpacePlugin, + session::OxrSessionPlugin, }; pub mod action_binding; @@ -28,6 +29,7 @@ pub mod layer_builder; pub mod reference_space; pub mod render; pub mod resources; +pub mod session; pub mod types; pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { @@ -37,6 +39,7 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { .disable::() .add_before::(XrSessionPlugin) .add_before::(OxrInitPlugin::default()) + .add(OxrSessionPlugin) .add(OxrReferenceSpacePlugin::default()) .add(OxrRenderPlugin) .add(OxrPassthroughPlugin) @@ -46,6 +49,8 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { .add(action_binding::OxrActionBindingPlugin) .add(action_set_syncing::OxrActionSyncingPlugin) // .add(XrActionPlugin) + // we should probably handle the exiting ourselfs so that we can correctly end the + // session and instance .set(WindowPlugin { primary_window: Some(Window { transparent: true, diff --git a/crates/bevy_openxr/src/openxr/reference_space.rs b/crates/bevy_openxr/src/openxr/reference_space.rs index c1649cb..a3001ec 100644 --- a/crates/bevy_openxr/src/openxr/reference_space.rs +++ b/crates/bevy_openxr/src/openxr/reference_space.rs @@ -2,10 +2,14 @@ use std::sync::Arc; use bevy::{ prelude::*, - render::extract_resource::{ExtractResource, ExtractResourcePlugin}, + render::{ + extract_resource::{ExtractResource, ExtractResourcePlugin}, + RenderApp, + }, }; +use bevy_xr::session::{XrSessionCreated, XrSessionExiting}; -use crate::{init::OxrSessionCreated, resources::OxrSession}; +use crate::session::OxrSession; pub struct OxrReferenceSpacePlugin { pub default_primary_ref_space: openxr::ReferenceSpaceType, @@ -19,8 +23,7 @@ impl Default for OxrReferenceSpacePlugin { } #[derive(Resource)] -struct OxrPrimaryReferenceSpaceType(openxr::ReferenceSpaceType); -// TODO: this will keep the session alive so we need to remove this in the render world too +struct OxrDefaultPrimaryReferenceSpaceType(openxr::ReferenceSpaceType); /// The Default Reference space used for locating things #[derive(Resource, Deref, ExtractResource, Clone)] pub struct OxrPrimaryReferenceSpace(pub Arc); @@ -31,18 +34,27 @@ pub struct OxrReferenceSpace(pub openxr::Space); impl Plugin for OxrReferenceSpacePlugin { fn build(&self, app: &mut App) { - app.insert_resource(OxrPrimaryReferenceSpaceType(self.default_primary_ref_space)); + app.insert_resource(OxrDefaultPrimaryReferenceSpaceType( + self.default_primary_ref_space, + )); app.add_plugins(ExtractResourcePlugin::::default()); - app.add_systems( - OxrSessionCreated, - set_primary_ref_space, // .in_set(OxrPreUpdateSet::UpdateCriticalComponents), - ); + app.add_systems(XrSessionCreated, set_primary_ref_space); + app.add_systems(XrSessionExiting, cleanup); + app.sub_app_mut(RenderApp) + .add_systems(XrSessionExiting, cleanup); + } +} + +fn cleanup(mut cmds: Commands, query: Query>) { + cmds.remove_resource::(); + for e in &query { + cmds.entity(e).remove::(); } } fn set_primary_ref_space( session: Res, - space_type: Res, + space_type: Res, mut cmds: Commands, ) { match session.create_reference_space(space_type.0, openxr::Posef::IDENTITY) { diff --git a/crates/bevy_openxr/src/openxr/render.rs b/crates/bevy_openxr/src/openxr/render.rs index f4baea6..89dd188 100644 --- a/crates/bevy_openxr/src/openxr/render.rs +++ b/crates/bevy_openxr/src/openxr/render.rs @@ -10,13 +10,18 @@ use bevy::{ Render, RenderApp, RenderSet, }, tasks::ComputeTaskPool, + transform::TransformSystem, +}; +use bevy_xr::{ + camera::{XrCamera, XrCameraBundle, XrProjection}, + session::{session_running, XrSessionExiting}, }; -use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection}; use openxr::ViewStateFlags; use crate::{ init::{session_started, OxrHandleEvents, OxrLast, OxrTrackingRoot}, layer_builder::ProjectionLayer, + session::OxrSession, }; use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*}; @@ -73,6 +78,7 @@ impl Plugin for OxrRenderPlugin { .chain() .after(OxrHandleEvents), ) + .add_systems(XrSessionExiting, clean_views) .init_resource::(); let render_app = app.sub_app_mut(RenderApp); @@ -90,6 +96,8 @@ impl Plugin for OxrRenderPlugin { .after(RenderSet::Render) .before(RenderSet::Cleanup), ) + .add_systems(Last, wait_frame.run_if(session_started)); + app.sub_app_mut(RenderApp) .add_systems( Render, ( @@ -111,6 +119,13 @@ impl Plugin for OxrRenderPlugin { .in_set(OxrRenderEnd), ) .insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)])); + // .add_systems( + // XrSessionExiting, + // ( + // |mut cmds: Commands| cmds.remove_resource::(), + // clean_views, + // ), + // ); // app.add_systems( // PreUpdate, @@ -181,6 +196,19 @@ fn update_rendering(app_world: &mut World, _sub_app: &mut App) { pub const XR_TEXTURE_INDEX: u32 = 3383858418; +pub fn clean_views( + mut manual_texture_views: ResMut, + mut commands: Commands, + cam_query: Query<(Entity, &XrCamera)>, +) { + for (e, cam) in &cam_query { + manual_texture_views.remove(&ManualTextureViewHandle(XR_TEXTURE_INDEX + cam.0)); + commands.entity(e).despawn_recursive(); + } +} + +// 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. pub fn init_views( graphics_info: Res, mut manual_texture_views: ResMut, diff --git a/crates/bevy_openxr/src/openxr/resources.rs b/crates/bevy_openxr/src/openxr/resources.rs index 4f15594..76de551 100644 --- a/crates/bevy_openxr/src/openxr/resources.rs +++ b/crates/bevy_openxr/src/openxr/resources.rs @@ -2,11 +2,11 @@ use std::sync::Arc; use bevy::prelude::*; use bevy::render::extract_resource::ExtractResource; -use openxr::AnyGraphics; use crate::error::OxrError; use crate::graphics::*; use crate::layer_builder::{CompositionLayer, LayerProvider}; +use crate::session::OxrSession; use crate::types::*; /// Wrapper around an [`Entry`](openxr::Entry) with some methods overridden to use bevy types. @@ -146,96 +146,6 @@ impl OxrInstance { } } -/// Graphics agnostic wrapper around [openxr::Session]. -/// -/// See [`openxr::Session`] for other available methods. -#[derive(Resource, Deref, Clone)] -pub struct OxrSession( - /// A session handle with [`AnyGraphics`]. - /// Having this here allows the majority of [`Session`](openxr::Session)'s methods to work without having to rewrite them. - #[deref] - pub(crate) openxr::Session, - /// A [`GraphicsWrap`] with [`openxr::Session`] as the inner type. - /// This is so that we can still operate on functions that don't take [`AnyGraphics`] as the generic. - pub(crate) GraphicsWrap, -); - -impl GraphicsType for OxrSession { - type Inner = openxr::Session; -} - -impl From> for OxrSession { - fn from(session: openxr::Session) -> Self { - Self::from_inner(session) - } -} - -impl OxrSession { - /// Creates a new [`OxrSession`] from an [`openxr::Session`]. - /// In the majority of cases, you should use [`create_session`](OxrInstance::create_session) instead. - pub fn from_inner(session: openxr::Session) -> Self { - Self(session.clone().into_any_graphics(), G::wrap(session)) - } - - /// Returns [`GraphicsWrap`] with [`openxr::Session`] as the inner type. - /// - /// This can be useful if you need access to the original [`openxr::Session`] with the graphics API still specified. - pub fn typed_session(&self) -> &GraphicsWrap { - &self.1 - } - - /// Enumerates all available swapchain formats and converts them to wgpu's [`TextureFormat`](wgpu::TextureFormat). - /// - /// Calls [`enumerate_swapchain_formats`](openxr::Session::enumerate_swapchain_formats) internally. - pub fn enumerate_swapchain_formats(&self) -> Result> { - graphics_match!( - &self.1; - session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::into_wgpu_format).collect()) - ) - } - - /// Creates an [OxrSwapchain]. - /// - /// Calls [`create_swapchain`](openxr::Session::create_swapchain) internally. - pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result { - Ok(OxrSwapchain(graphics_match!( - &self.1; - session => session.create_swapchain(&info.try_into()?)? => OxrSwapchain - ))) - } - - /// Creates a passthrough. - /// - /// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough). - /// - /// Calls [`create_passthrough`](openxr::Session::create_passthrough) internally. - pub fn create_passthrough(&self, flags: openxr::PassthroughFlagsFB) -> Result { - Ok(OxrPassthrough( - graphics_match! { - &self.1; - session => session.create_passthrough(flags)? - }, - flags, - )) - } - - /// Creates a passthrough layer that can be used to make a [`CompositionLayerPassthrough`](crate::layer_builder::CompositionLayerPassthrough) for frame submission. - /// - /// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough). - /// - /// Calls [`create_passthrough_layer`](openxr::Session::create_passthrough_layer) internally. - pub fn create_passthrough_layer( - &self, - passthrough: &OxrPassthrough, - purpose: openxr::PassthroughLayerPurposeFB, - ) -> Result { - Ok(OxrPassthroughLayer(graphics_match! { - &self.1; - session => session.create_passthrough_layer(&passthrough.0, passthrough.1, purpose)? - })) - } -} - /// Graphics agnostic wrapper around [openxr::FrameStream] #[derive(Resource)] pub struct OxrFrameStream(pub GraphicsWrap); @@ -365,15 +275,15 @@ impl OxrSwapchain { images.push(Api::to_wgpu_img(image, device, format, resolution)?); } } - Ok(OxrSwapchainImages(images.into())) + Ok(OxrSwapchainImages(images.leak())) } ) } } /// Stores the generated swapchain images. -#[derive(Debug, Deref, Resource, ExtractResource, Clone)] -pub struct OxrSwapchainImages(pub Arc>); +#[derive(Debug, Deref, Resource, Clone, Copy, ExtractResource)] +pub struct OxrSwapchainImages(pub &'static [wgpu::Texture]); /// Thread safe wrapper around [openxr::Space] representing the stage. // #[derive(Deref, Clone, Resource)] @@ -396,7 +306,7 @@ pub struct OxrSystemId(pub openxr::SystemId); pub struct OxrPassthrough( #[deref] pub openxr::Passthrough, /// The flags are stored here so that they don't need to be passed in again when creating an [`OxrPassthroughLayer`]. - openxr::PassthroughFlagsFB, + pub openxr::PassthroughFlagsFB, ); impl OxrPassthrough { diff --git a/crates/bevy_openxr/src/openxr/session.rs b/crates/bevy_openxr/src/openxr/session.rs new file mode 100644 index 0000000..522e552 --- /dev/null +++ b/crates/bevy_openxr/src/openxr/session.rs @@ -0,0 +1,171 @@ +use crate::init::{OxrHandleEvents, OxrLast}; +use crate::resources::{ + OxrCleanupSession, OxrPassthrough, OxrPassthroughLayer, OxrSessionStarted, OxrSwapchain, +}; +use crate::types::{Result, SwapchainCreateInfo}; +use bevy::ecs::event::ManualEventReader; +use bevy::ecs::system::RunSystemOnce; +use bevy::prelude::*; +use bevy::render::RenderApp; +use bevy_xr::session::{status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus}; +use openxr::AnyGraphics; + +use crate::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap}; + +#[derive(Event, Clone, Copy, PartialEq, Eq, Hash)] +pub enum OxrSessionStatusEvent { + Created, + AboutToBeDestroyed, +} + +pub struct OxrSessionPlugin; + +impl Plugin for OxrSessionPlugin { + fn build(&self, app: &mut App) { + app.add_event::(); + app.add_systems(OxrLast, run_session_status_schedules.after(OxrHandleEvents)); + app.add_systems(XrSessionExiting, clean_session); + app.sub_app_mut(RenderApp) + .add_systems(XrSessionExiting, |mut cmds: Commands| { + cmds.remove_resource::(); + }); + app.add_systems( + PreUpdate, + handle_stopping_state.run_if(status_changed_to(XrStatus::Stopping)), + ); + } +} + +fn handle_stopping_state(session: Res, mut session_started: ResMut) { + session.end().expect("Failed to end session"); + session_started.0 = false; +} + +fn clean_session(world: &mut World) { + world.insert_resource(OxrCleanupSession(true)); + // It should be impossible to call this if the session is Unavailable + *world.get_resource_mut::().unwrap() = XrStatus::Available; +} + +#[derive(Resource, Default)] +struct SessionStatusReader(ManualEventReader); + +fn run_session_status_schedules(world: &mut World) { + let mut reader = world + .remove_resource::() + .unwrap_or_default(); + + let events = reader + .0 + .read( + world + .get_resource::>() + .unwrap(), + ) + .copied() + .collect::>(); + for e in events.iter() { + match e { + OxrSessionStatusEvent::Created => { + world.run_schedule(XrSessionCreated); + world.run_system_once(apply_deferred); + } + OxrSessionStatusEvent::AboutToBeDestroyed => { + world.run_schedule(XrSessionExiting); + world.run_system_once(apply_deferred); + world.remove_resource::(); + } + } + } + world.insert_resource(reader); +} + +/// Graphics agnostic wrapper around [openxr::Session]. +/// +/// See [`openxr::Session`] for other available methods. +#[derive(Resource, Deref, Clone)] +pub struct OxrSession( + /// A session handle with [`AnyGraphics`]. + /// Having this here allows the majority of [`Session`](openxr::Session)'s methods to work without having to rewrite them. + #[deref] + pub(crate) openxr::Session, + /// A [`GraphicsWrap`] with [`openxr::Session`] as the inner type. + /// This is so that we can still operate on functions that don't take [`AnyGraphics`] as the generic. + pub(crate) GraphicsWrap, +); + +impl GraphicsType for OxrSession { + type Inner = openxr::Session; +} + +impl From> for OxrSession { + fn from(session: openxr::Session) -> Self { + Self::from_inner(session) + } +} + +impl OxrSession { + /// Creates a new [`OxrSession`] from an [`openxr::Session`]. + /// In the majority of cases, you should use [`create_session`](OxrInstance::create_session) instead. + pub fn from_inner(session: openxr::Session) -> Self { + Self(session.clone().into_any_graphics(), G::wrap(session)) + } + + /// Returns [`GraphicsWrap`] with [`openxr::Session`] as the inner type. + /// + /// This can be useful if you need access to the original [`openxr::Session`] with the graphics API still specified. + pub fn typed_session(&self) -> &GraphicsWrap { + &self.1 + } + + /// Enumerates all available swapchain formats and converts them to wgpu's [`TextureFormat`](wgpu::TextureFormat). + /// + /// Calls [`enumerate_swapchain_formats`](openxr::Session::enumerate_swapchain_formats) internally. + pub fn enumerate_swapchain_formats(&self) -> Result> { + graphics_match!( + &self.1; + session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::into_wgpu_format).collect()) + ) + } + + /// Creates an [OxrSwapchain]. + /// + /// Calls [`create_swapchain`](openxr::Session::create_swapchain) internally. + pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result { + Ok(OxrSwapchain(graphics_match!( + &self.1; + session => session.create_swapchain(&info.try_into()?)? => OxrSwapchain + ))) + } + + /// Creates a passthrough. + /// + /// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough). + /// + /// Calls [`create_passthrough`](openxr::Session::create_passthrough) internally. + pub fn create_passthrough(&self, flags: openxr::PassthroughFlagsFB) -> Result { + Ok(OxrPassthrough( + graphics_match! { + &self.1; + session => session.create_passthrough(flags)? + }, + flags, + )) + } + + /// Creates a passthrough layer that can be used to make a [`CompositionLayerPassthrough`](crate::layer_builder::CompositionLayerPassthrough) for frame submission. + /// + /// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough). + /// + /// Calls [`create_passthrough_layer`](openxr::Session::create_passthrough_layer) internally. + pub fn create_passthrough_layer( + &self, + passthrough: &OxrPassthrough, + purpose: openxr::PassthroughLayerPurposeFB, + ) -> Result { + Ok(OxrPassthroughLayer(graphics_match! { + &self.1; + session => session.create_passthrough_layer(&passthrough.0, passthrough.1, purpose)? + })) + } +} diff --git a/crates/bevy_xr/src/session.rs b/crates/bevy_xr/src/session.rs index 0afcb00..09002a6 100644 --- a/crates/bevy_xr/src/session.rs +++ b/crates/bevy_xr/src/session.rs @@ -1,9 +1,15 @@ -use bevy::{prelude::*, render::extract_resource::ExtractResource}; +use bevy::{ + ecs::schedule::ScheduleLabel, prelude::*, render::extract_resource::ExtractResource, + render::RenderApp, +}; pub struct XrSessionPlugin; impl Plugin for XrSessionPlugin { fn build(&self, app: &mut App) { + app.init_resource::(); + app.init_schedule(XrSessionCreated); + app.init_schedule(XrSessionExiting); app.add_event::() .add_event::() .add_event::() @@ -14,8 +20,31 @@ impl Plugin for XrSessionPlugin { handle_session.run_if(resource_exists::), ); } + fn finish(&self, app: &mut App) { + // This is in finnish because we need the RenderPlugin to already be added. + app.get_sub_app_mut(RenderApp) + .unwrap() + .init_schedule(XrSessionExiting); + } } +/// Automatically starts session when it's available, gets set to false after the start event was +/// sent +#[derive(Clone, Copy, Resource, Deref, DerefMut)] +pub struct XrCreateSessionWhenAvailabe(pub bool); + +impl Default for XrCreateSessionWhenAvailabe { + fn default() -> Self { + XrCreateSessionWhenAvailabe(true) + } +} + +#[derive(ScheduleLabel, Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct XrSessionCreated; + +#[derive(ScheduleLabel, Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct XrSessionExiting; + #[derive(Event, Clone, Copy, Deref)] pub struct XrStatusChanged(pub XrStatus); @@ -43,14 +72,18 @@ pub fn handle_session( mut previous_status: Local>, mut create_session: EventWriter, mut begin_session: EventWriter, - mut end_session: EventWriter, + // mut end_session: EventWriter, mut destroy_session: EventWriter, + mut should_start_session: ResMut, ) { if *previous_status != Some(*current_status) { match *current_status { XrStatus::Unavailable => {} XrStatus::Available => { - create_session.send_default(); + if **should_start_session { + create_session.send_default(); + **should_start_session = false; + } } XrStatus::Idle => {} XrStatus::Ready => { @@ -58,7 +91,7 @@ pub fn handle_session( } XrStatus::Running => {} XrStatus::Stopping => { - end_session.send_default(); + // end_session.send_default(); } XrStatus::Exiting => { destroy_session.send_default(); diff --git a/crates/bevy_xr_utils/src/xr_utils_actions.rs b/crates/bevy_xr_utils/src/xr_utils_actions.rs index 4a0cef5..ded0b19 100644 --- a/crates/bevy_xr_utils/src/xr_utils_actions.rs +++ b/crates/bevy_xr_utils/src/xr_utils_actions.rs @@ -1,9 +1,9 @@ //! This plugin and module are here to ease the creation of actions withing openxr //! The general idea is any plugin can create entities in startup before XRUtilsActionSystemSet::CreateEvents //! this plugin will then create the neccessary actions sets, actions, and bindings and get them ready for use. -//! +//! //! example creating actions -//! +//! //! //create a set //! let set = commands //! .spawn(( @@ -39,10 +39,10 @@ //! //TODO look into a better system //! commands.entity(action).add_child(binding); //! commands.entity(set).add_child(action); -//! +//! //! then you can read the action states after XRUtilsActionSystemSet::SyncActionStates //! for example -//! +//! //! fn read_action_with_marker_component( //! mut action_query: Query<&XRUtilsActionState, With>, //! ) { @@ -51,13 +51,12 @@ //! info!("action state is: {:?}", state); //! } //! } -//! -//! +//! +//! use bevy::prelude::*; use bevy_openxr::{ - action_binding::OxrSuggestActionBinding, - action_set_attaching::OxrAttachActionSet, - resources::{OxrInstance, OxrSession}, + action_binding::OxrSuggestActionBinding, action_set_attaching::OxrAttachActionSet, + resources::OxrInstance, session::OxrSession, }; use openxr::{ActiveActionSet, Path, Vector2f}; use std::borrow::Cow; @@ -65,7 +64,10 @@ use std::borrow::Cow; pub struct XRUtilsActionsPlugin; impl Plugin for XRUtilsActionsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, create_openxr_events.in_set(XRUtilsActionSystemSet::CreateEvents)); + app.add_systems( + Startup, + create_openxr_events.in_set(XRUtilsActionSystemSet::CreateEvents), + ); app.add_systems(Update, sync_active_action_sets); app.add_systems( Update,