correct pipelining

This commit is contained in:
awtterpip
2024-07-03 19:37:40 -05:00
parent c2a0d786b9
commit 3264bb9605
17 changed files with 660 additions and 720 deletions

View File

@@ -1,8 +1,7 @@
// a simple example showing basic actions using the xr utils actions // a simple example showing basic actions using the xr utils actions
use bevy::{math::vec3, prelude::*}; use bevy::{math::vec3, prelude::*};
use bevy_openxr::{ use bevy_openxr::{add_xr_plugins, helper_traits::ToQuat, resources::OxrViews};
add_xr_plugins, helper_traits::ToQuat, init::OxrTrackingRoot, resources::OxrViews, use bevy_xr::session::XrTrackingRoot;
};
use bevy_xr_utils::xr_utils_actions::{ use bevy_xr_utils::xr_utils_actions::{
ActiveSet, XRUtilsAction, XRUtilsActionSet, XRUtilsActionState, XRUtilsActionSystemSet, ActiveSet, XRUtilsAction, XRUtilsActionSet, XRUtilsActionState, XRUtilsActionSystemSet,
XRUtilsActionsPlugin, XRUtilsBinding, XRUtilsActionsPlugin, XRUtilsBinding,
@@ -114,7 +113,7 @@ fn read_action_with_marker_component(
//lets add some flycam stuff //lets add some flycam stuff
fn handle_flight_input( fn handle_flight_input(
action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>, action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>,
mut oxr_root: Query<&mut Transform, With<OxrTrackingRoot>>, mut oxr_root: Query<&mut Transform, With<XrTrackingRoot>>,
time: Res<Time>, time: Res<Time>,
//use the views for hmd orientation //use the views for hmd orientation
views: ResMut<OxrViews>, views: ResMut<OxrViews>,

View File

@@ -2,9 +2,9 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_openxr::{add_xr_plugins, init::OxrInitPlugin, types::OxrExtensions}; use bevy_openxr::{add_xr_plugins, init::OxrInitPlugin, types::OxrExtensions};
use bevy_xr::session::XrStatus; use bevy_xr::session::XrState;
use openxr::EnvironmentBlendMode; // use openxr::EnvironmentBlendMode;
use wgpu::TextureFormat; // use wgpu::TextureFormat;
fn main() { fn main() {
App::new() App::new()
@@ -40,11 +40,12 @@ fn main() {
fn handle_input( fn handle_input(
keys: Res<ButtonInput<KeyCode>>, keys: Res<ButtonInput<KeyCode>>,
mut end: EventWriter<bevy_xr::session::EndXrSession>, mut end: EventWriter<bevy_xr::session::XrEndSessionEvent>,
mut _destroy: EventWriter<bevy_xr::session::DestroyXrSession>, mut destroy: EventWriter<bevy_xr::session::XrDestroySessionEvent>,
mut _begin: EventWriter<bevy_xr::session::BeginXrSession>, mut begin: EventWriter<bevy_xr::session::XrBeginSessionEvent>,
mut create: EventWriter<bevy_xr::session::CreateXrSession>, mut create: EventWriter<bevy_xr::session::XrCreateSessionEvent>,
state: Res<XrStatus>, mut request_exit: EventWriter<bevy_xr::session::XrRequestExitEvent>,
state: Res<XrState>,
) { ) {
if keys.just_pressed(KeyCode::KeyE) { if keys.just_pressed(KeyCode::KeyE) {
info!("sending end"); info!("sending end");
@@ -54,6 +55,18 @@ fn handle_input(
info!("sending create"); info!("sending create");
create.send_default(); create.send_default();
} }
if keys.just_pressed(KeyCode::KeyD) {
info!("sending destroy");
destroy.send_default();
}
if keys.just_pressed(KeyCode::KeyB) {
info!("sending begin");
begin.send_default();
}
if keys.just_pressed(KeyCode::KeyR) {
info!("sending request exit");
request_exit.send_default();
}
if keys.just_pressed(KeyCode::KeyI) { if keys.just_pressed(KeyCode::KeyI) {
info!("current state: {:?}", *state); info!("current state: {:?}", *state);
} }

View File

@@ -6,13 +6,13 @@ use bevy_openxr::{
action_set_attaching::OxrAttachActionSet, action_set_attaching::OxrAttachActionSet,
action_set_syncing::{OxrActionSetSyncSet, OxrSyncActionSet}, action_set_syncing::{OxrActionSetSyncSet, OxrSyncActionSet},
add_xr_plugins, add_xr_plugins,
init::OxrTrackingRoot, init::create_xr_session,
resources::OxrInstance, resources::OxrInstance,
session::OxrSession, session::OxrSession,
spaces::OxrSpaceExt, spaces::OxrSpaceExt,
}; };
use bevy_xr::{ use bevy_xr::{
session::{session_available, XrSessionCreated}, session::{session_available, XrCreateSession, XrTrackingRoot},
spaces::XrSpace, spaces::XrSpace,
types::XrPose, types::XrPose,
}; };
@@ -21,8 +21,8 @@ use openxr::Posef;
fn main() { fn main() {
let mut app = App::new(); let mut app = App::new();
app.add_plugins(add_xr_plugins(DefaultPlugins)); app.add_plugins(add_xr_plugins(DefaultPlugins));
app.add_systems(XrSessionCreated, spawn_hands); app.add_systems(XrCreateSession, spawn_hands.after(create_xr_session));
app.add_systems(XrSessionCreated, attach_set); app.add_systems(XrCreateSession, attach_set.after(create_xr_session));
app.add_systems(PreUpdate, sync_actions.before(OxrActionSetSyncSet)); app.add_systems(PreUpdate, sync_actions.before(OxrActionSetSyncSet));
app.add_systems(OxrSendActionBindings, suggest_action_bindings); app.add_systems(OxrSendActionBindings, suggest_action_bindings);
app.add_systems(Startup, create_actions.run_if(session_available)); app.add_systems(Startup, create_actions.run_if(session_available));
@@ -108,7 +108,7 @@ fn create_actions(instance: Res<OxrInstance>, mut cmds: Commands) {
fn spawn_hands( fn spawn_hands(
actions: Res<ControllerActions>, actions: Res<ControllerActions>,
mut cmds: Commands, mut cmds: Commands,
root: Query<Entity, With<OxrTrackingRoot>>, root: Query<Entity, With<XrTrackingRoot>>,
session: Res<OxrSession>, session: Res<OxrSession>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,

View File

@@ -2,11 +2,11 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_openxr::add_xr_plugins; use bevy_openxr::add_xr_plugins;
use bevy_xr::session::{ XrStatus}; use bevy_xr::session::{XrSessionPlugin, XrState};
fn main() { fn main() {
App::new() App::new()
.add_plugins(add_xr_plugins(DefaultPlugins)) .add_plugins(add_xr_plugins(DefaultPlugins).set(XrSessionPlugin { auto_handle: false }))
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin) .add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, handle_input) .add_systems(Update, handle_input)
@@ -16,11 +16,12 @@ fn main() {
fn handle_input( fn handle_input(
keys: Res<ButtonInput<KeyCode>>, keys: Res<ButtonInput<KeyCode>>,
mut end: EventWriter<bevy_xr::session::EndXrSession>, mut end: EventWriter<bevy_xr::session::XrEndSessionEvent>,
mut _destroy: EventWriter<bevy_xr::session::DestroyXrSession>, mut destroy: EventWriter<bevy_xr::session::XrDestroySessionEvent>,
mut _begin: EventWriter<bevy_xr::session::BeginXrSession>, mut begin: EventWriter<bevy_xr::session::XrBeginSessionEvent>,
mut create: EventWriter<bevy_xr::session::CreateXrSession>, mut create: EventWriter<bevy_xr::session::XrCreateSessionEvent>,
state: Res<XrStatus>, mut request_exit: EventWriter<bevy_xr::session::XrRequestExitEvent>,
state: Res<XrState>,
) { ) {
if keys.just_pressed(KeyCode::KeyE) { if keys.just_pressed(KeyCode::KeyE) {
info!("sending end"); info!("sending end");
@@ -30,6 +31,18 @@ fn handle_input(
info!("sending create"); info!("sending create");
create.send_default(); create.send_default();
} }
if keys.just_pressed(KeyCode::KeyD) {
info!("sending destroy");
destroy.send_default();
}
if keys.just_pressed(KeyCode::KeyB) {
info!("sending begin");
begin.send_default();
}
if keys.just_pressed(KeyCode::KeyR) {
info!("sending request exit");
request_exit.send_default();
}
if keys.just_pressed(KeyCode::KeyI) { if keys.just_pressed(KeyCode::KeyI) {
info!("current state: {:?}", *state); info!("current state: {:?}", *state);
} }

View File

@@ -5,9 +5,10 @@ use bevy::ecs::schedule::ScheduleLabel;
use bevy::ecs::system::RunSystemOnce; use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::utils::HashMap; use bevy::utils::HashMap;
use bevy_xr::session::XrSessionCreatedEvent;
use openxr::sys::ActionSuggestedBinding; use openxr::sys::ActionSuggestedBinding;
use crate::{resources::OxrInstance, session::OxrSessionStatusEvent}; use crate::resources::OxrInstance;
impl Plugin for OxrActionBindingPlugin { impl Plugin for OxrActionBindingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@@ -15,13 +16,7 @@ impl Plugin for OxrActionBindingPlugin {
app.add_event::<OxrSuggestActionBinding>(); app.add_event::<OxrSuggestActionBinding>();
app.add_systems( app.add_systems(
Update, Update,
run_action_binding_sugestion.run_if( run_action_binding_sugestion.run_if(on_event::<XrSessionCreatedEvent>()),
|mut session_state: EventReader<OxrSessionStatusEvent>| {
session_state
.read()
.any(|s| *s == OxrSessionStatusEvent::Created)
},
),
); );
} }
} }

View File

@@ -1,8 +1,6 @@
use crate::{ use crate::{action_binding::run_action_binding_sugestion, session::OxrSession};
action_binding::run_action_binding_sugestion,
session::{OxrSession, OxrSessionStatusEvent},
};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xr::session::XrSessionCreatedEvent;
impl Plugin for OxrActionAttachingPlugin { impl Plugin for OxrActionAttachingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@@ -10,13 +8,7 @@ impl Plugin for OxrActionAttachingPlugin {
app.add_systems( app.add_systems(
PostUpdate, PostUpdate,
attach_sets attach_sets
.run_if( .run_if(on_event::<XrSessionCreatedEvent>())
|mut session_status_event: EventReader<OxrSessionStatusEvent>| {
session_status_event
.read()
.any(|s| *s == OxrSessionStatusEvent::Created)
},
)
.after(run_action_binding_sugestion), .after(run_action_binding_sugestion),
); );
} }

View File

@@ -1,18 +1,17 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xr::hands::{LeftHand, RightHand}; use bevy_xr::hands::{LeftHand, RightHand};
use bevy_xr::session::{XrCreateSession, XrDestroySession, XrTrackingRoot};
use bevy_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace}; use bevy_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace};
use bevy_xr::{ use bevy_xr::{
hands::{HandBone, HandBoneRadius}, hands::{HandBone, HandBoneRadius},
session::{session_running, XrSessionCreated, XrSessionExiting}, session::session_running,
}; };
use openxr::SpaceLocationFlags; use openxr::SpaceLocationFlags;
use crate::init::create_xr_session;
use crate::resources::OxrFrameState;
use crate::resources::Pipelined; use crate::resources::Pipelined;
use crate::{ use crate::session::OxrSession;
init::OxrTrackingRoot,
resources::OxrFrameState,
session::OxrSession,
};
pub struct HandTrackingPlugin { pub struct HandTrackingPlugin {
default_hands: bool, default_hands: bool,
@@ -29,8 +28,13 @@ impl Plugin for HandTrackingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, locate_hands.run_if(session_running)); app.add_systems(PreUpdate, locate_hands.run_if(session_running));
if self.default_hands { if self.default_hands {
app.add_systems(XrSessionExiting, clean_up_default_hands); app.add_systems(XrDestroySession, clean_up_default_hands)
app.add_systems(XrSessionCreated, spawn_default_hands); .add_systems(
XrCreateSession,
(spawn_default_hands, apply_deferred)
.chain()
.after(create_xr_session),
);
} }
} }
} }
@@ -38,7 +42,7 @@ impl Plugin for HandTrackingPlugin {
fn spawn_default_hands( fn spawn_default_hands(
mut cmds: Commands, mut cmds: Commands,
session: Res<OxrSession>, session: Res<OxrSession>,
root: Query<Entity, With<OxrTrackingRoot>>, root: Query<Entity, With<XrTrackingRoot>>,
) { ) {
debug!("spawning default hands"); debug!("spawning default hands");
let Ok(root) = root.get_single() else { let Ok(root) = root.get_single() else {

View File

@@ -1,6 +1,3 @@
use bevy::app::MainScheduleOrder;
use bevy::ecs::schedule::ScheduleLabel;
use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResourcePlugin; use bevy::render::extract_resource::ExtractResourcePlugin;
use bevy::render::renderer::RenderAdapter; use bevy::render::renderer::RenderAdapter;
@@ -10,48 +7,38 @@ use bevy::render::renderer::RenderInstance;
use bevy::render::renderer::RenderQueue; use bevy::render::renderer::RenderQueue;
use bevy::render::settings::RenderCreation; use bevy::render::settings::RenderCreation;
use bevy::render::MainWorld; use bevy::render::MainWorld;
use bevy::render::Render;
use bevy::render::RenderApp; use bevy::render::RenderApp;
use bevy::render::RenderPlugin; use bevy::render::RenderPlugin;
use bevy::render::RenderSet;
use bevy::transform::TransformSystem;
use bevy::winit::UpdateMode; use bevy::winit::UpdateMode;
use bevy::winit::WinitSettings; use bevy::winit::WinitSettings;
use bevy_xr::session::session_running; use bevy_xr::session::*;
use bevy_xr::session::status_equals;
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::error::OxrError;
use crate::features::overlay::OxrOverlaySessionEvent; use crate::features::overlay::OxrOverlaySessionEvent;
use crate::graphics::*; use crate::graphics::*;
use crate::openxr::exts::OxrEnabledExtensions;
use crate::resources::*; use crate::resources::*;
use crate::session::OxrSession; use crate::session::OxrSession;
use crate::session::OxrSessionCreateNextChain; use crate::session::OxrSessionCreateNextChain;
use crate::session::OxrSessionStatusEvent;
use crate::types::*; use crate::types::*;
use super::exts::OxrEnabledExtensions;
pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool { pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool {
started.is_some_and(|started| started.0) started.is_some_and(|started| started.0)
} }
pub fn should_run_frame_loop(
started: Option<Res<OxrSessionStarted>>,
state: Option<Res<XrState>>,
) -> bool {
started.is_some_and(|started| started.0)
&& state.is_some_and(|state| *state != XrState::Stopping)
}
pub fn should_render(frame_state: Option<Res<OxrFrameState>>) -> bool { pub fn should_render(frame_state: Option<Res<OxrFrameState>>) -> bool {
frame_state.is_some_and(|frame_state| frame_state.should_render) 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 { pub struct OxrInitPlugin {
/// Information about the app this is being used to build. /// Information about the app this is being used to build.
pub app_info: AppInfo, pub app_info: AppInfo,
@@ -88,9 +75,6 @@ impl Default for OxrInitPlugin {
} }
} }
#[derive(Component)]
pub struct OxrTrackingRoot;
impl Plugin for OxrInitPlugin { impl Plugin for OxrInitPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
match self.init_xr() { match self.init_xr() {
@@ -101,103 +85,70 @@ impl Plugin for OxrInitPlugin {
session_create_info, session_create_info,
enabled_exts, enabled_exts,
)) => { )) => {
app.insert_resource(enabled_exts); app.insert_resource(enabled_exts)
app.add_plugins(( .add_plugins((
RenderPlugin { RenderPlugin {
render_creation: RenderCreation::manual( render_creation: RenderCreation::manual(
device.into(), device.into(),
RenderQueue(queue.into()), RenderQueue(queue.into()),
RenderAdapterInfo(adapter_info), RenderAdapterInfo(adapter_info),
RenderAdapter(adapter.into()), RenderAdapter(adapter.into()),
RenderInstance(wgpu_instance.into()), RenderInstance(wgpu_instance.into()),
), ),
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation, synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
}, },
ExtractResourcePlugin::<OxrCleanupSession>::default(), ExtractResourcePlugin::<OxrSessionStarted>::default(),
ExtractResourcePlugin::<XrStatus>::default(), ))
ExtractResourcePlugin::<OxrSessionStarted>::default(), .add_systems(
)) XrFirst,
.init_schedule(OxrLast) poll_events
.add_systems( .before(XrHandleEvents)
OxrLast, .run_if(not(state_equals(XrState::Unavailable))),
(
reset_per_frame_resources,
poll_events,
create_xr_session
.run_if(on_event::<CreateXrSession>())
.run_if(status_equals(XrStatus::Available)),
begin_xr_session
.run_if(on_event::<BeginXrSession>())
.run_if(status_equals(XrStatus::Ready)),
end_xr_session
.run_if(on_event::<EndXrSession>())
.run_if(status_equals(XrStatus::Running)),
destroy_xr_session
.run_if(on_event::<DestroyXrSession>())
.run_if(status_equals(XrStatus::Exiting)),
) )
.chain() .add_systems(XrCreateSession, create_xr_session)
.in_set(OxrHandleEvents), .add_systems(XrDestroySession, destroy_xr_session)
) .add_systems(XrBeginSession, begin_xr_session)
.add_systems(XrSessionExiting, destroy_xr_session) .add_systems(XrEndSession, end_xr_session)
.add_systems( .add_systems(XrRequestExit, request_exit_xr_session)
PostUpdate, .insert_resource(instance.clone())
update_root_transform.after(TransformSystem::TransformPropagate), .insert_resource(system_id)
) .insert_resource(XrState::Available)
.insert_resource(instance.clone()) .insert_resource(WinitSettings {
.insert_resource(system_id) focused_mode: UpdateMode::Continuous,
.insert_resource(XrStatus::Available) unfocused_mode: UpdateMode::Continuous,
.insert_resource(WinitSettings { })
focused_mode: UpdateMode::Continuous, .insert_resource(OxrSessionStarted(false))
unfocused_mode: UpdateMode::Continuous, .insert_non_send_resource(session_create_info)
}) .init_non_send_resource::<OxrSessionCreateNextChain>();
.init_resource::<OxrCleanupSession>()
.init_resource::<OxrRootTransform>()
.insert_non_send_resource(session_create_info)
.configure_sets(OxrLast, OxrHandleEvents);
app.world app.world
.resource_mut::<MainScheduleOrder>() .spawn((TransformBundle::default(), XrTrackingRoot));
.insert_after(Last, OxrLast);
app.world app.world
.spawn((TransformBundle::default(), OxrTrackingRoot)); .resource_mut::<Events<XrStateChanged>>()
.send(XrStateChanged(XrState::Available));
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.add_systems( .add_systems(ExtractSchedule, transfer_xr_resources)
Render,
destroy_xr_session_render
.run_if(resource_equals(OxrCleanupSession(true)))
.after(RenderSet::Cleanup),
)
.add_systems(
ExtractSchedule,
transfer_xr_resources.run_if(not(session_running)),
)
.insert_resource(instance) .insert_resource(instance)
.insert_resource(system_id) .insert_resource(system_id)
.init_resource::<OxrRootTransform>() .insert_resource(XrState::Available)
.init_resource::<OxrCleanupSession>(); .insert_resource(OxrSessionStarted(false));
} }
Err(e) => { Err(e) => {
error!("Failed to initialize openxr: {e}"); error!("Failed to initialize openxr: {e}");
app.add_plugins(RenderPlugin::default()) app.add_plugins(RenderPlugin::default())
.insert_resource(XrStatus::Unavailable); .insert_resource(XrState::Unavailable);
} }
}; };
app.insert_resource(OxrSessionStarted(false));
} }
}
pub fn update_root_transform( fn finish(&self, app: &mut App) {
mut root_transform: ResMut<OxrRootTransform>, app.sub_app_mut(RenderApp)
root: Query<&GlobalTransform, With<OxrTrackingRoot>>, .add_systems(XrDestroySession, destroy_xr_session);
) { }
let transform = root.single();
root_transform.0 = *transform;
} }
impl OxrInitPlugin { impl OxrInitPlugin {
@@ -291,6 +242,63 @@ impl OxrInitPlugin {
} }
} }
/// Polls any OpenXR events and handles them accordingly
pub fn poll_events(
instance: Res<OxrInstance>,
mut status: ResMut<XrState>,
mut changed_event: EventWriter<XrStateChanged>,
mut overlay_writer: Option<ResMut<Events<OxrOverlaySessionEvent>>>,
) {
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 => XrState::Idle,
SessionState::READY => XrState::Ready,
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
XrState::Running
}
SessionState::STOPPING => XrState::Stopping,
SessionState::EXITING => XrState::Exiting {
should_restart: false,
},
SessionState::LOSS_PENDING => XrState::Exiting {
should_restart: true,
},
_ => unreachable!(),
};
changed_event.send(XrStateChanged(new_status));
*status = new_status;
}
InstanceLossPending(_) => {}
EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()),
MainSessionVisibilityChangedEXTX(d) => {
if let Some(writer) = overlay_writer.as_mut() {
writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged {
visible: d.visible(),
flags: d.flags(),
});
} else {
warn!("Overlay Event Recieved without the OverlayPlugin being added!");
}
}
_ => {}
}
}
}
fn init_xr_session( fn init_xr_session(
device: &wgpu::Device, device: &wgpu::Device,
instance: &OxrInstance, instance: &OxrInstance,
@@ -426,6 +434,49 @@ fn init_xr_session(
)) ))
} }
pub fn create_xr_session(world: &mut World) {
let mut chain = world
.remove_non_send_resource::<OxrSessionCreateNextChain>()
.unwrap();
let device = world.resource::<RenderDevice>();
let instance = world.resource::<OxrInstance>();
let create_info = world.non_send_resource::<SessionConfigInfo>();
let system_id = world.resource::<OxrSystemId>();
match init_xr_session(
device.wgpu_device(),
&instance,
**system_id,
&mut chain,
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,
});
}
Err(e) => error!("Failed to initialize XrSession: {e}"),
}
world.insert_non_send_resource(chain);
}
pub fn destroy_xr_session(world: &mut World) {
world.remove_resource::<OxrSession>();
world.remove_resource::<OxrFrameWaiter>();
world.remove_resource::<OxrFrameStream>();
world.remove_resource::<OxrSwapchain>();
world.remove_resource::<OxrSwapchainImages>();
world.remove_resource::<OxrGraphicsInfo>();
world.insert_resource(XrState::Available);
}
pub fn begin_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) { pub fn begin_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
let _span = info_span!("xr_begin_session"); let _span = info_span!("xr_begin_session");
session session
@@ -436,10 +487,14 @@ pub fn begin_xr_session(session: Res<OxrSession>, mut session_started: ResMut<Ox
pub fn end_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) { pub fn end_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
let _span = info_span!("xr_end_session"); let _span = info_span!("xr_end_session");
session.request_exit().expect("Failed to end session"); session.end().expect("Failed to end session");
session_started.0 = false; session_started.0 = false;
} }
pub fn request_exit_xr_session(session: Res<OxrSession>) {
session.request_exit().expect("Failed to request exit");
}
/// This is used solely to transport resources from the main world to the render world. /// This is used solely to transport resources from the main world to the render world.
#[derive(Resource)] #[derive(Resource)]
struct OxrRenderResources { struct OxrRenderResources {
@@ -450,38 +505,6 @@ struct OxrRenderResources {
graphics_info: OxrGraphicsInfo, graphics_info: OxrGraphicsInfo,
} }
pub fn create_xr_session(
device: Res<RenderDevice>,
instance: Res<OxrInstance>,
create_info: NonSend<SessionConfigInfo>,
system_id: Res<OxrSystemId>,
mut chain: NonSendMut<OxrSessionCreateNextChain>,
mut commands: Commands,
) {
match init_xr_session(
device.wgpu_device(),
&instance,
**system_id,
&mut chain,
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. /// 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<MainWorld>) { pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld>) {
let Some(OxrRenderResources { let Some(OxrRenderResources {
@@ -501,86 +524,3 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
commands.insert_resource(images); commands.insert_resource(images);
commands.insert_resource(graphics_info); commands.insert_resource(graphics_info);
} }
/// Polls any OpenXR events and handles them accordingly
pub fn poll_events(
instance: Res<OxrInstance>,
mut status: ResMut<XrStatus>,
mut changed_event: EventWriter<XrStatusChanged>,
mut session_status_events: EventWriter<OxrSessionStatusEvent>,
mut overlay_writer: Option<ResMut<Events<OxrOverlaySessionEvent>>>,
) {
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()),
MainSessionVisibilityChangedEXTX(d) => {
if let Some(writer) = overlay_writer.as_mut() {
writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged {
visible: d.visible(),
flags: d.flags(),
});
} else {
warn!("Overlay Event Recieved without the OverlayPlugin being added!");
}
}
_ => {}
}
}
}
pub fn reset_per_frame_resources(mut cleanup: ResMut<OxrCleanupSession>) {
**cleanup = false;
}
pub fn destroy_xr_session(mut commands: Commands) {
commands.remove_resource::<OxrFrameWaiter>();
commands.remove_resource::<OxrSwapchainImages>();
commands.remove_resource::<OxrGraphicsInfo>();
}
pub fn destroy_xr_session_render(world: &mut World) {
world.remove_resource::<OxrSwapchain>();
world.remove_resource::<OxrFrameStream>();
world.remove_resource::<OxrSwapchainImages>();
world.remove_resource::<OxrGraphicsInfo>();
world.run_system_once(apply_deferred);
world.run_schedule(XrSessionExiting);
world.run_system_once(apply_deferred);
world.remove_resource::<OxrSession>();
world.insert_resource(OxrSessionStarted(false));
}

View File

@@ -1,7 +1,7 @@
// use actions::XrActionPlugin; // use actions::XrActionPlugin;
use bevy::{ use bevy::{
app::{PluginGroup, PluginGroupBuilder}, app::{PluginGroup, PluginGroupBuilder},
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin}, render::RenderPlugin,
utils::default, utils::default,
window::{PresentMode, Window, WindowPlugin}, window::{PresentMode, Window, WindowPlugin},
}; };
@@ -13,7 +13,6 @@ use render::OxrRenderPlugin;
use self::{ use self::{
features::{handtracking::HandTrackingPlugin, passthrough::OxrPassthroughPlugin}, features::{handtracking::HandTrackingPlugin, passthrough::OxrPassthroughPlugin},
reference_space::OxrReferenceSpacePlugin, reference_space::OxrReferenceSpacePlugin,
session::OxrSessionPlugin,
}; };
pub mod action_binding; pub mod action_binding;
@@ -31,17 +30,16 @@ pub mod reference_space;
pub mod render; pub mod render;
pub mod resources; pub mod resources;
pub mod session; pub mod session;
pub mod types;
pub mod spaces; pub mod spaces;
pub mod types;
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder { pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
plugins plugins
.build() .build()
.disable::<RenderPlugin>() .disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>() // .disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(XrSessionPlugin) .add_before::<RenderPlugin, _>(XrSessionPlugin { auto_handle: true })
.add_before::<RenderPlugin, _>(OxrInitPlugin::default()) .add_before::<RenderPlugin, _>(OxrInitPlugin::default())
.add(OxrSessionPlugin)
.add(OxrReferenceSpacePlugin::default()) .add(OxrReferenceSpacePlugin::default())
.add(OxrRenderPlugin) .add(OxrRenderPlugin)
.add(OxrPassthroughPlugin) .add(OxrPassthroughPlugin)

View File

@@ -1,18 +1,13 @@
use std::sync::Arc;
use bevy::{ use bevy::{
prelude::*, prelude::*,
render::{ render::{extract_resource::ExtractResourcePlugin, RenderApp},
extract_resource::{ExtractResource, ExtractResourcePlugin},
RenderApp,
}, utils::HashSet,
}; };
use bevy_xr::{ use bevy_xr::{
session::{XrSessionCreated, XrSessionExiting}, session::{XrCreateSession, XrDestroySession},
spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace}, spaces::{XrPrimaryReferenceSpace, XrReferenceSpace},
}; };
use crate::{resources::OxrInstance, session::OxrSession}; use crate::{init::create_xr_session, session::OxrSession};
pub struct OxrReferenceSpacePlugin { pub struct OxrReferenceSpacePlugin {
pub default_primary_ref_space: openxr::ReferenceSpaceType, pub default_primary_ref_space: openxr::ReferenceSpaceType,
@@ -25,46 +20,40 @@ impl Default for OxrReferenceSpacePlugin {
} }
} }
/// Resource specifying what the type should be for [`OxrPrimaryReferenceSpace`]. Set through [`OxrReferenceSpacePlugin`].
#[derive(Resource)] #[derive(Resource)]
struct OxrDefaultPrimaryReferenceSpaceType(openxr::ReferenceSpaceType); struct OxrDefaultPrimaryReferenceSpaceType(openxr::ReferenceSpaceType);
/// The Default Reference space used for locating things /// The Default Reference space used for locating things
// #[derive(Resource, Deref, ExtrctResource, Clone)] // #[derive(Resource, Deref, ExtrctResource, Clone)]
// pub struct OxrPrimaryReferenceSpace(pub Arc<openxr::Space>); // pub struct OxrPrimaryReferenceSpace(pub Arc<openxr::Space>);
/// The Reference space used for locating spaces on this entity /// The Reference space used for locating spaces on this entity
// #[derive(Component)] #[derive(Component)]
// pub struct OxrReferenceSpace(pub openxr::Space); pub struct OxrReferenceSpace(pub openxr::Space);
impl Plugin for OxrReferenceSpacePlugin { impl Plugin for OxrReferenceSpacePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(OxrDefaultPrimaryReferenceSpaceType( app.add_plugins(ExtractResourcePlugin::<XrPrimaryReferenceSpace>::default())
self.default_primary_ref_space, .insert_resource(OxrDefaultPrimaryReferenceSpaceType(
)); self.default_primary_ref_space,
app.add_plugins(ExtractResourcePlugin::<XrPrimaryReferenceSpace>::default()); ))
app.add_systems(XrSessionCreated, set_primary_ref_space); .add_systems(
app.add_systems(XrSessionExiting, cleanup); XrCreateSession,
app.sub_app_mut(RenderApp) set_primary_ref_space.after(create_xr_session),
.add_systems(XrSessionExiting, cleanup); )
.add_systems(XrDestroySession, cleanup);
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(XrDestroySession, cleanup);
} }
} }
fn cleanup( fn cleanup(query: Query<Entity, With<XrReferenceSpace>>, mut cmds: Commands) {
query: Query<(Entity, &XrReferenceSpace)>,
mut cmds: Commands,
instance: Res<OxrInstance>,
ref_space: Option<Res<XrPrimaryReferenceSpace>>,
) {
let mut to_destroy = HashSet::<XrSpace>::new();
if let Some(space) = ref_space {
to_destroy.insert(***space);
}
cmds.remove_resource::<XrPrimaryReferenceSpace>(); cmds.remove_resource::<XrPrimaryReferenceSpace>();
for (e, space) in &query { for e in &query {
cmds.entity(e).remove::<XrReferenceSpace>(); cmds.entity(e).remove::<XrReferenceSpace>();
to_destroy.insert(**space);
}
for space in to_destroy.into_iter() {
let _ = instance.destroy_space(space);
} }
} }

View File

@@ -1,31 +1,26 @@
use bevy::{ use bevy::{
app::{MainScheduleOrder, SubApp}, ecs::query::QuerySingleError,
ecs::{query::QuerySingleError, schedule::MainThreadExecutor},
prelude::*, prelude::*,
render::{ render::{
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget}, camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget},
extract_resource::ExtractResourcePlugin, extract_resource::ExtractResourcePlugin,
pipelined_rendering::{PipelinedRenderingPlugin, RenderAppChannels, RenderExtractApp}, pipelined_rendering::PipelinedRenderingPlugin,
view::ExtractedView, view::ExtractedView,
Render, RenderApp, RenderSet, Render, RenderApp,
}, },
tasks::ComputeTaskPool,
transform::TransformSystem, transform::TransformSystem,
}; };
use bevy_xr::{ use bevy_xr::{
camera::{XrCamera, XrCameraBundle, XrProjection}, camera::{XrCamera, XrCameraBundle, XrProjection},
session::{session_running, XrSessionExiting}, session::{
XrDestroySession, XrFirst, XrHandleEvents, XrRenderSet, XrRootTransform, XrTrackingRoot,
},
spaces::XrPrimaryReferenceSpace, spaces::XrPrimaryReferenceSpace,
}; };
use openxr::ViewStateFlags; use openxr::ViewStateFlags;
use crate::resources::*; use crate::{init::should_run_frame_loop, resources::*};
use crate::spaces::OxrSpaceExt as _; use crate::{layer_builder::ProjectionLayer, session::OxrSession};
use crate::{
init::{session_started, OxrHandleEvents, OxrLast, OxrTrackingRoot},
layer_builder::ProjectionLayer,
session::OxrSession,
};
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub struct OxrRenderBegin; pub struct OxrRenderBegin;
@@ -40,66 +35,42 @@ impl Plugin for OxrRenderPlugin {
if app.is_plugin_added::<PipelinedRenderingPlugin>() { if app.is_plugin_added::<PipelinedRenderingPlugin>() {
app.init_resource::<Pipelined>(); app.init_resource::<Pipelined>();
let mut schedule_order = app.world.resource_mut::<MainScheduleOrder>(); // if let Some(sub_app) = app.remove_sub_app(RenderExtractApp) {
// app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app.app, update_rendering));
if let Some(pos) = schedule_order // }
.labels
.iter()
.position(|label| (**label).eq(&OxrLast))
{
schedule_order.labels.remove(pos);
}
if let Some(sub_app) = app.remove_sub_app(RenderExtractApp) {
app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app.app, update_rendering));
}
} }
app.add_plugins(( app.add_plugins((
ExtractResourcePlugin::<OxrFrameState>::default(), ExtractResourcePlugin::<OxrFrameState>::default(),
ExtractResourcePlugin::<OxrRootTransform>::default(),
ExtractResourcePlugin::<OxrGraphicsInfo>::default(), ExtractResourcePlugin::<OxrGraphicsInfo>::default(),
ExtractResourcePlugin::<OxrSwapchainImages>::default(), ExtractResourcePlugin::<OxrSwapchainImages>::default(),
ExtractResourcePlugin::<OxrViews>::default(), ExtractResourcePlugin::<OxrViews>::default(),
)) ))
.add_systems(XrDestroySession, clean_views)
.add_systems( .add_systems(
PreUpdate, XrFirst,
(locate_views, update_views)
.chain()
// .run_if(should_render)
.run_if(session_started),
)
.add_systems(
OxrLast,
( (
wait_frame.run_if(session_started), wait_frame.run_if(should_run_frame_loop),
update_cameras.run_if(session_started), update_cameras.run_if(should_run_frame_loop),
init_views.run_if(resource_added::<OxrSession>), init_views.run_if(resource_added::<OxrSession>),
apply_deferred,
) )
.chain() .chain()
.after(OxrHandleEvents), .after(XrHandleEvents),
)
.add_systems(
PostUpdate,
(locate_views, update_views)
.before(TransformSystem::TransformPropagate)
.chain()
// .run_if(should_render)
.run_if(should_run_frame_loop),
) )
.add_systems(XrSessionExiting, clean_views)
.init_resource::<OxrViews>(); .init_resource::<OxrViews>();
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.configure_sets( .add_systems(XrDestroySession, clean_views)
Render,
OxrRenderBegin
.after(RenderSet::ExtractCommands)
.before(RenderSet::PrepareAssets)
.before(RenderSet::ManageViews),
)
.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( .add_systems(
Render, Render,
( (
@@ -110,91 +81,47 @@ impl Plugin for OxrRenderPlugin {
wait_image, wait_image,
) )
.chain() .chain()
.run_if(session_started) .in_set(XrRenderSet::PreRender)
.in_set(OxrRenderBegin), .run_if(should_run_frame_loop),
) )
.add_systems( .add_systems(
Render, Render,
(release_image, end_frame) (release_image, end_frame)
.chain() .chain()
.run_if(session_started) .run_if(should_run_frame_loop)
.in_set(OxrRenderEnd), .in_set(XrRenderSet::PostRender),
) )
.insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)])); .insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)]));
// .add_systems(
// XrSessionExiting,
// (
// |mut cmds: Commands| cmds.remove_resource::<OxrRenderLayers>(),
// clean_views,
// ),
// );
// app.add_systems(
// PreUpdate,
// (
// init_views.run_if(resource_added::<OxrGraphicsInfo>),
// 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::<OxrPrimaryReferenceSpace>),
// 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)]));
} }
} }
// This function waits for the rendering world to be received, // fn update_rendering(app_world: &mut World, _sub_app: &mut App) {
// runs extract, and then sends the rendering world back to the render thread. // app_world.resource_scope(|world, main_thread_executor: Mut<MainThreadExecutor>| {
// // world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
// modified pipelined rendering extract function // // we use a scope here to run any main thread tasks that the render world still needs to run
fn update_rendering(app_world: &mut World, _sub_app: &mut App) { // // while we wait for the render world to be received.
app_world.resource_scope(|world, main_thread_executor: Mut<MainThreadExecutor>| { // let mut render_app = ComputeTaskPool::get()
world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| { // .scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
// we use a scope here to run any main thread tasks that the render world still needs to run // s.spawn(async { render_channels.recv().await });
// while we wait for the render world to be received. // })
let mut render_app = ComputeTaskPool::get() // .pop()
.scope_with_executor(true, Some(&*main_thread_executor.0), |s| { // .unwrap();
s.spawn(async { render_channels.recv().await });
})
.pop()
.unwrap();
world.run_schedule(OxrLast); // if matches!(world.resource::<XrState>(), XrState::Stopping) {
// world.run_schedule(XrEndSession);
// }
render_app.extract(world); // if matches!(world.resource::<XrState>(), XrState::Exiting { .. }) {
// world.run_schedule(XrDestroySession);
// render_app.app.world.run_schedule(XrDestroySession);
// }
render_channels.send_blocking(render_app); // render_app.extract(world);
});
}); // render_channels.send_blocking(render_app);
} // });
// });
// }
pub const XR_TEXTURE_INDEX: u32 = 3383858418; pub const XR_TEXTURE_INDEX: u32 = 3383858418;
@@ -209,13 +136,11 @@ pub fn clean_views(
} }
} }
// 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( pub fn init_views(
graphics_info: Res<OxrGraphicsInfo>, graphics_info: Res<OxrGraphicsInfo>,
mut manual_texture_views: ResMut<ManualTextureViews>, mut manual_texture_views: ResMut<ManualTextureViews>,
swapchain_images: Res<OxrSwapchainImages>, swapchain_images: Res<OxrSwapchainImages>,
root: Query<Entity, With<OxrTrackingRoot>>, root: Query<Entity, With<XrTrackingRoot>>,
mut commands: Commands, mut commands: Commands,
) { ) {
let _span = info_span!("xr_init_views"); let _span = info_span!("xr_init_views");
@@ -227,28 +152,24 @@ pub fn init_views(
add_texture_view(&mut manual_texture_views, temp_tex, &graphics_info, index); add_texture_view(&mut manual_texture_views, temp_tex, &graphics_info, index);
let cam = commands let cam = commands
.spawn(( .spawn((XrCameraBundle {
XrCameraBundle { camera: Camera {
camera: Camera { target: RenderTarget::TextureView(view_handle),
target: RenderTarget::TextureView(view_handle),
..Default::default()
},
view: XrCamera(index),
..Default::default() ..Default::default()
}, },
// OpenXrTracker, view: XrCamera(index),
// XrRoot::default(), ..Default::default()
)) },))
.id(); .id();
match root.get_single() { match root.get_single() {
Ok(root) => { Ok(root) => {
commands.entity(root).add_child(cam); commands.entity(root).add_child(cam);
} }
Err(QuerySingleError::NoEntities(_)) => { Err(QuerySingleError::NoEntities(_)) => {
warn!("No OxrTrackingRoot!"); warn!("No XrTrackingRoot!");
} }
Err(QuerySingleError::MultipleEntities(_)) => { Err(QuerySingleError::MultipleEntities(_)) => {
warn!("Multiple OxrTrackingRoots! this is not allowed"); warn!("Multiple XrTrackingRoots! this is not allowed");
} }
} }
} }
@@ -257,8 +178,6 @@ pub fn init_views(
pub fn wait_frame(mut frame_waiter: ResMut<OxrFrameWaiter>, mut commands: Commands) { pub fn wait_frame(mut frame_waiter: ResMut<OxrFrameWaiter>, mut commands: Commands) {
let _span = info_span!("xr_wait_frame"); let _span = info_span!("xr_wait_frame");
let state = frame_waiter.wait().expect("Failed to wait frame"); 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(OxrFrameState(state)); commands.insert_resource(OxrFrameState(state));
} }
@@ -344,7 +263,7 @@ pub fn update_views(
pub fn update_views_render_world( pub fn update_views_render_world(
views: Res<OxrViews>, views: Res<OxrViews>,
root: Res<OxrRootTransform>, root: Res<XrRootTransform>,
mut query: Query<(&mut ExtractedView, &XrCamera)>, mut query: Query<(&mut ExtractedView, &XrCamera)>,
) { ) {
for (mut extracted_view, camera) in query.iter_mut() { for (mut extracted_view, camera) in query.iter_mut() {
@@ -504,7 +423,8 @@ pub fn add_texture_view(
} }
pub fn begin_frame(mut frame_stream: ResMut<OxrFrameStream>) { pub fn begin_frame(mut frame_stream: ResMut<OxrFrameStream>) {
frame_stream.begin().expect("Failed to begin frame") let _span = info_span!("xr_begin_frame");
frame_stream.begin().expect("Failed to begin frame");
} }
pub fn release_image(mut swapchain: ResMut<OxrSwapchain>) { pub fn release_image(mut swapchain: ResMut<OxrSwapchain>) {
@@ -537,12 +457,12 @@ pub fn end_frame(world: &mut World) {
} }
} }
let layers: Vec<_> = layers.iter().map(Box::as_ref).collect(); let layers: Vec<_> = layers.iter().map(Box::as_ref).collect();
frame_stream if let Err(e) = frame_stream.end(
.end( frame_state.predicted_display_time,
frame_state.predicted_display_time, world.resource::<OxrGraphicsInfo>().blend_mode,
world.resource::<OxrGraphicsInfo>().blend_mode, &layers,
&layers, ) {
) error!("Failed to end frame stream: {e}");
.expect("Failed to end frame"); }
}); });
} }

View File

@@ -1,5 +1,3 @@
use std::sync::Arc;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResource; use bevy::render::extract_resource::ExtractResource;
@@ -357,14 +355,6 @@ pub struct OxrSessionStarted(pub bool);
#[derive(Clone, Deref, DerefMut, Resource, ExtractResource)] #[derive(Clone, Deref, DerefMut, Resource, ExtractResource)]
pub struct OxrFrameState(pub openxr::FrameState); 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)]
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 /// Instructs systems to add display period
#[derive(Clone, Copy, Default, Resource)] #[derive(Clone, Copy, Default, Resource)]
pub struct Pipelined; pub struct Pipelined;

View File

@@ -1,101 +1,13 @@
use std::ffi::c_void; use std::ffi::c_void;
use crate::init::{OxrHandleEvents, OxrLast};
use crate::next_chain::{OxrNextChain, OxrNextChainStructBase, OxrNextChainStructProvider}; use crate::next_chain::{OxrNextChain, OxrNextChainStructBase, OxrNextChainStructProvider};
use crate::resources::{ use crate::resources::{OxrPassthrough, OxrPassthroughLayer, OxrSwapchain};
OxrCleanupSession, OxrPassthrough, OxrPassthroughLayer, OxrSessionStarted, OxrSwapchain,
};
use crate::types::{Result, SwapchainCreateInfo}; use crate::types::{Result, SwapchainCreateInfo};
use bevy::app::AppExit;
use bevy::ecs::event::ManualEventReader;
use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::RenderApp;
use bevy_xr::session::{
session_running, status_changed_to, XrSessionCreated, XrSessionExiting, XrStatus,
};
use openxr::AnyGraphics; use openxr::AnyGraphics;
use crate::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap}; 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.init_non_send_resource::<OxrSessionCreateNextChain>();
app.add_event::<OxrSessionStatusEvent>();
app.add_systems(OxrLast, run_session_status_schedules.after(OxrHandleEvents));
app.add_systems(
OxrLast,
exits_session_on_app_exit
.before(OxrHandleEvents)
.run_if(on_event::<AppExit>().and_then(session_running)),
);
app.add_systems(XrSessionExiting, clean_session);
app.sub_app_mut(RenderApp)
.add_systems(XrSessionExiting, |mut cmds: Commands| {
cmds.remove_resource::<OxrCleanupSession>();
});
app.add_systems(
PreUpdate,
handle_stopping_state.run_if(status_changed_to(XrStatus::Stopping)),
);
}
}
fn exits_session_on_app_exit(session: Res<OxrSession>) {
session.request_exit().unwrap()
}
fn handle_stopping_state(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
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::<XrStatus>().unwrap() = XrStatus::Available;
}
#[derive(Resource, Default)]
struct SessionStatusReader(ManualEventReader<OxrSessionStatusEvent>);
fn run_session_status_schedules(world: &mut World) {
let mut reader = world
.remove_resource::<SessionStatusReader>()
.unwrap_or_default();
let events = reader
.0
.read(
world
.get_resource::<Events<OxrSessionStatusEvent>>()
.unwrap(),
)
.copied()
.collect::<Vec<_>>();
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::<OxrSession>();
}
}
}
world.insert_resource(reader);
}
/// Graphics agnostic wrapper around [openxr::Session]. /// Graphics agnostic wrapper around [openxr::Session].
/// ///
/// See [`openxr::Session`] for other available methods. /// See [`openxr::Session`] for other available methods.

View File

@@ -2,7 +2,7 @@ use std::{mem::MaybeUninit, ptr, sync::Mutex};
use bevy::{prelude::*, utils::hashbrown::HashSet}; use bevy::{prelude::*, utils::hashbrown::HashSet};
use bevy_xr::{ use bevy_xr::{
session::{session_available, session_running, XrSessionExiting}, session::{session_available, session_running, XrFirst, XrHandleEvents},
spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrSpatialOffset}, spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrSpatialOffset},
types::XrPose, types::XrPose,
}; };
@@ -13,7 +13,6 @@ use openxr::{
use crate::{ use crate::{
helper_traits::{ToPosef, ToQuat, ToVec3}, helper_traits::{ToPosef, ToQuat, ToVec3},
init::{OxrHandleEvents, OxrLast},
resources::{OxrFrameState, OxrInstance, Pipelined}, resources::{OxrFrameState, OxrInstance, Pipelined},
session::OxrSession, session::OxrSession,
}; };
@@ -21,7 +20,7 @@ use crate::{
#[derive(SystemSet, Hash, Debug, Clone, Copy, PartialEq, Eq)] #[derive(SystemSet, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct OxrSpaceSyncSet; pub struct OxrSpaceSyncSet;
/// VERY IMPORTENT!! only disable when you know what you are doing /// VERY IMPORTANT!! only disable when you know what you are doing
pub struct OxrSpacePatchingPlugin; pub struct OxrSpacePatchingPlugin;
impl Plugin for OxrSpacePatchingPlugin { impl Plugin for OxrSpacePatchingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@@ -31,30 +30,14 @@ impl Plugin for OxrSpacePatchingPlugin {
pub struct OxrSpatialPlugin; pub struct OxrSpatialPlugin;
impl Plugin for OxrSpatialPlugin { impl Plugin for OxrSpatialPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<XrDestroySpace>(); app.add_event::<XrDestroySpace>()
app.add_systems(OxrLast, destroy_space_event.before(OxrHandleEvents)); .add_systems(XrFirst, destroy_space_event.before(XrHandleEvents))
app.add_systems(XrSessionExiting, destroy_space_components); .add_systems(
app.add_systems( PreUpdate,
PreUpdate, update_space_transforms
update_space_transforms .in_set(OxrSpaceSyncSet)
.in_set(OxrSpaceSyncSet) .run_if(session_running),
.run_if(session_running), );
);
}
}
fn destroy_space_components(
query: Query<(Entity, &XrSpace)>,
mut cmds: Commands,
mut sender: EventWriter<XrDestroySpace>,
) {
let mut to_destroy = HashSet::<XrSpace>::new();
for (e, space) in &query {
to_destroy.insert(*space);
cmds.entity(e).remove::<XrSpace>();
}
for space in to_destroy.into_iter() {
sender.send(XrDestroySpace(space));
} }
} }

View File

@@ -1,147 +1,338 @@
use bevy::{ use bevy::app::{AppExit, MainScheduleOrder};
ecs::schedule::ScheduleLabel, prelude::*, render::extract_resource::ExtractResource, use bevy::ecs::schedule::ScheduleLabel;
render::RenderApp, use bevy::prelude::*;
}; use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin};
use bevy::render::{Render, RenderApp, RenderSet};
pub struct XrSessionPlugin; /// Event sent to instruct backends to create an XR session. Only works when the [`XrState`] is [`Available`](XrState::Available).
#[derive(Event, Clone, Copy, Default)]
pub struct XrCreateSessionEvent;
/// A schedule thats ran whenever an [`XrCreateSessionEvent`] is recieved while the [`XrState`] is [`Available`](XrState::Available)
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrCreateSession;
/// Event sent when [`XrCreateSession`] is ran
#[derive(Event, Clone, Copy, Default)]
pub struct XrSessionCreatedEvent;
/// Event sent to instruct backends to destroy an XR session. Only works when the [`XrState`] is [`Exiting`](XrState::Exiting).
/// If you would like to request that a running session be destroyed, send the [`XrRequestExitEvent`] instead.
#[derive(Event, Clone, Copy, Default)]
pub struct XrDestroySessionEvent;
/// Resource flag thats inserted into the world and extracted to the render world to inform any session resources in the render world to drop.
#[derive(Resource, ExtractResource, Clone, Copy, Default)]
pub struct XrDestroySessionRender;
/// Schedule thats ran whenever an [`XrDestroySessionEvent`] is recieved while the [`XrState`] is [`Exiting`](XrState::Exiting).
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrDestroySession;
/// Event sent to instruct backends to begin an XR session. Only works when the [`XrState`] is [`Ready`](XrState::Ready).
#[derive(Event, Clone, Copy, Default)]
pub struct XrBeginSessionEvent;
/// Schedule thats ran whenever an [`XrBeginSessionEvent`] is recieved while the [`XrState`] is [`Ready`](XrState::Ready).
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrBeginSession;
/// Event sent to backends to end an XR session. Only works when the [`XrState`] is [`Stopping`](XrState::Stopping).
#[derive(Event, Clone, Copy, Default)]
pub struct XrEndSessionEvent;
/// Schedule thats rna whenever an [`XrEndSessionEvent`] is recieved while the [`XrState`] is [`Stopping`](XrState::Stopping).
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrEndSession;
/// Event sent to backends to request the [`XrState`] proceed to [`Exiting`](XrState::Exiting) and for the session to be exited. Can be called at any time a session exists.
#[derive(Event, Clone, Copy, Default)]
pub struct XrRequestExitEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrRequestExit;
/// Schedule ran before [`First`] to handle XR events.
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrFirst;
/// System set for systems related to handling XR session events and updating the [`XrState`]
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub struct XrHandleEvents;
/// System sets ran in the render world for XR.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum XrRenderSet {
/// Ran before [`XrRenderSet::PreRender`] but after [`RenderSet::ExtractCommands`].
HandleEvents,
/// For any XR systems needing to be ran before rendering begins.
/// Ran after [`XrRenderSet::HandleEvents`] but before every render set except [`RenderSet::ExtractCommands`].
PreRender,
/// For any XR systems needing to be ran after [`RenderSet::Render`] but before [`RenderSet::Cleanup`].
PostRender,
}
/// The root transform's global position for late latching in the render world.
#[derive(ExtractResource, Resource, Clone, Copy, Default)]
pub struct XrRootTransform(pub GlobalTransform);
/// Component used to specify the entity we should use as the tracking root.
#[derive(Component)]
pub struct XrTrackingRoot;
pub struct XrSessionPlugin {
pub auto_handle: bool,
}
impl Plugin for XrSessionPlugin { impl Plugin for XrSessionPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<XrCreateSessionWhenAvailabe>(); let mut xr_first = Schedule::new(XrFirst);
app.init_schedule(XrSessionCreated); xr_first.set_executor_kind(bevy::ecs::schedule::ExecutorKind::Simple);
app.init_schedule(XrSessionExiting); app.add_event::<XrCreateSessionEvent>()
app.add_event::<CreateXrSession>() .add_event::<XrDestroySessionEvent>()
.add_event::<DestroyXrSession>() .add_event::<XrBeginSessionEvent>()
.add_event::<BeginXrSession>() .add_event::<XrEndSessionEvent>()
.add_event::<EndXrSession>() .add_event::<XrRequestExitEvent>()
.add_event::<XrStatusChanged>() .add_event::<XrStateChanged>()
.add_event::<XrSessionCreatedEvent>()
.init_schedule(XrCreateSession)
.init_schedule(XrDestroySession)
.init_schedule(XrBeginSession)
.init_schedule(XrEndSession)
.init_schedule(XrRequestExit)
.add_schedule(xr_first)
.add_systems( .add_systems(
PreUpdate, XrFirst,
handle_session.run_if(resource_exists::<XrStatus>), (
exits_session_on_app_exit
.run_if(on_event::<AppExit>())
.run_if(session_created),
reset_per_frame_resources,
run_xr_create_session
.run_if(state_equals(XrState::Available))
.run_if(on_event::<XrCreateSessionEvent>()),
run_xr_destroy_session
.run_if(state_matches!(XrState::Exiting { .. }))
.run_if(on_event::<XrDestroySessionEvent>()),
run_xr_begin_session
.run_if(state_equals(XrState::Ready))
.run_if(on_event::<XrBeginSessionEvent>()),
run_xr_end_session
.run_if(state_equals(XrState::Stopping))
.run_if(on_event::<XrEndSessionEvent>()),
run_xr_request_exit
.run_if(session_created)
.run_if(on_event::<XrRequestExitEvent>()),
)
.chain()
.in_set(XrHandleEvents),
); );
}
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 app.world
/// sent .resource_mut::<MainScheduleOrder>()
#[derive(Clone, Copy, Resource, Deref, DerefMut)] .labels
pub struct XrCreateSessionWhenAvailabe(pub bool); .insert(0, XrFirst.intern());
impl Default for XrCreateSessionWhenAvailabe { if self.auto_handle {
fn default() -> Self { app.add_systems(PreUpdate, auto_handle_session);
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);
#[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)]
#[repr(u8)]
pub enum XrStatus {
/// An XR session is not available here
Unavailable,
/// An XR session is available and ready to be created with a [`CreateXrSession`] event.
Available,
/// An XR session is created but not ready to begin.
Idle,
/// An XR session has been created and is ready to start rendering with a [`BeginXrSession`] event, or
Ready,
/// The XR session is running and can be stopped with an [`EndXrSession`] event.
Running,
/// The XR session is in the process of being stopped.
Stopping,
/// The XR session is in the process of being destroyed
Exiting,
}
pub fn handle_session(
current_status: Res<XrStatus>,
mut previous_status: Local<Option<XrStatus>>,
mut create_session: EventWriter<CreateXrSession>,
mut begin_session: EventWriter<BeginXrSession>,
// mut end_session: EventWriter<EndXrSession>,
mut destroy_session: EventWriter<DestroyXrSession>,
mut should_start_session: ResMut<XrCreateSessionWhenAvailabe>,
) {
if *previous_status != Some(*current_status) {
match *current_status {
XrStatus::Unavailable => {}
XrStatus::Available => {
if **should_start_session {
create_session.send_default();
**should_start_session = false;
}
}
XrStatus::Idle => {}
XrStatus::Ready => {
begin_session.send_default();
}
XrStatus::Running => {}
XrStatus::Stopping => {
// end_session.send_default();
}
XrStatus::Exiting => {
destroy_session.send_default();
}
} }
} }
*previous_status = Some(*current_status);
fn finish(&self, app: &mut App) {
if app.get_sub_app(RenderApp).is_err() {
return;
}
app.add_plugins((
ExtractResourcePlugin::<XrState>::default(),
ExtractResourcePlugin::<XrDestroySessionRender>::default(),
ExtractResourcePlugin::<XrRootTransform>::default(),
))
.init_resource::<XrRootTransform>()
.add_systems(
PostUpdate,
update_root_transform.after(TransformSystem::TransformPropagate),
)
.add_systems(
XrFirst,
exits_session_on_app_exit
.before(XrHandleEvents)
.run_if(on_event::<AppExit>().and_then(session_running)),
);
let render_app = app.sub_app_mut(RenderApp);
render_app
.init_schedule(XrDestroySession)
.init_resource::<XrRootTransform>()
.configure_sets(
Render,
(XrRenderSet::HandleEvents, XrRenderSet::PreRender).chain(),
)
.configure_sets(
Render,
XrRenderSet::HandleEvents.after(RenderSet::ExtractCommands),
)
.configure_sets(
Render,
XrRenderSet::PreRender
.before(RenderSet::ManageViews)
.before(RenderSet::PrepareAssets),
)
.configure_sets(
Render,
XrRenderSet::PostRender
.after(RenderSet::Render)
.before(RenderSet::Cleanup),
)
.add_systems(
Render,
(
run_xr_destroy_session
.run_if(resource_exists::<XrDestroySessionRender>)
.in_set(XrRenderSet::HandleEvents),
reset_per_frame_resources.in_set(RenderSet::Cleanup),
),
);
}
}
fn exits_session_on_app_exit(mut request_exit: EventWriter<XrRequestExitEvent>) {
request_exit.send_default();
}
/// Event sent by backends whenever [`XrState`] is changed.
#[derive(Event, Clone, Copy, Deref)]
pub struct XrStateChanged(pub XrState);
/// A resource in the main world and render world representing the current session state.
#[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)]
#[repr(u8)]
pub enum XrState {
/// An XR session is not available here
Unavailable,
/// An XR session is available and ready to be created with an [`XrCreateSessionEvent`].
Available,
/// An XR session is created but not ready to begin. Backends are not required to use this state.
Idle,
/// An XR session has been created and is ready to start rendering with an [`XrBeginSessionEvent`].
Ready,
/// The XR session is running and can be stopped with an [`XrEndSessionEvent`].
Running,
/// The runtime has requested that the session should be ended with an [`XrEndSessionEvent`].
Stopping,
/// The XR session should be destroyed with an [`XrDestroySessionEvent`].
Exiting {
/// Whether we should automatically restart the session
should_restart: bool,
},
}
pub fn run_xr_create_session(world: &mut World) {
world.run_schedule(XrCreateSession);
world.send_event(XrSessionCreatedEvent);
}
pub fn run_xr_destroy_session(world: &mut World) {
world.run_schedule(XrDestroySession);
world.insert_resource(XrDestroySessionRender);
}
pub fn run_xr_begin_session(world: &mut World) {
world.run_schedule(XrBeginSession);
}
pub fn run_xr_end_session(world: &mut World) {
world.run_schedule(XrEndSession);
}
pub fn run_xr_request_exit(world: &mut World) {
world.run_schedule(XrRequestExit);
}
pub fn reset_per_frame_resources(world: &mut World) {
world.remove_resource::<XrDestroySessionRender>();
}
pub fn auto_handle_session(
mut state_changed: EventReader<XrStateChanged>,
mut create_session: EventWriter<XrCreateSessionEvent>,
mut begin_session: EventWriter<XrBeginSessionEvent>,
mut end_session: EventWriter<XrEndSessionEvent>,
mut destroy_session: EventWriter<XrDestroySessionEvent>,
) {
for XrStateChanged(state) in state_changed.read() {
match state {
XrState::Available => {
create_session.send_default();
}
XrState::Ready => {
begin_session.send_default();
}
XrState::Stopping => {
end_session.send_default();
}
XrState::Exiting { .. } => {
destroy_session.send_default();
}
_ => (),
}
}
}
pub fn update_root_transform(
mut root_transform: ResMut<XrRootTransform>,
root: Query<&GlobalTransform, With<XrTrackingRoot>>,
) {
let Ok(transform) = root.get_single() else {
return;
};
root_transform.0 = *transform;
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`]. /// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`].
pub fn status_changed_to( pub fn status_changed_to(
status: XrStatus, status: XrState,
) -> impl FnMut(EventReader<XrStatusChanged>) -> bool + Clone { ) -> impl FnMut(EventReader<XrStateChanged>) -> bool + Clone {
move |mut reader: EventReader<XrStatusChanged>| { move |mut reader: EventReader<XrStateChanged>| {
reader.read().any(|new_status| new_status.0 == status) reader.read().any(|new_status| new_status.0 == status)
} }
} }
/// 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). /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrState`] exists and isn't [`Unavailable`](XrStatus::Unavailable).
pub fn session_available(status: Option<Res<XrStatus>>) -> bool { pub fn session_available(status: Option<Res<XrState>>) -> bool {
status.is_some_and(|s| *s != XrStatus::Unavailable) status.is_some_and(|s| *s != XrState::Unavailable)
}
pub fn session_created(status: Option<Res<XrState>>) -> bool {
!matches!(
status.as_deref(),
Some(XrState::Unavailable | XrState::Available) | None
)
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running
pub fn session_ready_or_running(status: Option<Res<XrStatus>>) -> bool { pub fn session_ready_or_running(status: Option<Res<XrState>>) -> bool {
matches!(status.as_deref(), Some(XrStatus::Ready | XrStatus::Running)) matches!(status.as_deref(), Some(XrState::Ready | XrState::Running))
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is running /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is running
pub fn session_running(status: Option<Res<XrStatus>>) -> bool { pub fn session_running(status: Option<Res<XrState>>) -> bool {
matches!(status.as_deref(), Some(XrStatus::Running)) matches!(status.as_deref(), Some(XrState::Running))
} }
/// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if an the [`XrStatus`] is in a specific state /// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if the [`XrState`] is in a specific state
pub fn status_equals(status: XrStatus) -> impl FnMut(Option<Res<XrStatus>>) -> bool { pub fn state_equals(status: XrState) -> impl FnMut(Option<Res<XrState>>) -> bool {
move |state: Option<Res<XrStatus>>| state.is_some_and(|s| *s == status) move |state: Option<Res<XrState>>| 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. #[macro_export]
#[derive(Event, Clone, Copy, Default)] macro_rules! state_matches {
pub struct CreateXrSession; ($match:pat) => {
(|state: Option<Res<XrState>>| core::matches!(state.as_deref(), Some($match)))
};
}
/// Event sent to the backends to destroy an XR session. use bevy::transform::TransformSystem;
#[derive(Event, Clone, Copy, Default)] pub use state_matches;
pub struct DestroyXrSession;
/// Event sent to backends to begin an XR session. Should only be called in the [`XrStatus::Ready`] state.
#[derive(Event, Clone, Copy, Default)]
pub struct BeginXrSession;
/// Event sent to backends to end an XR session. Should only be called in the [`XrStatus::Running`] state.
#[derive(Event, Clone, Copy, Default)]
pub struct EndXrSession;

View File

@@ -1,10 +1,9 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_openxr::{ use bevy_openxr::{
helper_traits::{ToQuat, ToVec3}, helper_traits::{ToQuat, ToVec3},
init::OxrTrackingRoot,
resources::OxrViews, resources::OxrViews,
}; };
use bevy_xr::session::XrTrackingRoot;
pub struct TransformUtilitiesPlugin; pub struct TransformUtilitiesPlugin;
@@ -24,7 +23,7 @@ pub struct SnapToRotation(pub Quat);
pub struct SnapToPosition(pub Vec3); pub struct SnapToPosition(pub Vec3);
pub fn handle_transform_events( pub fn handle_transform_events(
mut root_query: Query<&mut Transform, With<OxrTrackingRoot>>, mut root_query: Query<&mut Transform, With<XrTrackingRoot>>,
views: ResMut<OxrViews>, views: ResMut<OxrViews>,
mut position_reader: EventReader<SnapToPosition>, mut position_reader: EventReader<SnapToPosition>,
mut rotation_reader: EventReader<SnapToRotation>, mut rotation_reader: EventReader<SnapToRotation>,
@@ -38,19 +37,22 @@ pub fn handle_transform_events(
//we want the view translation with a height of zero for a few calculations //we want the view translation with a height of zero for a few calculations
let mut view_translation = view.pose.position.to_vec3(); let mut view_translation = view.pose.position.to_vec3();
view_translation.y = 0.0; view_translation.y = 0.0;
//position //position
for position in position_reader.read() { for position in position_reader.read() {
root_transform.translation = position.0 - root_transform.rotation.mul_vec3(view_translation); root_transform.translation =
position.0 - root_transform.rotation.mul_vec3(view_translation);
} }
//rotation //rotation
let root_local = root_transform.translation.clone(); let root_local = root_transform.translation.clone();
let hmd_global = root_transform.rotation.mul_vec3(view_translation) + root_local; let hmd_global =
root_transform.rotation.mul_vec3(view_translation) + root_local;
let view_rot = view.pose.orientation.to_quat(); let view_rot = view.pose.orientation.to_quat();
let root_rot = root_transform.rotation; let root_rot = root_transform.rotation;
let view_global_rotation = root_rot.mul_quat(view_rot).normalize(); let view_global_rotation = root_rot.mul_quat(view_rot).normalize();
let (global_view_yaw, _pitch, _roll) = view_global_rotation.to_euler(bevy::math::EulerRot::YXZ); let (global_view_yaw, _pitch, _roll) =
view_global_rotation.to_euler(bevy::math::EulerRot::YXZ);
let up = Vec3::Y; let up = Vec3::Y;
for rotation in rotation_reader.read() { for rotation in rotation_reader.read() {
let (target_yaw, _pitch, _roll) = let (target_yaw, _pitch, _roll) =

View File

@@ -60,7 +60,7 @@ use bevy_openxr::{
resources::OxrInstance, session::OxrSession, resources::OxrInstance, session::OxrSession,
}; };
use bevy_xr::session::{session_available, session_running}; use bevy_xr::session::{session_available, session_running};
use openxr::{ActiveActionSet, Path, Vector2f}; use openxr::{Path, Vector2f};
use std::borrow::Cow; use std::borrow::Cow;
@@ -77,7 +77,6 @@ impl Plugin for XRUtilsActionsPlugin {
); );
app.add_systems( app.add_systems(
Startup, Startup,
create_openxr_events create_openxr_events
.in_set(XRUtilsActionSystemSet::CreateEvents) .in_set(XRUtilsActionSystemSet::CreateEvents)
.run_if(session_available), .run_if(session_available),