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