Merge pull request #113 from awtterpip/judder-fix

Judder fix
This commit is contained in:
piper
2024-06-02 23:21:20 +02:00
committed by GitHub
8 changed files with 360 additions and 368 deletions

View File

@@ -1,4 +1,4 @@
use crate::{init::OxrPreUpdateSet, resources::OxrSession}; use crate::resources::OxrSession;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xr::session::session_running; use bevy_xr::session::session_running;
@@ -7,9 +7,7 @@ impl Plugin for OxrActionSyncingPlugin {
app.add_event::<OxrSyncActionSet>(); app.add_event::<OxrSyncActionSet>();
app.add_systems( app.add_systems(
PreUpdate, PreUpdate,
sync_sets sync_sets.run_if(session_running), // .in_set(OxrPreUpdateSet::SyncActions),
.run_if(session_running)
.in_set(OxrPreUpdateSet::SyncActions),
); );
} }
} }

View File

@@ -6,10 +6,11 @@ use bevy_xr::{
}; };
use openxr::SpaceLocationFlags; use openxr::SpaceLocationFlags;
use crate::resources::Pipelined;
use crate::{ use crate::{
init::OxrTrackingRoot, init::OxrTrackingRoot,
reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace}, reference_space::{OxrPrimaryReferenceSpace, OxrReferenceSpace},
resources::{OxrSession, OxrTime}, resources::{OxrFrameState, OxrSession},
}; };
pub struct HandTrackingPlugin { pub struct HandTrackingPlugin {
@@ -132,18 +133,29 @@ pub struct OxrHandTracker(pub openxr::HandTracker);
fn locate_hands( fn locate_hands(
default_ref_space: Res<OxrPrimaryReferenceSpace>, default_ref_space: Res<OxrPrimaryReferenceSpace>,
time: Res<OxrTime>, frame_state: Res<OxrFrameState>,
tracker_query: Query<( tracker_query: Query<(
&OxrHandTracker, &OxrHandTracker,
Option<&OxrReferenceSpace>, Option<&OxrReferenceSpace>,
&OxrHandBoneEntities, &OxrHandBoneEntities,
)>, )>,
mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>, mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>,
pipelined: Option<Res<Pipelined>>,
) { ) {
for (tracker, ref_space, hand_entities) in &tracker_query { for (tracker, ref_space, hand_entities) in &tracker_query {
let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0); let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0);
// relate_hand_joints also provides velocities // relate_hand_joints also provides velocities
let joints = match ref_space.locate_hand_joints(tracker, **time) { let joints = match ref_space.locate_hand_joints(
tracker,
if pipelined.is_some() {
openxr::Time::from_nanos(
frame_state.predicted_display_time.as_nanos()
+ frame_state.predicted_display_period.as_nanos(),
)
} else {
frame_state.predicted_display_time
},
) {
Ok(Some(v)) => v, Ok(Some(v)) => v,
Ok(None) => continue, Ok(None) => continue,
Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => { Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => {

View File

@@ -1,3 +1,5 @@
use bevy::app::MainScheduleOrder;
use bevy::ecs::schedule::ScheduleLabel;
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;
@@ -14,37 +16,38 @@ use bevy::render::RenderSet;
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy::winit::UpdateMode; use bevy::winit::UpdateMode;
use bevy::winit::WinitSettings; use bevy::winit::WinitSettings;
use bevy_xr::session::handle_session;
use bevy_xr::session::session_available;
use bevy_xr::session::session_running; use bevy_xr::session::session_running;
use bevy_xr::session::status_equals; use bevy_xr::session::status_equals;
use bevy_xr::session::BeginXrSession; use bevy_xr::session::BeginXrSession;
use bevy_xr::session::CreateXrSession; use bevy_xr::session::CreateXrSession;
use bevy_xr::session::DestroyXrSession; use bevy_xr::session::DestroyXrSession;
use bevy_xr::session::EndXrSession; use bevy_xr::session::EndXrSession;
use bevy_xr::session::XrSharedStatus;
use bevy_xr::session::XrStatus; use bevy_xr::session::XrStatus;
use bevy_xr::session::XrStatusChanged; use bevy_xr::session::XrStatusChanged;
use crate::error::OxrError; use crate::error::OxrError;
use crate::graphics::*; use crate::graphics::*;
use crate::reference_space::OxrPrimaryReferenceSpace;
use crate::resources::*; use crate::resources::*;
use crate::types::*; use crate::types::*;
pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool { pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool {
started.is_some_and(|started| started.get()) started.is_some_and(|started| started.0)
} }
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)] pub fn should_render(frame_state: Option<Res<OxrFrameState>>) -> bool {
pub enum OxrPreUpdateSet { frame_state.is_some_and(|frame_state| frame_state.should_render)
PollEvents,
HandleEvents,
UpdateCriticalComponents,
UpdateNonCriticalComponents,
SyncActions,
} }
/// 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;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct OxrSessionCreated;
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,
@@ -93,8 +96,6 @@ impl Plugin for OxrInitPlugin {
WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance), WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance),
session_create_info, session_create_info,
)) => { )) => {
let status = XrSharedStatus::new(XrStatus::Available);
app.add_plugins(( app.add_plugins((
RenderPlugin { RenderPlugin {
render_creation: RenderCreation::manual( render_creation: RenderCreation::manual(
@@ -107,19 +108,17 @@ impl Plugin for OxrInitPlugin {
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation, synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
}, },
ExtractResourcePlugin::<OxrCleanupSession>::default(), ExtractResourcePlugin::<OxrCleanupSession>::default(),
ExtractResourcePlugin::<OxrTime>::default(), ExtractResourcePlugin::<XrStatus>::default(),
ExtractResourcePlugin::<OxrRootTransform>::default(), ExtractResourcePlugin::<OxrSessionStarted>::default(),
)) ))
.add_systems(First, reset_per_frame_resources) .init_schedule(OxrLast)
.init_schedule(OxrSessionCreated)
.add_systems( .add_systems(
PreUpdate, OxrLast,
( (
poll_events reset_per_frame_resources,
.run_if(session_available) poll_events,
.in_set(OxrPreUpdateSet::PollEvents), create_xr_session
(
(create_xr_session, apply_deferred)
.chain()
.run_if(on_event::<CreateXrSession>()) .run_if(on_event::<CreateXrSession>())
.run_if(status_equals(XrStatus::Available)), .run_if(status_equals(XrStatus::Available)),
begin_xr_session begin_xr_session
@@ -132,8 +131,8 @@ impl Plugin for OxrInitPlugin {
.run_if(on_event::<DestroyXrSession>()) .run_if(on_event::<DestroyXrSession>())
.run_if(status_equals(XrStatus::Exiting)), .run_if(status_equals(XrStatus::Exiting)),
) )
.in_set(OxrPreUpdateSet::HandleEvents), .chain()
), .in_set(OxrHandleEvents),
) )
.add_systems( .add_systems(
PostUpdate, PostUpdate,
@@ -141,25 +140,25 @@ impl Plugin for OxrInitPlugin {
) )
.insert_resource(instance.clone()) .insert_resource(instance.clone())
.insert_resource(system_id) .insert_resource(system_id)
.insert_resource(status.clone()) .insert_resource(XrStatus::Available)
.insert_resource(WinitSettings { .insert_resource(WinitSettings {
focused_mode: UpdateMode::Continuous, focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous,
}) })
.init_resource::<OxrCleanupSession>() .init_resource::<OxrCleanupSession>()
.init_resource::<OxrRootTransform>() .init_resource::<OxrRootTransform>()
.insert_non_send_resource(session_create_info); .insert_non_send_resource(session_create_info)
.configure_sets(OxrLast, OxrHandleEvents);
app.world
.resource_mut::<MainScheduleOrder>()
.insert_after(Last, OxrLast);
app.world app.world
.spawn((TransformBundle::default(), OxrTrackingRoot)); .spawn((TransformBundle::default(), OxrTrackingRoot));
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.insert_resource(instance)
.insert_resource(system_id)
.insert_resource(status)
.init_resource::<OxrRootTransform>()
.init_resource::<OxrCleanupSession>()
.add_systems( .add_systems(
Render, Render,
destroy_xr_session_render destroy_xr_session_render
@@ -169,39 +168,20 @@ impl Plugin for OxrInitPlugin {
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
transfer_xr_resources.run_if(not(session_running)), transfer_xr_resources.run_if(not(session_running)),
); )
.insert_resource(instance)
.insert_resource(system_id)
.init_resource::<OxrRootTransform>()
.init_resource::<OxrCleanupSession>();
} }
Err(e) => { Err(e) => {
error!("Failed to initialize openxr: {e}"); error!("Failed to initialize openxr: {e}");
let status = XrSharedStatus::new(XrStatus::Unavailable);
app.add_plugins(RenderPlugin::default()) app.add_plugins(RenderPlugin::default())
.insert_resource(status.clone()); .insert_resource(XrStatus::Unavailable);
let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(status);
} }
}; };
app.configure_sets( app.insert_resource(OxrSessionStarted(false));
PreUpdate,
(
OxrPreUpdateSet::PollEvents.before(handle_session),
OxrPreUpdateSet::HandleEvents.after(handle_session),
OxrPreUpdateSet::UpdateCriticalComponents,
OxrPreUpdateSet::UpdateNonCriticalComponents,
)
.chain(),
);
let session_started = OxrSessionStarted::default();
app.insert_resource(session_started.clone());
let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(session_started);
} }
} }
@@ -214,17 +194,12 @@ pub fn update_root_transform(
root_transform.0 = *transform; root_transform.0 = *transform;
} }
fn xr_entry() -> Result<OxrEntry> {
#[cfg(windows)]
let entry = openxr::Entry::linked();
#[cfg(not(windows))]
let entry = unsafe { openxr::Entry::load()? };
Ok(OxrEntry(entry))
}
impl OxrInitPlugin { impl OxrInitPlugin {
fn init_xr(&self) -> Result<(OxrInstance, OxrSystemId, WgpuGraphics, SessionConfigInfo)> { fn init_xr(&self) -> Result<(OxrInstance, OxrSystemId, WgpuGraphics, SessionConfigInfo)> {
let entry = xr_entry()?; #[cfg(windows)]
let entry = OxrEntry(openxr::Entry::linked());
#[cfg(not(windows))]
let entry = OxrEntry(unsafe { openxr::Entry::load()? });
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
entry.initialize_android_loader()?; entry.initialize_android_loader()?;
@@ -301,6 +276,51 @@ impl OxrInitPlugin {
} }
} }
pub fn reset_per_frame_resources(mut cleanup: ResMut<OxrCleanupSession>) {
**cleanup = false;
}
/// Polls any OpenXR events and handles them accordingly
pub fn poll_events(
instance: Res<OxrInstance>,
mut status: ResMut<XrStatus>,
mut changed_event: EventWriter<XrStatusChanged>,
) {
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 => XrStatus::Idle,
SessionState::READY => XrStatus::Ready,
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
XrStatus::Running
}
SessionState::STOPPING => XrStatus::Stopping,
SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting,
_ => unreachable!(),
};
changed_event.send(XrStatusChanged(new_status));
*status = new_status;
}
InstanceLossPending(_) => {}
EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()),
_ => {}
}
}
}
fn init_xr_session( fn init_xr_session(
device: &wgpu::Device, device: &wgpu::Device,
instance: &OxrInstance, instance: &OxrInstance,
@@ -435,6 +455,49 @@ fn init_xr_session(
)) ))
} }
pub fn create_xr_session(world: &mut World) {
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,
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,
});
world.run_schedule(OxrSessionCreated);
}
Err(e) => error!("Failed to initialize XrSession: {e}"),
}
}
pub fn begin_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
let _span = info_span!("xr_begin_session");
session
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
.expect("Failed to begin session");
session_started.0 = true;
}
pub fn end_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
let _span = info_span!("xr_end_session");
session.end().expect("Failed to end session");
session_started.0 = false;
}
/// 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 {
@@ -445,50 +508,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 commands: Commands,
) {
match init_xr_session(
device.wgpu_device(),
&instance,
**system_id,
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}"),
}
}
pub fn begin_xr_session(session: Res<OxrSession>, session_started: Res<OxrSessionStarted>) {
let _span = info_span!("xr_begin_session");
session
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
.expect("Failed to begin session");
session_started.set(true);
}
pub fn end_xr_session(session: Res<OxrSession>, session_started: Res<OxrSessionStarted>) {
let _span = info_span!("xr_end_session");
session.end().expect("Failed to end session");
session_started.set(false);
}
/// 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 {
@@ -509,64 +528,17 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
commands.insert_resource(graphics_info); commands.insert_resource(graphics_info);
} }
/// Polls any OpenXR events and handles them accordingly pub fn destroy_xr_session(world: &mut World) {
pub fn poll_events( world.remove_resource::<OxrSession>();
instance: Res<OxrInstance>, world.remove_resource::<OxrFrameWaiter>();
status: Res<XrSharedStatus>, world.remove_resource::<OxrSwapchainImages>();
mut changed_event: EventWriter<XrStatusChanged>, world.remove_resource::<OxrGraphicsInfo>();
) { world.insert_resource(OxrCleanupSession(true));
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 => XrStatus::Idle,
SessionState::READY => XrStatus::Ready,
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
XrStatus::Running
}
SessionState::STOPPING => XrStatus::Stopping,
SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting,
_ => unreachable!(),
};
changed_event.send(XrStatusChanged(new_status));
status.set(new_status);
}
InstanceLossPending(_) => {}
EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()),
_ => {}
}
}
}
pub fn reset_per_frame_resources(mut cleanup: ResMut<OxrCleanupSession>) {
**cleanup = false;
}
pub fn destroy_xr_session(mut commands: Commands) {
commands.remove_resource::<OxrSession>();
commands.remove_resource::<OxrFrameWaiter>();
commands.remove_resource::<OxrSwapchainImages>();
commands.remove_resource::<OxrGraphicsInfo>();
commands.remove_resource::<OxrPrimaryReferenceSpace>();
commands.insert_resource(OxrCleanupSession(true));
} }
pub fn destroy_xr_session_render(world: &mut World) { pub fn destroy_xr_session_render(world: &mut World) {
world.remove_resource::<OxrSwapchain>(); world.remove_resource::<OxrSwapchain>();
world.remove_resource::<OxrFrameStream>(); world.remove_resource::<OxrFrameStream>();
world.remove_resource::<OxrPrimaryReferenceSpace>();
world.remove_resource::<OxrSwapchainImages>(); world.remove_resource::<OxrSwapchainImages>();
world.remove_resource::<OxrGraphicsInfo>(); world.remove_resource::<OxrGraphicsInfo>();
world.remove_resource::<OxrSession>(); world.remove_resource::<OxrSession>();

View File

@@ -8,7 +8,7 @@ use crate::reference_space::OxrPrimaryReferenceSpace;
use crate::resources::*; use crate::resources::*;
pub trait LayerProvider { pub trait LayerProvider {
fn get<'a>(&'a self, world: &'a World) -> Box<dyn CompositionLayer + '_>; fn get<'a>(&'a self, world: &'a World) -> Option<Box<dyn CompositionLayer + '_>>;
} }
pub struct ProjectionLayer; pub struct ProjectionLayer;
@@ -16,11 +16,11 @@ pub struct ProjectionLayer;
pub struct PassthroughLayer; pub struct PassthroughLayer;
impl LayerProvider for ProjectionLayer { impl LayerProvider for ProjectionLayer {
fn get<'a>(&self, world: &'a World) -> Box<dyn CompositionLayer<'a> + 'a> { fn get<'a>(&self, world: &'a World) -> Option<Box<dyn CompositionLayer<'a> + 'a>> {
let stage = world.resource::<OxrPrimaryReferenceSpace>(); let stage = world.get_resource::<OxrPrimaryReferenceSpace>()?;
let openxr_views = world.resource::<OxrViews>(); let openxr_views = world.get_resource::<OxrViews>()?;
let swapchain = world.resource::<OxrSwapchain>(); let swapchain = world.get_resource::<OxrSwapchain>()?;
let graphics_info = world.resource::<OxrGraphicsInfo>(); let graphics_info = world.get_resource::<OxrGraphicsInfo>()?;
let rect = openxr::Rect2Di { let rect = openxr::Rect2Di {
offset: openxr::Offset2Di { x: 0, y: 0 }, offset: openxr::Offset2Di { x: 0, y: 0 },
extent: openxr::Extent2Di { extent: openxr::Extent2Di {
@@ -29,7 +29,11 @@ impl LayerProvider for ProjectionLayer {
}, },
}; };
Box::new( if openxr_views.len() < 2 {
return None;
}
Some(Box::new(
CompositionLayerProjection::new() CompositionLayerProjection::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.space(&stage) .space(&stage)
@@ -53,17 +57,17 @@ impl LayerProvider for ProjectionLayer {
.image_rect(rect), .image_rect(rect),
), ),
]), ]),
) ))
} }
} }
impl LayerProvider for PassthroughLayer { impl LayerProvider for PassthroughLayer {
fn get<'a>(&'a self, world: &'a World) -> Box<dyn CompositionLayer + '_> { fn get<'a>(&'a self, world: &'a World) -> Option<Box<dyn CompositionLayer + '_>> {
Box::new( Some(Box::new(
CompositionLayerPassthrough::new() CompositionLayerPassthrough::new()
.layer_handle(world.resource::<OxrPassthroughLayer>()) .layer_handle(world.get_resource::<OxrPassthroughLayer>()?)
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA), .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA),
) ))
} }
} }

View File

@@ -4,9 +4,8 @@ use bevy::{
prelude::*, prelude::*,
render::extract_resource::{ExtractResource, ExtractResourcePlugin}, render::extract_resource::{ExtractResource, ExtractResourcePlugin},
}; };
use bevy_xr::session::{status_changed_to, XrStatus};
use crate::{init::OxrPreUpdateSet, resources::OxrSession}; use crate::{init::OxrSessionCreated, resources::OxrSession};
pub struct OxrReferenceSpacePlugin { pub struct OxrReferenceSpacePlugin {
pub default_primary_ref_space: openxr::ReferenceSpaceType, pub default_primary_ref_space: openxr::ReferenceSpaceType,
@@ -35,10 +34,8 @@ impl Plugin for OxrReferenceSpacePlugin {
app.insert_resource(OxrPrimaryReferenceSpaceType(self.default_primary_ref_space)); app.insert_resource(OxrPrimaryReferenceSpaceType(self.default_primary_ref_space));
app.add_plugins(ExtractResourcePlugin::<OxrPrimaryReferenceSpace>::default()); app.add_plugins(ExtractResourcePlugin::<OxrPrimaryReferenceSpace>::default());
app.add_systems( app.add_systems(
PreUpdate, OxrSessionCreated,
set_primary_ref_space set_primary_ref_space, // .in_set(OxrPreUpdateSet::UpdateCriticalComponents),
.run_if(status_changed_to(XrStatus::Ready))
.in_set(OxrPreUpdateSet::UpdateCriticalComponents),
); );
} }
} }

View File

@@ -4,76 +4,142 @@ use bevy::{
render::{ render::{
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget}, camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget},
extract_resource::ExtractResourcePlugin, extract_resource::ExtractResourcePlugin,
renderer::render_system, pipelined_rendering::PipelinedRenderingPlugin,
view::ExtractedView, view::ExtractedView,
Render, RenderApp, RenderSet, Render, RenderApp, RenderSet,
}, },
transform::TransformSystem,
};
use bevy_xr::{
camera::{XrCamera, XrCameraBundle, XrProjection},
session::session_running,
}; };
use bevy_xr::camera::{XrCamera, XrCameraBundle, XrProjection};
use openxr::ViewStateFlags; use openxr::ViewStateFlags;
use crate::{ use crate::{
init::{session_started, OxrPreUpdateSet, OxrTrackingRoot}, init::{session_started, OxrHandleEvents, OxrLast, OxrTrackingRoot},
layer_builder::ProjectionLayer, layer_builder::ProjectionLayer,
}; };
use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*}; use crate::{reference_space::OxrPrimaryReferenceSpace, resources::*};
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub struct OxrRenderBegin;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub struct OxrRenderEnd;
pub struct OxrRenderPlugin; pub struct OxrRenderPlugin;
impl Plugin for OxrRenderPlugin { impl Plugin for OxrRenderPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins((ExtractResourcePlugin::<OxrViews>::default(),)) if app.is_plugin_added::<PipelinedRenderingPlugin>() {
app.init_resource::<Pipelined>();
}
app.add_plugins((
ExtractResourcePlugin::<OxrFrameState>::default(),
ExtractResourcePlugin::<OxrRootTransform>::default(),
ExtractResourcePlugin::<OxrGraphicsInfo>::default(),
ExtractResourcePlugin::<OxrSwapchainImages>::default(),
ExtractResourcePlugin::<OxrViews>::default(),
))
.add_systems( .add_systems(
PreUpdate, 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) (locate_views, update_views)
.chain() .chain()
.run_if(session_running) // .run_if(should_render)
.before(TransformSystem::TransformPropagate), .run_if(session_started),
)
.add_systems(
OxrLast,
(
wait_frame.run_if(session_started),
update_cameras.run_if(session_started),
init_views.run_if(resource_added::<OxrSession>),
apply_deferred,
)
.chain()
.after(OxrHandleEvents),
)
.init_resource::<OxrViews>();
let render_app = app.sub_app_mut(RenderApp);
render_app
.configure_sets(
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,
( (
( begin_frame,
insert_texture_views, insert_texture_views,
locate_views.run_if(resource_exists::<OxrPrimaryReferenceSpace>), locate_views,
update_views_render_world, update_views_render_world,
wait_image,
) )
.chain() .chain()
.in_set(RenderSet::PrepareAssets), .run_if(session_started)
begin_frame .in_set(OxrRenderBegin),
.before(RenderSet::Queue) )
.before(insert_texture_views), .add_systems(
wait_image.in_set(RenderSet::Render).before(render_system), Render,
(release_image, end_frame) (release_image, end_frame)
.chain() .chain()
.in_set(RenderSet::Cleanup), .run_if(session_started)
) .in_set(OxrRenderEnd),
.run_if(session_started),
) )
.insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)])); .insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)]));
// 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)]));
} }
} }
pub const XR_TEXTURE_INDEX: u32 = 3383858418; pub const XR_TEXTURE_INDEX: u32 = 3383858418;
// 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>,
@@ -84,7 +150,6 @@ pub fn init_views(
let _span = info_span!("xr_init_views"); let _span = info_span!("xr_init_views");
let temp_tex = swapchain_images.first().unwrap(); let temp_tex = swapchain_images.first().unwrap();
// this for loop is to easily add support for quad or mono views in the future. // this for loop is to easily add support for quad or mono views in the future.
let mut views = Vec::with_capacity(2);
for index in 0..2 { for index in 0..2 {
info!("{}", graphics_info.resolution); info!("{}", graphics_info.resolution);
let view_handle = let view_handle =
@@ -115,10 +180,7 @@ pub fn init_views(
warn!("Multiple OxrTrackingRoots! this is not allowed"); warn!("Multiple OxrTrackingRoots! this is not allowed");
} }
} }
views.push(default());
} }
commands.insert_resource(OxrViews(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) {
@@ -126,26 +188,43 @@ pub fn wait_frame(mut frame_waiter: ResMut<OxrFrameWaiter>, mut commands: Comman
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. // 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 // TODO: don't add predicted_display_period if pipelined rendering plugin not enabled
commands.insert_resource(OxrTime(state.predicted_display_time)); commands.insert_resource(OxrFrameState(state));
}
pub fn update_cameras(
frame_state: Res<OxrFrameState>,
mut cameras: Query<&mut Camera, With<XrCamera>>,
) {
if frame_state.is_changed() {
for mut camera in &mut cameras {
camera.is_active = frame_state.should_render
}
}
} }
pub fn locate_views( pub fn locate_views(
session: Res<OxrSession>, session: Res<OxrSession>,
ref_space: Res<OxrPrimaryReferenceSpace>, ref_space: Res<OxrPrimaryReferenceSpace>,
time: Res<OxrTime>, frame_state: Res<OxrFrameState>,
mut openxr_views: ResMut<OxrViews>, mut openxr_views: ResMut<OxrViews>,
pipelined: Option<Res<Pipelined>>,
) { ) {
let _span = info_span!("xr_locate_views"); let time = if pipelined.is_some() {
openxr::Time::from_nanos(
frame_state.predicted_display_time.as_nanos()
+ frame_state.predicted_display_period.as_nanos(),
)
} else {
frame_state.predicted_display_time
};
let (flags, xr_views) = session let (flags, xr_views) = session
.locate_views( .locate_views(
openxr::ViewConfigurationType::PRIMARY_STEREO, openxr::ViewConfigurationType::PRIMARY_STEREO,
**time, time,
&ref_space, &ref_space,
) )
.expect("Failed to locate views"); .expect("Failed to locate views");
if openxr_views.len() != xr_views.len() {
openxr_views.resize(xr_views.len(), default());
}
match ( match (
flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID, flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID,
flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID, flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID,
@@ -153,12 +232,18 @@ pub fn locate_views(
(true, true) => *openxr_views = OxrViews(xr_views), (true, true) => *openxr_views = OxrViews(xr_views),
(true, false) => { (true, false) => {
for (i, view) in openxr_views.iter_mut().enumerate() { for (i, view) in openxr_views.iter_mut().enumerate() {
view.pose.orientation = xr_views[i].pose.orientation; let Some(xr_view) = xr_views.get(i) else {
break;
};
view.pose.orientation = xr_view.pose.orientation;
} }
} }
(false, true) => { (false, true) => {
for (i, view) in openxr_views.iter_mut().enumerate() { for (i, view) in openxr_views.iter_mut().enumerate() {
view.pose.position = xr_views[i].pose.position; let Some(xr_view) = xr_views.get(i) else {
break;
};
view.pose.position = xr_view.pose.position;
} }
} }
(false, false) => {} (false, false) => {}
@@ -372,64 +457,21 @@ pub fn end_frame(world: &mut World) {
} }
world.resource_scope::<OxrFrameStream, ()>(|world, mut frame_stream| { world.resource_scope::<OxrFrameStream, ()>(|world, mut frame_stream| {
let mut layers = vec![]; let mut layers = vec![];
let frame_state = world.resource::<OxrFrameState>();
if frame_state.should_render {
for layer in world.resource::<OxrRenderLayers>().iter() { for layer in world.resource::<OxrRenderLayers>().iter() {
layers.push(layer.get(world)); if let Some(layer) = layer.get(world) {
layers.push(layer);
}
}
} }
let layers: Vec<_> = layers.iter().map(Box::as_ref).collect(); let layers: Vec<_> = layers.iter().map(Box::as_ref).collect();
frame_stream frame_stream
.end( .end(
**world.resource::<OxrTime>(), frame_state.predicted_display_time,
world.resource::<OxrGraphicsInfo>().blend_mode, world.resource::<OxrGraphicsInfo>().blend_mode,
&layers, &layers,
) )
.expect("Failed to end frame"); .expect("Failed to end frame");
}); });
} }
// pub fn end_frame(
// mut frame_stream: ResMut<OxrFrameStream>,
// mut swapchain: ResMut<OxrSwapchain>,
// stage: Res<OxrStage>,
// display_time: Res<OxrTime>,
// graphics_info: Res<OxrGraphicsInfo>,
// openxr_views: Res<OxrViews>,
// ) {
// let _span = info_span!("xr_end_frame");
// swapchain.release_image().unwrap();
// let rect = openxr::Rect2Di {
// offset: openxr::Offset2Di { x: 0, y: 0 },
// extent: openxr::Extent2Di {
// width: graphics_info.resolution.x as _,
// height: graphics_info.resolution.y as _,
// },
// };
// frame_stream
// .end(
// **display_time,
// graphics_info.blend_mode,
// &[&CompositionLayerProjection::new()
// .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
// .space(&stage)
// .views(&[
// CompositionLayerProjectionView::new()
// .pose(openxr_views.0[0].pose)
// .fov(openxr_views.0[0].fov)
// .sub_image(
// SwapchainSubImage::new()
// .swapchain(&swapchain)
// .image_array_index(0)
// .image_rect(rect),
// ),
// CompositionLayerProjectionView::new()
// .pose(openxr_views.0[1].pose)
// .fov(openxr_views.0[1].fov)
// .sub_image(
// SwapchainSubImage::new()
// .swapchain(&swapchain)
// .image_array_index(1)
// .image_rect(rect),
// ),
// ])],
// )
// .expect("Failed to end frame");
// }

View File

@@ -1,4 +1,3 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use bevy::prelude::*; use bevy::prelude::*;
@@ -373,7 +372,7 @@ impl OxrSwapchain {
} }
/// Stores the generated swapchain images. /// Stores the generated swapchain images.
#[derive(Debug, Deref, Resource, Clone)] #[derive(Debug, Deref, Resource, ExtractResource, Clone)]
pub struct OxrSwapchainImages(pub Arc<Vec<wgpu::Texture>>); pub struct OxrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
/// Thread safe wrapper around [openxr::Space] representing the stage. /// Thread safe wrapper around [openxr::Space] representing the stage.
@@ -381,7 +380,7 @@ pub struct OxrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
// pub struct OxrStage(pub Arc<openxr::Space>); // pub struct OxrStage(pub Arc<openxr::Space>);
/// Stores the latest generated [OxrViews] /// Stores the latest generated [OxrViews]
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)] #[derive(Clone, Resource, ExtractResource, Deref, DerefMut, Default)]
pub struct OxrViews(pub Vec<openxr::View>); pub struct OxrViews(pub Vec<openxr::View>);
/// Wrapper around [openxr::SystemId] to allow it to be stored as a resource. /// Wrapper around [openxr::SystemId] to allow it to be stored as a resource.
@@ -420,7 +419,7 @@ pub struct OxrPassthroughLayer(pub openxr::PassthroughLayer);
pub struct OxrRenderLayers(pub Vec<Box<dyn LayerProvider + Send + Sync>>); pub struct OxrRenderLayers(pub Vec<Box<dyn LayerProvider + Send + Sync>>);
/// Resource storing graphics info for the currently running session. /// Resource storing graphics info for the currently running session.
#[derive(Clone, Copy, Resource)] #[derive(Clone, Copy, Resource, ExtractResource)]
pub struct OxrGraphicsInfo { pub struct OxrGraphicsInfo {
pub blend_mode: EnvironmentBlendMode, pub blend_mode: EnvironmentBlendMode,
pub resolution: UVec2, pub resolution: UVec2,
@@ -440,22 +439,12 @@ pub struct SessionConfigInfo {
pub graphics_info: SessionCreateInfo, pub graphics_info: SessionCreateInfo,
} }
#[derive(Resource, Clone, Default)] #[derive(ExtractResource, Resource, Clone, Default)]
pub struct OxrSessionStarted(Arc<AtomicBool>); pub struct OxrSessionStarted(pub bool);
impl OxrSessionStarted { /// The frame state returned from [FrameWaiter::wait_frame](openxr::FrameWaiter::wait)
pub fn set(&self, val: bool) { #[derive(Clone, Deref, DerefMut, Resource, ExtractResource)]
self.0.store(val, Ordering::SeqCst); pub struct OxrFrameState(pub openxr::FrameState);
}
pub fn get(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
/// The calculated display time for the app. Passed through the pipeline.
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
pub struct OxrTime(pub openxr::Time);
/// The root transform's global position for late latching in the render world. /// The root transform's global position for late latching in the render world.
#[derive(ExtractResource, Resource, Clone, Copy, Default)] #[derive(ExtractResource, Resource, Clone, Copy, Default)]
@@ -464,3 +453,7 @@ pub struct OxrRootTransform(pub GlobalTransform);
#[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)] #[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)]
/// This is inserted into the world to signify if the session should be cleaned up. /// This is inserted into the world to signify if the session should be cleaned up.
pub struct OxrCleanupSession(pub bool); pub struct OxrCleanupSession(pub bool);
/// Instructs systems to add display period
#[derive(Clone, Copy, Default, Resource)]
pub struct Pipelined;

View File

@@ -1,6 +1,4 @@
use std::sync::{Arc, RwLock}; use bevy::{prelude::*, render::extract_resource::ExtractResource};
use bevy::prelude::*;
pub struct XrSessionPlugin; pub struct XrSessionPlugin;
@@ -13,7 +11,7 @@ impl Plugin for XrSessionPlugin {
.add_event::<XrStatusChanged>() .add_event::<XrStatusChanged>()
.add_systems( .add_systems(
PreUpdate, PreUpdate,
handle_session.run_if(resource_exists::<XrSharedStatus>), handle_session.run_if(resource_exists::<XrStatus>),
); );
} }
} }
@@ -21,24 +19,7 @@ impl Plugin for XrSessionPlugin {
#[derive(Event, Clone, Copy, Deref)] #[derive(Event, Clone, Copy, Deref)]
pub struct XrStatusChanged(pub XrStatus); pub struct XrStatusChanged(pub XrStatus);
#[derive(Resource, Clone)] #[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)]
pub struct XrSharedStatus(Arc<RwLock<XrStatus>>);
impl XrSharedStatus {
pub fn new(status: XrStatus) -> Self {
Self(Arc::new(RwLock::new(status)))
}
pub fn get(&self) -> XrStatus {
*self.0.read().unwrap()
}
pub fn set(&self, status: XrStatus) {
*self.0.write().unwrap() = status;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum XrStatus { pub enum XrStatus {
/// An XR session is not available here /// An XR session is not available here
@@ -58,16 +39,15 @@ pub enum XrStatus {
} }
pub fn handle_session( pub fn handle_session(
status: Res<XrSharedStatus>, current_status: Res<XrStatus>,
mut previous_status: Local<Option<XrStatus>>, mut previous_status: Local<Option<XrStatus>>,
mut create_session: EventWriter<CreateXrSession>, mut create_session: EventWriter<CreateXrSession>,
mut begin_session: EventWriter<BeginXrSession>, mut begin_session: EventWriter<BeginXrSession>,
mut end_session: EventWriter<EndXrSession>, mut end_session: EventWriter<EndXrSession>,
mut destroy_session: EventWriter<DestroyXrSession>, mut destroy_session: EventWriter<DestroyXrSession>,
) { ) {
let current_status = status.get(); if *previous_status != Some(*current_status) {
if *previous_status != Some(current_status) { match *current_status {
match current_status {
XrStatus::Unavailable => {} XrStatus::Unavailable => {}
XrStatus::Available => { XrStatus::Available => {
create_session.send_default(); create_session.send_default();
@@ -85,7 +65,7 @@ pub fn handle_session(
} }
} }
} }
*previous_status = Some(current_status); *previous_status = Some(*current_status);
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`]. /// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`].
@@ -98,29 +78,23 @@ pub fn status_changed_to(
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrStatus`] exists and isn't [`Unavailable`](XrStatus::Unavailable). /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrStatus`] exists and isn't [`Unavailable`](XrStatus::Unavailable).
pub fn session_available(status: Option<Res<XrSharedStatus>>) -> bool { pub fn session_available(status: Option<Res<XrStatus>>) -> bool {
status.is_some_and(|s| s.get() != XrStatus::Unavailable) status.is_some_and(|s| *s != XrStatus::Unavailable)
} }
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running
pub fn session_created(status: Option<Res<XrSharedStatus>>) -> bool { pub fn session_ready_or_running(status: Option<Res<XrStatus>>) -> bool {
matches!( matches!(status.as_deref(), Some(XrStatus::Ready | XrStatus::Running))
status.as_deref().map(XrSharedStatus::get),
Some(XrStatus::Ready | XrStatus::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<XrSharedStatus>>) -> bool { pub fn session_running(status: Option<Res<XrStatus>>) -> bool {
matches!( matches!(status.as_deref(), Some(XrStatus::Running))
status.as_deref().map(XrSharedStatus::get),
Some(XrStatus::Running)
)
} }
/// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if an the [`XrStatus`] is in a specific state /// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if an the [`XrStatus`] is in a specific state
pub fn status_equals(status: XrStatus) -> impl FnMut(Option<Res<XrSharedStatus>>) -> bool { pub fn status_equals(status: XrStatus) -> impl FnMut(Option<Res<XrStatus>>) -> bool {
move |state: Option<Res<XrSharedStatus>>| state.is_some_and(|s| s.get() == status) move |state: Option<Res<XrStatus>>| 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. /// Event sent to backends to create an XR session. Should only be called in the [`XrStatus::Available`] state.