cleaned up code and created webxr crate
This commit is contained in:
555
crates/bevy_openxr/src/openxr/init.rs
Normal file
555
crates/bevy_openxr/src/openxr/init.rs
Normal file
@@ -0,0 +1,555 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_resource::ExtractResourcePlugin;
|
||||
use bevy::render::renderer::RenderAdapter;
|
||||
use bevy::render::renderer::RenderAdapterInfo;
|
||||
use bevy::render::renderer::RenderDevice;
|
||||
use bevy::render::renderer::RenderInstance;
|
||||
use bevy::render::renderer::RenderQueue;
|
||||
use bevy::render::settings::RenderCreation;
|
||||
use bevy::render::MainWorld;
|
||||
use bevy::render::Render;
|
||||
use bevy::render::RenderApp;
|
||||
use bevy::render::RenderPlugin;
|
||||
use bevy::render::RenderSet;
|
||||
use bevy::transform::TransformSystem;
|
||||
use bevy::winit::UpdateMode;
|
||||
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::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::XrSharedStatus;
|
||||
use bevy_xr::session::XrStatus;
|
||||
|
||||
use crate::error::OXrError;
|
||||
use crate::graphics::*;
|
||||
use crate::resources::*;
|
||||
use crate::types::*;
|
||||
|
||||
pub fn session_started(started: Option<Res<OXrSessionStarted>>) -> bool {
|
||||
started.is_some_and(|started| started.get())
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub enum OXrPreUpdateSet {
|
||||
PollEvents,
|
||||
HandleEvents,
|
||||
}
|
||||
|
||||
pub struct OXrInitPlugin {
|
||||
/// 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: OXrExtensions,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct OXrTrackingRoot;
|
||||
|
||||
impl Plugin for OXrInitPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
match self.init_xr() {
|
||||
Ok((
|
||||
instance,
|
||||
system_id,
|
||||
WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance),
|
||||
session_create_info,
|
||||
)) => {
|
||||
let status = XrSharedStatus::new(XrStatus::Available);
|
||||
|
||||
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: self.synchronous_pipeline_compilation,
|
||||
},
|
||||
ExtractResourcePlugin::<OXrCleanupSession>::default(),
|
||||
ExtractResourcePlugin::<OXrTime>::default(),
|
||||
ExtractResourcePlugin::<OXrRootTransform>::default(),
|
||||
))
|
||||
.add_systems(First, reset_per_frame_resources)
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
(
|
||||
poll_events
|
||||
.run_if(session_available)
|
||||
.in_set(OXrPreUpdateSet::PollEvents),
|
||||
(
|
||||
(create_xr_session, apply_deferred)
|
||||
.chain()
|
||||
.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::Stopping)),
|
||||
destroy_xr_session
|
||||
.run_if(on_event::<DestroyXrSession>())
|
||||
.run_if(status_equals(XrStatus::Exiting)),
|
||||
)
|
||||
.in_set(OXrPreUpdateSet::HandleEvents),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
update_root_transform.after(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.insert_resource(instance.clone())
|
||||
.insert_resource(system_id)
|
||||
.insert_resource(status.clone())
|
||||
.insert_resource(WinitSettings {
|
||||
focused_mode: UpdateMode::Continuous,
|
||||
unfocused_mode: UpdateMode::Continuous,
|
||||
})
|
||||
.init_resource::<OXrCleanupSession>()
|
||||
.init_resource::<OXrRootTransform>()
|
||||
.insert_non_send_resource(session_create_info);
|
||||
|
||||
app.world
|
||||
.spawn((TransformBundle::default(), OXrTrackingRoot));
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
render_app
|
||||
.insert_resource(instance)
|
||||
.insert_resource(system_id)
|
||||
.insert_resource(status)
|
||||
.init_resource::<OXrRootTransform>()
|
||||
.init_resource::<OXrCleanupSession>()
|
||||
.add_systems(
|
||||
Render,
|
||||
destroy_xr_session_render
|
||||
.run_if(resource_equals(OXrCleanupSession(true)))
|
||||
.after(RenderSet::ExtractCommands),
|
||||
)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
transfer_xr_resources.run_if(not(session_running)),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to initialize openxr: {e}");
|
||||
let status = XrSharedStatus::new(XrStatus::Unavailable);
|
||||
|
||||
app.add_plugins(RenderPlugin::default())
|
||||
.insert_resource(status.clone());
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app.insert_resource(status);
|
||||
}
|
||||
};
|
||||
|
||||
app.configure_sets(
|
||||
PreUpdate,
|
||||
(
|
||||
OXrPreUpdateSet::PollEvents.before(handle_session),
|
||||
OXrPreUpdateSet::HandleEvents.after(handle_session),
|
||||
),
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_root_transform(
|
||||
mut root_transform: ResMut<OXrRootTransform>,
|
||||
root: Query<&GlobalTransform, With<OXrTrackingRoot>>,
|
||||
) {
|
||||
let transform = root.single();
|
||||
|
||||
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 {
|
||||
fn init_xr(&self) -> Result<(OXrInstance, OXrSystemId, WgpuGraphics, SessionConfigInfo)> {
|
||||
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(&self.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) = &self.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(OXrError::NoAvailableBackend)?;
|
||||
|
||||
let exts = self.exts.clone() & available_exts;
|
||||
|
||||
let instance = entry.create_instance(
|
||||
self.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 (graphics, graphics_info) = instance.init_graphics(system_id)?;
|
||||
|
||||
let session_create_info = SessionConfigInfo {
|
||||
blend_modes: self.blend_modes.clone(),
|
||||
formats: self.formats.clone(),
|
||||
resolutions: self.resolutions.clone(),
|
||||
graphics_info,
|
||||
};
|
||||
|
||||
Ok((
|
||||
instance,
|
||||
OXrSystemId(system_id),
|
||||
graphics,
|
||||
session_create_info,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn init_xr_session(
|
||||
device: &wgpu::Device,
|
||||
instance: &OXrInstance,
|
||||
system_id: openxr::SystemId,
|
||||
SessionConfigInfo {
|
||||
blend_modes,
|
||||
formats,
|
||||
resolutions,
|
||||
graphics_info,
|
||||
}: SessionConfigInfo,
|
||||
) -> Result<(
|
||||
OXrSession,
|
||||
OXrFrameWaiter,
|
||||
OXrFrameStream,
|
||||
OXrSwapchain,
|
||||
OXrSwapchainImages,
|
||||
OXrGraphicsInfo,
|
||||
OXrStage,
|
||||
)> {
|
||||
let (session, frame_waiter, frame_stream) =
|
||||
unsafe { instance.create_session(system_id, graphics_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(OXrError::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) = &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::new(
|
||||
config.recommended_image_rect_width,
|
||||
config.recommended_image_rect_height,
|
||||
),
|
||||
*config,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
.ok_or(OXrError::NoAvailableViewConfiguration)?;
|
||||
|
||||
let available_formats = session.enumerate_swapchain_formats()?;
|
||||
|
||||
let format = if let Some(formats) = &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(OXrError::NoAvailableFormat)?;
|
||||
|
||||
let 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(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) = &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(OXrError::NoAvailableBackend)?;
|
||||
|
||||
let stage = OXrStage(
|
||||
session
|
||||
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
let graphics_info = OXrGraphicsInfo {
|
||||
blend_mode,
|
||||
resolution,
|
||||
format,
|
||||
};
|
||||
|
||||
Ok((
|
||||
session,
|
||||
frame_waiter,
|
||||
frame_stream,
|
||||
swapchain,
|
||||
images,
|
||||
graphics_info,
|
||||
stage,
|
||||
))
|
||||
}
|
||||
|
||||
/// This is used solely to transport resources from the main world to the render world.
|
||||
#[derive(Resource)]
|
||||
struct XrRenderResources {
|
||||
session: OXrSession,
|
||||
frame_stream: OXrFrameStream,
|
||||
swapchain: OXrSwapchain,
|
||||
images: OXrSwapchainImages,
|
||||
graphics_info: OXrGraphicsInfo,
|
||||
stage: OXrStage,
|
||||
}
|
||||
|
||||
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, stage)) => {
|
||||
commands.insert_resource(session.clone());
|
||||
commands.insert_resource(frame_waiter);
|
||||
commands.insert_resource(images.clone());
|
||||
commands.insert_resource(graphics_info.clone());
|
||||
commands.insert_resource(stage.clone());
|
||||
commands.insert_resource(XrRenderResources {
|
||||
session,
|
||||
frame_stream,
|
||||
swapchain,
|
||||
images,
|
||||
graphics_info,
|
||||
stage,
|
||||
});
|
||||
}
|
||||
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.
|
||||
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);
|
||||
}
|
||||
|
||||
/// Polls any OpenXR events and handles them accordingly
|
||||
pub fn poll_events(instance: Res<OXrInstance>, status: Res<XrSharedStatus>) {
|
||||
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!(),
|
||||
};
|
||||
|
||||
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::<OXrStage>();
|
||||
commands.insert_resource(OXrCleanupSession(true));
|
||||
}
|
||||
|
||||
pub fn destroy_xr_session_render(world: &mut World) {
|
||||
world.remove_resource::<OXrSwapchain>();
|
||||
world.remove_resource::<OXrFrameStream>();
|
||||
world.remove_resource::<OXrStage>();
|
||||
world.remove_resource::<OXrSwapchainImages>();
|
||||
world.remove_resource::<OXrGraphicsInfo>();
|
||||
world.remove_resource::<OXrSession>();
|
||||
}
|
||||
Reference in New Issue
Block a user