mod graphics; pub mod input; pub mod resource_macros; pub mod resources; pub mod xr_input; use std::sync::{Arc, Mutex}; use crate::xr_input::oculus_touch::ActionSets; use bevy::app::PluginGroupBuilder; use bevy::ecs::system::SystemState; use bevy::prelude::*; use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews}; use bevy::render::pipelined_rendering::PipelinedRenderingPlugin; use bevy::render::renderer::{render_system, RenderInstance}; use bevy::render::settings::RenderCreation; use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet}; use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper}; use input::XrInput; use openxr as xr; use resources::*; use xr_input::controllers::XrControllerType; use xr_input::OpenXrInput; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591); pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418); /// Adds OpenXR support to an App #[derive(Default)] pub struct OpenXrPlugin; #[derive(Resource)] pub struct FutureXrResources( pub Arc< Mutex< Option<( XrInstance, XrSession, XrEnvironmentBlendMode, XrResolution, XrFormat, XrSessionRunning, XrFrameWaiter, XrSwapchain, XrInput, XrViews, XrFrameState, )>, >, >, ); impl Plugin for OpenXrPlugin { fn build(&self, app: &mut App) { let future_xr_resources_wrapper = Arc::new(Mutex::new(None)); app.insert_resource(FutureXrResources(future_xr_resources_wrapper.clone())); let mut system_state: SystemState>> = SystemState::new(&mut app.world); let primary_window = system_state.get(&app.world).get_single().ok().cloned(); let ( device, queue, adapter_info, render_adapter, instance, xr_instance, session, blend_mode, resolution, format, session_running, frame_waiter, swapchain, input, views, frame_state, ) = graphics::initialize_xr_graphics(primary_window).unwrap(); debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Features: {:#?}", device.features()); let mut future_xr_resources_inner = future_xr_resources_wrapper.lock().unwrap(); *future_xr_resources_inner = Some(( xr_instance, session, blend_mode, resolution, format, session_running, frame_waiter, swapchain, input, views, frame_state, )); app.insert_resource(ActionSets(vec![])); app.add_plugins(RenderPlugin { render_creation: RenderCreation::Manual( device, queue, adapter_info, render_adapter, RenderInstance(Arc::new(instance)), ), }); } fn ready(&self, app: &App) -> bool { app.world .get_resource::() .and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok()) .unwrap_or(true) } fn finish(&self, app: &mut App) { if let Some(future_renderer_resources) = app.world.remove_resource::() { let ( xr_instance, session, blend_mode, resolution, format, session_running, frame_waiter, swapchain, input, views, frame_state, ) = future_renderer_resources.0.lock().unwrap().take().unwrap(); let action_sets = app.world.resource::().clone(); app.insert_resource(xr_instance.clone()) .insert_resource(session.clone()) .insert_resource(blend_mode.clone()) .insert_resource(resolution.clone()) .insert_resource(format.clone()) .insert_resource(session_running.clone()) .insert_resource(frame_waiter.clone()) .insert_resource(swapchain.clone()) .insert_resource(input.clone()) .insert_resource(views.clone()) .insert_resource(frame_state.clone()) .insert_resource(action_sets.clone()); let (left, right) = swapchain.get_render_views(); let left = ManualTextureView { texture_view: left.into(), size: *resolution, format: *format, }; let right = ManualTextureView { texture_view: right.into(), size: *resolution, format: *format, }; app.add_systems(PreUpdate, xr_begin_frame); let mut manual_texture_views = app.world.resource_mut::(); manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); drop(manual_texture_views); let render_app = app.sub_app_mut(RenderApp); render_app .insert_resource(xr_instance) .insert_resource(session) .insert_resource(blend_mode) .insert_resource(resolution) .insert_resource(format) .insert_resource(session_running) .insert_resource(frame_waiter) .insert_resource(swapchain) .insert_resource(input) .insert_resource(views) .insert_resource(frame_state) .insert_resource(action_sets); render_app.add_systems( Render, ( post_frame .before(render_system) .after(RenderSet::ExtractCommands), end_frame.after(render_system), ), ); } } } pub struct DefaultXrPlugins; impl PluginGroup for DefaultXrPlugins { fn build(self) -> PluginGroupBuilder { DefaultPlugins .build() .disable::() .disable::() .add_before::(OpenXrPlugin) .add_after::(OpenXrInput::new(XrControllerType::OculusTouch)) .set(WindowPlugin { primary_window: Some(Window { present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() }) } } pub fn xr_begin_frame( instance: Res, session: Res, session_running: Res, frame_state: Res, frame_waiter: Res, swapchain: Res, views: Res, input: Res, ) { { let _span = info_span!("xr_poll_events"); while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() { use xr::Event::*; match event { SessionStateChanged(e) => { // Session state change is where we can begin and end sessions, as well as // find quit messages! info!("entered XR state {:?}", e.state()); match e.state() { xr::SessionState::READY => { session.begin(VIEW_TYPE).unwrap(); session_running.store(true, std::sync::atomic::Ordering::Relaxed); } xr::SessionState::STOPPING => { session.end().unwrap(); session_running.store(false, std::sync::atomic::Ordering::Relaxed); } xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return, _ => {} } } InstanceLossPending(_) => return, EventsLost(e) => { warn!("lost {} XR events", e.lost_event_count()); } _ => {} } } } { let _span = info_span!("xr_wait_frame").entered(); *frame_state.lock().unwrap() = frame_waiter.lock().unwrap().wait().unwrap(); } { let _span = info_span!("xr_begin_frame").entered(); swapchain.begin().unwrap() } { let _span = info_span!("xr_locate_views").entered(); *views.lock().unwrap() = session .locate_views( VIEW_TYPE, frame_state.lock().unwrap().predicted_display_time, &input.stage, ) .unwrap() .1; } } pub fn post_frame( resolution: Res, format: Res, swapchain: Res, mut manual_texture_views: ResMut, ) { { let _span = info_span!("xr_acquire_image").entered(); swapchain.acquire_image().unwrap() } { let _span = info_span!("xr_wait_image").entered(); swapchain.wait_image().unwrap(); } { let _span = info_span!("xr_update_manual_texture_views").entered(); let (left, right) = swapchain.get_render_views(); let left = ManualTextureView { texture_view: left.into(), size: **resolution, format: **format, }; let right = ManualTextureView { texture_view: right.into(), size: **resolution, format: **format, }; manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); } } pub fn end_frame( xr_frame_state: Res, views: Res, input: Res, swapchain: Res, resolution: Res, environment_blend_mode: Res, ) { { let _span = info_span!("xr_release_image").entered(); swapchain.release_image().unwrap(); } { let _span = info_span!("xr_end_frame").entered(); swapchain .end( xr_frame_state.lock().unwrap().predicted_display_time, &*views.lock().unwrap(), &input.stage, **resolution, **environment_blend_mode, ) .unwrap(); } } pub fn locate_views( views: Res, input: Res, session: Res, xr_frame_state: Res, ) { let _span = info_span!("xr_locate_views").entered(); *views.lock().unwrap() = match session.locate_views( VIEW_TYPE, xr_frame_state.lock().unwrap().predicted_display_time, &input.stage, ) { Ok(this) => this, Err(err) => { warn!("error: {}", err); return; } } .1; }