From 80d6cadadf7c914f616673f5f37a8de1465f644a Mon Sep 17 00:00:00 2001 From: awtterpip Date: Sun, 18 Feb 2024 21:12:48 -0600 Subject: [PATCH] openxr changes --- src/openxr.rs | 27 +- src/openxr/extensions.rs | 36 +- src/openxr/graphics.rs | 133 ++++++ src/openxr/{init => graphics}/vulkan.rs | 524 ++++++++++++------------ src/openxr/init.rs | 224 ---------- src/openxr/resources.rs | 236 +++++------ src/openxr/types.rs | 261 +++++++++++- 7 files changed, 782 insertions(+), 659 deletions(-) create mode 100644 src/openxr/graphics.rs rename src/openxr/{init => graphics}/vulkan.rs (63%) delete mode 100644 src/openxr/init.rs diff --git a/src/openxr.rs b/src/openxr.rs index e511552..a43f34e 100644 --- a/src/openxr.rs +++ b/src/openxr.rs @@ -1,16 +1,39 @@ -pub mod extensions; -pub mod init; +mod extensions; +pub mod graphics; mod resources; pub mod types; pub use resources::*; +pub use types::*; use bevy::app::{App, Plugin}; +pub fn xr_entry() -> Result { + #[cfg(windows)] + let entry = openxr::Entry::linked(); + #[cfg(not(windows))] + let entry = unsafe { openxr::Entry::load()? }; + Ok(entry.into()) +} + +fn init_xr() -> Result<()> { + let entry = xr_entry()?; + let instance = entry.create_instance( + AppInfo::default(), + XrExtensions::default(), + GraphicsBackend::Vulkan(()), + )?; + let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?; + + instance.create_session(system_id)?; + Ok(()) +} + pub struct XrInitPlugin; impl Plugin for XrInitPlugin { fn build(&self, app: &mut App) { + let entry = xr_entry(); todo!() } } diff --git a/src/openxr/extensions.rs b/src/openxr/extensions.rs index b5d8f09..2c376ec 100644 --- a/src/openxr/extensions.rs +++ b/src/openxr/extensions.rs @@ -49,40 +49,6 @@ impl Default for XrExtensions { } } -macro_rules! unavailable_exts { - ( - $exts:ty; - $( - $( - #[$meta:meta] - )* - $ident:ident - ),* - $(,)? - ) => { - impl $exts { - /// Returns any extensions needed by `required_exts` that aren't available in `self` - pub(crate) fn unavailable_exts(&self, required_exts: &Self) -> Vec> { - let mut exts = vec![]; - $( - $( - #[$meta] - )* - if required_exts.0.$ident && !self.0.$ident { - exts.push(std::borrow::Cow::Borrowed(stringify!($ident))) - } - )* - for ext in required_exts.0.other.iter() { - if !self.0.other.contains(ext) { - exts.push(std::borrow::Cow::Owned(ext.clone())) - } - } - exts - } - } - }; -} - macro_rules! bitor { ( $exts:ty; @@ -288,4 +254,4 @@ macro_rules! impl_ext { }; } -impl_ext!(bitor, bitand, unavailable_exts); +impl_ext!(bitor, bitand); diff --git a/src/openxr/graphics.rs b/src/openxr/graphics.rs new file mode 100644 index 0000000..acf0f7d --- /dev/null +++ b/src/openxr/graphics.rs @@ -0,0 +1,133 @@ +pub mod vulkan; + +use bevy::math::UVec2; + +use crate::openxr::resources::*; +use crate::openxr::types::{AppInfo, Result, XrError}; +use crate::types::BlendMode; + +trait GraphicWrapper { + type Inner; + type Func: Fn(); +} + +pub trait GraphicsExt: openxr::Graphics { + fn from_wgpu_format(format: wgpu::TextureFormat) -> Option; + fn to_wgpu_format(format: Self::Format) -> Option; + fn create_session( + app_info: &AppInfo, + instance: &openxr::Instance, + system_id: openxr::SystemId, + ) -> Result<( + wgpu::Device, + wgpu::Queue, + wgpu::Adapter, + wgpu::Instance, + XrSession, + XrFrameWaiter, + XrFrameStream, + )>; + unsafe fn to_wgpu_img( + image: Self::SwapchainImage, + device: &wgpu::Device, + format: wgpu::TextureFormat, + resolution: UVec2, + ) -> Result; + fn required_exts() -> XrExtensions; +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GraphicsWrap { + Vulkan(T::Inner), +} + +impl GraphicsWrap { + /// Returns the name of the graphics api this struct is using. + pub fn graphics_name(&self) -> &'static str { + graphics_match!( + self; + _ => std::any::type_name::() + ) + } + + fn graphics_type(&self) -> std::any::TypeId { + graphics_match!( + self; + _ => std::any::TypeId::of::() + ) + } + + /// Checks if this struct is using the wanted graphics api. + pub fn using_graphics(&self) -> bool { + self.graphics_type() == std::any::TypeId::of::() + } +} + +pub trait GraphicsType { + type Inner; +} + +impl GraphicsType for () { + type Inner = (); +} + +macro_rules! graphics_match { + ( + $field:expr; + $var:pat => $expr:expr $(=> $($return:tt)*)? + ) => { + match $field { + $crate::openxr::graphics::GraphicsWrap::Vulkan($var) => { + #[allow(unused)] + type Api = openxr::Vulkan; + graphics_match!(@arm_impl Vulkan; $expr $(=> $($return)*)?) + }, + } + }; + + ( + @arm_impl + $variant:ident; + $expr:expr => $wrap_ty:ty + ) => { + GraphicsWrap::<$wrap_ty>::$variant($expr) + }; + + ( + @arm_impl + $variant:ident; + $expr:expr + ) => { + $expr + }; +} + +pub(crate) use graphics_match; + +use super::XrExtensions; + +impl From for BlendMode { + fn from(value: openxr::EnvironmentBlendMode) -> Self { + use openxr::EnvironmentBlendMode; + if value == EnvironmentBlendMode::OPAQUE { + BlendMode::Opaque + } else if value == EnvironmentBlendMode::ADDITIVE { + BlendMode::Additive + } else if value == EnvironmentBlendMode::ALPHA_BLEND { + BlendMode::AlphaBlend + } else { + unreachable!() + } + } +} + +impl From for openxr::EnvironmentBlendMode { + fn from(value: BlendMode) -> Self { + use openxr::EnvironmentBlendMode; + match value { + BlendMode::Opaque => EnvironmentBlendMode::OPAQUE, + BlendMode::Additive => EnvironmentBlendMode::ADDITIVE, + BlendMode::AlphaBlend => EnvironmentBlendMode::ALPHA_BLEND, + } + } +} diff --git a/src/openxr/init/vulkan.rs b/src/openxr/graphics/vulkan.rs similarity index 63% rename from src/openxr/init/vulkan.rs rename to src/openxr/graphics/vulkan.rs index 3a4445f..2fd09f4 100644 --- a/src/openxr/init/vulkan.rs +++ b/src/openxr/graphics/vulkan.rs @@ -9,14 +9,9 @@ use wgpu_hal::Api; use crate::openxr::extensions::XrExtensions; use crate::openxr::resources::*; +use crate::openxr::types::Result; -use super::{AppInfo, GraphicsExt, XrInitError}; - -pub fn required_exts() -> XrExtensions { - let mut extensions = openxr::ExtensionSet::default(); - extensions.khr_vulkan_enable2 = true; - extensions.into() -} +use super::{AppInfo, GraphicsExt, XrError}; #[cfg(not(target_os = "android"))] const VK_TARGET_VERSION: Version = Version::new(1, 2, 0); @@ -30,237 +25,6 @@ const VK_TARGET_VERSION_ASH: u32 = ash::vk::make_api_version( VK_TARGET_VERSION.patch() as u32, ); -pub fn enumerate_swapchain_formats( - session: &openxr::Session, -) -> openxr::Result> { - let formats = session - .enumerate_swapchain_formats()? - .into_iter() - .filter_map(|f| vulkan_to_wgpu(ash::vk::Format::from_raw(f as _))) - .collect(); - Ok(formats) -} - -pub fn create_session( - app_info: AppInfo, - instance: &openxr::Instance, - system_id: openxr::SystemId, - format: wgpu::TextureFormat, - resolution: UVec2, -) -> Result< - ( - wgpu::Device, - wgpu::Queue, - wgpu::Adapter, - wgpu::Instance, - openxr::Session, - openxr::FrameWaiter, - FrameStreamInner, - ), - XrInitError, -> { - let reqs = instance.graphics_requirements::(system_id)?; - if VK_TARGET_VERSION < reqs.min_api_version_supported - || VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major() - { - error!( - "OpenXR runtime requires Vulkan version > {}, < {}.0.0", - reqs.min_api_version_supported, - reqs.max_api_version_supported.major() + 1 - ); - return Err(XrInitError::FailedGraphicsRequirements); - }; - let vk_entry = unsafe { ash::Entry::load() }?; - let flags = wgpu_hal::InstanceFlags::empty(); - let extensions = - ::Instance::required_extensions(&vk_entry, VK_TARGET_VERSION_ASH, flags)?; - let device_extensions = vec![ - ash::extensions::khr::Swapchain::name(), - ash::extensions::khr::DrawIndirectCount::name(), - #[cfg(target_os = "android")] - ash::extensions::khr::TimelineSemaphore::name(), - ]; - - let vk_instance = unsafe { - let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect(); - - let app_name = CString::new(app_info.name)?; - let vk_app_info = ash::vk::ApplicationInfo::builder() - .application_name(&app_name) - .application_version(1) - .engine_name(&app_name) - .engine_version(1) - .api_version(VK_TARGET_VERSION_ASH); - - let vk_instance = instance - .create_vulkan_instance( - system_id, - std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), - &ash::vk::InstanceCreateInfo::builder() - .application_info(&vk_app_info) - .enabled_extension_names(&extensions_cchar) as *const _ - as *const _, - )? - .map_err(ash::vk::Result::from_raw)?; - - ash::Instance::load( - vk_entry.static_fn(), - ash::vk::Instance::from_raw(vk_instance as _), - ) - }; - - let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void; - - let vk_physical_device = ash::vk::PhysicalDevice::from_raw(unsafe { - instance.vulkan_graphics_device(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_ASH { - unsafe { vk_instance.destroy_instance(None) } - error!( - "Vulkan physical device doesn't support version {}.{}.{}", - VK_TARGET_VERSION.major(), - VK_TARGET_VERSION.minor(), - VK_TARGET_VERSION.patch() - ); - return Err(XrInitError::FailedGraphicsRequirements); - } - - let wgpu_vk_instance = unsafe { - ::Instance::from_raw( - vk_entry.clone(), - vk_instance.clone(), - VK_TARGET_VERSION_ASH, - 0, - None, - extensions, - flags, - false, - Some(Box::new(())), - )? - }; - - 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 Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else { - error!("WGPU failed to provide an adapter"); - return Err(XrInitError::FailedGraphicsRequirements); - }; - - 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 = ash::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( - ash::vk::DeviceCreateInfo::builder() - .queue_create_infos(&family_infos) - .push_next(&mut ash::vk::PhysicalDeviceMultiviewFeatures { - multiview: ash::vk::TRUE, - ..Default::default() - }), - ) - .enabled_extension_names(&extensions_cchar) - .build(); - let vk_device = unsafe { - let vk_device = instance - .create_vulkan_device( - 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(ash::vk::Result::from_raw)?; - - ash::Device::load( - vk_instance.fp_v1_0(), - ash::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, - ) - }?; - - ( - 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, - ) - }?; - - let (session, frame_wait, frame_stream) = unsafe { - instance.create_session::( - system_id, - &openxr::vulkan::SessionCreateInfo { - instance: vk_instance_ptr, - physical_device: vk_physical_device_ptr, - device: vk_device_ptr, - queue_family_index, - queue_index: 0, - }, - ) - }?; - - Ok(( - wgpu_device, - wgpu_queue, - wgpu_adapter, - wgpu_instance, - session.into_any_graphics(), - frame_wait, - FrameStreamInner::Vulkan(frame_stream), - )) -} - impl GraphicsExt for openxr::Vulkan { fn from_wgpu_format(format: wgpu::TextureFormat) -> Option { wgpu_to_vulkan(format).map(|f| f.as_raw() as _) @@ -271,24 +35,280 @@ impl GraphicsExt for openxr::Vulkan { } fn create_session( - app_info: AppInfo, + app_info: &AppInfo, instance: &openxr::Instance, system_id: openxr::SystemId, + ) -> Result<( + wgpu::Device, + wgpu::Queue, + wgpu::Adapter, + wgpu::Instance, + XrSession, + XrFrameWaiter, + XrFrameStream, + )> { + let reqs = instance.graphics_requirements::(system_id)?; + if VK_TARGET_VERSION < reqs.min_api_version_supported + || VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major() + { + error!( + "OpenXR runtime requires Vulkan version > {}, < {}.0.0", + reqs.min_api_version_supported, + reqs.max_api_version_supported.major() + 1 + ); + return Err(XrError::FailedGraphicsRequirements); + }; + let vk_entry = unsafe { ash::Entry::load() }?; + let flags = wgpu_hal::InstanceFlags::empty(); + let extensions = ::Instance::required_extensions( + &vk_entry, + VK_TARGET_VERSION_ASH, + flags, + )?; + let device_extensions = vec![ + ash::extensions::khr::Swapchain::name(), + ash::extensions::khr::DrawIndirectCount::name(), + #[cfg(target_os = "android")] + ash::extensions::khr::TimelineSemaphore::name(), + ]; + + let vk_instance = unsafe { + let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect(); + + let app_name = CString::new(app_info.name.clone().into_owned())?; + let vk_app_info = ash::vk::ApplicationInfo::builder() + .application_name(&app_name) + .application_version(1) + .engine_name(&app_name) + .engine_version(1) + .api_version(VK_TARGET_VERSION_ASH); + + let vk_instance = instance + .create_vulkan_instance( + system_id, + std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), + &ash::vk::InstanceCreateInfo::builder() + .application_info(&vk_app_info) + .enabled_extension_names(&extensions_cchar) as *const _ + as *const _, + )? + .map_err(ash::vk::Result::from_raw)?; + + ash::Instance::load( + vk_entry.static_fn(), + ash::vk::Instance::from_raw(vk_instance as _), + ) + }; + + let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void; + + let vk_physical_device = ash::vk::PhysicalDevice::from_raw(unsafe { + instance.vulkan_graphics_device(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_ASH { + unsafe { vk_instance.destroy_instance(None) } + error!( + "Vulkan physical device doesn't support version {}.{}.{}", + VK_TARGET_VERSION.major(), + VK_TARGET_VERSION.minor(), + VK_TARGET_VERSION.patch() + ); + return Err(XrError::FailedGraphicsRequirements); + } + + let wgpu_vk_instance = unsafe { + ::Instance::from_raw( + vk_entry.clone(), + vk_instance.clone(), + VK_TARGET_VERSION_ASH, + 0, + None, + extensions, + flags, + false, + Some(Box::new(())), + )? + }; + + 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 Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else { + error!("WGPU failed to provide an adapter"); + return Err(XrError::FailedGraphicsRequirements); + }; + + 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 = ash::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( + ash::vk::DeviceCreateInfo::builder() + .queue_create_infos(&family_infos) + .push_next(&mut ash::vk::PhysicalDeviceMultiviewFeatures { + multiview: ash::vk::TRUE, + ..Default::default() + }), + ) + .enabled_extension_names(&extensions_cchar) + .build(); + let vk_device = unsafe { + let vk_device = instance + .create_vulkan_device( + 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(ash::vk::Result::from_raw)?; + + ash::Device::load( + vk_instance.fp_v1_0(), + ash::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, + ) + }?; + + ( + 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, + ) + }?; + + let (session, frame_wait, frame_stream) = unsafe { + instance.create_session::( + system_id, + &openxr::vulkan::SessionCreateInfo { + instance: vk_instance_ptr, + physical_device: vk_physical_device_ptr, + device: vk_device_ptr, + queue_family_index, + queue_index: 0, + }, + ) + }?; + + Ok(( + wgpu_device, + wgpu_queue, + wgpu_adapter, + wgpu_instance, + XrSession( + session.clone().into_any_graphics(), + super::GraphicsWrap::Vulkan(session), + ), + XrFrameWaiter(frame_wait), + XrFrameStream(super::GraphicsWrap::Vulkan(frame_stream)), + )) + } + + unsafe fn to_wgpu_img( + color_image: Self::SwapchainImage, + device: &wgpu::Device, format: wgpu::TextureFormat, resolution: UVec2, - ) -> Result< - ( - wgpu::Device, - wgpu::Queue, - wgpu::Adapter, - wgpu::Instance, - openxr::Session, - openxr::FrameWaiter, - FrameStreamInner, - ), - XrInitError, - > { - create_session(app_info, instance, system_id, format, resolution) + ) -> Result { + let color_image = ash::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: 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 { + 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: format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }, + ) + }; + Ok(texture) + } + + fn required_exts() -> XrExtensions { + let mut extensions = openxr::ExtensionSet::default(); + extensions.khr_vulkan_enable2 = true; + extensions.into() } } diff --git a/src/openxr/init.rs b/src/openxr/init.rs deleted file mode 100644 index 38de9bc..0000000 --- a/src/openxr/init.rs +++ /dev/null @@ -1,224 +0,0 @@ -pub mod vulkan; - -use bevy::log::{info, warn}; -use bevy::math::{uvec2, UVec2}; -use thiserror::Error; - -use crate::openxr::resources::*; -use crate::types::BlendMode; - -use super::extensions::XrExtensions; - -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct Version(pub u8, pub u8, pub u16); - -impl Version { - pub const BEVY: Self = Self(0, 12, 1); - - pub const fn to_u32(self) -> u32 { - let major = (self.0 as u32) << 24; - let minor = (self.1 as u32) << 16; - self.2 as u32 | major | minor - } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct AppInfo<'a> { - pub name: &'a str, - pub version: Version, -} - -#[derive(Clone, Copy, Debug)] -pub enum GraphicsBackend { - Vulkan, -} - -impl GraphicsBackend { - const ALL: &'static [Self] = &[Self::Vulkan]; - - pub fn available_backends(exts: &XrExtensions) -> Vec { - Self::ALL - .iter() - .copied() - .filter(|backend| backend.is_available(exts)) - .collect() - } - - pub fn is_available(&self, exts: &XrExtensions) -> bool { - self.required_exts().is_available(exts) - } - - pub fn required_exts(&self) -> XrExtensions { - match self { - GraphicsBackend::Vulkan => vulkan::required_exts(), - } - } -} - -#[derive(Error, Debug)] -pub enum XrInitError { - #[error("OpenXR error: {0}")] - OpenXrError(#[from] openxr::sys::Result), - #[error("OpenXR loading error: {0}")] - OpenXrLoadingError(#[from] openxr::LoadError), - #[error("WGPU instance error: {0}")] - WgpuInstanceError(#[from] wgpu_hal::InstanceError), - #[error("WGPU device error: {0}")] - WgpuDeviceError(#[from] wgpu_hal::DeviceError), - #[error("WGPU request device error: {0}")] - WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError), - #[error("Unsupported texture format: {0:?}")] - UnsupportedTextureFormat(wgpu::TextureFormat), - #[error("Vulkan error: {0}")] - VulkanError(#[from] ash::vk::Result), - #[error("Vulkan loading error: {0}")] - VulkanLoadingError(#[from] ash::LoadingError), - #[error("Graphics backend '{0:?}' is not available")] - UnavailableBackend(GraphicsBackend), - #[error("Could not meet graphics requirements for platform. See console for details")] - FailedGraphicsRequirements, - #[error("Failed to create CString: {0}")] - NulError(#[from] std::ffi::NulError), -} - -pub trait GraphicsExt: openxr::Graphics { - fn from_wgpu_format(format: wgpu::TextureFormat) -> Option; - fn to_wgpu_format(format: Self::Format) -> Option; - fn create_session( - app_info: AppInfo, - instance: &openxr::Instance, - system_id: openxr::SystemId, - format: wgpu::TextureFormat, - resolution: UVec2, - ) -> Result< - ( - wgpu::Device, - wgpu::Queue, - wgpu::Adapter, - wgpu::Instance, - openxr::Session, - openxr::FrameWaiter, - FrameStreamInner, - ), - XrInitError, - >; -} - -fn xr_entry() -> Result { - #[cfg(windows)] - let entry = Some(openxr::Entry::linked()); - #[cfg(not(windows))] - let entry = unsafe { openxr::Entry::load()? }; - Ok(entry) -} - -// pub fn init_xr( -// app_info: AppInfo, -// requested_exts: XrExtensions, -// format: wgpu::TextureFormat, -// preferred_blend_mode: BlendMode, -// ) -> Result< -// ( -// wgpu::Device, -// wgpu::Queue, -// wgpu::Adapter, -// wgpu::Instance, -// openxr::Instance, -// openxr::Session, -// openxr::FrameWaiter, -// FrameStreamInner, -// XrSwapchain, -// ), -// XrInitError, -// > { -// let entry = xr_entry().unwrap(); - -// let required_exts = vulkan::required_exts() | requested_exts; -// let available_exts: XrExtensions = entry.enumerate_extensions()?.into(); -// for ext in available_exts.unavailable_exts(&required_exts) { -// warn!("OpenXR extension '{ext}' is not supported by the current OpenXR runtime") -// } -// let enabled_exts = required_exts & available_exts; - -// let instance = entry.create_instance( -// &openxr::ApplicationInfo { -// application_name: app_info.name, -// application_version: app_info.version.to_u32(), -// engine_name: "Bevy", -// engine_version: Version::BEVY.to_u32(), -// }, -// &enabled_exts.into(), -// &[], -// )?; -// info!("Created OpenXR Instance: {:#?}", instance.properties()?); - -// let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?; -// info!( -// "Using system: {:#?}", -// instance.system_properties(system_id)? -// ); - -// let view = instance -// .enumerate_view_configurations(system_id)? -// .first() -// .copied() -// .unwrap_or(openxr::ViewConfigurationType::PRIMARY_STEREO); - -// let resolution = instance -// .enumerate_view_configuration_views(system_id, view)? -// .first() -// .map(|c| { -// uvec2( -// c.recommended_image_rect_width, -// c.recommended_image_rect_height, -// ) -// }) -// .unwrap(); - -// let (wgpu_device, wgpu_queue, wgpu_adapter, wgpu_instance, session, frame_waiter, frame_stream) = -// vulkan::create_session(app_info, &instance, system_id, format, resolution)?; - -// let blend_modes = instance.enumerate_environment_blend_modes(system_id, view)?; - -// let blend_mode = if blend_modes.contains(&preferred_blend_mode.into()) { -// preferred_blend_mode.into() -// } else { -// warn!( -// "Runtime does not support blend mode '{:?}'", -// preferred_blend_mode -// ); -// blend_modes -// .first() -// .copied() -// .unwrap_or(openxr::EnvironmentBlendMode::OPAQUE) -// }; -// info!("Using blend mode '{:?}'", blend_mode); - -// todo!() -// } - -impl From for BlendMode { - fn from(value: openxr::EnvironmentBlendMode) -> Self { - use openxr::EnvironmentBlendMode; - if value == EnvironmentBlendMode::OPAQUE { - BlendMode::Opaque - } else if value == EnvironmentBlendMode::ADDITIVE { - BlendMode::Additive - } else if value == EnvironmentBlendMode::ALPHA_BLEND { - BlendMode::AlphaBlend - } else { - unreachable!() - } - } -} - -impl From for openxr::EnvironmentBlendMode { - fn from(value: BlendMode) -> Self { - use openxr::EnvironmentBlendMode; - match value { - BlendMode::Opaque => EnvironmentBlendMode::OPAQUE, - BlendMode::Additive => EnvironmentBlendMode::ADDITIVE, - BlendMode::AlphaBlend => EnvironmentBlendMode::ALPHA_BLEND, - } - } -} diff --git a/src/openxr/resources.rs b/src/openxr/resources.rs index 6ee6304..b4356be 100644 --- a/src/openxr/resources.rs +++ b/src/openxr/resources.rs @@ -1,15 +1,8 @@ -use std::sync::{Arc, Mutex}; - -use ash::vk::Handle; use bevy::prelude::*; -use openxr::{AnyGraphics, Vulkan}; +use openxr::AnyGraphics; -use crate::openxr::init::Version; - -use super::extensions::XrExtensions; -use super::init::{self, AppInfo, GraphicsBackend, GraphicsExt, XrInitError}; -type Result = std::result::Result; +use super::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap}; use super::types::*; #[derive(Deref, Clone)] @@ -21,22 +14,22 @@ impl XrEntry { } pub fn create_instance( - entry: XrEntry, + &self, app_info: AppInfo, exts: XrExtensions, backend: GraphicsBackend, ) -> Result { - let available_exts = entry.enumerate_extensions()?; + let available_exts = self.enumerate_extensions()?; if !backend.is_available(&available_exts) { - return Err(XrInitError::UnavailableBackend(backend)); + return Err(XrError::UnavailableBackend(backend)); } let required_exts = exts | backend.required_exts(); - let instance = entry.create_instance( + let instance = self.0.create_instance( &openxr::ApplicationInfo { - application_name: app_info.name, + application_name: &app_info.name, application_version: app_info.version.to_u32(), engine_name: "Bevy", engine_version: Version::BEVY.to_u32(), @@ -45,7 +38,7 @@ impl XrEntry { &[], )?; - Ok(XrInstance(instance, backend)) + Ok(XrInstance(instance, backend, app_info)) } pub fn available_backends(&self) -> Result> { @@ -65,187 +58,148 @@ impl From for XrEntry { pub struct XrInstance( #[deref] pub(crate) openxr::Instance, pub(crate) GraphicsBackend, + pub(crate) AppInfo, ); impl XrInstance { pub fn create_session( &self, - app_info: AppInfo, system_id: openxr::SystemId, - format: wgpu::TextureFormat, - resolution: UVec2, ) -> Result<( wgpu::Device, wgpu::Queue, wgpu::Adapter, wgpu::Instance, - openxr::Session, - openxr::FrameWaiter, - FrameStreamInner, + XrSession, + XrFrameWaiter, + XrFrameStream, )> { - match self.1 { - GraphicsBackend::Vulkan => { - openxr::Vulkan::create_session(app_info, &self.0, system_id, format, resolution) - } - } + graphics_match!( + self.1; + _ => Api::create_session(&self.2, &self.0, system_id) + ) } } +impl GraphicsType for XrSession { + type Inner = openxr::Session; +} + #[derive(Resource, Deref, Clone)] pub struct XrSession( #[deref] pub(crate) openxr::Session, - pub(crate) TypedSession, + pub(crate) GraphicsWrap, ); impl XrSession { pub fn enumerate_swapchain_formats(&self) -> Result> { - self.1.enumerate_swapchain_formats() + graphics_match!( + &self.1; + session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::to_wgpu_format).collect()) + ) } pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result { - self.1.create_swapchain(info) + Ok(XrSwapchain(graphics_match!( + &self.1; + session => session.create_swapchain(&info.try_into()?)? => XrSwapchain + ))) } } -#[derive(Clone)] -pub enum TypedSession { - Vulkan(openxr::Session), +pub struct XrFrameStream(pub(crate) GraphicsWrap); + +impl GraphicsType for XrFrameStream { + type Inner = openxr::FrameStream; } -impl TypedSession { - pub fn into_any_graphics(&self) -> openxr::Session { - match self { - TypedSession::Vulkan(session) => session.clone().into_any_graphics(), - } +impl XrFrameStream { + pub fn begin(&mut self) -> openxr::Result<()> { + graphics_match!( + &mut self.0; + stream => stream.begin() + ) } - pub fn enumerate_swapchain_formats(&self) -> Result> { - Ok(match self { - TypedSession::Vulkan(session) => init::vulkan::enumerate_swapchain_formats(session), - }?) - } + pub fn end( + &mut self, + display_time: openxr::Time, + environment_blend_mode: openxr::EnvironmentBlendMode, + layers: &[&dyn CompositionLayer], + ) -> Result<()> { + graphics_match!( + &mut self.0; + stream => { + let mut new_layers = vec![]; - pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result { - Ok(match self { - TypedSession::Vulkan(session) => { - XrSwapchain::Vulkan(session.create_swapchain(&info.try_into()?)?) + for (i, layer) in layers.into_iter().enumerate() { + if let Some(swapchain) = layer.swapchain() { + if !swapchain.0.using_graphics::() { + warn!( + "composition layer {i} is using graphics api '{}', expected graphics api '{}'. Excluding layer from frame submission.", + swapchain.0.graphics_name(), + std::any::type_name::(), + ); + continue; + } + } + new_layers.push(unsafe { std::mem::transmute(layer.header()) }); + } + + Ok(stream.end(display_time, environment_blend_mode, new_layers.as_slice())?) } - }) + ) } } -#[derive(Resource, Default, Deref)] -pub struct Framebuffers(pub Vec); +#[derive(Deref, DerefMut)] +pub struct XrFrameWaiter(pub openxr::FrameWaiter); -#[derive(Clone)] -pub struct Swapchain { - pub inner: Arc>, - pub format: wgpu::TextureFormat, - pub resolution: UVec2, -} +pub struct XrSwapchain(pub(crate) GraphicsWrap); -pub enum FrameStreamInner { - Vulkan(openxr::FrameStream), -} - -pub enum XrSwapchain { - Vulkan(openxr::Swapchain), +impl GraphicsType for XrSwapchain { + type Inner = openxr::Swapchain; } impl XrSwapchain { pub fn acquire_image(&mut self) -> Result { - Ok(match self { - XrSwapchain::Vulkan(swap) => swap.acquire_image()?, - }) + graphics_match!( + &mut self.0; + swap => Ok(swap.acquire_image()?) + ) } pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> { - Ok(match self { - XrSwapchain::Vulkan(swap) => swap.wait_image(timeout)?, - }) + graphics_match!( + &mut self.0; + swap => Ok(swap.wait_image(timeout)?) + ) } pub fn release_image(&mut self) -> Result<()> { - Ok(match self { - XrSwapchain::Vulkan(swap) => swap.release_image()?, - }) + graphics_match!( + &mut self.0; + swap => Ok(swap.release_image()?) + ) } pub fn enumerate_images( &mut self, - device: wgpu::Device, + device: &wgpu::Device, format: wgpu::TextureFormat, resolution: UVec2, ) -> Result> { - match self { - XrSwapchain::Vulkan(swap) => swap.enumerate_imgs(device, format, resolution), - } - } -} - -trait EnumerateImages { - fn enumerate_imgs( - &mut self, - device: wgpu::Device, - format: wgpu::TextureFormat, - resolution: UVec2, - ) -> Result>; -} - -impl EnumerateImages for openxr::Swapchain { - fn enumerate_imgs( - &mut self, - device: wgpu::Device, - format: wgpu::TextureFormat, - resolution: UVec2, - ) -> Result> { - let images = self.enumerate_images()?; - let images = images.into_iter().map(|color_image| { - let color_image = ash::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: 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 { - 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: format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }, - ) - }; - texture - }); - Ok(images.collect()) + graphics_match!( + &mut self.0; + swap => { + let mut images = vec![]; + for image in swap.enumerate_images()? { + unsafe { + images.push(Api::to_wgpu_img(image, device, format, resolution)?); + } + } + Ok(images) + } + ) } } diff --git a/src/openxr/types.rs b/src/openxr/types.rs index 334ef3d..0043667 100644 --- a/src/openxr/types.rs +++ b/src/openxr/types.rs @@ -1,6 +1,83 @@ -pub use openxr::{SwapchainCreateFlags, SwapchainUsageFlags, SystemId}; +use std::borrow::Cow; +use thiserror::Error; -use super::init::{GraphicsExt, XrInitError}; +use super::graphics::{graphics_match, GraphicsExt, GraphicsWrap}; + +pub use super::extensions::XrExtensions; +pub use openxr::{ + Extent2Di, Offset2Di, Rect2Di, SwapchainCreateFlags, SwapchainUsageFlags, SystemId, +}; +pub type Result = std::result::Result; + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Version(pub u8, pub u8, pub u16); + +impl Version { + pub const BEVY: Self = Self(0, 12, 1); + + pub const fn to_u32(self) -> u32 { + let major = (self.0 as u32) << 24; + let minor = (self.1 as u32) << 16; + self.2 as u32 | major | minor + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct AppInfo { + pub name: Cow<'static, str>, + pub version: Version, +} + +pub type GraphicsBackend = GraphicsWrap<()>; + +impl GraphicsBackend { + const ALL: &'static [Self] = &[Self::Vulkan(())]; + + pub fn available_backends(exts: &XrExtensions) -> Vec { + Self::ALL + .iter() + .copied() + .filter(|backend| backend.is_available(exts)) + .collect() + } + + pub fn is_available(&self, exts: &XrExtensions) -> bool { + self.required_exts().is_available(exts) + } + + pub fn required_exts(&self) -> XrExtensions { + graphics_match!( + self; + _ => Api::required_exts() + ) + } +} + +#[derive(Error, Debug)] +pub enum XrError { + #[error("OpenXR error: {0}")] + OpenXrError(#[from] openxr::sys::Result), + #[error("OpenXR loading error: {0}")] + OpenXrLoadingError(#[from] openxr::LoadError), + #[error("WGPU instance error: {0}")] + WgpuInstanceError(#[from] wgpu_hal::InstanceError), + #[error("WGPU device error: {0}")] + WgpuDeviceError(#[from] wgpu_hal::DeviceError), + #[error("WGPU request device error: {0}")] + WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError), + #[error("Unsupported texture format: {0:?}")] + UnsupportedTextureFormat(wgpu::TextureFormat), + #[error("Vulkan error: {0}")] + VulkanError(#[from] ash::vk::Result), + #[error("Vulkan loading error: {0}")] + VulkanLoadingError(#[from] ash::LoadingError), + #[error("Graphics backend '{0:?}' is not available")] + UnavailableBackend(GraphicsBackend), + #[error("Could not meet graphics requirements for platform. See console for details")] + FailedGraphicsRequirements, + #[error("Failed to create CString: {0}")] + NulError(#[from] std::ffi::NulError), +} #[derive(Debug, Copy, Clone)] pub struct SwapchainCreateInfo { @@ -16,14 +93,14 @@ pub struct SwapchainCreateInfo { } impl TryFrom for openxr::SwapchainCreateInfo { - type Error = XrInitError; + type Error = XrError; - fn try_from(value: SwapchainCreateInfo) -> Result { + fn try_from(value: SwapchainCreateInfo) -> Result { Ok(openxr::SwapchainCreateInfo { create_flags: value.create_flags, usage_flags: value.usage_flags, format: G::from_wgpu_format(value.format) - .ok_or(XrInitError::UnsupportedTextureFormat(value.format))?, + .ok_or(XrError::UnsupportedTextureFormat(value.format))?, sample_count: value.sample_count, width: value.width, height: value.height, @@ -33,3 +110,177 @@ impl TryFrom for openxr::SwapchainCreateInf }) } } + +pub use builder::*; + +/// Copied with modification from the openxr crate to allow for a safe, graphics agnostic api to work with Bevy. +mod builder { + use std::mem; + + use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space}; + + use crate::openxr::{graphics::graphics_match, XrSwapchain}; + + #[derive(Copy, Clone)] + pub struct SwapchainSubImage<'a> { + inner: sys::SwapchainSubImage, + swapchain: Option<&'a XrSwapchain>, + } + + impl<'a> SwapchainSubImage<'a> { + #[inline] + pub fn new() -> Self { + Self { + inner: sys::SwapchainSubImage { + ..unsafe { mem::zeroed() } + }, + swapchain: None, + } + } + #[inline] + pub fn into_raw(self) -> sys::SwapchainSubImage { + self.inner + } + #[inline] + pub fn as_raw(&self) -> &sys::SwapchainSubImage { + &self.inner + } + #[inline] + pub fn swapchain(mut self, value: &'a XrSwapchain) -> Self { + graphics_match!( + &value.0; + swap => self.inner.swapchain = swap.as_raw() + ); + self.swapchain = Some(value); + self + } + #[inline] + pub fn image_rect(mut self, value: Rect2Di) -> Self { + self.inner.image_rect = value; + self + } + #[inline] + pub fn image_array_index(mut self, value: u32) -> Self { + self.inner.image_array_index = value; + self + } + } + + impl<'a> Default for SwapchainSubImage<'a> { + fn default() -> Self { + Self::new() + } + } + + #[derive(Copy, Clone)] + pub struct CompositionLayerProjectionView<'a> { + inner: sys::CompositionLayerProjectionView, + swapchain: Option<&'a XrSwapchain>, + } + + impl<'a> CompositionLayerProjectionView<'a> { + #[inline] + pub fn new() -> Self { + Self { + inner: sys::CompositionLayerProjectionView { + ty: sys::StructureType::COMPOSITION_LAYER_PROJECTION_VIEW, + ..unsafe { mem::zeroed() } + }, + swapchain: None, + } + } + #[inline] + pub fn into_raw(self) -> sys::CompositionLayerProjectionView { + self.inner + } + #[inline] + pub fn as_raw(&self) -> &sys::CompositionLayerProjectionView { + &self.inner + } + #[inline] + pub fn pose(mut self, value: Posef) -> Self { + self.inner.pose = value; + self + } + #[inline] + pub fn fov(mut self, value: Fovf) -> Self { + self.inner.fov = value; + self + } + #[inline] + pub fn sub_image(mut self, value: SwapchainSubImage<'a>) -> Self { + self.inner.sub_image = value.inner; + self.swapchain = value.swapchain; + self + } + } + impl<'a> Default for CompositionLayerProjectionView<'a> { + fn default() -> Self { + Self::new() + } + } + pub unsafe trait CompositionLayer<'a> { + fn swapchain(&self) -> Option<&'a XrSwapchain>; + fn header(&self) -> &'a sys::CompositionLayerBaseHeader; + } + #[derive(Clone)] + pub struct CompositionLayerProjection<'a> { + inner: sys::CompositionLayerProjection, + swapchain: Option<&'a XrSwapchain>, + views: Vec, + } + impl<'a> CompositionLayerProjection<'a> { + #[inline] + pub fn new() -> Self { + Self { + inner: sys::CompositionLayerProjection { + ty: sys::StructureType::COMPOSITION_LAYER_PROJECTION, + ..unsafe { mem::zeroed() } + }, + swapchain: None, + views: Vec::new(), + } + } + #[inline] + pub fn into_raw(self) -> sys::CompositionLayerProjection { + self.inner + } + #[inline] + pub fn as_raw(&self) -> &sys::CompositionLayerProjection { + &self.inner + } + #[inline] + pub fn layer_flags(mut self, value: CompositionLayerFlags) -> Self { + self.inner.layer_flags = value; + self + } + #[inline] + pub fn space(mut self, value: &'a Space) -> Self { + self.inner.space = value.as_raw(); + self + } + #[inline] + pub fn views(mut self, value: &'a [CompositionLayerProjectionView<'a>]) -> Self { + for view in value { + self.views.push(view.inner.clone()); + } + self.inner.views = self.views.as_slice().as_ptr() as *const _ as _; + self.inner.view_count = value.len() as u32; + self + } + } + unsafe impl<'a> CompositionLayer<'a> for CompositionLayerProjection<'a> { + fn swapchain(&self) -> Option<&'a XrSwapchain> { + self.swapchain + } + + fn header(&self) -> &'a sys::CompositionLayerBaseHeader { + unsafe { std::mem::transmute(&self.inner) } + } + } + impl<'a> Default for CompositionLayerProjection<'a> { + fn default() -> Self { + Self::new() + } + } +}