diff --git a/crates/bevy_openxr/examples/sessions.rs b/crates/bevy_openxr/examples/sessions.rs index b85a2d9..353293f 100644 --- a/crates/bevy_openxr/examples/sessions.rs +++ b/crates/bevy_openxr/examples/sessions.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use bevy_openxr::add_xr_plugins; -use bevy_xr::session::{XrSharedStatus, XrStatus}; +use bevy_xr::session::{ XrStatus}; fn main() { App::new() @@ -20,7 +20,7 @@ fn handle_input( mut destroy: EventWriter, mut begin: EventWriter, mut create: EventWriter, - state: Res, + state: Res, ) { if keys.just_pressed(KeyCode::KeyE) { info!("sending end"); @@ -39,7 +39,7 @@ fn handle_input( create.send_default(); } if keys.just_pressed(KeyCode::KeyI) { - info!("current state: {:?}", state.get()); + info!("current state: {:?}", *state); } } diff --git a/crates/bevy_openxr/src/openxr/action_binding.rs b/crates/bevy_openxr/src/openxr/action_binding.rs index 3240e84..2d5af6d 100644 --- a/crates/bevy_openxr/src/openxr/action_binding.rs +++ b/crates/bevy_openxr/src/openxr/action_binding.rs @@ -7,7 +7,7 @@ use bevy::prelude::*; use bevy::utils::HashMap; use openxr::sys::ActionSuggestedBinding; -use crate::{init::OxrPreUpdateSet, resources::OxrInstance, session::OxrSessionStatusEvent}; +use crate::{resources::OxrInstance, session::OxrSessionStatusEvent}; impl Plugin for OxrActionBindingPlugin { fn build(&self, app: &mut App) { @@ -15,13 +15,13 @@ impl Plugin for OxrActionBindingPlugin { app.add_event::(); app.add_systems( PostUpdate, - run_action_binding_sugestion - .run_if(|mut session_state: EventReader| { + run_action_binding_sugestion.run_if( + |mut session_state: EventReader| { session_state .read() .any(|s| *s == OxrSessionStatusEvent::Created) - }) - .after(OxrPreUpdateSet::HandleEvents), + }, + ), ); } } diff --git a/crates/bevy_openxr/src/openxr/action_set_syncing.rs b/crates/bevy_openxr/src/openxr/action_set_syncing.rs index 6b04224..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::{init::OxrPreUpdateSet, session::OxrSession}; +use crate::session::OxrSession; use bevy::prelude::*; use bevy_xr::session::session_running; @@ -7,9 +7,7 @@ impl Plugin for OxrActionSyncingPlugin { app.add_event::(); app.add_systems( PreUpdate, - sync_sets - .run_if(session_running) - .in_set(OxrPreUpdateSet::SyncActions), + sync_sets.run_if(session_running), // .in_set(OxrPreUpdateSet::SyncActions), ); } } diff --git a/crates/bevy_openxr/src/openxr/features/handtracking.rs b/crates/bevy_openxr/src/openxr/features/handtracking.rs index f784c3b..6232ab1 100644 --- a/crates/bevy_openxr/src/openxr/features/handtracking.rs +++ b/crates/bevy_openxr/src/openxr/features/handtracking.rs @@ -6,10 +6,11 @@ use bevy_xr::{ }; use openxr::SpaceLocationFlags; +use crate::resources::Pipelined; use crate::{ init::OxrTrackingRoot, reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace}, - resources::OxrTime, + resources::OxrFrameState, session::OxrSession, }; @@ -130,18 +131,29 @@ pub struct OxrHandTracker(pub openxr::HandTracker); fn locate_hands( default_ref_space: Res, - time: Res, + frame_state: Res, tracker_query: Query<( &OxrHandTracker, Option<&OxrReferenceSpace>, &OxrHandBoneEntities, )>, mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>, + pipelined: Option>, ) { for (tracker, ref_space, hand_entities) in &tracker_query { let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0); // relate_hand_joints also provides velocities - let joints = match ref_space.locate_hand_joints(tracker, **time) { + let joints = match ref_space.locate_hand_joints( + tracker, + if pipelined.is_some() { + openxr::Time::from_nanos( + frame_state.predicted_display_time.as_nanos() + + frame_state.predicted_display_period.as_nanos(), + ) + } else { + frame_state.predicted_display_time + }, + ) { Ok(Some(v)) => v, Ok(None) => continue, Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index 32d1c84..b22a2c1 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -1,3 +1,5 @@ +use bevy::app::MainScheduleOrder; +use bevy::ecs::schedule::ScheduleLabel; use bevy::ecs::system::RunSystemOnce; use bevy::prelude::*; use bevy::render::extract_resource::ExtractResourcePlugin; @@ -15,8 +17,6 @@ use bevy::render::RenderSet; use bevy::transform::TransformSystem; use bevy::winit::UpdateMode; use bevy::winit::WinitSettings; -use bevy_xr::session::handle_session; -use bevy_xr::session::session_available; use bevy_xr::session::session_running; use bevy_xr::session::status_equals; use bevy_xr::session::BeginXrSession; @@ -24,31 +24,31 @@ use bevy_xr::session::CreateXrSession; use bevy_xr::session::DestroyXrSession; use bevy_xr::session::EndXrSession; use bevy_xr::session::XrSessionExiting; -use bevy_xr::session::XrSharedStatus; use bevy_xr::session::XrStatus; use bevy_xr::session::XrStatusChanged; use crate::error::OxrError; use crate::graphics::*; -use crate::reference_space::OxrPrimaryReferenceSpace; use crate::resources::*; use crate::session::OxrSession; use crate::session::OxrSessionStatusEvent; use crate::types::*; pub fn session_started(started: Option>) -> bool { - started.is_some_and(|started| started.get()) + started.is_some_and(|started| started.0) } -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] -pub enum OxrPreUpdateSet { - PollEvents, - HandleEvents, - UpdateCriticalComponents, - UpdateNonCriticalComponents, - SyncActions, +pub fn should_render(frame_state: Option>) -> bool { + frame_state.is_some_and(|frame_state| frame_state.should_render) } +/// TODO!() better name pls +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OxrLast; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] +pub struct OxrHandleEvents; + pub struct OxrInitPlugin { /// Information about the app this is being used to build. pub app_info: AppInfo, @@ -97,8 +97,6 @@ impl Plugin for OxrInitPlugin { WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance), session_create_info, )) => { - let status = XrSharedStatus::new(XrStatus::Available); - app.add_plugins(( RenderPlugin { render_creation: RenderCreation::manual( @@ -111,30 +109,30 @@ impl Plugin for OxrInitPlugin { synchronous_pipeline_compilation: self.synchronous_pipeline_compilation, }, ExtractResourcePlugin::::default(), - ExtractResourcePlugin::::default(), - ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), )) - .add_systems(First, reset_per_frame_resources) + .init_schedule(OxrLast) .add_systems( - PreUpdate, + OxrLast, ( - poll_events - .run_if(session_available) - .in_set(OxrPreUpdateSet::PollEvents), - ( - (create_xr_session, apply_deferred) - .chain() - .run_if(on_event::()) - .run_if(status_equals(XrStatus::Available)), - begin_xr_session - .run_if(on_event::()) - .run_if(status_equals(XrStatus::Ready)), - end_xr_session - .run_if(on_event::()) - .run_if(status_equals(XrStatus::Running)), - ) - .in_set(OxrPreUpdateSet::HandleEvents), - ), + reset_per_frame_resources, + poll_events, + create_xr_session + .run_if(on_event::()) + .run_if(status_equals(XrStatus::Available)), + begin_xr_session + .run_if(on_event::()) + .run_if(status_equals(XrStatus::Ready)), + end_xr_session + .run_if(on_event::()) + .run_if(status_equals(XrStatus::Running)), + destroy_xr_session + .run_if(on_event::()) + .run_if(status_equals(XrStatus::Exiting)), + ) + .chain() + .in_set(OxrHandleEvents), ) .add_systems(XrSessionExiting, destroy_xr_session) .add_systems( @@ -143,25 +141,25 @@ impl Plugin for OxrInitPlugin { ) .insert_resource(instance.clone()) .insert_resource(system_id) - .insert_resource(status.clone()) + .insert_resource(XrStatus::Available) .insert_resource(WinitSettings { focused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous, }) .init_resource::() .init_resource::() - .insert_non_send_resource(session_create_info); + .insert_non_send_resource(session_create_info) + .configure_sets(OxrLast, OxrHandleEvents); + + app.world + .resource_mut::() + .insert_after(Last, OxrLast); app.world .spawn((TransformBundle::default(), OxrTrackingRoot)); let render_app = app.sub_app_mut(RenderApp); render_app - .insert_resource(instance) - .insert_resource(system_id) - .insert_resource(status) - .init_resource::() - .init_resource::() .add_systems( Render, destroy_xr_session_render @@ -171,39 +169,20 @@ impl Plugin for OxrInitPlugin { .add_systems( ExtractSchedule, transfer_xr_resources.run_if(not(session_running)), - ); + ) + .insert_resource(instance) + .insert_resource(system_id) + .init_resource::() + .init_resource::(); } Err(e) => { error!("Failed to initialize openxr: {e}"); - let status = XrSharedStatus::new(XrStatus::Unavailable); - app.add_plugins(RenderPlugin::default()) - .insert_resource(status.clone()); - - let render_app = app.sub_app_mut(RenderApp); - - render_app.insert_resource(status); + .insert_resource(XrStatus::Unavailable); } }; - app.configure_sets( - PreUpdate, - ( - OxrPreUpdateSet::PollEvents.before(handle_session), - OxrPreUpdateSet::HandleEvents.after(handle_session), - OxrPreUpdateSet::UpdateCriticalComponents, - OxrPreUpdateSet::UpdateNonCriticalComponents, - ) - .chain(), - ); - - let session_started = OxrSessionStarted::default(); - - app.insert_resource(session_started.clone()); - - let render_app = app.sub_app_mut(RenderApp); - - render_app.insert_resource(session_started); + app.insert_resource(OxrSessionStarted(false)); } } @@ -216,17 +195,12 @@ pub fn update_root_transform( root_transform.0 = *transform; } -fn xr_entry() -> Result { - #[cfg(windows)] - let entry = openxr::Entry::linked(); - #[cfg(not(windows))] - let entry = unsafe { openxr::Entry::load()? }; - Ok(OxrEntry(entry)) -} - impl OxrInitPlugin { fn init_xr(&self) -> Result<(OxrInstance, OxrSystemId, WgpuGraphics, SessionConfigInfo)> { - let entry = xr_entry()?; + #[cfg(windows)] + let entry = OxrEntry(openxr::Entry::linked()); + #[cfg(not(windows))] + let entry = OxrEntry(unsafe { openxr::Entry::load()? }); #[cfg(target_os = "android")] entry.initialize_android_loader()?; @@ -437,6 +411,20 @@ fn init_xr_session( )) } +pub fn begin_xr_session(session: Res, mut session_started: ResMut) { + let _span = info_span!("xr_begin_session"); + session + .begin(openxr::ViewConfigurationType::PRIMARY_STEREO) + .expect("Failed to begin session"); + session_started.0 = true; +} + +pub fn end_xr_session(session: Res, mut session_started: ResMut) { + let _span = info_span!("xr_end_session"); + session.request_exit().expect("Failed to end session"); + session_started.0 = false; +} + /// This is used solely to transport resources from the main world to the render world. #[derive(Resource)] struct OxrRenderResources { @@ -477,21 +465,6 @@ pub fn create_xr_session( } } -pub fn begin_xr_session(session: Res, session_started: Res) { - let _span = info_span!("xr_begin_session"); - session - .begin(openxr::ViewConfigurationType::PRIMARY_STEREO) - .expect("Failed to begin session"); - session_started.set(true); -} - -pub fn end_xr_session(session: Res, session_started: Res) { - let _span = info_span!("xr_end_session"); - session - .request_exit() - .expect("Failed to request session exit"); -} - /// 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 { @@ -515,7 +488,7 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut, - status: Res, + mut status: ResMut, mut changed_event: EventWriter, mut session_status_events: EventWriter, ) { @@ -536,7 +509,7 @@ pub fn poll_events( let new_status = match state { SessionState::IDLE => { - if status.get() == XrStatus::Available { + if *status == XrStatus::Available { session_status_events.send(OxrSessionStatusEvent::Created); info!("sending create info"); } @@ -555,7 +528,7 @@ pub fn poll_events( _ => unreachable!(), }; changed_event.send(XrStatusChanged(new_status)); - status.set(new_status); + *status = new_status; } InstanceLossPending(_) => {} EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()), @@ -579,7 +552,10 @@ 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)); + info!("Render App destroy"); } diff --git a/crates/bevy_openxr/src/openxr/layer_builder.rs b/crates/bevy_openxr/src/openxr/layer_builder.rs index d3601ae..1042c67 100644 --- a/crates/bevy_openxr/src/openxr/layer_builder.rs +++ b/crates/bevy_openxr/src/openxr/layer_builder.rs @@ -8,7 +8,7 @@ use crate::reference_space::OxrPrimaryReferenceSpace; use crate::resources::*; pub trait LayerProvider { - fn get<'a>(&'a self, world: &'a World) -> Box; + fn get<'a>(&'a self, world: &'a World) -> Option>; } pub struct ProjectionLayer; @@ -16,11 +16,11 @@ pub struct ProjectionLayer; pub struct PassthroughLayer; impl LayerProvider for ProjectionLayer { - fn get<'a>(&self, world: &'a World) -> Box + 'a> { - let stage = world.resource::(); - let openxr_views = world.resource::(); - let swapchain = world.resource::(); - let graphics_info = world.resource::(); + fn get<'a>(&self, world: &'a World) -> Option + 'a>> { + let stage = world.get_resource::()?; + let openxr_views = world.get_resource::()?; + let swapchain = world.get_resource::()?; + let graphics_info = world.get_resource::()?; let rect = openxr::Rect2Di { offset: openxr::Offset2Di { x: 0, y: 0 }, extent: openxr::Extent2Di { @@ -29,7 +29,11 @@ impl LayerProvider for ProjectionLayer { }, }; - Box::new( + if openxr_views.len() < 2 { + return None; + } + + Some(Box::new( CompositionLayerProjection::new() .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .space(&stage) @@ -53,17 +57,17 @@ impl LayerProvider for ProjectionLayer { .image_rect(rect), ), ]), - ) + )) } } impl LayerProvider for PassthroughLayer { - fn get<'a>(&'a self, world: &'a World) -> Box { - Box::new( + fn get<'a>(&'a self, world: &'a World) -> Option> { + Some(Box::new( CompositionLayerPassthrough::new() - .layer_handle(world.resource::()) + .layer_handle(world.get_resource::()?) .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA), - ) + )) } } diff --git a/crates/bevy_openxr/src/openxr/reference_space.rs b/crates/bevy_openxr/src/openxr/reference_space.rs index 96a4d61..a3001ec 100644 --- a/crates/bevy_openxr/src/openxr/reference_space.rs +++ b/crates/bevy_openxr/src/openxr/reference_space.rs @@ -7,9 +7,9 @@ use bevy::{ RenderApp, }, }; -use bevy_xr::session::{status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus}; +use bevy_xr::session::{XrSessionCreated, XrSessionExiting}; -use crate::{init::OxrPreUpdateSet, session::OxrSession}; +use crate::session::OxrSession; pub struct OxrReferenceSpacePlugin { pub default_primary_ref_space: openxr::ReferenceSpaceType, diff --git a/crates/bevy_openxr/src/openxr/render.rs b/crates/bevy_openxr/src/openxr/render.rs index d7a07e7..0fd0a20 100644 --- a/crates/bevy_openxr/src/openxr/render.rs +++ b/crates/bevy_openxr/src/openxr/render.rs @@ -4,7 +4,7 @@ use bevy::{ render::{ camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget}, extract_resource::ExtractResourcePlugin, - renderer::render_system, + pipelined_rendering::PipelinedRenderingPlugin, view::ExtractedView, Render, RenderApp, RenderSet, }, @@ -17,57 +17,144 @@ use bevy_xr::{ use openxr::ViewStateFlags; use crate::{ - init::{session_started, OxrPreUpdateSet, OxrTrackingRoot}, - layer_builder::ProjectionLayer, session::OxrSession, + init::{session_started, OxrHandleEvents, OxrLast, OxrTrackingRoot}, + layer_builder::ProjectionLayer, + session::OxrSession, }; use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*}; +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] +pub struct OxrRenderBegin; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] +pub struct OxrRenderEnd; + pub struct OxrRenderPlugin; impl Plugin for OxrRenderPlugin { fn build(&self, app: &mut App) { - app.add_plugins((ExtractResourcePlugin::::default(),)) - .add_systems( - PreUpdate, - ( - init_views.run_if(resource_added::), - locate_views.run_if(session_running), - update_views.run_if(session_running), - ) - .chain() - .after(OxrPreUpdateSet::UpdateNonCriticalComponents), + if app.is_plugin_added::() { + app.init_resource::(); + } + + app.add_plugins(( + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + ExtractResourcePlugin::::default(), + )) + .add_systems( + PreUpdate, + (locate_views, update_views) + .chain() + // .run_if(should_render) + .run_if(session_started), + ) + .add_systems( + OxrLast, + ( + wait_frame.run_if(session_started), + update_cameras.run_if(session_started), + init_views.run_if(resource_added::), + apply_deferred, ) - .add_systems( - PostUpdate, - (locate_views, update_views) - .chain() - .run_if(session_running) - .before(TransformSystem::TransformPropagate), + .chain() + .after(OxrHandleEvents), + ) + .add_systems(XrSessionExiting, clean_views) + .init_resource::(); + + let render_app = app.sub_app_mut(RenderApp); + render_app + .configure_sets( + Render, + OxrRenderBegin + .after(RenderSet::ExtractCommands) + .before(RenderSet::PrepareAssets) + .before(RenderSet::ManageViews), ) - .add_systems(Last, wait_frame.run_if(session_started)) - .add_systems(XrSessionExiting, clean_views); + .configure_sets( + Render, + OxrRenderEnd + .after(RenderSet::Render) + .before(RenderSet::Cleanup), + ) + .add_systems(Last, wait_frame.run_if(session_started)); app.sub_app_mut(RenderApp) + .add_systems( + Render, + (|q: Query<&XrCamera>| info!("cams render: {}", q.iter().len())) + .in_set(OxrRenderBegin), + ) .add_systems( Render, ( - ( - insert_texture_views, - locate_views.run_if(resource_exists::), - update_views_render_world, - ) - .chain() - .in_set(RenderSet::PrepareAssets), - begin_frame - .before(RenderSet::Queue) - .before(insert_texture_views), - wait_image.in_set(RenderSet::Render).before(render_system), - (release_image, end_frame) - .chain() - .in_set(RenderSet::Cleanup), + begin_frame, + insert_texture_views, + locate_views, + update_views_render_world, + wait_image, ) - .run_if(session_started), + .chain() + .run_if(session_started) + .in_set(OxrRenderBegin), + ) + .add_systems( + Render, + (release_image, end_frame) + .chain() + .run_if(session_started) + .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, + // ( + // init_views.run_if(resource_added::), + // locate_views.run_if(session_running), + // update_views.run_if(session_running), + // ) + // .chain(), // .after(OxrPreUpdateSet::UpdateNonCriticalComponents), + // ) + // .add_systems( + // PostUpdate, + // (locate_views, update_views) + // .chain() + // .run_if(session_running) + // .before(TransformSystem::TransformPropagate), + // ) + // .add_systems(Last, wait_frame.run_if(session_started)); + // app.sub_app_mut(RenderApp) + // .add_systems( + // Render, + // ( + // ( + // insert_texture_views, + // locate_views.run_if(resource_exists::), + // update_views_render_world, + // ) + // .chain() + // .in_set(RenderSet::PrepareAssets), + // begin_frame + // .before(RenderSet::Queue) + // .before(insert_texture_views), + // wait_image.in_set(RenderSet::Render).before(render_system), + // (release_image, end_frame) + // .chain() + // .in_set(RenderSet::Cleanup), + // ) + // .run_if(session_started), + // ) + // .insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)])); } } @@ -81,6 +168,7 @@ pub fn clean_views( for (e, cam) in &cam_query { manual_texture_views.remove(&ManualTextureViewHandle(XR_TEXTURE_INDEX + cam.0)); commands.entity(e).despawn_recursive(); + info!("removing cam") } } @@ -96,7 +184,6 @@ pub fn init_views( let _span = info_span!("xr_init_views"); let temp_tex = swapchain_images.first().unwrap(); // this for loop is to easily add support for quad or mono views in the future. - let mut views = Vec::with_capacity(2); for index in 0..2 { info!("{}", graphics_info.resolution); let view_handle = @@ -127,10 +214,7 @@ pub fn init_views( warn!("Multiple OxrTrackingRoots! this is not allowed"); } } - - views.push(default()); } - commands.insert_resource(OxrViews(views)); } pub fn wait_frame(mut frame_waiter: ResMut, mut commands: Commands) { @@ -138,26 +222,43 @@ pub fn wait_frame(mut frame_waiter: ResMut, mut commands: Comman let state = frame_waiter.wait().expect("Failed to wait frame"); // 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 - commands.insert_resource(OxrTime(state.predicted_display_time)); + commands.insert_resource(OxrFrameState(state)); +} + +pub fn update_cameras( + frame_state: Res, + mut cameras: Query<&mut Camera, With>, +) { + if frame_state.is_changed() { + for mut camera in &mut cameras { + camera.is_active = frame_state.should_render + } + } } pub fn locate_views( session: Res, ref_space: Res, - time: Res, + frame_state: Res, mut openxr_views: ResMut, + pipelined: Option>, ) { - let _span = info_span!("xr_locate_views"); + let time = if pipelined.is_some() { + openxr::Time::from_nanos( + frame_state.predicted_display_time.as_nanos() + + frame_state.predicted_display_period.as_nanos(), + ) + } else { + frame_state.predicted_display_time + }; let (flags, xr_views) = session .locate_views( openxr::ViewConfigurationType::PRIMARY_STEREO, - **time, + time, &ref_space, ) .expect("Failed to locate views"); - if openxr_views.len() != xr_views.len() { - openxr_views.resize(xr_views.len(), default()); - } + match ( flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID, flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID, @@ -165,12 +266,18 @@ pub fn locate_views( (true, true) => *openxr_views = OxrViews(xr_views), (true, false) => { for (i, view) in openxr_views.iter_mut().enumerate() { - view.pose.orientation = xr_views[i].pose.orientation; + let Some(xr_view) = xr_views.get(i) else { + break; + }; + view.pose.orientation = xr_view.pose.orientation; } } (false, true) => { for (i, view) in openxr_views.iter_mut().enumerate() { - view.pose.position = xr_views[i].pose.position; + let Some(xr_view) = xr_views.get(i) else { + break; + }; + view.pose.position = xr_view.pose.position; } } (false, false) => {} @@ -384,64 +491,21 @@ pub fn end_frame(world: &mut World) { } world.resource_scope::(|world, mut frame_stream| { let mut layers = vec![]; - for layer in world.resource::().iter() { - layers.push(layer.get(world)); + let frame_state = world.resource::(); + if frame_state.should_render { + for layer in world.resource::().iter() { + if let Some(layer) = layer.get(world) { + layers.push(layer); + } + } } let layers: Vec<_> = layers.iter().map(Box::as_ref).collect(); frame_stream .end( - **world.resource::(), + frame_state.predicted_display_time, world.resource::().blend_mode, &layers, ) .expect("Failed to end frame"); }); } - -// pub fn end_frame( -// mut frame_stream: ResMut, -// mut swapchain: ResMut, -// stage: Res, -// display_time: Res, -// graphics_info: Res, -// openxr_views: Res, -// ) { -// let _span = info_span!("xr_end_frame"); -// swapchain.release_image().unwrap(); -// let rect = openxr::Rect2Di { -// offset: openxr::Offset2Di { x: 0, y: 0 }, -// extent: openxr::Extent2Di { -// width: graphics_info.resolution.x as _, -// height: graphics_info.resolution.y as _, -// }, -// }; -// frame_stream -// .end( -// **display_time, -// graphics_info.blend_mode, -// &[&CompositionLayerProjection::new() -// .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) -// .space(&stage) -// .views(&[ -// CompositionLayerProjectionView::new() -// .pose(openxr_views.0[0].pose) -// .fov(openxr_views.0[0].fov) -// .sub_image( -// SwapchainSubImage::new() -// .swapchain(&swapchain) -// .image_array_index(0) -// .image_rect(rect), -// ), -// CompositionLayerProjectionView::new() -// .pose(openxr_views.0[1].pose) -// .fov(openxr_views.0[1].fov) -// .sub_image( -// SwapchainSubImage::new() -// .swapchain(&swapchain) -// .image_array_index(1) -// .image_rect(rect), -// ), -// ])], -// ) -// .expect("Failed to end frame"); -// } diff --git a/crates/bevy_openxr/src/openxr/resources.rs b/crates/bevy_openxr/src/openxr/resources.rs index cc143d7..76de551 100644 --- a/crates/bevy_openxr/src/openxr/resources.rs +++ b/crates/bevy_openxr/src/openxr/resources.rs @@ -1,4 +1,3 @@ -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use bevy::prelude::*; @@ -283,7 +282,7 @@ impl OxrSwapchain { } /// Stores the generated swapchain images. -#[derive(Debug, Deref, Resource, Clone, Copy)] +#[derive(Debug, Deref, Resource, Clone, Copy, ExtractResource)] pub struct OxrSwapchainImages(pub &'static [wgpu::Texture]); /// Thread safe wrapper around [openxr::Space] representing the stage. @@ -291,7 +290,7 @@ pub struct OxrSwapchainImages(pub &'static [wgpu::Texture]); // pub struct OxrStage(pub Arc); /// Stores the latest generated [OxrViews] -#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)] +#[derive(Clone, Resource, ExtractResource, Deref, DerefMut, Default)] pub struct OxrViews(pub Vec); /// Wrapper around [openxr::SystemId] to allow it to be stored as a resource. @@ -330,7 +329,7 @@ pub struct OxrPassthroughLayer(pub openxr::PassthroughLayer); pub struct OxrRenderLayers(pub Vec>); /// Resource storing graphics info for the currently running session. -#[derive(Clone, Copy, Resource)] +#[derive(Clone, Copy, Resource, ExtractResource)] pub struct OxrGraphicsInfo { pub blend_mode: EnvironmentBlendMode, pub resolution: UVec2, @@ -350,22 +349,12 @@ pub struct SessionConfigInfo { pub graphics_info: SessionCreateInfo, } -#[derive(Resource, Clone, Default)] -pub struct OxrSessionStarted(Arc); +#[derive(ExtractResource, Resource, Clone, Default)] +pub struct OxrSessionStarted(pub bool); -impl OxrSessionStarted { - pub fn set(&self, val: bool) { - self.0.store(val, Ordering::SeqCst); - } - - pub fn get(&self) -> bool { - self.0.load(Ordering::SeqCst) - } -} - -/// 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 frame state returned from [FrameWaiter::wait_frame](openxr::FrameWaiter::wait) +#[derive(Clone, Deref, DerefMut, Resource, ExtractResource)] +pub struct OxrFrameState(pub openxr::FrameState); /// The root transform's global position for late latching in the render world. #[derive(ExtractResource, Resource, Clone, Copy, Default)] @@ -374,3 +363,7 @@ pub struct OxrRootTransform(pub GlobalTransform); #[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)] /// This is inserted into the world to signify if the session should be cleaned up. pub struct OxrCleanupSession(pub bool); + +/// Instructs systems to add display period +#[derive(Clone, Copy, Default, Resource)] +pub struct Pipelined; diff --git a/crates/bevy_openxr/src/openxr/session.rs b/crates/bevy_openxr/src/openxr/session.rs index d3125a5..bb5dd55 100644 --- a/crates/bevy_openxr/src/openxr/session.rs +++ b/crates/bevy_openxr/src/openxr/session.rs @@ -1,4 +1,4 @@ -use crate::init::OxrPreUpdateSet; +use crate::init::{OxrHandleEvents, OxrLast}; use crate::resources::{ OxrCleanupSession, OxrPassthrough, OxrPassthroughLayer, OxrSessionStarted, OxrSwapchain, }; @@ -7,9 +7,7 @@ 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, XrSharedStatus, XrStatus, -}; +use bevy_xr::session::{status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus}; use openxr::AnyGraphics; use crate::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap}; @@ -25,10 +23,7 @@ pub struct OxrSessionPlugin; impl Plugin for OxrSessionPlugin { fn build(&self, app: &mut App) { app.add_event::(); - app.add_systems( - PreUpdate, - run_session_status_schedules.in_set(OxrPreUpdateSet::HandleEvents), - ); + 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| { @@ -41,18 +36,15 @@ impl Plugin for OxrSessionPlugin { } } -fn handle_stopping_state(session: Res, session_started: Res) { +fn handle_stopping_state(session: Res, mut session_started: ResMut) { session.end().expect("Failed to end session"); - session_started.set(false); + 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::() - .unwrap() - .set(XrStatus::Available); + *world.get_resource_mut::().unwrap() = XrStatus::Available; } #[derive(Resource, Default)] @@ -82,6 +74,7 @@ fn run_session_status_schedules(world: &mut World) { world.run_schedule(XrSessionExiting); world.run_system_once(apply_deferred); world.remove_resource::(); + info!("Main App destroy"); } } } diff --git a/crates/bevy_xr/src/session.rs b/crates/bevy_xr/src/session.rs index e70b835..09002a6 100644 --- a/crates/bevy_xr/src/session.rs +++ b/crates/bevy_xr/src/session.rs @@ -1,6 +1,7 @@ -use std::sync::{Arc, RwLock}; - -use bevy::{ecs::schedule::ScheduleLabel, prelude::*, render::RenderApp}; +use bevy::{ + ecs::schedule::ScheduleLabel, prelude::*, render::extract_resource::ExtractResource, + render::RenderApp, +}; pub struct XrSessionPlugin; @@ -16,7 +17,7 @@ impl Plugin for XrSessionPlugin { .add_event::() .add_systems( PreUpdate, - handle_session.run_if(resource_exists::), + handle_session.run_if(resource_exists::), ); } fn finish(&self, app: &mut App) { @@ -47,24 +48,7 @@ pub struct XrSessionExiting; #[derive(Event, Clone, Copy, Deref)] pub struct XrStatusChanged(pub XrStatus); -#[derive(Resource, Clone)] -pub struct XrSharedStatus(Arc>); - -impl XrSharedStatus { - pub fn new(status: XrStatus) -> Self { - Self(Arc::new(RwLock::new(status))) - } - - pub fn get(&self) -> XrStatus { - *self.0.read().unwrap() - } - - pub fn set(&self, status: XrStatus) { - *self.0.write().unwrap() = status; - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)] #[repr(u8)] pub enum XrStatus { /// An XR session is not available here @@ -84,7 +68,7 @@ pub enum XrStatus { } pub fn handle_session( - status: Res, + current_status: Res, mut previous_status: Local>, mut create_session: EventWriter, mut begin_session: EventWriter, @@ -92,9 +76,8 @@ pub fn handle_session( mut destroy_session: EventWriter, mut should_start_session: ResMut, ) { - let current_status = status.get(); - if *previous_status != Some(current_status) { - match current_status { + if *previous_status != Some(*current_status) { + match *current_status { XrStatus::Unavailable => {} XrStatus::Available => { if **should_start_session { @@ -115,7 +98,7 @@ pub fn handle_session( } } } - *previous_status = Some(current_status); + *previous_status = Some(*current_status); } /// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`]. @@ -128,29 +111,23 @@ pub fn status_changed_to( } /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrStatus`] exists and isn't [`Unavailable`](XrStatus::Unavailable). -pub fn session_available(status: Option>) -> bool { - status.is_some_and(|s| s.get() != XrStatus::Unavailable) +pub fn session_available(status: Option>) -> bool { + status.is_some_and(|s| *s != XrStatus::Unavailable) } /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running -pub fn session_created(status: Option>) -> bool { - matches!( - status.as_deref().map(XrSharedStatus::get), - Some(XrStatus::Ready | XrStatus::Running) - ) +pub fn session_ready_or_running(status: Option>) -> bool { + matches!(status.as_deref(), Some(XrStatus::Ready | XrStatus::Running)) } /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is running -pub fn session_running(status: Option>) -> bool { - matches!( - status.as_deref().map(XrSharedStatus::get), - Some(XrStatus::Running) - ) +pub fn session_running(status: Option>) -> bool { + matches!(status.as_deref(), Some(XrStatus::Running)) } /// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if an the [`XrStatus`] is in a specific state -pub fn status_equals(status: XrStatus) -> impl FnMut(Option>) -> bool { - move |state: Option>| state.is_some_and(|s| s.get() == status) +pub fn status_equals(status: XrStatus) -> impl FnMut(Option>) -> bool { + move |state: Option>| state.is_some_and(|s| *s == status) } /// Event sent to backends to create an XR session. Should only be called in the [`XrStatus::Available`] state.