reworked code
This commit is contained in:
@@ -6,8 +6,16 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.75"
|
||||||
bevy = { git = "https://github.com/awtterpip/bevy", default-features = false, features = ["bevy_render"] }
|
bevy = { git = "https://github.com/awtterpip/bevy", default-features = false, features = ["bevy_render"] }
|
||||||
openxr = "0.17.1"
|
openxr = "0.17.1"
|
||||||
wgpu = "0.16.0"
|
wgpu = "0.16.0"
|
||||||
wgpu-core = "0.16.0"
|
wgpu-core = "0.16.0"
|
||||||
wgpu-hal = "0.16.0"
|
wgpu-hal = "0.16.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bevy = { git = "https://github.com/awtterpip/bevy" }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "xr"
|
||||||
|
path = "examples/xr.rs"
|
||||||
46
examples/xr.rs
Normal file
46
examples/xr.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||||
|
use bevy_openxr::DefaultXrPlugins;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultXrPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// plane
|
||||||
|
commands.spawn(PbrBundle {
|
||||||
|
mesh: meshes.add(shape::Plane::from_size(5.0).into()),
|
||||||
|
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
// cube
|
||||||
|
commands.spawn(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
// light
|
||||||
|
commands.spawn(PointLightBundle {
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 1500.0,
|
||||||
|
shadows_enabled: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
// camera
|
||||||
|
commands.spawn(Camera3dBundle {
|
||||||
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
55
src/input.rs
Normal file
55
src/input.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use openxr as xr;
|
||||||
|
|
||||||
|
type XrPose = (Vec3, Quat);
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct XrInput {
|
||||||
|
action_set: xr::ActionSet,
|
||||||
|
right_action: xr::Action<xr::Posef>,
|
||||||
|
left_action: xr::Action<xr::Posef>,
|
||||||
|
right_space: xr::Space,
|
||||||
|
left_space: xr::Space,
|
||||||
|
stage: xr::Space,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XrInput {
|
||||||
|
pub fn new(
|
||||||
|
instance: xr::Instance,
|
||||||
|
session: xr::Session<xr::AnyGraphics>,
|
||||||
|
) -> xr::Result<Self> {
|
||||||
|
let action_set = instance.create_action_set("input", "input pose information", 0)?;
|
||||||
|
let right_action =
|
||||||
|
action_set.create_action::<xr::Posef>("right_hand", "Right Hand Controller", &[])?;
|
||||||
|
let left_action =
|
||||||
|
action_set.create_action::<xr::Posef>("left_hand", "Left Hand Controller", &[])?;
|
||||||
|
instance.suggest_interaction_profile_bindings(
|
||||||
|
instance.string_to_path("/interaction_profiles/khr/simple_controller")?,
|
||||||
|
&[
|
||||||
|
xr::Binding::new(
|
||||||
|
&right_action,
|
||||||
|
instance.string_to_path("/user/hand/right/input/grip/pose")?,
|
||||||
|
),
|
||||||
|
xr::Binding::new(
|
||||||
|
&left_action,
|
||||||
|
instance.string_to_path("/user/hand/left/input/grip/pose")?,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
session.attach_action_sets(&[&action_set])?;
|
||||||
|
let right_space =
|
||||||
|
right_action.create_space(session.clone(), xr::Path::NULL, xr::Posef::IDENTITY)?;
|
||||||
|
let left_space =
|
||||||
|
left_action.create_space(session.clone(), xr::Path::NULL, xr::Posef::IDENTITY)?;
|
||||||
|
let stage =
|
||||||
|
session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;
|
||||||
|
Ok(Self {
|
||||||
|
action_set,
|
||||||
|
right_action,
|
||||||
|
left_action,
|
||||||
|
right_space,
|
||||||
|
left_space,
|
||||||
|
stage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/lib.rs
102
src/lib.rs
@@ -1,52 +1,118 @@
|
|||||||
|
pub mod input;
|
||||||
|
pub mod resource_macros;
|
||||||
|
pub mod resources;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use bevy::ecs::system::SystemState;
|
use bevy::ecs::system::SystemState;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::settings::WgpuSettings;
|
use bevy::render::settings::WgpuSettings;
|
||||||
use bevy::render::FutureRendererResources;
|
use bevy::render::{FutureRendererResources, RenderPlugin, renderer};
|
||||||
use bevy::window::{PrimaryWindow, RawHandleWrapper};
|
use bevy::window::{PrimaryWindow, RawHandleWrapper};
|
||||||
use state::XrState;
|
use input::XrInput;
|
||||||
|
use resources::*;
|
||||||
|
|
||||||
mod state;
|
/// Adds OpenXR support to an App
|
||||||
|
#[derive(Default)]
|
||||||
pub struct OpenXrPlugin {
|
pub struct OpenXrPlugin;
|
||||||
pub wgpu_settings: WgpuSettings,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct FutureXrResources (
|
pub struct FutureXrResources(
|
||||||
Arc<
|
pub Arc<
|
||||||
Mutex<
|
Mutex<
|
||||||
Option<
|
Option<(
|
||||||
XrState
|
XrInstance,
|
||||||
>
|
XrSession,
|
||||||
>
|
XrEnvironmentBlendMode,
|
||||||
>
|
XrSessionRunning,
|
||||||
|
XrFrameWaiter,
|
||||||
|
XrSwapchain,
|
||||||
|
XrInput,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Plugin for OpenXrPlugin {
|
impl Plugin for OpenXrPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
if let Some(backends) = self.wgpu_settings.backends {
|
|
||||||
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None));
|
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None));
|
||||||
let future_xr_resources_wrapper = Arc::new(Mutex::new(None));
|
|
||||||
app.insert_resource(FutureRendererResources(
|
app.insert_resource(FutureRendererResources(
|
||||||
future_renderer_resources_wrapper.clone(),
|
future_renderer_resources_wrapper.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let future_xr_resources_wrapper = Arc::new(Mutex::new(None));
|
||||||
app.insert_resource(FutureXrResources(
|
app.insert_resource(FutureXrResources(
|
||||||
future_xr_resources_wrapper.clone(),
|
future_xr_resources_wrapper.clone()
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||||
SystemState::new(&mut app.world);
|
SystemState::new(&mut app.world);
|
||||||
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
||||||
|
|
||||||
let settings = self.wgpu_settings.clone();
|
let settings = WgpuSettings::default();
|
||||||
bevy::tasks::IoTaskPool::get()
|
bevy::tasks::IoTaskPool::get()
|
||||||
.spawn_local(async move {
|
.spawn_local(async move {
|
||||||
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
|
backends: settings.backends.unwrap(),
|
||||||
|
dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
|
||||||
|
});
|
||||||
|
let surface = primary_window.map(|wrapper| unsafe {
|
||||||
|
// SAFETY: Plugins should be set up on the main thread.
|
||||||
|
let handle = wrapper.get_handle();
|
||||||
|
instance
|
||||||
|
.create_surface(&handle)
|
||||||
|
.expect("Failed to create wgpu surface")
|
||||||
|
});
|
||||||
|
|
||||||
|
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: settings.power_preference,
|
||||||
|
compatible_surface: surface.as_ref(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (device, queue, adapter_info, render_adapter) =
|
||||||
|
renderer::initialize_renderer(&instance, &settings, &request_adapter_options)
|
||||||
|
.await;
|
||||||
|
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
|
||||||
|
debug!("Configured wgpu adapter Features: {:#?}", device.features());
|
||||||
|
let mut future_renderer_resources_inner =
|
||||||
|
future_renderer_resources_wrapper.lock().unwrap();
|
||||||
|
*future_renderer_resources_inner =
|
||||||
|
Some((device, queue, adapter_info, render_adapter, instance));
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ready(&self, app: &App) -> bool {
|
||||||
|
app.world
|
||||||
|
.get_resource::<FutureXrResources>()
|
||||||
|
.and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok())
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self, app: &mut App) {
|
||||||
|
if let Some(future_renderer_resources) =
|
||||||
|
app.world.remove_resource::<FutureXrResources>()
|
||||||
|
{
|
||||||
|
let (instance, session, blend_mode, session_running, frame_waiter, swapchain, input) =
|
||||||
|
future_renderer_resources.0.lock().unwrap().take().unwrap();
|
||||||
|
|
||||||
|
app.insert_resource(instance.clone())
|
||||||
|
.insert_resource(session.clone())
|
||||||
|
.insert_resource(blend_mode.clone())
|
||||||
|
.insert_resource(session_running.clone())
|
||||||
|
.insert_resource(frame_waiter.clone())
|
||||||
|
.insert_resource(swapchain.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultXrPlugins;
|
||||||
|
|
||||||
|
impl PluginGroup for DefaultXrPlugins {
|
||||||
|
fn build(self) -> bevy::app::PluginGroupBuilder {
|
||||||
|
DefaultPlugins
|
||||||
|
.build()
|
||||||
|
.add_before::<RenderPlugin, _>(OpenXrPlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/resource_macros.rs
Normal file
58
src/resource_macros.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! xr_resource_wrapper {
|
||||||
|
($wrapper_type:ident, $xr_type:ty) => {
|
||||||
|
#[derive(Clone, bevy::prelude::Resource)]
|
||||||
|
pub struct $wrapper_type($xr_type);
|
||||||
|
|
||||||
|
impl $wrapper_type {
|
||||||
|
pub fn new(value: $xr_type) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for $wrapper_type {
|
||||||
|
type Target = $xr_type;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$xr_type> for $wrapper_type {
|
||||||
|
fn from(value: $xr_type) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! xr_arc_resource_wrapper {
|
||||||
|
($wrapper_type:ident, $xr_type:ty) => {
|
||||||
|
#[derive(Clone, bevy::prelude::Resource)]
|
||||||
|
pub struct $wrapper_type(std::sync::Arc<$xr_type>);
|
||||||
|
|
||||||
|
impl $wrapper_type {
|
||||||
|
pub fn new(value: $xr_type) -> Self {
|
||||||
|
Self(std::sync::Arc::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for $wrapper_type {
|
||||||
|
type Target = $xr_type;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$xr_type> for $wrapper_type {
|
||||||
|
fn from(value: $xr_type) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use xr_resource_wrapper;
|
||||||
|
pub use xr_arc_resource_wrapper;
|
||||||
23
src/resources.rs
Normal file
23
src/resources.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use crate::resource_macros::*;
|
||||||
|
use openxr as xr;
|
||||||
|
|
||||||
|
xr_resource_wrapper!(XrInstance, xr::Instance);
|
||||||
|
xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>);
|
||||||
|
xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
|
||||||
|
xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool);
|
||||||
|
xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<XrFrameWaiter>);
|
||||||
|
xr_arc_resource_wrapper!(XrSwapchain, Mutex<Swapchain>);
|
||||||
|
|
||||||
|
pub enum Swapchain {
|
||||||
|
Vulkan(SwapchainInner<xr::Vulkan>)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SwapchainInner<G: xr::Graphics> {
|
||||||
|
stream: xr::FrameStream<G>,
|
||||||
|
handle: xr::Swapchain<G>,
|
||||||
|
resolution: (u32, u32),
|
||||||
|
buffers: Vec<wgpu::Texture>,
|
||||||
|
}
|
||||||
67
src/state.rs
67
src/state.rs
@@ -1,67 +0,0 @@
|
|||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use openxr as xr;
|
|
||||||
use xr::Result as XrResult;
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct XrState {
|
|
||||||
instance: xr::Instance,
|
|
||||||
session: xr::Session<xr::AnyGraphics>,
|
|
||||||
session_running: AtomicBool,
|
|
||||||
event_buffer: xr::EventDataBuffer,
|
|
||||||
views: Vec<xr::ViewConfigurationView>,
|
|
||||||
graphics: XrGraphics,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum XrGraphics {
|
|
||||||
Vulkan(Mutex<XrGraphicsInner<xr::Vulkan>>)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XrGraphics {
|
|
||||||
fn begin(&self) -> XrResult<xr::FrameState> {
|
|
||||||
match self {
|
|
||||||
XrGraphics::Vulkan(inner) => inner.lock().unwrap().begin(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct XrGraphicsInner<G: xr::Graphics> {
|
|
||||||
wait: xr::FrameWaiter,
|
|
||||||
stream: xr::FrameStream<G>,
|
|
||||||
swapchain: xr::Swapchain<G>,
|
|
||||||
blend_mode: xr::EnvironmentBlendMode,
|
|
||||||
resolution: Extent2D,
|
|
||||||
buffers: Vec<wgpu::Texture>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<G: xr::Graphics> XrGraphicsInner<G> {
|
|
||||||
fn begin(&mut self) -> XrResult<xr::FrameState> {
|
|
||||||
let frame_state = self.wait.wait()?;
|
|
||||||
self.stream.begin()?;
|
|
||||||
Ok(frame_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_render_view(&mut self, layer: u32) -> wgpu::TextureView {
|
|
||||||
let image_index = self.swapchain.acquire_image().unwrap();
|
|
||||||
self.swapchain.wait_image(xr::Duration::INFINITE).unwrap();
|
|
||||||
|
|
||||||
let texture = &self.buffers[image_index as usize];
|
|
||||||
|
|
||||||
texture.create_view(&wgpu::TextureViewDescriptor {
|
|
||||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
|
||||||
array_layer_count: Some(1),
|
|
||||||
base_array_layer: layer,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Extent2D {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Sync for XrState {}
|
|
||||||
unsafe impl Send for XrState {}
|
|
||||||
141
srcold/input.rs
Normal file
141
srcold/input.rs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
use openxr as xr;
|
||||||
|
|
||||||
|
use crate::xr::{VIEW_TYPE, XrPose};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PostFrameData {
|
||||||
|
pub views: Vec<xr::View>,
|
||||||
|
pub left_hand: Option<XrPose>,
|
||||||
|
pub right_hand: Option<XrPose>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct XrInput {
|
||||||
|
session: xr::Session<xr::Vulkan>,
|
||||||
|
action_set: xr::ActionSet,
|
||||||
|
right_action: xr::Action<xr::Posef>,
|
||||||
|
left_action: xr::Action<xr::Posef>,
|
||||||
|
right_space: xr::Space,
|
||||||
|
left_space: xr::Space,
|
||||||
|
stage: xr::Space,
|
||||||
|
left_hand: Mutex<XrPose>,
|
||||||
|
right_hand: Mutex<XrPose>,
|
||||||
|
views: Mutex<Vec<XrPose>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XrInput {
|
||||||
|
pub(crate) fn new(
|
||||||
|
instance: xr::Instance,
|
||||||
|
session: xr::Session<xr::Vulkan>,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let action_set = instance.create_action_set("input", "input pose information", 0)?;
|
||||||
|
let right_action =
|
||||||
|
action_set.create_action::<xr::Posef>("right_hand", "Right Hand Controller", &[])?;
|
||||||
|
let left_action =
|
||||||
|
action_set.create_action::<xr::Posef>("left_hand", "Left Hand Controller", &[])?;
|
||||||
|
instance.suggest_interaction_profile_bindings(
|
||||||
|
instance.string_to_path("/interaction_profiles/khr/simple_controller")?,
|
||||||
|
&[
|
||||||
|
xr::Binding::new(
|
||||||
|
&right_action,
|
||||||
|
instance.string_to_path("/user/hand/right/input/grip/pose")?,
|
||||||
|
),
|
||||||
|
xr::Binding::new(
|
||||||
|
&left_action,
|
||||||
|
instance.string_to_path("/user/hand/left/input/grip/pose")?,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
session.attach_action_sets(&[&action_set])?;
|
||||||
|
let right_space =
|
||||||
|
right_action.create_space(session.clone(), xr::Path::NULL, xr::Posef::IDENTITY)?;
|
||||||
|
let left_space =
|
||||||
|
left_action.create_space(session.clone(), xr::Path::NULL, xr::Posef::IDENTITY)?;
|
||||||
|
let stage =
|
||||||
|
session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;
|
||||||
|
Ok(Self {
|
||||||
|
left_action,
|
||||||
|
left_space,
|
||||||
|
right_action,
|
||||||
|
right_space,
|
||||||
|
action_set,
|
||||||
|
stage,
|
||||||
|
session,
|
||||||
|
left_hand: Default::default(),
|
||||||
|
right_hand: Default::default(),
|
||||||
|
views: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn post_frame(
|
||||||
|
&self,
|
||||||
|
xr_frame_state: xr::FrameState,
|
||||||
|
) -> xr::Result<PostFrameData> {
|
||||||
|
self.session.sync_actions(&[(&self.action_set).into()])?;
|
||||||
|
let locate_hand_pose = |action: &xr::Action<xr::Posef>,
|
||||||
|
space: &xr::Space|
|
||||||
|
-> xr::Result<Option<(Vec3, Quat)>> {
|
||||||
|
if action.is_active(&self.session, xr::Path::NULL)? {
|
||||||
|
Ok(Some(openxr_pose_to_glam(
|
||||||
|
&space
|
||||||
|
.locate(&self.stage, xr_frame_state.predicted_display_time)?
|
||||||
|
.pose,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_hand = locate_hand_pose(&self.left_action, &self.left_space)?;
|
||||||
|
let right_hand = locate_hand_pose(&self.right_action, &self.right_space)?;
|
||||||
|
let (_, views) = self.session.locate_views(
|
||||||
|
VIEW_TYPE,
|
||||||
|
xr_frame_state.predicted_display_time,
|
||||||
|
&self.stage,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(left_hand) = &left_hand {
|
||||||
|
*self.left_hand.lock().unwrap() = left_hand.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(right_hand) = &left_hand {
|
||||||
|
*self.right_hand.lock().unwrap() = right_hand.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.views.lock().unwrap() = views.iter().map(|f| openxr_pose_to_glam(&f.pose)).collect();
|
||||||
|
|
||||||
|
Ok(PostFrameData {
|
||||||
|
views,
|
||||||
|
left_hand,
|
||||||
|
right_hand,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage(&self) -> &xr::Space {
|
||||||
|
&self.stage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn left_hand(&self) -> XrPose {
|
||||||
|
*self.left_hand.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right_hand(&self) -> XrPose {
|
||||||
|
*self.right_hand.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn views(&self) -> Vec<XrPose> {
|
||||||
|
self.views.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn openxr_pose_to_glam(pose: &openxr::Posef) -> (Vec3, Quat) {
|
||||||
|
// with enough sign errors anything is possible
|
||||||
|
let rotation = {
|
||||||
|
let o = pose.orientation;
|
||||||
|
Quat::from_rotation_x(180.0f32.to_radians()) * glam::quat(o.w, o.z, o.y, o.x)
|
||||||
|
};
|
||||||
|
let translation = glam::vec3(-pose.position.x, pose.position.y, -pose.position.z);
|
||||||
|
(translation, rotation)
|
||||||
|
}
|
||||||
52
srcold/lib.rs
Normal file
52
srcold/lib.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use bevy::ecs::system::SystemState;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::settings::WgpuSettings;
|
||||||
|
use bevy::render::FutureRendererResources;
|
||||||
|
use bevy::window::{PrimaryWindow, RawHandleWrapper};
|
||||||
|
|
||||||
|
mod xr;
|
||||||
|
mod input;
|
||||||
|
|
||||||
|
pub struct OpenXrPlugin {
|
||||||
|
pub wgpu_settings: WgpuSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct FutureXrResources (
|
||||||
|
Arc<
|
||||||
|
Mutex<
|
||||||
|
Option<
|
||||||
|
()
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Plugin for OpenXrPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
if let Some(backends) = self.wgpu_settings.backends {
|
||||||
|
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None));
|
||||||
|
let future_xr_resources_wrapper = Arc::new(Mutex::new(None));
|
||||||
|
app.insert_resource(FutureRendererResources(
|
||||||
|
future_renderer_resources_wrapper.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
app.insert_resource(FutureXrResources(
|
||||||
|
future_xr_resources_wrapper.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||||
|
SystemState::new(&mut app.world);
|
||||||
|
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
||||||
|
|
||||||
|
let settings = self.wgpu_settings.clone();
|
||||||
|
bevy::tasks::IoTaskPool::get()
|
||||||
|
.spawn_local(async move {
|
||||||
|
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
srcold/xr/mod.rs
Normal file
178
srcold/xr/mod.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
use openxr as xr;
|
||||||
|
|
||||||
|
use crate::input::{PostFrameData, XrInput};
|
||||||
|
|
||||||
|
pub type XrPose = (Vec3, Quat);
|
||||||
|
pub const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
|
||||||
|
|
||||||
|
pub enum XrState {
|
||||||
|
Vulkan(XrStateInner<xr::Vulkan>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct XrStateInner<G: xr::Graphics> {
|
||||||
|
instance: xr::Instance,
|
||||||
|
session: xr::Session<G>,
|
||||||
|
session_running: AtomicBool,
|
||||||
|
frame: Mutex<FrameInner<G>>,
|
||||||
|
frame_state: Mutex<Option<xr::FrameState>>,
|
||||||
|
post_frame_data: Mutex<Option<PostFrameData>>,
|
||||||
|
event_buffer: UnsafeCell<xr::EventDataBuffer>,
|
||||||
|
input: XrInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<G: xr::Graphics> Sync for XrStateInner<G> {}
|
||||||
|
unsafe impl<G: xr::Graphics> Send for XrStateInner<G> {}
|
||||||
|
|
||||||
|
impl<G: xr::Graphics> XrStateInner<G> {
|
||||||
|
pub fn preframe(&self) -> xr::Result<()> {
|
||||||
|
let event_buffer = unsafe { &mut *self.event_buffer.get() };
|
||||||
|
while let Some(event) = self.instance.poll_event(event_buffer)? {
|
||||||
|
use xr::Event::*;
|
||||||
|
match event {
|
||||||
|
SessionStateChanged(e) => {
|
||||||
|
// Session state change is where we can begin and end sessions, as well as
|
||||||
|
// find quit messages!
|
||||||
|
match e.state() {
|
||||||
|
xr::SessionState::READY => {
|
||||||
|
self.session
|
||||||
|
.begin(VIEW_TYPE)?; // TODO! support other view types
|
||||||
|
self.session_running
|
||||||
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
xr::SessionState::STOPPING => {
|
||||||
|
self.session.end()?;
|
||||||
|
self.session_running
|
||||||
|
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => {
|
||||||
|
*self.frame_state.lock().unwrap() = None;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstanceLossPending(_) => {
|
||||||
|
*self.frame_state.lock().unwrap() = None;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
EventsLost(e) => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self
|
||||||
|
.session_running
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
// Don't grind up the CPU
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
*self.frame_state.lock().unwrap() = None;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.frame_state.lock().unwrap() = Some(self.frame.lock().unwrap().begin()?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_frame(&self) -> xr::Result<(wgpu::TextureView, wgpu::TextureView)> {
|
||||||
|
*self.post_frame_data.lock().unwrap() = Some(self.input.post_frame(self.frame_state.lock().unwrap().unwrap().clone())?);
|
||||||
|
Ok(self.frame.lock().unwrap().get_render_views())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_queue_submit(&self) -> xr::Result<()> {
|
||||||
|
let pfd = self.post_frame_data.lock().unwrap();
|
||||||
|
self.frame.lock().unwrap().post_queue_submit(self.frame_state.lock().unwrap().unwrap().clone(), &(*pfd).clone().unwrap().views, self.input.stage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrameInner<G: xr::Graphics> {
|
||||||
|
waiter: xr::FrameWaiter,
|
||||||
|
stream: xr::FrameStream<G>,
|
||||||
|
blend_mode: xr::EnvironmentBlendMode,
|
||||||
|
views: Vec<xr::ViewConfigurationView>,
|
||||||
|
swapchain: xr::Swapchain<G>,
|
||||||
|
resolution: Extent2D,
|
||||||
|
buffers: Vec<wgpu::Texture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<G: xr::Graphics> FrameInner<G> {
|
||||||
|
fn begin(&mut self) -> xr::Result<xr::FrameState> {
|
||||||
|
let frame_state = self.waiter.wait()?;
|
||||||
|
self.stream.begin()?;
|
||||||
|
Ok(frame_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_render_views(&mut self) -> (wgpu::TextureView, wgpu::TextureView) {
|
||||||
|
let image_index = self.swapchain.acquire_image().unwrap();
|
||||||
|
self.swapchain.wait_image(xr::Duration::INFINITE).unwrap();
|
||||||
|
|
||||||
|
let texture = &self.buffers[image_index as usize];
|
||||||
|
|
||||||
|
(
|
||||||
|
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
base_array_layer: 0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||||
|
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||||
|
array_layer_count: Some(1),
|
||||||
|
base_array_layer: 1,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_queue_submit(
|
||||||
|
&mut self,
|
||||||
|
xr_frame_state: xr::FrameState,
|
||||||
|
views: &[openxr::View],
|
||||||
|
stage: &xr::Space,
|
||||||
|
) -> xr::Result<()> {
|
||||||
|
self.swapchain.release_image()?;
|
||||||
|
let rect = xr::Rect2Di {
|
||||||
|
offset: xr::Offset2Di { x: 0, y: 0 },
|
||||||
|
extent: xr::Extent2Di {
|
||||||
|
width: self.resolution.width as _,
|
||||||
|
height: self.resolution.height as _,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.stream.end(
|
||||||
|
xr_frame_state.predicted_display_time,
|
||||||
|
self.blend_mode,
|
||||||
|
&[&xr::CompositionLayerProjection::new().space(stage).views(&[
|
||||||
|
xr::CompositionLayerProjectionView::new()
|
||||||
|
.pose(views[0].pose)
|
||||||
|
.fov(views[0].fov)
|
||||||
|
.sub_image(
|
||||||
|
xr::SwapchainSubImage::new()
|
||||||
|
.swapchain(&self.swapchain)
|
||||||
|
.image_array_index(0)
|
||||||
|
.image_rect(rect),
|
||||||
|
),
|
||||||
|
xr::CompositionLayerProjectionView::new()
|
||||||
|
.pose(views[1].pose)
|
||||||
|
.fov(views[1].fov)
|
||||||
|
.sub_image(
|
||||||
|
xr::SwapchainSubImage::new()
|
||||||
|
.swapchain(&self.swapchain)
|
||||||
|
.image_array_index(1)
|
||||||
|
.image_rect(rect),
|
||||||
|
),
|
||||||
|
])],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Extent2D {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user