diff --git a/.gitignore b/.gitignore index 59322f2..6797086 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/runtime_libs/arm64-v8a/* .vscode \.DS_Store +srcold \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b080fbc..a7f274b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ linked = ["openxr/linked"] vulkan = ["dep:ash"] [dependencies] +xr_api = { path = "./xr_api" } +async-std = "1.12.0" bevy = "0.12.1" paste = "1.0.14" wgpu = "0.17.1" @@ -43,6 +45,7 @@ web-sys = { version = "0.3.61", features = [ 'HtmlCanvasElement', 'WebGl2RenderingContext', 'WebGlFramebuffer', + 'GamepadHapticActuator', ## XR 'DomPointReadOnly', 'XrWebGlLayer', @@ -81,3 +84,6 @@ web-sys = { version = "0.3.61", features = [ 'XrSystem', ] } wasm-bindgen-futures = "0.4" + +[workspace] +members = ["xr_api"] diff --git a/src/backend/graphics/mod.rs b/src/backend/graphics/mod.rs deleted file mode 100644 index e688848..0000000 --- a/src/backend/graphics/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[cfg(feature = "vulkan")] -pub mod vulkan; - -use bevy::window::RawHandleWrapper; -use openxr::{FrameStream, FrameWaiter, Instance, Swapchain, ViewConfigurationType}; - -use crate::{error::XrError, types::GraphicsFeatures}; - -use super::OXrSession; - -const VIEW_TYPE: ViewConfigurationType = ViewConfigurationType::PRIMARY_STEREO; - -pub enum OXrGraphics { - #[cfg(feature = "vulkan")] - Vulkan { - swapchain: Swapchain, - frame_stream: FrameStream, - frame_waiter: FrameWaiter, - }, -} - -pub fn init_oxr_graphics( - instance: Instance, - graphics: GraphicsFeatures, - window: Option, -) -> Result { - #[cfg(feature = "vulkan")] - if graphics.vulkan { - if let Ok(session) = vulkan::init_oxr_graphics(instance, window) { - return Ok(session); - } - } - - Err(XrError {}) -} diff --git a/src/backend/graphics/vulkan.rs b/src/backend/graphics/vulkan.rs deleted file mode 100644 index 137301d..0000000 --- a/src/backend/graphics/vulkan.rs +++ /dev/null @@ -1,478 +0,0 @@ -use std::ffi::{c_void, CString}; -use std::sync::{Arc, Mutex}; - -use ash::vk::{self, Handle}; -use bevy::log::info; -use bevy::math::uvec2; -use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderQueue}; -use bevy::window::RawHandleWrapper; -use openxr as xr; -use openxr::Instance; - -use crate::backend::graphics::{OXrGraphics, VIEW_TYPE}; -use crate::backend::OXrSession; -use crate::error::XrError; - -pub fn init_oxr_graphics( - xr_instance: Instance, - window: Option, -) -> Result { - use wgpu_hal::{api::Vulkan as V, Api}; - - let instance_props = xr_instance.properties()?; - let xr_system_id = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?; - info!("created system"); - let system_props = xr_instance.system_properties(xr_system_id).unwrap(); - info!( - "loaded OpenXR runtime: {} {} {}", - instance_props.runtime_name, - instance_props.runtime_version, - if system_props.system_name.is_empty() { - "" - } else { - &system_props.system_name - } - ); - - let blend_mode = xr_instance.enumerate_environment_blend_modes(xr_system_id, VIEW_TYPE)?[0]; - - #[cfg(not(target_os = "android"))] - let vk_target_version = vk::make_api_version(0, 1, 2, 0); - #[cfg(not(target_os = "android"))] - let vk_target_version_xr = xr::Version::new(1, 2, 0); - - #[cfg(target_os = "android")] - let vk_target_version = vk::make_api_version(0, 1, 1, 0); - #[cfg(target_os = "android")] - let vk_target_version_xr = xr::Version::new(1, 1, 0); - - let reqs = xr_instance.graphics_requirements::(xr_system_id)?; - if vk_target_version_xr < reqs.min_api_version_supported - || vk_target_version_xr.major() > reqs.max_api_version_supported.major() - { - panic!( - "OpenXR runtime requires Vulkan version > {}, < {}.0.0", - reqs.min_api_version_supported, - reqs.max_api_version_supported.major() + 1 - ); - } - - let vk_entry = unsafe { ash::Entry::load() }.map_err(|_| XrError {})?; - let flags = wgpu_hal::InstanceFlags::empty(); - let extensions = ::Instance::required_extensions(&vk_entry, vk_target_version, flags) - .map_err(|_| XrError {})?; - let device_extensions = vec![ - ash::extensions::khr::Swapchain::name(), - ash::extensions::khr::DrawIndirectCount::name(), - #[cfg(target_os = "android")] - ash::extensions::khr::TimelineSemaphore::name(), - ]; - info!( - "creating vulkan instance with these extensions: {:#?}", - extensions - ); - - let vk_instance = unsafe { - let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect(); - - let app_name = CString::new("Bevy").map_err(|_| XrError {})?; - let vk_app_info = vk::ApplicationInfo::builder() - .application_name(&app_name) - .application_version(1) - .engine_name(&app_name) - .engine_version(1) - .api_version(vk_target_version); - - let vk_instance = xr_instance - .create_vulkan_instance( - xr_system_id, - std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), - &vk::InstanceCreateInfo::builder() - .application_info(&vk_app_info) - .enabled_extension_names(&extensions_cchar) as *const _ - as *const _, - ) - .map_err(|_| XrError {})? - .map_err(|_| XrError {})?; - - ash::Instance::load( - vk_entry.static_fn(), - vk::Instance::from_raw(vk_instance as _), - ) - }; - info!("created vulkan instance"); - - let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void; - - let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe { - xr_instance.vulkan_graphics_device(xr_system_id, vk_instance.handle().as_raw() as _)? as _ - }); - let vk_physical_device_ptr = vk_physical_device.as_raw() as *const c_void; - - let vk_device_properties = - unsafe { vk_instance.get_physical_device_properties(vk_physical_device) }; - if vk_device_properties.api_version < vk_target_version { - unsafe { vk_instance.destroy_instance(None) } - panic!("Vulkan physical device doesn't support version 1.1"); - } - - let wgpu_vk_instance = unsafe { - ::Instance::from_raw( - vk_entry.clone(), - vk_instance.clone(), - vk_target_version, - 0, - None, - extensions, - flags, - false, - Some(Box::new(())), - ) - .map_err(|_| XrError {})? - }; - - let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES - | wgpu::Features::MULTIVIEW - | wgpu::Features::MULTI_DRAW_INDIRECT_COUNT - | wgpu::Features::MULTI_DRAW_INDIRECT; - - let wgpu_exposed_adapter = wgpu_vk_instance - .expose_adapter(vk_physical_device) - .ok_or(XrError {})?; - - let enabled_extensions = wgpu_exposed_adapter - .adapter - .required_device_extensions(wgpu_features); - - let (wgpu_open_device, vk_device_ptr, queue_family_index) = { - let extensions_cchar: Vec<_> = device_extensions.iter().map(|s| s.as_ptr()).collect(); - let mut enabled_phd_features = wgpu_exposed_adapter - .adapter - .physical_device_features(&enabled_extensions, wgpu_features); - let family_index = 0; - let family_info = vk::DeviceQueueCreateInfo::builder() - .queue_family_index(family_index) - .queue_priorities(&[1.0]) - .build(); - let family_infos = [family_info]; - let info = enabled_phd_features - .add_to_device_create_builder( - vk::DeviceCreateInfo::builder() - .queue_create_infos(&family_infos) - .push_next(&mut vk::PhysicalDeviceMultiviewFeatures { - multiview: vk::TRUE, - ..Default::default() - }), - ) - .enabled_extension_names(&extensions_cchar) - .build(); - let vk_device = unsafe { - let vk_device = xr_instance - .create_vulkan_device( - xr_system_id, - std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), - vk_physical_device.as_raw() as _, - &info as *const _ as *const _, - ) - .map_err(|_| XrError {})? - .map_err(|_| XrError {})?; - - ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _)) - }; - let vk_device_ptr = vk_device.handle().as_raw() as *const c_void; - - let wgpu_open_device = unsafe { - wgpu_exposed_adapter.adapter.device_from_raw( - vk_device, - true, - &enabled_extensions, - wgpu_features, - family_info.queue_family_index, - 0, - ) - } - .map_err(|_| XrError {})?; - - ( - wgpu_open_device, - vk_device_ptr, - family_info.queue_family_index, - ) - }; - - let wgpu_instance = - unsafe { wgpu::Instance::from_hal::(wgpu_vk_instance) }; - let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) }; - let (wgpu_device, wgpu_queue) = unsafe { - wgpu_adapter.create_device_from_hal( - wgpu_open_device, - &wgpu::DeviceDescriptor { - label: None, - features: wgpu_features, - limits: wgpu::Limits { - max_bind_groups: 8, - max_storage_buffer_binding_size: wgpu_adapter - .limits() - .max_storage_buffer_binding_size, - max_push_constant_size: 4, - ..Default::default() - }, - }, - None, - ) - } - .map_err(|_| XrError {})?; - - let (session, frame_waiter, frame_stream) = unsafe { - xr_instance.create_session::( - xr_system_id, - &xr::vulkan::SessionCreateInfo { - instance: vk_instance_ptr, - physical_device: vk_physical_device_ptr, - device: vk_device_ptr, - queue_family_index, - queue_index: 0, - }, - ) - }?; - - let views = xr_instance.enumerate_view_configuration_views(xr_system_id, VIEW_TYPE)?; - - let surface = window.map(|wrapper| unsafe { - // SAFETY: Plugins should be set up on the main thread. - let handle = wrapper.get_handle(); - wgpu_instance - .create_surface(&handle) - .expect("Failed to create wgpu surface") - }); - let swapchain_format = surface - .as_ref() - .map(|surface| surface.get_capabilities(&wgpu_adapter).formats[0]) - .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb); - - let resolution = uvec2( - views[0].recommended_image_rect_width, - views[0].recommended_image_rect_height, - ); - - let swapchain = session - .create_swapchain(&xr::SwapchainCreateInfo { - create_flags: xr::SwapchainCreateFlags::EMPTY, - usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT - | xr::SwapchainUsageFlags::SAMPLED, - format: wgpu_to_vulkan(swapchain_format).ok_or(XrError {})?.as_raw() as _, - // The Vulkan graphics pipeline we create is not set up for multisampling, - // so we hardcode this to 1. If we used a proper multisampling setup, we - // could set this to `views[0].recommended_swapchain_sample_count`. - sample_count: 1, - width: resolution.x, - height: resolution.y, - face_count: 1, - array_size: 2, - mip_count: 1, - }) - .unwrap(); - let images = swapchain.enumerate_images().unwrap(); - - let buffers: Vec<_> = images - .into_iter() - .map(|color_image| { - let color_image = vk::Image::from_raw(color_image); - let wgpu_hal_texture = unsafe { - ::Device::texture_from_raw( - color_image, - &wgpu_hal::TextureDescriptor { - label: Some("VR Swapchain"), - size: wgpu::Extent3d { - width: resolution.x, - height: resolution.y, - depth_or_array_layers: 2, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: swapchain_format, - usage: wgpu_hal::TextureUses::COLOR_TARGET - | wgpu_hal::TextureUses::COPY_DST, - memory_flags: wgpu_hal::MemoryFlags::empty(), - view_formats: vec![], - }, - None, - ) - }; - let texture = unsafe { - wgpu_device.create_texture_from_hal::( - wgpu_hal_texture, - &wgpu::TextureDescriptor { - label: Some("VR Swapchain"), - size: wgpu::Extent3d { - width: resolution.x, - height: resolution.y, - depth_or_array_layers: 2, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: swapchain_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }, - ) - }; - texture - }) - .collect(); - - OXrSession { - graphics: OXrGraphics::Vulkan { - swapchain, - frame_stream, - frame_waiter, - }, - device: wgpu_device.into(), - queue: RenderQueue(Arc::new(wgpu_queue)), - adapter_info: RenderAdapterInfo(wgpu_adapter.get_info()), - adapter: RenderAdapter(Arc::new(wgpu_adapter)), - session: session.into_any_graphics(), - blend_mode, - resolution, - format: swapchain_format, - buffers, - image_index: Mutex::new(0), - instance: xr_instance, - }; - - todo!() -} - -fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option { - // Copied with minor modification from: - // https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151 - // license: MIT OR Apache-2.0 - use ash::vk::Format as F; - use wgpu::TextureFormat as Tf; - use wgpu::{AstcBlock, AstcChannel}; - Some(match format { - Tf::R8Unorm => F::R8_UNORM, - Tf::R8Snorm => F::R8_SNORM, - Tf::R8Uint => F::R8_UINT, - Tf::R8Sint => F::R8_SINT, - Tf::R16Uint => F::R16_UINT, - Tf::R16Sint => F::R16_SINT, - Tf::R16Unorm => F::R16_UNORM, - Tf::R16Snorm => F::R16_SNORM, - Tf::R16Float => F::R16_SFLOAT, - Tf::Rg8Unorm => F::R8G8_UNORM, - Tf::Rg8Snorm => F::R8G8_SNORM, - Tf::Rg8Uint => F::R8G8_UINT, - Tf::Rg8Sint => F::R8G8_SINT, - Tf::Rg16Unorm => F::R16G16_UNORM, - Tf::Rg16Snorm => F::R16G16_SNORM, - Tf::R32Uint => F::R32_UINT, - Tf::R32Sint => F::R32_SINT, - Tf::R32Float => F::R32_SFLOAT, - Tf::Rg16Uint => F::R16G16_UINT, - Tf::Rg16Sint => F::R16G16_SINT, - Tf::Rg16Float => F::R16G16_SFLOAT, - Tf::Rgba8Unorm => F::R8G8B8A8_UNORM, - Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB, - Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB, - Tf::Rgba8Snorm => F::R8G8B8A8_SNORM, - Tf::Bgra8Unorm => F::B8G8R8A8_UNORM, - Tf::Rgba8Uint => F::R8G8B8A8_UINT, - Tf::Rgba8Sint => F::R8G8B8A8_SINT, - Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32, - Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32, - Tf::Rg32Uint => F::R32G32_UINT, - Tf::Rg32Sint => F::R32G32_SINT, - Tf::Rg32Float => F::R32G32_SFLOAT, - Tf::Rgba16Uint => F::R16G16B16A16_UINT, - Tf::Rgba16Sint => F::R16G16B16A16_SINT, - Tf::Rgba16Unorm => F::R16G16B16A16_UNORM, - Tf::Rgba16Snorm => F::R16G16B16A16_SNORM, - Tf::Rgba16Float => F::R16G16B16A16_SFLOAT, - Tf::Rgba32Uint => F::R32G32B32A32_UINT, - Tf::Rgba32Sint => F::R32G32B32A32_SINT, - Tf::Rgba32Float => F::R32G32B32A32_SFLOAT, - Tf::Depth32Float => F::D32_SFLOAT, - Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT, - Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties - Tf::Depth16Unorm => F::D16_UNORM, - Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32, - Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK, - Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK, - Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK, - Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK, - Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK, - Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK, - Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK, - Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK, - Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK, - Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK, - Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK, - Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK, - Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK, - Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK, - Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK, - Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK, - Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK, - Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK, - Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK, - Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK, - Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK, - Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK, - Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK, - Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK, - Tf::Astc { block, channel } => match channel { - AstcChannel::Unorm => match block { - AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK, - AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK, - AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK, - AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK, - AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK, - AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK, - AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK, - AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK, - AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK, - AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK, - AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK, - AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK, - AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK, - AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK, - }, - AstcChannel::UnormSrgb => match block { - AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK, - AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK, - AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK, - AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK, - AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK, - AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK, - AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK, - AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK, - AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK, - AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK, - AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK, - AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK, - AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK, - AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK, - }, - AstcChannel::Hdr => match block { - AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT, - AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT, - AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT, - AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT, - AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT, - AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT, - AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT, - AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT, - AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT, - AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT, - AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT, - AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT, - AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT, - AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT, - }, - }, - }) -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs deleted file mode 100644 index 699ab8a..0000000 --- a/src/backend/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -#[cfg(not(target_family = "wasm"))] -pub(crate) mod graphics; -#[cfg(not(target_family = "wasm"))] -mod oxr; -#[cfg(not(target_family = "wasm"))] -pub(crate) mod oxr_utils; -pub mod traits; -#[cfg(target_family = "wasm")] -pub(crate) mod web_utils; -#[cfg(target_family = "wasm")] -mod webxr; - -#[cfg(not(target_family = "wasm"))] -pub use oxr::*; -#[cfg(target_family = "wasm")] -pub use webxr::*; - -macro_rules! xr_inner { - ($res:ty, $oxr:ty, $webxr:ty) => { - paste::paste! { - pub enum [<$res Inner>] { - #[cfg(not(target_family = "wasm"))] - OpenXR($oxr), - #[cfg(target_family = "wasm")] - WebXR($webxr), - } - - #[cfg(not(target_family = "wasm"))] - impl From<$oxr> for $res { - fn from(value: $oxr) -> $res { - $res(std::rc::Rc::new([<$res Inner>]::OpenXR(value))) - } - } - - #[cfg(target_family = "wasm")] - impl From<$webxr> for $res { - fn from(value: $webxr) -> $res { - $res(std::rc::Rc::new([<$res Inner>]::WebXR(value))) - } - } - } - }; -} - -use crate::resources::*; - -xr_inner!(XrEntry, OXrEntry, WebXrEntry); -xr_inner!(XrInstance, OXrInstance, WebXrInstance); -xr_inner!(XrSession, OXrSession, WebXrSession); -xr_inner!(XrInput, OXrAction, WebXrAction); -xr_inner!(XrController, OXrController, WebXrActionSet); -xr_inner!(XrActionSpace, OXrActionSpace, WebXrActionSpace); -xr_inner!(XrReferenceSpace, OXrReferenceSpace, WebXrReferenceSpace); diff --git a/src/backend/oxr.rs b/src/backend/oxr.rs deleted file mode 100644 index 9ece1bd..0000000 --- a/src/backend/oxr.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::rc::Rc; -use std::sync::Mutex; - -use bevy::math::UVec2; -use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; - -use bevy::utils::HashMap; -use openxr::{ - ActionSet, AnyGraphics, ApplicationInfo, Entry, EnvironmentBlendMode, ExtensionSet, Instance, - Session, -}; - -use super::graphics::OXrGraphics; -use super::traits::*; -use super::{oxr_utils::*, XrControllerInner}; - -use crate::backend::graphics::init_oxr_graphics; -use crate::error::XrError; -use crate::resources::*; -use crate::types::*; - -pub struct OXrEntry(Entry); - -impl XrEntryTrait for OXrEntry { - fn get_xr_entry(&self) -> Result { - Ok(OXrEntry(xr_entry()?).into()) - } - - fn get_available_features(&self) -> Result { - let available_extensions = self.0.enumerate_extensions()?; - let mut feature_list = FeatureList::default(); - feature_list.graphics.vulkan = available_extensions.khr_vulkan_enable2; - Ok(feature_list) - } - - fn create_instance(&self, features: FeatureList) -> Result { - let mut enabled_extensions = ExtensionSet::default(); - enabled_extensions.khr_vulkan_enable2 = features.graphics.vulkan; - let instance = self.0.create_instance( - &ApplicationInfo { - application_name: "Ambient", - ..Default::default() - }, - &enabled_extensions, - &[], - )?; - - Ok(OXrInstance { - enabled_features: features, - inner: instance, - } - .into()) - } -} - -pub struct OXrInstance { - inner: Instance, - enabled_features: FeatureList, -} - -impl XrInstanceTrait for OXrInstance { - fn requested_features(&self) -> FeatureList { - self.enabled_features - } - - fn create_session(&self, info: SessionCreateInfo) -> Result { - Ok(init_oxr_graphics( - self.inner.clone(), - self.enabled_features.graphics, - info.window, - )? - .into()) - } -} - -pub struct OXrSession { - pub(crate) instance: Instance, - pub(crate) graphics: OXrGraphics, - pub(crate) device: RenderDevice, - pub(crate) queue: RenderQueue, - pub(crate) adapter: RenderAdapter, - pub(crate) adapter_info: RenderAdapterInfo, - pub(crate) session: Session, - pub(crate) blend_mode: EnvironmentBlendMode, - pub(crate) resolution: UVec2, - pub(crate) format: wgpu::TextureFormat, - pub(crate) buffers: Vec, - pub(crate) image_index: Mutex, -} - -impl XrSessionTrait for OXrSession { - fn sync_controllers(&self, (left, _): (XrController, XrController)) -> Result<(), XrError> { - let XrControllerInner::OpenXR(controller) = &**left; - self.session - .sync_actions(&[(&controller.action_set).into()])?; - Ok(()) - } - - fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result { - todo!() - } - - fn create_controllers( - &self, - info: ActionSetCreateInfo, - ) -> Result<(XrController, XrController), XrError> { - let instance = &self.instance; - let action_set = instance.create_action_set("controllers", "XR Controllers", 0)?; - let left_path = instance.string_to_path("/user/hand/left").unwrap(); - let right_path = instance.string_to_path("/user/hand/right").unwrap(); - let hand_subpaths = &[left_path, right_path]; - use ActionPath::*; - let actions = &[ - HandPose, - PointerPose, - GripPull, - TriggerPull, - TriggerTouch, - HapticFeedback, - PrimaryButton, - PrimaryButtonTouch, - SecondaryButton, - SecondaryButtonTouch, - MenuButton, - ThumbstickX, - ThumbstickY, - ThumbstickTouch, - ThumbstickClick, - ThumbrestTouch, - ]; - let action_map = Rc::new(create_actions(&action_set, actions, hand_subpaths)?); - Ok(( - OXrController { - action_set: action_set.clone(), - actions: action_map.clone(), - side: Side::Left, - } - .into(), - OXrController { - action_set, - actions: action_map, - side: Side::Right, - } - .into(), - )) - } -} - -pub struct OXrAction {} - -pub struct OXrController { - action_set: ActionSet, - actions: Rc>, - side: Side, -} - -pub struct OXrActionSpace {} - -pub struct OXrReferenceSpace {} diff --git a/src/backend/oxr_utils.rs b/src/backend/oxr_utils.rs deleted file mode 100644 index 46c76f8..0000000 --- a/src/backend/oxr_utils.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::{ - error::XrError, - types::{ActionPath, ActionSidedness, ActionType, ControllerType, Side}, -}; -use bevy::utils::HashMap; -use openxr::{Action, ActionSet, Binding, Entry, Haptic, Path, Posef}; - -#[cfg(feature = "linked")] -pub fn xr_entry() -> Result { - Ok(Entry::linked()) -} - -#[cfg(not(feature = "linked"))] -pub fn xr_entry() -> Result { - unsafe { Entry::load().map_err(|_| XrError {}) } -} - -pub fn create_actions( - action_set: &ActionSet, - actions: &[ActionPath], - hand_subpaths: &[Path], -) -> Result, XrError> { - let mut action_map = HashMap::new(); - for action in actions { - let subaction_paths = match action.sidedness() { - ActionSidedness::Single => &[], - ActionSidedness::Double => hand_subpaths, - }; - let name = action.action_name(); - let localized_name = action.pretty_action_name(); - let typed_action = match action.action_type() { - ActionType::Bool => TypedAction::Bool(action_set.create_action( - name, - localized_name, - subaction_paths, - )?), - ActionType::Float => { - TypedAction::F32(action_set.create_action(name, localized_name, subaction_paths)?) - } - ActionType::Haptics => TypedAction::Haptic(action_set.create_action( - name, - localized_name, - subaction_paths, - )?), - ActionType::Pose => TypedAction::PoseF(action_set.create_action( - name, - localized_name, - subaction_paths, - )?), - }; - - action_map.insert(*action, typed_action); - } - Ok(action_map) -} - -pub enum TypedAction { - F32(Action), - Bool(Action), - PoseF(Action), - Haptic(Action), -} - -impl TypedAction { - fn make_binding(&self, name: Path) -> Binding { - match self { - TypedAction::F32(a) => Binding::new(a, name), - TypedAction::Bool(a) => Binding::new(a, name), - TypedAction::PoseF(a) => Binding::new(a, name), - TypedAction::Haptic(a) => Binding::new(a, name), - } - } -} - -impl ControllerType { - pub(crate) fn set_controller_bindings( - &self, - instance: &openxr::Instance, - bindings: &HashMap, - ) -> Result<(), XrError> { - match self { - ControllerType::OculusTouch => { - instance.suggest_interaction_profile_bindings( - instance.string_to_path("/interaction_profiles/oculus/touch_controller")?, - &[], - )?; - } - }; - Ok(()) - } - - fn get_binding_paths(&self, path: ActionPath) -> &[&'static str] { - match self { - ControllerType::OculusTouch => match path { - ActionPath::HandPose => &[ - "/user/hand/left/input/grip/pose", - "/user/hand/right/input/grip/pose", - ], - ActionPath::PointerPose => &[ - "/user/hand/left/input/aim/pose", - "/user/hand/right/input/aim/pose", - ], - ActionPath::GripPull => &[ - "/user/hand/left/input/squeeze/value", - "/user/hand/right/input/squeeze/value", - ], - ActionPath::TriggerPull => &[ - "/user/hand/left/input/trigger/value", - "/user/hand/right/input/trigger/value", - ], - ActionPath::TriggerTouch => &[ - "/user/hand/left/input/trigger/touch", - "/user/hand/right/input/trigger/touch", - ], - ActionPath::HapticFeedback => &[ - "/user/hand/left/output/haptic", - "/user/hand/right/output/haptic", - ], - ActionPath::PrimaryButton => &[], - ActionPath::PrimaryButtonTouch => todo!(), - ActionPath::SecondaryButton => todo!(), - ActionPath::SecondaryButtonTouch => todo!(), - ActionPath::MenuButton => todo!(), - ActionPath::ThumbstickX => todo!(), - ActionPath::ThumbstickY => todo!(), - ActionPath::ThumbstickTouch => todo!(), - ActionPath::ThumbstickClick => todo!(), - ActionPath::ThumbrestTouch => todo!(), - }, - } - } -} diff --git a/src/backend/traits.rs b/src/backend/traits.rs deleted file mode 100644 index 9a12253..0000000 --- a/src/backend/traits.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::error::XrError; -use crate::resources::*; -use crate::types::*; - -use bevy::render::primitives::Aabb; - -pub trait XrEntryTrait { - fn get_xr_entry(&self) -> Result; - fn get_available_features(&self) -> Result; - fn create_instance(&self, features: FeatureList) -> Result; -} - -pub trait XrInstanceTrait { - fn requested_features(&self) -> FeatureList; - fn create_session(&self, info: SessionCreateInfo) -> Result; -} - -pub trait XrSessionTrait { - fn sync_controllers(&self, controllers: (XrController, XrController)) -> Result<(), XrError>; - fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result; - fn create_controllers( - &self, - info: ActionSetCreateInfo, - ) -> Result<(XrController, XrController), XrError>; -} - -pub trait XrControllerTrait { - fn get_action_space(&self, info: ActionSpaceInfo) -> Result; - fn get_action(&self, id: ActionId) -> Option; -} - -pub trait XrInputTrait { - fn get_action_state(&self) -> ActionState; - fn get_action_bool(&self) -> Option { - if let ActionState::Bool(b) = self.get_action_state() { - Some(b) - } else { - None - } - } - fn get_action_float(&self) -> Option { - if let ActionState::Float(f) = self.get_action_state() { - Some(f) - } else { - None - } - } - fn get_action_haptic(&self) -> Option { - if let ActionState::Haptics(h) = self.get_action_state() { - Some(h) - } else { - None - } - } -} - -pub trait XrActionSpaceTrait { - fn locate(&self, base: XrReferenceSpace) -> Pose; -} - -pub trait XrReferenceSpaceTrait { - fn bounds(&self) -> Aabb; -} diff --git a/src/backend/web_utils.rs b/src/backend/web_utils.rs deleted file mode 100644 index 40cc5f9..0000000 --- a/src/backend/web_utils.rs +++ /dev/null @@ -1,56 +0,0 @@ -use wasm_bindgen::{JsCast, JsValue}; -use wasm_bindgen_futures::JsFuture; -use web_sys::{ - js_sys::{Object, Promise, Reflect}, - HtmlCanvasElement, WebGl2RenderingContext, -}; - -pub fn get_canvas(canvas_id: &str) -> Result { - let query = format!("#{}", canvas_id); - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let canvas = document - .query_selector(&query) - .unwrap() - .expect("bevy_webxr - could not find canvas"); - let canvas = canvas.dyn_into::()?; - Ok(canvas) -} - -pub fn create_webgl_context( - xr_mode: bool, - canvas: &str, -) -> Result { - let canvas = get_canvas(canvas)?; - - let gl: WebGl2RenderingContext = if xr_mode { - let gl_attribs = Object::new(); - Reflect::set( - &gl_attribs, - &JsValue::from_str("xrCompatible"), - &JsValue::TRUE, - )?; - canvas - .get_context_with_context_options("webgl2", &gl_attribs)? - .unwrap() - .dyn_into()? - } else { - canvas.get_context("webgl2")?.unwrap().dyn_into()? - }; - - Ok(gl) -} - -pub trait PromiseRes { - fn resolve>(self) -> Result; -} - -impl PromiseRes for Promise { - fn resolve>(self) -> Result { - resolve_promise(self) - } -} - -pub fn resolve_promise>(promise: Promise) -> Result { - bevy::tasks::block_on(async move { JsFuture::from(promise).await.map(Into::into) }) -} diff --git a/src/backend/webxr.rs b/src/backend/webxr.rs deleted file mode 100644 index 69d8329..0000000 --- a/src/backend/webxr.rs +++ /dev/null @@ -1,90 +0,0 @@ -use web_sys::XrRenderStateInit; -use web_sys::XrWebGlLayer; - -use super::traits::*; -use super::web_utils::*; - -use crate::error::XrError; -use crate::resources::*; -use crate::types::*; - -pub const BEVY_CANVAS_ID: &str = "bevy_canvas"; -pub const BEVY_CANVAS_QUERY: &str = "canvas[data-bevy-webxr=\"bevy_canvas\"]"; - -pub struct WebXrEntry(web_sys::XrSystem); - -impl XrEntryTrait for WebXrEntry { - fn get_xr_entry(&self) -> Result { - if let Some(window) = web_sys::window() { - Ok(WebXrEntry(window.navigator().xr()).into()) - } else { - Err(XrError {}) - } - } - - fn get_available_features(&self) -> Result { - Ok(FeatureList::default()) - } - - fn create_instance(&self, features: FeatureList) -> Result { - Ok(WebXrInstance { - xr: self.0.clone(), - features, - } - .into()) - } -} - -pub struct WebXrInstance { - xr: web_sys::XrSystem, - features: FeatureList, -} - -impl XrInstanceTrait for WebXrInstance { - fn requested_features(&self) -> FeatureList { - self.features - } - - fn create_session(&self, info: SessionCreateInfo) -> Result { - let session = self - .xr - .request_session(web_sys::XrSessionMode::ImmersiveVr) - .resolve()?; - - let gl = create_webgl_context(true, &info.canvas.expect("Expected canvas string"))?; - let xr_gl_layer = XrWebGlLayer::new_with_web_gl2_rendering_context(&session, &gl)?; - let mut render_state_init = XrRenderStateInit::new(); - render_state_init.base_layer(Some(&xr_gl_layer)); - session.update_render_state_with_state(&render_state_init); - - Ok(WebXrSession(session).into()) - } -} - -pub struct WebXrSession(web_sys::XrSession); - -impl XrSessionTrait for WebXrSession { - fn sync_actions(&self, action_set: XrActionSet) { - todo!() - } - - fn get_reference_space(&self, _info: ReferenceSpaceInfo) -> Result { - let space = self - .0 - .request_reference_space(web_sys::XrReferenceSpaceType::BoundedFloor) - .resolve()?; - Ok(WebXrReferenceSpace(space).into()) - } - - fn create_action_set(&self, info: ActionSetCreateInfo) -> Result { - todo!() - } -} - -pub struct WebXrActionSet(Vec); - -pub struct WebXrAction(web_sys::XrInputSource); - -pub struct WebXrActionSpace(web_sys::XrJointSpace); - -pub struct WebXrReferenceSpace(web_sys::XrBoundedReferenceSpace); diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 054b86e..0000000 --- a/src/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub struct XrError {} - -#[cfg(target_family = "wasm")] -impl From for XrError { - fn from(_: wasm_bindgen::JsValue) -> Self { - Self {} - } -} - -#[cfg(not(target_family = "wasm"))] -impl From for XrError { - fn from(_: openxr::sys::Result) -> Self { - Self {} - } -} diff --git a/src/lib.rs b/src/lib.rs index 758ea73..e69de29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +0,0 @@ -pub mod backend; -pub mod error; -mod resource_macros; -pub mod resources; -pub mod types; diff --git a/src/resource_macros.rs b/src/resource_macros.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/resource_macros.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/resources.rs b/src/resources.rs deleted file mode 100644 index f57f542..0000000 --- a/src/resources.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::rc::Rc; - -use bevy::prelude::{Deref, DerefMut}; - -use crate::backend; - -macro_rules! xr_resources { - ( - $( - $(#[$attr:meta])* - $name:ident; - )* - ) => { - paste::paste! { - $( - $(#[$attr])* - #[derive(Clone, Deref, DerefMut)] - pub struct $name(pub(crate) Rc]>); - )* - } - }; -} - -xr_resources! { - XrEntry; - XrInstance; - XrSession; - XrInput; - XrController; - XrActionSpace; - XrReferenceSpace; -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 6068b5d..0000000 --- a/src/types.rs +++ /dev/null @@ -1,191 +0,0 @@ -use bevy::{ - render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}, - window::RawHandleWrapper, -}; - -/// This struct stores all of the render resources required to initialize the bevy render plugin -/// -/// Returned from [XrSessionTrait::get_render_resources](crate::backend::traits::XrSessionTrait::get_render_resources) -pub struct RenderResources { - pub adapter: RenderAdapter, - pub adapter_info: RenderAdapterInfo, - pub device: RenderDevice, - pub queue: RenderQueue, -} - -/// Information used to create the [XrSession](crate::resources::XrSession) -/// -/// Passed into [XrInstanceTrait::create_session](crate::backend::traits::XrInstanceTrait::create_session) -pub struct SessionCreateInfo { - /// This field is required to be [Some] when using WebXR - pub canvas: Option, - pub window: Option, -} - -#[derive(Clone, Copy)] -pub enum ControllerType { - OculusTouch, -} - -#[derive(Clone)] -pub struct ActionSetCreateInfo { - pub controller: ControllerType, - // TODO!() allow custom fields -} - -pub struct ReferenceSpaceInfo {} - -pub struct ActionSpaceInfo {} - -#[derive(Clone, Copy, Default)] -pub struct FeatureList { - pub graphics: GraphicsFeatures, -} - -#[derive(Clone, Copy, Default)] -pub struct GraphicsFeatures { - pub vulkan: bool, -} - -pub struct Pose; - -pub struct Haptics; - -pub enum ActionState { - Bool(bool), - Float(f32), - Haptics(Haptics), - Pose(Pose), -} - -pub enum ActionType { - Bool, - Float, - Haptics, - Pose, -} - -pub struct ActionId { - pub action_path: ActionPath, - pub side: Option, -} - -#[derive(Clone, Copy, Hash, PartialEq, Eq)] -pub enum ActionPath { - HandPose, - PointerPose, - GripPull, - TriggerPull, - TriggerTouch, - HapticFeedback, - PrimaryButton, - PrimaryButtonTouch, - SecondaryButton, - SecondaryButtonTouch, - MenuButton, - ThumbstickX, - ThumbstickY, - ThumbstickTouch, - ThumbstickClick, - ThumbrestTouch, -} - -impl ActionPath { - pub fn action_name(&self) -> &'static str { - use ActionPath::*; - match self { - HandPose => "hand_pose", - PointerPose => "pointer_pose", - GripPull => "grip_pull", - TriggerPull => "trigger_pull", - TriggerTouch => "trigger_touch", - HapticFeedback => "haptic_feedback", - PrimaryButton => "primary_button", - PrimaryButtonTouch => "primary_button_touch", - SecondaryButton => "secondary_button", - SecondaryButtonTouch => "secondary_button_touch", - MenuButton => "menu_button", - ThumbstickX => "thumbstick_x", - ThumbstickY => "thumbstick_y", - ThumbstickTouch => "thumbstick_touch", - ThumbstickClick => "thumbstick_click", - ThumbrestTouch => "thumbrest_touch", - } - } - - pub fn pretty_action_name(&self) -> &'static str { - use ActionPath::*; - match self { - HandPose => "Hand Pose", - PointerPose => "Pointer Pose", - GripPull => "Grip Pull", - TriggerPull => "Trigger Pull", - TriggerTouch => "Trigger Touch", - HapticFeedback => "Haptic Feedback", - PrimaryButton => "Primary Button", - PrimaryButtonTouch => "Primary Button Touch", - SecondaryButton => "Secondary Button", - SecondaryButtonTouch => "Secondary Button Touch", - MenuButton => "Menu Button", - ThumbstickX => "Thumbstick X", - ThumbstickY => "Thumbstick Y", - ThumbstickTouch => "Thumbstick Touch", - ThumbstickClick => "Thumbstick Click", - ThumbrestTouch => "Thumbrest Touch", - } - } - - pub fn action_type(&self) -> ActionType { - use ActionPath::*; - match self { - HandPose => ActionType::Pose, - PointerPose => ActionType::Pose, - GripPull => ActionType::Float, - TriggerPull => ActionType::Float, - TriggerTouch => ActionType::Bool, - HapticFeedback => ActionType::Haptics, - PrimaryButton => ActionType::Bool, - PrimaryButtonTouch => ActionType::Bool, - SecondaryButton => ActionType::Bool, - SecondaryButtonTouch => ActionType::Bool, - MenuButton => ActionType::Bool, - ThumbstickX => ActionType::Float, - ThumbstickY => ActionType::Float, - ThumbstickTouch => ActionType::Bool, - ThumbstickClick => ActionType::Bool, - ThumbrestTouch => ActionType::Bool, - } - } - - pub fn sidedness(&self) -> ActionSidedness { - use ActionPath::*; - match self { - HandPose => ActionSidedness::Double, - PointerPose => ActionSidedness::Double, - GripPull => ActionSidedness::Double, - TriggerPull => ActionSidedness::Double, - TriggerTouch => ActionSidedness::Double, - HapticFeedback => ActionSidedness::Double, - PrimaryButton => ActionSidedness::Double, - PrimaryButtonTouch => ActionSidedness::Double, - SecondaryButton => ActionSidedness::Double, - SecondaryButtonTouch => ActionSidedness::Double, - MenuButton => ActionSidedness::Double, - ThumbstickX => ActionSidedness::Double, - ThumbstickY => ActionSidedness::Double, - ThumbstickTouch => ActionSidedness::Double, - ThumbstickClick => ActionSidedness::Double, - ThumbrestTouch => ActionSidedness::Double, - } - } -} - -pub enum ActionSidedness { - Single, - Double, -} - -pub enum Side { - Left, - Right, -} diff --git a/xr_api/Cargo.toml b/xr_api/Cargo.toml new file mode 100644 index 0000000..8936b3a --- /dev/null +++ b/xr_api/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "xr_api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = "0.3.29" +thiserror = "1.0.51" + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +openxr = "0.17.1" + +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-bindgen = "0.2.87" +web-sys = { version = "0.3.61", features = [ + # STANDARD + 'console', + 'Document', + 'Element', + 'Headers', + 'Navigator', + 'Window', + # IO + # 'Url', + # WEBGL + 'Gpu', + 'HtmlCanvasElement', + 'WebGl2RenderingContext', + 'WebGlFramebuffer', + ## XR + 'DomPointReadOnly', + 'XrWebGlLayer', + 'XrBoundedReferenceSpace', + 'XrEye', + 'XrFrame', + 'XrHandedness', + 'XrInputSource', + 'XrInputSourceArray', + 'XrInputSourceEvent', + 'XrInputSourceEventInit', + 'XrInputSourcesChangeEvent', + 'XrJointPose', + 'XrJointSpace', + 'XrPose', + 'XrReferenceSpace', + 'XrReferenceSpaceEvent', + 'XrReferenceSpaceEventInit', + 'XrReferenceSpaceType', + 'XrRenderState', + 'XrRenderStateInit', + 'XrRigidTransform', + 'XrSession', + 'XrSessionEvent', + 'XrSessionEventInit', + 'XrSessionInit', + 'XrSessionMode', + 'XrSpace', + 'XrTargetRayMode', + 'XrView', + 'XrViewerPose', + 'XrViewport', + 'XrVisibilityState', + 'XrWebGlLayer', + 'XrWebGlLayerInit', + 'XrSystem', +] } +wasm-bindgen-futures = "0.4" diff --git a/xr_api/src/api.rs b/xr_api/src/api.rs new file mode 100644 index 0000000..f25508c --- /dev/null +++ b/xr_api/src/api.rs @@ -0,0 +1,111 @@ +use std::ops::Deref; +use std::rc::Rc; + +use crate::prelude::*; + +#[derive(Clone)] +pub struct Entry(Rc); + +#[derive(Clone)] +pub struct Instance(Rc); + +#[derive(Clone)] +pub struct Session(Rc); + +#[derive(Clone)] +pub struct Input(Rc); + +#[derive(Clone)] +pub struct Action(A::Inner); + +impl Deref for Entry { + type Target = dyn EntryTrait; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Deref for Instance { + type Target = dyn InstanceTrait; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Deref for Session { + type Target = dyn SessionTrait; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Deref for Input { + type Target = dyn InputTrait; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Deref for Action +where + A: ActionType, + A::Inner: Deref, +{ + type Target = O; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl From for Entry { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl From for Instance { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl From for Session { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl From for Input { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl From for Action { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl + 'static> From for Action { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl + 'static> From for Action { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} + +impl + 'static> From for Action { + fn from(value: T) -> Self { + Self(Rc::new(value)) + } +} diff --git a/xr_api/src/api_traits.rs b/xr_api/src/api_traits.rs new file mode 100644 index 0000000..61ee541 --- /dev/null +++ b/xr_api/src/api_traits.rs @@ -0,0 +1,67 @@ +use crate::prelude::*; + +pub trait EntryTrait { + /// Return currently available extensions + fn available_extensions(&self) -> Result; + /// Create an [Instance] with the enabled extensions. + fn create_instance(&self, exts: ExtensionSet) -> Result; +} + +pub trait InstanceTrait { + /// Returns the [Entry] used to create this. + fn entry(&self) -> Entry; + /// Returns an [ExtensionSet] listing all enabled extensions. + fn enabled_extensions(&self) -> ExtensionSet; + /// Creates a [Session] with the requested properties + fn create_session(&self, info: SessionCreateInfo) -> Result; +} + +pub trait SessionTrait { + /// Returns the [Instance] used to create this. + fn instance(&self) -> &Instance; + /// Request input modules with the specified bindings. + fn create_input(&self, bindings: Bindings) -> Result; + /// Blocks until a rendering frame is available and then begins it. + fn begin_frame(&self) -> Result<()>; + /// Submits rendering work for this frame. + fn end_frame(&self) -> Result<()>; +} + +pub trait InputTrait { + fn get_haptics(&self, path: ActionId) -> Result>; + fn get_pose(&self, path: ActionId) -> Result>; + fn get_float(&self, path: ActionId) -> Result>; + fn get_bool(&self, path: ActionId) -> Result>; +} + +pub trait ActionTrait { + fn id(&self) -> ActionId; +} + +pub trait ActionInputTrait {} + +pub trait HapticTrait {} + +impl EntryTrait for T { + fn available_extensions(&self) -> Result { + self.entry().available_extensions() + } + + fn create_instance(&self, exts: ExtensionSet) -> Result { + self.entry().create_instance(exts) + } +} + +impl InstanceTrait for T { + fn entry(&self) -> Entry { + self.instance().entry() + } + + fn enabled_extensions(&self) -> ExtensionSet { + self.instance().enabled_extensions() + } + + fn create_session(&self, info: SessionCreateInfo) -> Result { + self.instance().create_session(info) + } +} diff --git a/xr_api/src/backend/mod.rs b/xr_api/src/backend/mod.rs new file mode 100644 index 0000000..407ca25 --- /dev/null +++ b/xr_api/src/backend/mod.rs @@ -0,0 +1,4 @@ +#[cfg(not(target_family = "wasm"))] +pub mod oxr; +#[cfg(target_family = "wasm")] +pub mod webxr; diff --git a/xr_api/src/backend/oxr.rs b/xr_api/src/backend/oxr.rs new file mode 100644 index 0000000..93c3fff --- /dev/null +++ b/xr_api/src/backend/oxr.rs @@ -0,0 +1,30 @@ +use crate::prelude::*; + +pub struct OXrEntry(openxr::Entry); + +impl EntryTrait for OXrEntry { + fn available_extensions(&self) -> Result { + // self.0.enumerate_extensions(); + Ok(ExtensionSet::default()) + } + + fn create_instance(&self, exts: ExtensionSet) -> Result { + todo!() + } +} + +pub struct OXrInstance(openxr::Instance); + +impl InstanceTrait for OXrInstance { + fn entry(&self) -> Entry { + OXrEntry(self.0.entry().clone()).into() + } + + fn enabled_extensions(&self) -> ExtensionSet { + todo!() + } + + fn create_session(&self, info: SessionCreateInfo) -> Result { + todo!() + } +} diff --git a/xr_api/src/backend/oxr/utils.rs b/xr_api/src/backend/oxr/utils.rs new file mode 100644 index 0000000..e69de29 diff --git a/xr_api/src/backend/webxr.rs b/xr_api/src/backend/webxr.rs new file mode 100644 index 0000000..6925393 --- /dev/null +++ b/xr_api/src/backend/webxr.rs @@ -0,0 +1,181 @@ +use std::sync::{ + mpsc::{channel, Sender}, + Mutex, +}; + +use crate::prelude::*; + +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{js_sys, XrFrame, XrInputSource}; + +mod utils; + +use utils::*; + +#[derive(Clone)] +pub struct WebXrEntry(web_sys::XrSystem); + +impl EntryTrait for WebXrEntry { + fn available_extensions(&self) -> Result { + Ok(ExtensionSet::default()) + } + + fn create_instance(&self, exts: ExtensionSet) -> Result { + Ok(WebXrInstance { + entry: self.clone(), + exts, + } + .into()) + } +} + +#[derive(Clone)] +pub struct WebXrInstance { + entry: WebXrEntry, + exts: ExtensionSet, +} + +impl InstanceTrait for WebXrInstance { + fn entry(&self) -> Entry { + self.entry.clone().into() + } + + fn enabled_extensions(&self) -> ExtensionSet { + self.exts + } + + fn create_session(&self, info: SessionCreateInfo) -> Result { + Ok(WebXrSession { + instance: self.clone().into(), + session: self + .entry + .0 + .request_session(web_sys::XrSessionMode::ImmersiveVr) + .resolve() + .map_err(|_| XrError::Placeholder)?, + end_frame_sender: Mutex::default(), + } + .into()) + } +} + +pub struct WebXrSession { + instance: Instance, + session: web_sys::XrSession, + end_frame_sender: Mutex>>, +} + +impl SessionTrait for WebXrSession { + fn instance(&self) -> &Instance { + &self.instance + } + + fn create_input(&self, bindings: Bindings) -> Result { + Ok(WebXrInput { + devices: self.session.input_sources(), + bindings, + } + .into()) + } + + fn begin_frame(&self) -> Result<()> { + let mut end_frame_sender = self.end_frame_sender.lock().unwrap(); + if end_frame_sender.is_some() { + Err(XrError::Placeholder)? + } + let (tx, rx) = channel::<()>(); + let (tx_end, rx_end) = channel::<()>(); + *end_frame_sender = Some(tx_end); + let on_frame: Closure = + Closure::new(move |time: f64, frame: XrFrame| { + tx.send(()).ok(); + rx_end.recv().ok(); + }); + + self.session + .request_animation_frame(on_frame.as_ref().unchecked_ref()); + + rx.recv().ok(); + Ok(()) + } + + fn end_frame(&self) -> Result<()> { + let mut end_frame_sender = self.end_frame_sender.lock().unwrap(); + match std::mem::take(&mut *end_frame_sender) { + Some(sender) => sender.send(()).ok(), + None => Err(XrError::Placeholder)?, + }; + Ok(()) + } +} + +pub struct WebXrInput { + devices: web_sys::XrInputSourceArray, + bindings: Bindings, +} + +impl From for Handedness { + fn from(value: web_sys::XrHandedness) -> Self { + match value { + web_sys::XrHandedness::None => Handedness::None, + web_sys::XrHandedness::Left => Handedness::Left, + web_sys::XrHandedness::Right => Handedness::Right, + _ => todo!(), + } + } +} + +impl WebXrInput { + fn get_controller(&self, handedness: Handedness) -> Option { + js_sys::try_iter(&self.devices).ok()??.find_map(|dev| { + if let Ok(dev) = dev { + let dev: XrInputSource = dev.into(); + if Into::::into(dev.handedness()) == handedness { + Some(dev) + } else { + None + } + } else { + None + } + }) + } +} + +impl InputTrait for WebXrInput { + fn get_haptics(&self, path: ActionId) -> Result> { + let haptics = self + .get_controller(path.handedness) + .ok_or(XrError::Placeholder)? + .gamepad() + .ok_or(XrError::Placeholder)? + .haptic_actuators() + .iter() + .next() + .ok_or(XrError::Placeholder)? + .into(); + Ok(WebXrHaptics(haptics, path).into()) + } + + fn get_pose(&self, path: ActionId) -> Result> { + todo!() + } + + fn get_float(&self, path: ActionId) -> Result> { + todo!() + } + + fn get_bool(&self, path: ActionId) -> Result> { + todo!() + } +} + +pub struct WebXrHaptics(web_sys::GamepadHapticActuator, ActionId); + +impl ActionTrait for WebXrHaptics { + fn id(&self) -> ActionId { + self.1 + } +} + +impl HapticTrait for WebXrHaptics {} diff --git a/xr_api/src/backend/webxr/utils.rs b/xr_api/src/backend/webxr/utils.rs new file mode 100644 index 0000000..3a9b15c --- /dev/null +++ b/xr_api/src/backend/webxr/utils.rs @@ -0,0 +1,17 @@ +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; +use web_sys::js_sys::Promise; + +pub trait PromiseRes { + fn resolve>(self) -> Result; +} + +impl PromiseRes for Promise { + fn resolve>(self) -> Result { + resolve_promise(self) + } +} + +pub fn resolve_promise>(promise: Promise) -> Result { + futures::executor::block_on(async move { JsFuture::from(promise).await.map(Into::into) }) +} diff --git a/xr_api/src/error.rs b/xr_api/src/error.rs new file mode 100644 index 0000000..9626a69 --- /dev/null +++ b/xr_api/src/error.rs @@ -0,0 +1,16 @@ +use std::fmt::Display; + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum XrError { + Placeholder, +} + +impl Display for XrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} diff --git a/xr_api/src/lib.rs b/xr_api/src/lib.rs new file mode 100644 index 0000000..49025fa --- /dev/null +++ b/xr_api/src/lib.rs @@ -0,0 +1,12 @@ +pub mod api; +pub mod api_traits; +pub mod backend; +pub mod error; +pub mod types; + +pub mod prelude { + pub use super::api::*; + pub use super::api_traits::*; + pub use super::error::*; + pub use super::types::*; +} diff --git a/xr_api/src/types.rs b/xr_api/src/types.rs new file mode 100644 index 0000000..432e2f0 --- /dev/null +++ b/xr_api/src/types.rs @@ -0,0 +1,51 @@ +use std::rc::Rc; + +use crate::api_traits::{ActionInputTrait, HapticTrait}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ExtensionSet {} + +pub enum SessionCreateInfo {} + +pub struct Bindings {} + +#[derive(Clone, Copy, PartialEq)] +pub struct ActionId { + pub handedness: Handedness, + pub device: Device, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Handedness { + Left, + Right, + None, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Device { + Controller, +} + +pub struct Haptic; +pub struct Pose; + +pub trait ActionType { + type Inner; +} + +impl ActionType for Haptic { + type Inner = Rc; +} + +impl ActionType for Pose { + type Inner = Rc>; +} + +impl ActionType for f32 { + type Inner = Rc>; +} + +impl ActionType for bool { + type Inner = Rc>; +}