rendering code
This commit is contained in:
11
Cargo.toml
11
Cargo.toml
@@ -14,11 +14,11 @@ vulkan = ["dep:ash"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.79"
|
||||||
async-std = "1.12.0"
|
async-std = "1.12.0"
|
||||||
bevy = "0.12.1"
|
bevy = "0.13.0"
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
wgpu = "0.17.1"
|
wgpu = "0.19.3"
|
||||||
wgpu-hal = "0.17.1"
|
wgpu-hal = "0.19.3"
|
||||||
winit = "0.28.7"
|
winit = "0.28.7"
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dependencies]
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
@@ -34,7 +34,7 @@ ash = { version = "0.37.3", optional = true }
|
|||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.91"
|
||||||
glow = "0.12.1"
|
glow = "0.12.1"
|
||||||
web-sys = { version = "0.3.68", features = [
|
web-sys = { version = "0.3.67", features = [
|
||||||
# STANDARD
|
# STANDARD
|
||||||
'console',
|
'console',
|
||||||
'Document',
|
'Document',
|
||||||
@@ -88,6 +88,3 @@ web-sys = { version = "0.3.68", features = [
|
|||||||
'XrSystem',
|
'XrSystem',
|
||||||
] }
|
] }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["xr_api"]
|
|
||||||
|
|||||||
@@ -1,56 +1,51 @@
|
|||||||
// //! A simple 3D scene with light shining over a cube sitting on a plane.
|
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||||
|
|
||||||
// use bevy::prelude::*;
|
use bevy::{prelude::*, render::camera::RenderTarget};
|
||||||
// use bevy::render::camera::RenderTarget;
|
use bevy_oxr::openxr::{render::LEFT_XR_TEXTURE_HANDLE, DefaultXrPlugins};
|
||||||
// use bevy_oxr::webxr::render::{XrRenderingPlugin, XR_TEXTURE_VIEW_HANDLE};
|
|
||||||
// use bevy_oxr::webxr::XrInitPlugin;
|
|
||||||
|
|
||||||
// fn main() {
|
fn main() {
|
||||||
// App::new()
|
App::new()
|
||||||
// .add_plugins((DefaultPlugins, XrInitPlugin, XrRenderingPlugin))
|
.add_plugins(DefaultXrPlugins)
|
||||||
// .add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
// .run();
|
.run();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// /// set up a simple 3D scene
|
/// set up a simple 3D scene
|
||||||
// fn setup(
|
fn setup(
|
||||||
// mut commands: Commands,
|
mut commands: Commands,
|
||||||
// mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
// mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
// ) {
|
) {
|
||||||
// // circular base
|
// circular base
|
||||||
// commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
// mesh: meshes.add(shape::Circle::new(4.0).into()),
|
mesh: meshes.add(Circle::new(4.0)),
|
||||||
// material: materials.add(Color::WHITE.into()),
|
material: materials.add(Color::WHITE),
|
||||||
// transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||||
// ..default()
|
..default()
|
||||||
// });
|
});
|
||||||
// // cube
|
// cube
|
||||||
// commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
// mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||||
// material: materials.add(Color::rgb_u8(124, 144, 255).into()),
|
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||||
// transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
// ..default()
|
..default()
|
||||||
// });
|
});
|
||||||
// // light
|
// light
|
||||||
// commands.spawn(PointLightBundle {
|
commands.spawn(PointLightBundle {
|
||||||
// point_light: PointLight {
|
point_light: PointLight {
|
||||||
// intensity: 1500.0,
|
shadows_enabled: true,
|
||||||
// shadows_enabled: true,
|
..default()
|
||||||
// ..default()
|
},
|
||||||
// },
|
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
// transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
..default()
|
||||||
// ..default()
|
});
|
||||||
// });
|
// camera
|
||||||
// // camera
|
commands.spawn(Camera3dBundle {
|
||||||
// commands.spawn(Camera3dBundle {
|
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
// transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
camera: Camera {
|
||||||
// camera: Camera {
|
target: RenderTarget::TextureView(LEFT_XR_TEXTURE_HANDLE),
|
||||||
// target: RenderTarget::TextureView(XR_TEXTURE_VIEW_HANDLE),
|
..default()
|
||||||
// ..default()
|
},
|
||||||
// },
|
..default()
|
||||||
// ..default()
|
});
|
||||||
// });
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
|
|||||||
130
src/action_paths.rs
Normal file
130
src/action_paths.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
pub mod oculus_touch;
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
use bevy::math::Vec2;
|
||||||
|
|
||||||
|
use crate::types::{Haptic, Pose};
|
||||||
|
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl Sealed for bool {}
|
||||||
|
impl Sealed for f32 {}
|
||||||
|
impl Sealed for Vec2 {}
|
||||||
|
impl Sealed for Pose {}
|
||||||
|
impl Sealed for Haptic {}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub trait ActionType: private::Sealed {}
|
||||||
|
|
||||||
|
impl<T: private::Sealed> ActionType for T {}
|
||||||
|
|
||||||
|
pub trait ActionPathTrait {
|
||||||
|
type PathType: ActionType;
|
||||||
|
fn path(&self) -> Cow<'_, str>;
|
||||||
|
fn name(&self) -> Cow<'_, str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActionPath<T: ActionType> {
|
||||||
|
pub path: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! actions {
|
||||||
|
// create path struct
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
}
|
||||||
|
) => {};
|
||||||
|
|
||||||
|
// handle action path attrs
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
name: $name:literal;
|
||||||
|
path_type: $path_type:ty;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
paste::paste! {
|
||||||
|
pub const [<$id:snake:upper>]: crate::action_paths::ActionPath<$path_type> = crate::action_paths::ActionPath {
|
||||||
|
path: concat!($($subpath,)* $path),
|
||||||
|
name: $name,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle action path attrs
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
name: $name:literal;
|
||||||
|
path_type: $path_type:ty;
|
||||||
|
$($children:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
crate::action_paths::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
name: $name;
|
||||||
|
path_type: $path_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::action_paths::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
$($children)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle children
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
$($children:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
pub mod $id {
|
||||||
|
crate::action_paths::actions! {
|
||||||
|
$($subpath,)* $path
|
||||||
|
$($children)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle siblings
|
||||||
|
(
|
||||||
|
$($subpath:literal),*
|
||||||
|
$id:ident {
|
||||||
|
path: $path:literal;
|
||||||
|
$($attrs:tt)*
|
||||||
|
}
|
||||||
|
$($siblings:tt)*
|
||||||
|
) => {
|
||||||
|
crate::action_paths::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$id {
|
||||||
|
path: $path;
|
||||||
|
$($attrs)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::action_paths::actions! {
|
||||||
|
$($subpath),*
|
||||||
|
$($siblings)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use actions;
|
||||||
147
src/actions.rs
147
src/actions.rs
@@ -1,142 +1,23 @@
|
|||||||
pub mod oculus_touch;
|
use std::{borrow::Cow, marker::PhantomData};
|
||||||
|
|
||||||
mod private {
|
use bevy::prelude::*;
|
||||||
use bevy::math::Vec2;
|
|
||||||
|
|
||||||
use crate::types::{Haptic, Pose};
|
pub use crate::action_paths::*;
|
||||||
|
|
||||||
pub trait Sealed {}
|
#[derive(Event)]
|
||||||
|
pub struct XrCreateActionSet {
|
||||||
impl Sealed for bool {}
|
pub handle: Handle<XrActionSet>,
|
||||||
impl Sealed for f32 {}
|
pub name: String,
|
||||||
impl Sealed for Vec2 {}
|
|
||||||
impl Sealed for Pose {}
|
|
||||||
impl Sealed for Haptic {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::borrow::Cow;
|
pub struct XrAction<'a, T: ActionType> {
|
||||||
use std::marker::PhantomData;
|
pub name: Cow<'a, str>,
|
||||||
|
pub pretty_name: Cow<'a, str>,
|
||||||
pub trait ActionType: private::Sealed {}
|
pub action_set: Handle<XrActionSet>,
|
||||||
|
|
||||||
impl<T: private::Sealed> ActionType for T {}
|
|
||||||
|
|
||||||
pub trait ActionPathTrait {
|
|
||||||
type PathType: ActionType;
|
|
||||||
fn path(&self) -> Cow<'_, str>;
|
|
||||||
fn name(&self) -> Cow<'_, str>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ActionPath<T: ActionType> {
|
|
||||||
path: &'static str,
|
|
||||||
name: &'static str,
|
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ActionType> ActionPathTrait for ActionPath<T> {
|
#[derive(TypePath, Asset)]
|
||||||
type PathType = T;
|
pub struct XrActionSet {
|
||||||
|
pub name: String,
|
||||||
fn path(&self) -> Cow<'_, str> {
|
|
||||||
self.path.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> Cow<'_, str> {
|
|
||||||
self.name.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! actions {
|
|
||||||
// create path struct
|
|
||||||
(
|
|
||||||
$($subpath:literal),*
|
|
||||||
$id:ident {
|
|
||||||
path: $path:literal;
|
|
||||||
}
|
|
||||||
) => {};
|
|
||||||
|
|
||||||
// handle action path attrs
|
|
||||||
(
|
|
||||||
$($subpath:literal),*
|
|
||||||
$id:ident {
|
|
||||||
path: $path:literal;
|
|
||||||
name: $name:literal;
|
|
||||||
path_type: $path_type:ty;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
paste::paste! {
|
|
||||||
pub const [<$id:snake:upper>]: crate::actions::ActionPath<$path_type> = crate::actions::ActionPath {
|
|
||||||
path: concat!($($subpath,)* $path),
|
|
||||||
name: $name,
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle action path attrs
|
|
||||||
(
|
|
||||||
$($subpath:literal),*
|
|
||||||
$id:ident {
|
|
||||||
path: $path:literal;
|
|
||||||
name: $name:literal;
|
|
||||||
path_type: $path_type:ty;
|
|
||||||
$($children:tt)*
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
crate::path::actions! {
|
|
||||||
$($subpath),*
|
|
||||||
$id {
|
|
||||||
path: $path;
|
|
||||||
name: $name;
|
|
||||||
path_type: $path_type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::path::actions! {
|
|
||||||
$($subpath),*
|
|
||||||
$id {
|
|
||||||
path: $path;
|
|
||||||
$($children)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle children
|
|
||||||
(
|
|
||||||
$($subpath:literal),*
|
|
||||||
$id:ident {
|
|
||||||
path: $path:literal;
|
|
||||||
$($children:tt)*
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
pub mod $id {
|
|
||||||
crate::actions::actions! {
|
|
||||||
$($subpath,)* $path
|
|
||||||
$($children)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle siblings
|
|
||||||
(
|
|
||||||
$($subpath:literal),*
|
|
||||||
$id:ident {
|
|
||||||
path: $path:literal;
|
|
||||||
$($attrs:tt)*
|
|
||||||
}
|
|
||||||
$($siblings:tt)*
|
|
||||||
) => {
|
|
||||||
crate::actions::actions! {
|
|
||||||
$($subpath),*
|
|
||||||
$id {
|
|
||||||
path: $path;
|
|
||||||
$($attrs)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::actions::actions! {
|
|
||||||
$($subpath),*
|
|
||||||
$($siblings)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use actions;
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod action_paths;
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub mod openxr;
|
pub mod openxr;
|
||||||
|
|||||||
278
src/openxr.rs
278
src/openxr.rs
@@ -1,13 +1,23 @@
|
|||||||
mod extensions;
|
mod extensions;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
|
pub mod render;
|
||||||
mod resources;
|
mod resources;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
use bevy::ecs::schedule::common_conditions::resource_equals;
|
||||||
|
use bevy::ecs::system::{Res, ResMut};
|
||||||
|
use bevy::math::{uvec2, UVec2};
|
||||||
|
use bevy::render::extract_resource::ExtractResourcePlugin;
|
||||||
|
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderInstance, RenderQueue};
|
||||||
|
use bevy::render::settings::RenderCreation;
|
||||||
|
use bevy::render::{RenderApp, RenderPlugin};
|
||||||
|
use bevy::utils::default;
|
||||||
|
use bevy::DefaultPlugins;
|
||||||
pub use resources::*;
|
pub use resources::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
use bevy::app::{App, Plugin};
|
use bevy::app::{App, First, Plugin, PluginGroup};
|
||||||
use bevy::log::error;
|
use bevy::log::{error, info, warn};
|
||||||
|
|
||||||
pub fn xr_entry() -> Result<XrEntry> {
|
pub fn xr_entry() -> Result<XrEntry> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -21,24 +31,35 @@ pub struct XrInitPlugin {
|
|||||||
/// 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,
|
||||||
/// Extensions wanted for this session.
|
/// Extensions wanted for this session.
|
||||||
// This should preferably be changed into a simpler list of features wanted that this crate supports. i.e. hand tracking
|
// TODO!() This should be changed to take a simpler list of features wanted that this crate supports. i.e. hand tracking
|
||||||
pub exts: XrExtensions,
|
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.
|
/// List of backends the openxr session can use. If [None], pick the first available backend.
|
||||||
pub backends: Option<Vec<GraphicsBackend>>,
|
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 {
|
impl Plugin for XrInitPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
init_xr(self, app).unwrap();
|
if let Err(e) = init_xr(self, app) {
|
||||||
todo!()
|
panic!("Encountered an error while trying to initialize XR: {e}");
|
||||||
|
}
|
||||||
|
app.add_systems(First, poll_events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_xr(config: &XrInitPlugin, _app: &mut App) -> Result<()> {
|
fn init_xr(config: &XrInitPlugin, app: &mut App) -> Result<()> {
|
||||||
let entry = xr_entry()?;
|
let entry = xr_entry()?;
|
||||||
|
|
||||||
let available_exts = entry.enumerate_extensions()?;
|
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) {
|
for ext in available_exts.unavailable_exts(&config.exts) {
|
||||||
error!(
|
error!(
|
||||||
"Extension \"{ext}\" not available in the current openxr runtime. Disabling extension."
|
"Extension \"{ext}\" not available in the current openxr runtime. Disabling extension."
|
||||||
@@ -65,8 +86,249 @@ fn init_xr(config: &XrInitPlugin, _app: &mut App) -> Result<()> {
|
|||||||
let exts = config.exts.clone() & available_exts;
|
let exts = config.exts.clone() & available_exts;
|
||||||
|
|
||||||
let instance = entry.create_instance(config.app_info.clone(), exts, backend)?;
|
let instance = entry.create_instance(config.app_info.clone(), exts, backend)?;
|
||||||
let _system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 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 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 (WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance), create_info) =
|
||||||
|
instance.init_graphics(system_id)?;
|
||||||
|
|
||||||
|
let (session, frame_waiter, frame_stream) =
|
||||||
|
unsafe { instance.create_session(system_id, create_info)? };
|
||||||
|
|
||||||
|
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(&device, format, resolution)?;
|
||||||
|
|
||||||
|
let stage = XrStage(
|
||||||
|
session
|
||||||
|
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
ExtractResourcePlugin::<XrTime>::default(),
|
||||||
|
ExtractResourcePlugin::<XrStatus>::default(),
|
||||||
|
));
|
||||||
|
let graphics_info = XrGraphicsInfo {
|
||||||
|
blend_mode,
|
||||||
|
swapchain_resolution: resolution,
|
||||||
|
swapchain_format: format,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.insert_resource(instance.clone())
|
||||||
|
.insert_resource(session.clone())
|
||||||
|
.insert_resource(frame_waiter)
|
||||||
|
.insert_resource(images.clone())
|
||||||
|
.insert_resource(graphics_info)
|
||||||
|
.insert_resource(stage.clone())
|
||||||
|
.init_resource::<XrStatus>();
|
||||||
|
app.sub_app_mut(RenderApp)
|
||||||
|
.insert_resource(instance)
|
||||||
|
.insert_resource(session)
|
||||||
|
.insert_resource(frame_stream)
|
||||||
|
.insert_resource(swapchain)
|
||||||
|
.insert_resource(images)
|
||||||
|
.insert_resource(graphics_info)
|
||||||
|
.insert_resource(stage)
|
||||||
|
.init_resource::<XrStatus>();
|
||||||
|
|
||||||
//instance.create_session(system_id)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_running() -> impl FnMut(Res<XrStatus>) -> bool {
|
||||||
|
resource_equals(XrStatus::Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_events(
|
||||||
|
instance: Res<XrInstance>,
|
||||||
|
session: Res<XrSession>,
|
||||||
|
mut xr_status: ResMut<XrStatus>,
|
||||||
|
) {
|
||||||
|
while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() {
|
||||||
|
use openxr::Event::*;
|
||||||
|
match event {
|
||||||
|
SessionStateChanged(e) => {
|
||||||
|
// Session state change is where we can begin and end sessions, as well as
|
||||||
|
// find quit messages!
|
||||||
|
info!("entered XR state {:?}", e.state());
|
||||||
|
match e.state() {
|
||||||
|
openxr::SessionState::READY => {
|
||||||
|
info!("Calling Session begin :3");
|
||||||
|
session
|
||||||
|
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
|
||||||
|
.unwrap();
|
||||||
|
*xr_status = XrStatus::Enabled;
|
||||||
|
}
|
||||||
|
openxr::SessionState::STOPPING => {
|
||||||
|
session.end().unwrap();
|
||||||
|
*xr_status = XrStatus::Disabled;
|
||||||
|
}
|
||||||
|
// openxr::SessionState::EXITING => {
|
||||||
|
// if *exit_type == ExitAppOnSessionExit::Always
|
||||||
|
// || *exit_type == ExitAppOnSessionExit::OnlyOnExit
|
||||||
|
// {
|
||||||
|
// app_exit.send_default();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// openxr::SessionState::LOSS_PENDING => {
|
||||||
|
// if *exit_type == ExitAppOnSessionExit::Always {
|
||||||
|
// app_exit.send_default();
|
||||||
|
// }
|
||||||
|
// if *exit_type == ExitAppOnSessionExit::OnlyOnExit {
|
||||||
|
// start_session.send_default();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// InstanceLossPending(_) => {
|
||||||
|
// app_exit.send_default();
|
||||||
|
// }
|
||||||
|
EventsLost(e) => {
|
||||||
|
warn!("lost {} XR events", e.lost_event_count());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultXrPlugins;
|
||||||
|
|
||||||
|
impl PluginGroup for DefaultXrPlugins {
|
||||||
|
fn build(self) -> bevy::app::PluginGroupBuilder {
|
||||||
|
DefaultPlugins
|
||||||
|
.build()
|
||||||
|
.disable::<RenderPlugin>()
|
||||||
|
.add_before::<RenderPlugin, _>(XrInitPlugin {
|
||||||
|
app_info: default(),
|
||||||
|
exts: default(),
|
||||||
|
blend_modes: default(),
|
||||||
|
backends: default(),
|
||||||
|
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
|
||||||
|
resolutions: default(),
|
||||||
|
synchronous_pipeline_compilation: default(),
|
||||||
|
})
|
||||||
|
.add(render::XrRenderPlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ impl From<XrExtensions> for ExtensionSet {
|
|||||||
}
|
}
|
||||||
impl Default for XrExtensions {
|
impl Default for XrExtensions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut exts = ExtensionSet::default();
|
let exts = ExtensionSet::default();
|
||||||
exts.ext_hand_tracking = true;
|
//exts.ext_hand_tracking = true;
|
||||||
Self(exts)
|
Self(exts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,7 @@ pub unsafe trait GraphicsExt: openxr::Graphics {
|
|||||||
app_info: &AppInfo,
|
app_info: &AppInfo,
|
||||||
instance: &openxr::Instance,
|
instance: &openxr::Instance,
|
||||||
system_id: openxr::SystemId,
|
system_id: openxr::SystemId,
|
||||||
) -> Result<(
|
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
|
||||||
wgpu::Device,
|
|
||||||
wgpu::Queue,
|
|
||||||
wgpu::Adapter,
|
|
||||||
wgpu::Instance,
|
|
||||||
Self::SessionCreateInfo,
|
|
||||||
)>;
|
|
||||||
/// Convert a swapchain function
|
/// Convert a swapchain function
|
||||||
unsafe fn to_wgpu_img(
|
unsafe fn to_wgpu_img(
|
||||||
image: Self::SwapchainImage,
|
image: Self::SwapchainImage,
|
||||||
@@ -109,7 +103,7 @@ macro_rules! graphics_match {
|
|||||||
|
|
||||||
pub(crate) use graphics_match;
|
pub(crate) use graphics_match;
|
||||||
|
|
||||||
use super::XrExtensions;
|
use super::{WgpuGraphics, XrExtensions};
|
||||||
|
|
||||||
impl From<openxr::EnvironmentBlendMode> for BlendMode {
|
impl From<openxr::EnvironmentBlendMode> for BlendMode {
|
||||||
fn from(value: openxr::EnvironmentBlendMode) -> Self {
|
fn from(value: openxr::EnvironmentBlendMode) -> Self {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use openxr::Version;
|
|||||||
use wgpu_hal::api::Vulkan;
|
use wgpu_hal::api::Vulkan;
|
||||||
use wgpu_hal::Api;
|
use wgpu_hal::Api;
|
||||||
|
|
||||||
use crate::openxr::extensions::XrExtensions;
|
|
||||||
use crate::openxr::types::Result;
|
use crate::openxr::types::Result;
|
||||||
|
use crate::openxr::{extensions::XrExtensions, WgpuGraphics};
|
||||||
|
|
||||||
use super::{AppInfo, GraphicsExt, XrError};
|
use super::{AppInfo, GraphicsExt, XrError};
|
||||||
|
|
||||||
@@ -37,13 +37,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
|
|||||||
app_info: &AppInfo,
|
app_info: &AppInfo,
|
||||||
instance: &openxr::Instance,
|
instance: &openxr::Instance,
|
||||||
system_id: openxr::SystemId,
|
system_id: openxr::SystemId,
|
||||||
) -> Result<(
|
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)> {
|
||||||
wgpu::Device,
|
|
||||||
wgpu::Queue,
|
|
||||||
wgpu::Adapter,
|
|
||||||
wgpu::Instance,
|
|
||||||
Self::SessionCreateInfo,
|
|
||||||
)> {
|
|
||||||
let reqs = instance.graphics_requirements::<openxr::Vulkan>(system_id)?;
|
let reqs = instance.graphics_requirements::<openxr::Vulkan>(system_id)?;
|
||||||
if VK_TARGET_VERSION < reqs.min_api_version_supported
|
if VK_TARGET_VERSION < reqs.min_api_version_supported
|
||||||
|| VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major()
|
|| VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major()
|
||||||
@@ -56,12 +50,9 @@ unsafe impl GraphicsExt for openxr::Vulkan {
|
|||||||
return Err(XrError::FailedGraphicsRequirements);
|
return Err(XrError::FailedGraphicsRequirements);
|
||||||
};
|
};
|
||||||
let vk_entry = unsafe { ash::Entry::load() }?;
|
let vk_entry = unsafe { ash::Entry::load() }?;
|
||||||
let flags = wgpu_hal::InstanceFlags::empty();
|
let flags = wgpu::InstanceFlags::empty();
|
||||||
let extensions = <Vulkan as Api>::Instance::required_extensions(
|
let extensions =
|
||||||
&vk_entry,
|
<Vulkan as Api>::Instance::desired_extensions(&vk_entry, VK_TARGET_VERSION_ASH, flags)?;
|
||||||
VK_TARGET_VERSION_ASH,
|
|
||||||
flags,
|
|
||||||
)?;
|
|
||||||
let device_extensions = vec![
|
let device_extensions = vec![
|
||||||
ash::extensions::khr::Swapchain::name(),
|
ash::extensions::khr::Swapchain::name(),
|
||||||
ash::extensions::khr::DrawIndirectCount::name(),
|
ash::extensions::khr::DrawIndirectCount::name(),
|
||||||
@@ -211,8 +202,8 @@ unsafe impl GraphicsExt for openxr::Vulkan {
|
|||||||
wgpu_open_device,
|
wgpu_open_device,
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
features: wgpu_features,
|
required_features: wgpu_features,
|
||||||
limits: wgpu::Limits {
|
required_limits: wgpu::Limits {
|
||||||
max_bind_groups: 8,
|
max_bind_groups: 8,
|
||||||
max_storage_buffer_binding_size: wgpu_adapter
|
max_storage_buffer_binding_size: wgpu_adapter
|
||||||
.limits()
|
.limits()
|
||||||
@@ -226,10 +217,13 @@ unsafe impl GraphicsExt for openxr::Vulkan {
|
|||||||
}?;
|
}?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
|
WgpuGraphics(
|
||||||
wgpu_device,
|
wgpu_device,
|
||||||
wgpu_queue,
|
wgpu_queue,
|
||||||
|
wgpu_adapter.get_info(),
|
||||||
wgpu_adapter,
|
wgpu_adapter,
|
||||||
wgpu_instance,
|
wgpu_instance,
|
||||||
|
),
|
||||||
openxr::vulkan::SessionCreateInfo {
|
openxr::vulkan::SessionCreateInfo {
|
||||||
instance: vk_instance_ptr,
|
instance: vk_instance_ptr,
|
||||||
physical_device: vk_physical_device_ptr,
|
physical_device: vk_physical_device_ptr,
|
||||||
@@ -334,6 +328,7 @@ fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
|
|||||||
F::B8G8R8A8_UNORM => Tf::Bgra8Unorm,
|
F::B8G8R8A8_UNORM => Tf::Bgra8Unorm,
|
||||||
F::R8G8B8A8_UINT => Tf::Rgba8Uint,
|
F::R8G8B8A8_UINT => Tf::Rgba8Uint,
|
||||||
F::R8G8B8A8_SINT => Tf::Rgba8Sint,
|
F::R8G8B8A8_SINT => Tf::Rgba8Sint,
|
||||||
|
F::A2B10G10R10_UINT_PACK32 => Tf::Rgb10a2Uint,
|
||||||
F::A2B10G10R10_UNORM_PACK32 => Tf::Rgb10a2Unorm,
|
F::A2B10G10R10_UNORM_PACK32 => Tf::Rgb10a2Unorm,
|
||||||
F::B10G11R11_UFLOAT_PACK32 => Tf::Rg11b10Float,
|
F::B10G11R11_UFLOAT_PACK32 => Tf::Rg11b10Float,
|
||||||
F::R32G32_UINT => Tf::Rg32Uint,
|
F::R32G32_UINT => Tf::Rg32Uint,
|
||||||
@@ -350,6 +345,7 @@ fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
|
|||||||
F::D32_SFLOAT => Tf::Depth32Float,
|
F::D32_SFLOAT => Tf::Depth32Float,
|
||||||
F::D32_SFLOAT_S8_UINT => Tf::Depth32FloatStencil8,
|
F::D32_SFLOAT_S8_UINT => Tf::Depth32FloatStencil8,
|
||||||
F::D16_UNORM => Tf::Depth16Unorm,
|
F::D16_UNORM => Tf::Depth16Unorm,
|
||||||
|
F::G8_B8R8_2PLANE_420_UNORM => Tf::NV12,
|
||||||
F::E5B9G9R9_UFLOAT_PACK32 => Tf::Rgb9e5Ufloat,
|
F::E5B9G9R9_UFLOAT_PACK32 => Tf::Rgb9e5Ufloat,
|
||||||
F::BC1_RGBA_UNORM_BLOCK => Tf::Bc1RgbaUnorm,
|
F::BC1_RGBA_UNORM_BLOCK => Tf::Bc1RgbaUnorm,
|
||||||
F::BC1_RGBA_SRGB_BLOCK => Tf::Bc1RgbaUnormSrgb,
|
F::BC1_RGBA_SRGB_BLOCK => Tf::Bc1RgbaUnormSrgb,
|
||||||
@@ -583,6 +579,7 @@ fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<ash::vk::Format> {
|
|||||||
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
|
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
|
||||||
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
|
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
|
||||||
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
|
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
|
||||||
|
Tf::Rgb10a2Uint => F::A2B10G10R10_UINT_PACK32,
|
||||||
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
|
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
|
||||||
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
|
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
|
||||||
Tf::Rg32Uint => F::R32G32_UINT,
|
Tf::Rg32Uint => F::R32G32_UINT,
|
||||||
@@ -600,6 +597,7 @@ fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<ash::vk::Format> {
|
|||||||
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
|
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
|
||||||
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
|
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
|
||||||
Tf::Depth16Unorm => F::D16_UNORM,
|
Tf::Depth16Unorm => F::D16_UNORM,
|
||||||
|
Tf::NV12 => F::G8_B8R8_2PLANE_420_UNORM,
|
||||||
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
|
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
|
||||||
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
|
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
|
||||||
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
|
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
|
||||||
|
|||||||
176
src/openxr/render.rs
Normal file
176
src/openxr/render.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
use bevy::{
|
||||||
|
prelude::*,
|
||||||
|
render::{
|
||||||
|
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews},
|
||||||
|
Render, RenderApp, RenderSet,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use openxr::CompositionLayerFlags;
|
||||||
|
|
||||||
|
use crate::openxr::resources::*;
|
||||||
|
use crate::openxr::types::*;
|
||||||
|
use crate::openxr::XrTime;
|
||||||
|
|
||||||
|
use super::{poll_events, session_running};
|
||||||
|
|
||||||
|
pub struct XrRenderPlugin;
|
||||||
|
|
||||||
|
impl Plugin for XrRenderPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
First,
|
||||||
|
wait_frame.after(poll_events).run_if(session_running()),
|
||||||
|
)
|
||||||
|
.add_systems(Startup, init_texture_views);
|
||||||
|
app.sub_app_mut(RenderApp).add_systems(
|
||||||
|
Render,
|
||||||
|
(
|
||||||
|
(begin_frame, insert_texture_views)
|
||||||
|
.chain()
|
||||||
|
.in_set(RenderSet::PrepareAssets),
|
||||||
|
end_frame.in_set(RenderSet::Cleanup),
|
||||||
|
)
|
||||||
|
.run_if(session_running()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591);
|
||||||
|
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
|
||||||
|
|
||||||
|
fn init_texture_views(
|
||||||
|
graphics_info: Res<XrGraphicsInfo>,
|
||||||
|
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||||
|
swapchain_images: Res<SwapchainImages>,
|
||||||
|
) {
|
||||||
|
let temp_tex = swapchain_images.first().unwrap();
|
||||||
|
let left = temp_tex.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let right = temp_tex.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
base_array_layer: 1,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let resolution = graphics_info.swapchain_resolution;
|
||||||
|
let format = graphics_info.swapchain_format;
|
||||||
|
let left = ManualTextureView {
|
||||||
|
texture_view: left.into(),
|
||||||
|
size: resolution,
|
||||||
|
format: format,
|
||||||
|
};
|
||||||
|
let right = ManualTextureView {
|
||||||
|
texture_view: right.into(),
|
||||||
|
size: resolution,
|
||||||
|
format: format,
|
||||||
|
};
|
||||||
|
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
|
||||||
|
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_frame(mut frame_waiter: ResMut<XrFrameWaiter>, mut commands: Commands) {
|
||||||
|
let state = frame_waiter.wait().expect("Failed to wait frame");
|
||||||
|
commands.insert_resource(XrTime(openxr::Time::from_nanos(
|
||||||
|
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_frame(mut frame_stream: ResMut<XrFrameStream>) {
|
||||||
|
frame_stream.begin().expect("Failed to begin frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_texture_views(
|
||||||
|
swapchain_images: Res<SwapchainImages>,
|
||||||
|
mut swapchain: ResMut<XrSwapchain>,
|
||||||
|
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||||
|
graphics_info: Res<XrGraphicsInfo>,
|
||||||
|
) {
|
||||||
|
let index = swapchain.acquire_image().expect("Failed to acquire image");
|
||||||
|
swapchain
|
||||||
|
.wait_image(openxr::Duration::INFINITE)
|
||||||
|
.expect("Failed to wait image");
|
||||||
|
let image = &swapchain_images[index as usize];
|
||||||
|
let left = image.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let right = image.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
base_array_layer: 1,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let resolution = graphics_info.swapchain_resolution;
|
||||||
|
let format = graphics_info.swapchain_format;
|
||||||
|
let left = ManualTextureView {
|
||||||
|
texture_view: left.into(),
|
||||||
|
size: resolution,
|
||||||
|
format: format,
|
||||||
|
};
|
||||||
|
let right = ManualTextureView {
|
||||||
|
texture_view: right.into(),
|
||||||
|
size: resolution,
|
||||||
|
format: format,
|
||||||
|
};
|
||||||
|
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
|
||||||
|
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_frame(
|
||||||
|
mut frame_stream: ResMut<XrFrameStream>,
|
||||||
|
session: Res<XrSession>,
|
||||||
|
mut swapchain: ResMut<XrSwapchain>,
|
||||||
|
stage: Res<XrStage>,
|
||||||
|
display_time: Res<XrTime>,
|
||||||
|
graphics_info: Res<XrGraphicsInfo>,
|
||||||
|
) {
|
||||||
|
swapchain.release_image().unwrap();
|
||||||
|
let (_flags, views) = session
|
||||||
|
.locate_views(
|
||||||
|
openxr::ViewConfigurationType::PRIMARY_STEREO,
|
||||||
|
**display_time,
|
||||||
|
&stage,
|
||||||
|
)
|
||||||
|
.expect("Failed to locate views");
|
||||||
|
|
||||||
|
let rect = openxr::Rect2Di {
|
||||||
|
offset: openxr::Offset2Di { x: 0, y: 0 },
|
||||||
|
extent: openxr::Extent2Di {
|
||||||
|
width: graphics_info.swapchain_resolution.x as _,
|
||||||
|
height: graphics_info.swapchain_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(views[0].pose)
|
||||||
|
.fov(views[0].fov)
|
||||||
|
.sub_image(
|
||||||
|
SwapchainSubImage::new()
|
||||||
|
.swapchain(&swapchain)
|
||||||
|
.image_array_index(0)
|
||||||
|
.image_rect(rect),
|
||||||
|
),
|
||||||
|
CompositionLayerProjectionView::new()
|
||||||
|
.pose(views[0].pose)
|
||||||
|
.fov(views[0].fov)
|
||||||
|
.sub_image(
|
||||||
|
SwapchainSubImage::new()
|
||||||
|
.swapchain(&swapchain)
|
||||||
|
.image_array_index(0)
|
||||||
|
.image_rect(rect),
|
||||||
|
),
|
||||||
|
])],
|
||||||
|
)
|
||||||
|
.expect("Failed to end stream");
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderInstance, RenderQueue};
|
use bevy::render::extract_resource::ExtractResource;
|
||||||
use bevy::render::settings::RenderCreation;
|
|
||||||
use openxr::AnyGraphics;
|
use openxr::AnyGraphics;
|
||||||
|
|
||||||
use super::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap};
|
use super::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap};
|
||||||
@@ -50,6 +51,16 @@ impl XrEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)]
|
||||||
|
pub struct SystemId(pub openxr::SystemId);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Resource)]
|
||||||
|
pub struct XrGraphicsInfo {
|
||||||
|
pub blend_mode: EnvironmentBlendMode,
|
||||||
|
pub swapchain_resolution: UVec2,
|
||||||
|
pub swapchain_format: wgpu::TextureFormat,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Resource, Deref, Clone)]
|
#[derive(Resource, Deref, Clone)]
|
||||||
pub struct XrInstance(
|
pub struct XrInstance(
|
||||||
#[deref] pub openxr::Instance,
|
#[deref] pub openxr::Instance,
|
||||||
@@ -61,13 +72,13 @@ impl XrInstance {
|
|||||||
pub fn init_graphics(
|
pub fn init_graphics(
|
||||||
&self,
|
&self,
|
||||||
system_id: openxr::SystemId,
|
system_id: openxr::SystemId,
|
||||||
) -> Result<(RenderCreation, SessionCreateInfo)> {
|
) -> Result<(WgpuGraphics, SessionCreateInfo)> {
|
||||||
graphics_match!(
|
graphics_match!(
|
||||||
self.1;
|
self.1;
|
||||||
_ => {
|
_ => {
|
||||||
let (device, queue, adapter, instance, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
|
let (graphics, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
|
||||||
|
|
||||||
Ok((RenderCreation::manual(device.into(), RenderQueue(queue.into()), RenderAdapterInfo(adapter.get_info()), RenderAdapter(adapter.into()), RenderInstance(instance.into())), SessionCreateInfo(Api::wrap(session_info))))
|
Ok((graphics, SessionCreateInfo(Api::wrap(session_info))))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -90,7 +101,7 @@ impl XrInstance {
|
|||||||
graphics_match!(
|
graphics_match!(
|
||||||
info.0;
|
info.0;
|
||||||
info => {
|
info => {
|
||||||
let (session, frame_waiter, frame_stream) = unsafe { self.0.create_session::<Api>(system_id, &info)? };
|
let (session, frame_waiter, frame_stream) = self.0.create_session::<Api>(system_id, &info)?;
|
||||||
Ok((session.into(), XrFrameWaiter(frame_waiter), XrFrameStream(Api::wrap(frame_stream))))
|
Ok((session.into(), XrFrameWaiter(frame_waiter), XrFrameStream(Api::wrap(frame_stream))))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -218,7 +229,7 @@ impl XrSwapchain {
|
|||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
resolution: UVec2,
|
resolution: UVec2,
|
||||||
) -> Result<Vec<wgpu::Texture>> {
|
) -> Result<SwapchainImages> {
|
||||||
graphics_match!(
|
graphics_match!(
|
||||||
&mut self.0;
|
&mut self.0;
|
||||||
swap => {
|
swap => {
|
||||||
@@ -228,8 +239,24 @@ impl XrSwapchain {
|
|||||||
images.push(Api::to_wgpu_img(image, device, format, resolution)?);
|
images.push(Api::to_wgpu_img(image, device, format, resolution)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(images)
|
Ok(SwapchainImages(images.into()))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deref, Clone, Resource)]
|
||||||
|
pub struct XrStage(pub Arc<openxr::Space>);
|
||||||
|
|
||||||
|
#[derive(Debug, Deref, Resource, Clone)]
|
||||||
|
pub struct SwapchainImages(pub Arc<Vec<wgpu::Texture>>);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
|
||||||
|
pub struct XrTime(pub openxr::Time);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, Default, Resource, ExtractResource)]
|
||||||
|
pub enum XrStatus {
|
||||||
|
Enabled,
|
||||||
|
#[default]
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,19 @@ use super::graphics::{graphics_match, GraphicsExt, GraphicsWrap};
|
|||||||
|
|
||||||
pub use super::extensions::XrExtensions;
|
pub use super::extensions::XrExtensions;
|
||||||
pub use openxr::{
|
pub use openxr::{
|
||||||
Extent2Di, Graphics, Offset2Di, Rect2Di, SwapchainCreateFlags, SwapchainUsageFlags, SystemId,
|
EnvironmentBlendMode, Extent2Di, FormFactor, Graphics, Offset2Di, Rect2Di,
|
||||||
|
SwapchainCreateFlags, SwapchainUsageFlags,
|
||||||
};
|
};
|
||||||
pub type Result<T> = std::result::Result<T, XrError>;
|
pub type Result<T> = std::result::Result<T, XrError>;
|
||||||
|
|
||||||
|
pub struct WgpuGraphics(
|
||||||
|
pub wgpu::Device,
|
||||||
|
pub wgpu::Queue,
|
||||||
|
pub wgpu::AdapterInfo,
|
||||||
|
pub wgpu::Adapter,
|
||||||
|
pub wgpu::Instance,
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
pub struct Version(pub u8, pub u8, pub u16);
|
pub struct Version(pub u8, pub u8, pub u16);
|
||||||
|
|
||||||
@@ -21,12 +30,21 @@ impl Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct AppInfo {
|
pub struct AppInfo {
|
||||||
pub name: Cow<'static, str>,
|
pub name: Cow<'static, str>,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for AppInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Bevy".into(),
|
||||||
|
version: Version::BEVY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type GraphicsBackend = GraphicsWrap<()>;
|
pub type GraphicsBackend = GraphicsWrap<()>;
|
||||||
|
|
||||||
impl GraphicsBackend {
|
impl GraphicsBackend {
|
||||||
@@ -78,8 +96,14 @@ mod error {
|
|||||||
VulkanLoadingError(#[from] ash::LoadingError),
|
VulkanLoadingError(#[from] ash::LoadingError),
|
||||||
#[error("Graphics backend '{0:?}' is not available")]
|
#[error("Graphics backend '{0:?}' is not available")]
|
||||||
UnavailableBackend(GraphicsBackend),
|
UnavailableBackend(GraphicsBackend),
|
||||||
#[error("No available backend")]
|
#[error("No compatible backend available")]
|
||||||
NoAvailableBackend,
|
NoAvailableBackend,
|
||||||
|
#[error("No compatible view configuration available")]
|
||||||
|
NoAvailableViewConfiguration,
|
||||||
|
#[error("No compatible blend mode available")]
|
||||||
|
NoAvailableBlendMode,
|
||||||
|
#[error("No compatible format available")]
|
||||||
|
NoAvailableFormat,
|
||||||
#[error("OpenXR runtime does not support these extensions: {0}")]
|
#[error("OpenXR runtime does not support these extensions: {0}")]
|
||||||
UnavailableExtensions(UnavailableExts),
|
UnavailableExtensions(UnavailableExts),
|
||||||
#[error("Could not meet graphics requirements for platform. See console for details")]
|
#[error("Could not meet graphics requirements for platform. See console for details")]
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
use bevy::ecs::component::Component;
|
||||||
use bevy::math::{Quat, Vec3};
|
use bevy::math::{Quat, Vec3};
|
||||||
|
use bevy::render::camera::ManualTextureViewHandle;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pose {
|
pub struct Pose {
|
||||||
@@ -6,6 +8,12 @@ pub struct Pose {
|
|||||||
pub rotation: Quat,
|
pub rotation: Quat,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Component)]
|
||||||
|
pub struct XrView {
|
||||||
|
pub view_handle: ManualTextureViewHandle,
|
||||||
|
pub view_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Haptic;
|
pub struct Haptic;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user