pub mod graphics; pub mod input; // pub mod passthrough; pub mod resource_macros; pub mod resources; pub mod xr_init; pub mod xr_input; use std::fs::File; use std::io::{BufWriter, Write}; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use crate::xr_init::{StartXrSession, XrInitPlugin}; use crate::xr_input::hands::hand_tracking::DisableHandTracking; use crate::xr_input::oculus_touch::ActionSets; use bevy::app::{AppExit, PluginGroupBuilder}; use bevy::core::TaskPoolThreadAssignmentPolicy; use bevy::ecs::system::SystemState; use bevy::prelude::*; use bevy::render::camera::{ camera_system, ManualTextureView, ManualTextureViewHandle, ManualTextureViews, }; use bevy::render::extract_resource::ExtractResourcePlugin; use bevy::render::pipelined_rendering::PipelinedRenderingPlugin; use bevy::render::renderer::{render_system, RenderInstance}; use bevy::render::settings::RenderCreation; use bevy::render::view::ExtractedView; use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet}; use bevy::tasks::available_parallelism; use bevy::transform::systems::{propagate_transforms, sync_simple_transforms}; use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper}; use graphics::extensions::XrExtensions; use graphics::{XrAppInfo, XrPreferdBlendMode}; use input::XrInput; use openxr as xr; // use passthrough::{start_passthrough, supports_passthrough, XrPassthroughLayer}; use resources::*; use xr::{FormFactor, FrameState}; use xr_init::{ xr_after_wait_only, xr_only, xr_render_only, CleanupXrData, XrEarlyInitPlugin, XrHasWaited, XrShouldRender, XrStatus, }; use xr_input::controllers::XrControllerType; use xr_input::hands::emulated::HandEmulationPlugin; use xr_input::hands::hand_tracking::{HandTrackingData, HandTrackingPlugin}; use xr_input::hands::XrHandPlugins; use xr_input::xr_camera::{XRProjection, XrCameraType}; 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 { reqeusted_extensions: XrExtensions, prefered_blend_mode: XrPreferdBlendMode, app_info: XrAppInfo, } impl Plugin for OpenXrPlugin { fn build(&self, app: &mut App) { app.insert_resource(XrSessionRunning::new(AtomicBool::new(false))); #[cfg(not(target_arch = "wasm32"))] match graphics::initialize_xr_instance( SystemState::>>::new(&mut app.world) .get(&app.world) .get_single() .ok() .cloned(), self.reqeusted_extensions.clone(), self.prefered_blend_mode, self.app_info.clone(), ) { Ok(( xr_instance, oxr_session_setup_info, blend_mode, device, queue, adapter_info, render_adapter, instance, )) => { debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Features: {:#?}", device.features()); warn!("Starting with OpenXR Instance"); app.insert_resource(ActionSets(vec![])); app.insert_resource(xr_instance); app.insert_resource(blend_mode); app.insert_non_send_resource(oxr_session_setup_info); let render_instance = RenderInstance(instance.into()); app.insert_resource(render_instance.clone()); app.add_plugins(RenderPlugin { render_creation: RenderCreation::Manual( device, queue, adapter_info, render_adapter, render_instance, ), }); app.insert_resource(XrStatus::Disabled); app.world.send_event(StartXrSession); } Err(err) => { warn!("OpenXR Instance Failed to initialize: {}", err); app.add_plugins(RenderPlugin::default()); app.insert_resource(XrStatus::NoInstance); } } #[cfg(target_arch = "wasm32")] { app.add_plugins(RenderPlugin::default()); app.insert_resource(XrStatus::Disabled); } app.add_systems( PreUpdate, xr_poll_events.run_if(|status: Res| *status != XrStatus::NoInstance), ); app.add_systems( PreUpdate, ( xr_reset_per_frame_resources, xr_wait_frame.run_if(xr_only()), locate_views.run_if(xr_only()), apply_deferred, ) .chain() .after(xr_poll_events), ); let render_app = app.sub_app_mut(RenderApp); render_app.add_systems( Render, xr_begin_frame .run_if(xr_only()) .run_if(xr_after_wait_only()) // .run_if(xr_render_only()) .after(RenderSet::ExtractCommands) .before(xr_pre_frame), ); render_app.add_systems( Render, xr_pre_frame .run_if(xr_only()) .run_if(xr_after_wait_only()) .run_if(xr_render_only()) // Do NOT touch this ordering! idk why but you can NOT just put in a RenderSet // right before rendering .before(render_system) .after(RenderSet::ExtractCommands), // .in_set(RenderSet::Prepare), ); render_app.add_systems( Render, xr_end_frame .run_if(xr_only()) .run_if(xr_after_wait_only()) .run_if(xr_render_only()) .in_set(RenderSet::Cleanup), ); render_app.add_systems( Render, xr_skip_frame .run_if(xr_only()) .run_if(xr_after_wait_only()) .run_if(not(xr_render_only())) .in_set(RenderSet::Cleanup), ); } } fn xr_skip_frame( xr_swapchain: Res, xr_frame_state: Res, environment_blend_mode: Res, ) { let swapchain: &Swapchain = &xr_swapchain; match swapchain { Swapchain::Vulkan(swap) => { swap.stream .lock() .unwrap() .end( xr_frame_state.predicted_display_time, **environment_blend_mode, &[], ) .unwrap(); } } } #[derive(Default)] pub struct DefaultXrPlugins { pub reqeusted_extensions: XrExtensions, pub prefered_blend_mode: XrPreferdBlendMode, pub app_info: XrAppInfo, } impl PluginGroup for DefaultXrPlugins { fn build(self) -> PluginGroupBuilder { DefaultPlugins .build() .set(TaskPoolPlugin { task_pool_options: TaskPoolOptions { compute: TaskPoolThreadAssignmentPolicy { min_threads: 2, max_threads: std::usize::MAX, // unlimited max threads percent: 1.0, // this value is irrelevant in this case }, // keep the defaults for everything else ..default() }, }) .disable::() .disable::() .add_before::(OpenXrPlugin { prefered_blend_mode: self.prefered_blend_mode, reqeusted_extensions: self.reqeusted_extensions, app_info: self.app_info.clone(), }) .add_after::(OpenXrInput::new(XrControllerType::OculusTouch)) .add_after::(XrInitPlugin) .add_before::(XrEarlyInitPlugin) .add(XrHandPlugins) .add(XrResourcePlugin) .set(WindowPlugin { #[cfg(not(target_os = "android"))] primary_window: Some(Window { transparent: true, present_mode: PresentMode::AutoNoVsync, title: self.app_info.name.clone(), ..default() }), #[cfg(target_os = "android")] primary_window: None, #[cfg(target_os = "android")] exit_condition: bevy::window::ExitCondition::DontExit, #[cfg(target_os = "android")] close_when_requested: true, ..default() }) } } fn xr_reset_per_frame_resources( mut should: ResMut, mut waited: ResMut, ) { **should = false; **waited = false; } fn xr_poll_events( instance: Option>, session: Option>, session_running: Res, mut app_exit: EventWriter, mut cleanup_xr: EventWriter, ) { if let (Some(instance), Some(session)) = (instance, session) { 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 => { info!("Calling Session begin :3"); 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); cleanup_xr.send_default(); } xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => { // app_exit.send(AppExit); return; } _ => {} } } InstanceLossPending(_) => { app_exit.send_default(); } EventsLost(e) => { warn!("lost {} XR events", e.lost_event_count()); } _ => {} } } } } fn xr_begin_frame(swapchain: Res) { let _span = info_span!("xr_begin_frame").entered(); swapchain.begin().unwrap() } pub fn xr_wait_frame( mut frame_state: ResMut, mut frame_waiter: ResMut, mut should_render: ResMut, mut waited: ResMut, ) { { let _span = info_span!("xr_wait_frame").entered(); info!("Pre Frame Wait"); *frame_state = match frame_waiter.wait() { Ok(a) => a.into(), Err(e) => { warn!("error: {}", e); return; } }; info!( "Post Wait Time: {}", frame_state.predicted_display_time.as_nanos() ); // frame_state.predicted_display_time = xr::Time::from_nanos( // frame_state.predicted_display_time.as_nanos() // + frame_state.predicted_display_period.as_nanos(), // ); info!("Post Frame Wait"); **should_render = frame_state.should_render; **waited = true; } } pub fn xr_pre_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 xr_end_frame( xr_frame_state: Res, views: Res, input: Res, swapchain: Res, resolution: Res, environment_blend_mode: Res, ) { #[cfg(target_os = "android")] { let ctx = ndk_context::android_context(); let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap(); let env = vm.attach_current_thread_as_daemon(); } { let _span = info_span!("xr_release_image").entered(); swapchain.release_image().unwrap(); } { let _span = info_span!("xr_end_frame").entered(); info!( "End Frame Time: {}", xr_frame_state.predicted_display_time.as_nanos() ); let result = swapchain.end( xr_frame_state.predicted_display_time, &views, &input.stage, **resolution, **environment_blend_mode, // passthrough_layer.map(|p| p.into_inner()), ); match result { Ok(_) => {} Err(e) => warn!("error: {}", e), } } } pub fn locate_views( mut views: ResMut, input: Res, session: Res, xr_frame_state: Res, ) { let _span = info_span!("xr_locate_views").entered(); **views = match session.locate_views( VIEW_TYPE, xr_frame_state.predicted_display_time, &input.stage, ) { Ok(this) => this, Err(err) => { warn!("error: {}", err); return; } } .1; }