From 05e7f2a9a8912a68efe67bbc2cee2eb4820111cd Mon Sep 17 00:00:00 2001 From: awtterpip Date: Sun, 17 Mar 2024 20:57:01 -0500 Subject: [PATCH] xr cleanup code --- crates/bevy_openxr/src/init.rs | 87 +++-- crates/bevy_openxr/src/initt.rs | 486 ---------------------------- crates/bevy_openxr/src/resources.rs | 4 + crates/bevy_xr/src/session.rs | 5 +- 4 files changed, 48 insertions(+), 534 deletions(-) delete mode 100644 crates/bevy_openxr/src/initt.rs diff --git a/crates/bevy_openxr/src/init.rs b/crates/bevy_openxr/src/init.rs index e2b1ee8..2142197 100644 --- a/crates/bevy_openxr/src/init.rs +++ b/crates/bevy_openxr/src/init.rs @@ -1,4 +1,3 @@ -use bevy::app::AppExit; use bevy::math::uvec2; use bevy::prelude::*; use bevy::render::extract_resource::ExtractResourcePlugin; @@ -6,12 +5,12 @@ use bevy::render::renderer::{ RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, }; use bevy::render::settings::RenderCreation; -use bevy::render::{MainWorld, RenderApp, RenderPlugin}; +use bevy::render::{MainWorld, Render, RenderApp, RenderPlugin, RenderSet}; use bevy::transform::TransformSystem; use bevy::winit::{UpdateMode, WinitSettings}; use bevy_xr::session::{ handle_session, session_available, session_running, status_equals, BeginXrSession, - CreateXrSession, EndXrSession, XrSharedStatus, XrStatus, + CreateXrSession, DestroyXrSession, EndXrSession, XrSharedStatus, XrStatus, }; use crate::graphics::*; @@ -31,9 +30,6 @@ pub enum XrPreUpdateSet { #[derive(Component)] pub struct XrTrackingRoot; -#[derive(Resource, Clone, Copy, PartialEq)] -pub struct AppExiting(bool); - pub struct XrInitPlugin { /// Information about the app this is being used to build. pub app_info: AppInfo, @@ -74,9 +70,11 @@ impl Plugin for XrInitPlugin { ), synchronous_pipeline_compilation: self.synchronous_pipeline_compilation, }, + ExtractResourcePlugin::::default(), ExtractResourcePlugin::::default(), ExtractResourcePlugin::::default(), )) + .add_systems(First, reset_per_frame_resources) .add_systems( PreUpdate, ( @@ -94,6 +92,9 @@ impl Plugin for XrInitPlugin { end_xr_session .run_if(on_event::()) .run_if(status_equals(XrStatus::Stopping)), + destroy_xr_session + .run_if(on_event::()) + .run_if(status_equals(XrStatus::Exiting)), ) .in_set(XrPreUpdateSet::HandleEvents), ), @@ -102,12 +103,6 @@ impl Plugin for XrInitPlugin { PostUpdate, update_root_transform.after(TransformSystem::TransformPropagate), ) - .add_systems( - Last, - app_exit_xr - .run_if(resource_equals(AppExiting(false))) - .run_if(on_event::()), - ) .insert_resource(instance.clone()) .insert_resource(system_id) .insert_resource(status.clone()) @@ -115,6 +110,7 @@ impl Plugin for XrInitPlugin { focused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous, }) + .init_resource::() .init_resource::() .insert_non_send_resource(session_create_info); @@ -127,6 +123,13 @@ impl Plugin for XrInitPlugin { .insert_resource(system_id) .insert_resource(status) .init_resource::() + .init_resource::() + .add_systems( + Render, + destroy_xr_session_render + .run_if(resource_equals(XrCleanupSession(true))) + .after(RenderSet::ExtractCommands), + ) .add_systems( ExtractSchedule, transfer_xr_resources.run_if(not(session_running)), @@ -155,8 +158,7 @@ impl Plugin for XrInitPlugin { let session_started = XrSessionStarted::default(); - app.insert_resource(session_started.clone()) - .insert_resource(AppExiting(false)); + app.insert_resource(session_started.clone()); let render_app = app.sub_app_mut(RenderApp); @@ -458,21 +460,6 @@ pub fn end_xr_session(session: Res, session_started: Res, - mut app_exit_events: ResMut>, - session_started: Res, - session: Option>, -) { - // we need to temporarily intercept the exit event to allow the session to exit. - app_exit_events.clear(); - *app_exiting = AppExiting(true); - session_started.set(false); - if let Some(session) = &session { - session.request_exit().expect("Failed to request 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(XrRenderResources { @@ -496,13 +483,7 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut, - status: Res, - session: Option>, - app_exiting: Res, - mut exit_app: EventWriter, -) { +pub fn poll_events(instance: Res, status: Res) { let _span = info_span!("xr_poll_events"); let mut buffer = Default::default(); while let Some(event) = instance @@ -524,17 +505,7 @@ pub fn poll_events( SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { XrStatus::Running } - SessionState::STOPPING => { - if app_exiting.0 { - if let Some(session) = &session { - session.end().expect("Failed to end session"); - } - - exit_app.send_default(); - } - - XrStatus::Stopping - } + SessionState::STOPPING => XrStatus::Stopping, SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting, _ => unreachable!(), }; @@ -547,3 +518,25 @@ pub fn poll_events( } } } + +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::(); + commands.remove_resource::(); + commands.remove_resource::(); + commands.insert_resource(XrCleanupSession(true)); +} + +pub fn destroy_xr_session_render(world: &mut World) { + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); + world.remove_resource::(); +} diff --git a/crates/bevy_openxr/src/initt.rs b/crates/bevy_openxr/src/initt.rs deleted file mode 100644 index 18cb0f5..0000000 --- a/crates/bevy_openxr/src/initt.rs +++ /dev/null @@ -1,486 +0,0 @@ -use bevy::app::{App, First, Plugin, PostUpdate, PreUpdate}; -use bevy::ecs::change_detection::DetectChangesMut; -use bevy::ecs::component::Component; -use bevy::ecs::entity::Entity; -use bevy::ecs::query::{With, Without}; -use bevy::ecs::schedule::common_conditions::{not, on_event}; -use bevy::ecs::schedule::IntoSystemConfigs; -use bevy::ecs::system::{Commands, Query, Res, ResMut, Resource}; -use bevy::ecs::world::World; -use bevy::hierarchy::{BuildChildren, Parent}; -use bevy::log::{error, info, warn}; -use bevy::math::{uvec2, UVec2}; -use bevy::prelude::{Deref, DerefMut}; -use bevy::render::extract_component::{ExtractComponent, ExtractComponentPlugin}; -use bevy::render::extract_resource::ExtractResourcePlugin; -use bevy::render::renderer::{ - RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, -}; -use bevy::render::settings::RenderCreation; -use bevy::render::{ExtractSchedule, MainWorld, RenderApp, RenderPlugin}; -use bevy::transform::components::GlobalTransform; -use bevy::transform::{TransformBundle, TransformSystem}; -use bevy_xr::session::{ - handle_session, session_available, session_running, status_equals, BeginXrSession, - CreateXrSession, XrStatus, -}; - -use crate::error::XrError; -use crate::graphics::GraphicsBackend; -use crate::resources::*; -use crate::types::*; - -pub struct XrInitPlugin { - /// Information about the app this is being used to build. - pub app_info: AppInfo, - /// 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 - pub exts: XrExtensions, - /// List of blend modes the openxr session can use. If [None], pick the first available blend mode. - pub blend_modes: Option>, - /// List of backends the openxr session can use. If [None], pick the first available backend. - pub backends: Option>, - /// List of formats the openxr session can use. If [None], pick the first available format - pub formats: Option>, - /// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution. - pub resolutions: Option>, - /// Passed into the render plugin when added to the app. - pub synchronous_pipeline_compilation: bool, -} - -impl Plugin for XrInitPlugin { - fn build(&self, app: &mut App) { - if let Err(e) = init_xr(&self, app) { - error!("Failed to initialize openxr instance: {e}."); - app.add_plugins(RenderPlugin::default()) - .insert_resource(XrStatus::Unavailable); - } - } -} - -fn xr_entry() -> Result { - #[cfg(windows)] - let entry = openxr::Entry::linked(); - #[cfg(not(windows))] - let entry = unsafe { openxr::Entry::load()? }; - Ok(XrEntry(entry)) -} - -/// This is called from [`XrInitPlugin::build()`]. Its a separate function so that we can return a [Result] and control flow is cleaner. -fn init_xr(config: &XrInitPlugin, app: &mut App) -> Result<()> { - let entry = xr_entry()?; - - let available_exts = entry.enumerate_extensions()?; - - // check available extensions and send a warning for any wanted extensions that aren't available. - for ext in available_exts.unavailable_exts(&config.exts) { - error!( - "Extension \"{ext}\" not available in the current OpenXR runtime. Disabling extension." - ); - } - - let available_backends = GraphicsBackend::available_backends(&available_exts); - - // Backend selection - let backend = if let Some(wanted_backends) = &config.backends { - let mut backend = None; - for wanted_backend in wanted_backends { - if available_backends.contains(wanted_backend) { - backend = Some(*wanted_backend); - break; - } - } - backend - } else { - available_backends.first().copied() - } - .ok_or(XrError::NoAvailableBackend)?; - - let exts = config.exts.clone() & available_exts; - - let instance = entry.create_instance( - config.app_info.clone(), - exts, - &["XR_APILAYER_LUNARG_api_dump"], - backend, - )?; - let instance_props = instance.properties()?; - - info!( - "Loaded OpenXR runtime: {} {}", - instance_props.runtime_name, instance_props.runtime_version - ); - - let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?; - let system_props = instance.system_properties(system_id)?; - - info!( - "Using system: {}", - if system_props.system_name.is_empty() { - "" - } else { - &system_props.system_name - } - ); - - let (WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance), create_info) = - instance.init_graphics(system_id)?; - - app.add_plugins(( - RenderPlugin { - render_creation: RenderCreation::manual( - device.into(), - RenderQueue(queue.into()), - RenderAdapterInfo(adapter_info), - RenderAdapter(adapter.into()), - RenderInstance(wgpu_instance.into()), - ), - synchronous_pipeline_compilation: config.synchronous_pipeline_compilation, - }, - ExtractComponentPlugin::::default(), - ExtractResourcePlugin::::default(), - ExtractResourcePlugin::::default(), - )) - .insert_resource(instance.clone()) - .insert_resource(SystemId(system_id)) - .insert_resource(XrStatus::Available) - .insert_non_send_resource(XrSessionInitConfig { - blend_modes: config.blend_modes.clone(), - formats: config.formats.clone(), - resolutions: config.resolutions.clone(), - create_info, - }) - .add_systems( - First, - poll_events.run_if(session_available).before(handle_session), - ) - .add_systems( - PreUpdate, - ( - create_xr_session - .run_if(on_event::()) - .run_if(status_equals(XrStatus::Available)), - begin_xr_session - .run_if(status_equals(XrStatus::Ready)) - .run_if(on_event::()), - adopt_open_xr_trackers, - ) - .chain(), - ) - .add_systems( - PostUpdate, - update_root_transform_components.after(TransformSystem::TransformPropagate), - ) - .sub_app_mut(RenderApp) - .insert_resource(instance) - .insert_resource(SystemId(system_id)) - .add_systems( - ExtractSchedule, - transfer_xr_resources.run_if(not(session_running)), - ); - - app.world - .spawn((TransformBundle::default(), OpenXrTrackingRoot)); - - Ok(()) -} - -#[derive(Component, ExtractComponent, Clone, Deref, DerefMut, Default)] -pub struct XrRoot(pub GlobalTransform); - -#[derive(Component)] -/// This is the root location of the playspace. Moving this entity around moves the rest of the playspace around. -pub struct OpenXrTrackingRoot; - -#[derive(Component)] -/// Marker component for any entities that should be children of [`OpenXrTrackingRoot`] -pub struct OpenXrTracker; - -pub fn adopt_open_xr_trackers( - query: Query, Without)>, - mut commands: Commands, - tracking_root_query: Query>, -) { - let root = tracking_root_query.get_single(); - match root { - Ok(root) => { - // info!("root is"); - for tracker in query.iter() { - info!("we got a new tracker"); - commands.entity(root).add_child(tracker); - } - } - Err(_) => info!("root isnt spawned yet?"), - } -} - -fn update_root_transform_components( - mut component_query: Query<&mut XrRoot>, - root_query: Query<&GlobalTransform, With>, -) { - let root = match root_query.get_single() { - Ok(v) => v, - Err(err) => { - warn!("No or too many XrTracking Roots: {}", err); - return; - } - }; - component_query - .par_iter_mut() - .for_each(|mut root_transform| **root_transform = *root); -} - -/// This is used to store information from startup that is needed to create the session after the instance has been created. -struct XrSessionInitConfig { - /// List of blend modes the openxr session can use. If [None], pick the first available blend mode. - blend_modes: Option>, - /// List of formats the openxr session can use. If [None], pick the first available format - formats: Option>, - /// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution. - resolutions: Option>, - /// Graphics info used to create a session. - create_info: SessionCreateInfo, -} - -pub fn create_xr_session(world: &mut World) { - let Some(create_info) = world.remove_non_send_resource() else { - error!( - "Failed to retrive SessionCreateInfo. This is likely due to improper initialization." - ); - return; - }; - - let Some(instance) = world.get_resource().cloned() else { - error!("Failed to retrieve XrInstance. This is likely due to improper initialization."); - return; - }; - - let Some(system_id) = world.get_resource::().cloned() else { - error!("Failed to retrieve SystemId. THis is likely due to improper initialization"); - return; - }; - - if let Err(e) = create_xr_session_inner(world, instance, *system_id, create_info) { - error!("Failed to initialize XrSession: {e}"); - } -} - -/// This is called from [create_xr_session]. It is a separate function to allow us to return a [Result] and make control flow cleaner. -fn create_xr_session_inner( - world: &mut World, - instance: XrInstance, - system_id: openxr::SystemId, - config: XrSessionInitConfig, -) -> Result<()> { - let (session, frame_waiter, frame_stream) = - unsafe { instance.create_session(system_id, config.create_info)? }; - - // TODO!() support other view configurations - let available_view_configurations = instance.enumerate_view_configurations(system_id)?; - if !available_view_configurations.contains(&openxr::ViewConfigurationType::PRIMARY_STEREO) { - return Err(XrError::NoAvailableViewConfiguration); - } - - let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO; - - let view_configuration_views = - instance.enumerate_view_configuration_views(system_id, view_configuration_type)?; - - let (resolution, _view) = if let Some(resolutions) = &config.resolutions { - let mut preferred = None; - for resolution in resolutions { - for view_config in view_configuration_views.iter() { - if view_config.recommended_image_rect_height == resolution.y - && view_config.recommended_image_rect_width == resolution.x - { - preferred = Some((*resolution, *view_config)); - } - } - } - - if preferred.is_none() { - for resolution in resolutions { - for view_config in view_configuration_views.iter() { - if view_config.max_image_rect_height >= resolution.y - && view_config.max_image_rect_width >= resolution.x - { - preferred = Some((*resolution, *view_config)); - } - } - } - } - - preferred - } else { - if let Some(config) = view_configuration_views.first() { - Some(( - uvec2( - config.recommended_image_rect_width, - config.recommended_image_rect_height, - ), - *config, - )) - } else { - None - } - } - .ok_or(XrError::NoAvailableViewConfiguration)?; - - let available_formats = session.enumerate_swapchain_formats()?; - - let format = if let Some(formats) = &config.formats { - let mut format = None; - for wanted_format in formats { - if available_formats.contains(wanted_format) { - format = Some(*wanted_format); - } - } - format - } else { - available_formats.first().copied() - } - .ok_or(XrError::NoAvailableFormat)?; - - let mut swapchain = session.create_swapchain(SwapchainCreateInfo { - create_flags: SwapchainCreateFlags::EMPTY, - usage_flags: SwapchainUsageFlags::COLOR_ATTACHMENT | SwapchainUsageFlags::SAMPLED, - format, - // TODO() add support for multisampling - sample_count: 1, - width: resolution.x, - height: resolution.y, - face_count: 1, - array_size: 2, - mip_count: 1, - })?; - - let images = swapchain.enumerate_images( - world.resource::().wgpu_device(), - format, - resolution, - )?; - - let available_blend_modes = - instance.enumerate_environment_blend_modes(system_id, view_configuration_type)?; - - // blend mode selection - let blend_mode = if let Some(wanted_blend_modes) = &config.blend_modes { - let mut blend_mode = None; - for wanted_blend_mode in wanted_blend_modes { - if available_blend_modes.contains(wanted_blend_mode) { - blend_mode = Some(*wanted_blend_mode); - break; - } - } - blend_mode - } else { - available_blend_modes.first().copied() - } - .ok_or(XrError::NoAvailableBackend)?; - - let stage = XrStage( - session - .create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)? - .into(), - ); - - let graphics_info = XrGraphicsInfo { - blend_mode, - resolution, - format, - }; - - world.insert_resource(session.clone()); - world.insert_resource(frame_waiter); - world.insert_resource(images.clone()); - world.insert_resource(graphics_info.clone()); - world.insert_resource(stage.clone()); - world.insert_resource(frame_stream.clone()); - world.insert_resource(XrRenderResources { - session, - frame_stream, - swapchain, - images, - graphics_info, - stage, - }); - - Ok(()) -} - -pub fn begin_xr_session(session: Res, mut status: ResMut) { - session - .begin(openxr::ViewConfigurationType::PRIMARY_STEREO) - .expect("Failed to begin session"); - *status = XrStatus::Running; -} - -/// This is used solely to transport resources from the main world to the render world. -#[derive(Resource)] -struct XrRenderResources { - session: XrSession, - frame_stream: XrFrameStream, - swapchain: XrSwapchain, - images: XrSwapchainImages, - graphics_info: XrGraphicsInfo, - stage: XrStage, -} - -/// 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(XrRenderResources { - session, - frame_stream, - swapchain, - images, - graphics_info, - stage, - }) = world.remove_resource() - else { - return; - }; - - commands.insert_resource(session); - commands.insert_resource(frame_stream); - commands.insert_resource(swapchain); - commands.insert_resource(images); - commands.insert_resource(graphics_info); - commands.insert_resource(stage); -} - -/// Poll any OpenXR events and handle them accordingly -pub fn poll_events(instance: Res, mut status: ResMut) { - 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(e) => { - info!("entered XR state {:?}", e.state()); - use openxr::SessionState; - - match e.state() { - SessionState::IDLE => { - *status = XrStatus::Idle; - } - SessionState::READY => { - *status = XrStatus::Ready; - } - SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { - status.set_if_neq(XrStatus::Running); - } - SessionState::STOPPING => *status = XrStatus::Stopping, - // TODO: figure out how to destroy the session - SessionState::EXITING | SessionState::LOSS_PENDING => { - *status = XrStatus::Exiting; - } - _ => {} - } - } - InstanceLossPending(_) => {} - _ => {} - } - } -} diff --git a/crates/bevy_openxr/src/resources.rs b/crates/bevy_openxr/src/resources.rs index 123f9b5..7f35b50 100644 --- a/crates/bevy_openxr/src/resources.rs +++ b/crates/bevy_openxr/src/resources.rs @@ -294,3 +294,7 @@ impl XrSessionStarted { #[derive(ExtractResource, Resource, Clone, Copy, Default)] pub struct XrRootTransform(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 XrCleanupSession(pub bool); diff --git a/crates/bevy_xr/src/session.rs b/crates/bevy_xr/src/session.rs index 04c76b9..91d45cd 100644 --- a/crates/bevy_xr/src/session.rs +++ b/crates/bevy_xr/src/session.rs @@ -59,6 +59,7 @@ pub fn handle_session( mut create_session: EventWriter, mut begin_session: EventWriter, mut end_session: EventWriter, + mut destroy_session: EventWriter, ) { let current_status = status.get(); if *previous_status != Some(current_status) { @@ -75,7 +76,9 @@ pub fn handle_session( XrStatus::Stopping => { end_session.send_default(); } - XrStatus::Exiting => {} + XrStatus::Exiting => { + destroy_session.send_default(); + } } } *previous_status = Some(current_status);