openxr changes
This commit is contained in:
@@ -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<XrEntry> {
|
||||
#[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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<std::borrow::Cow<'static, str>> {
|
||||
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);
|
||||
|
||||
133
src/openxr/graphics.rs
Normal file
133
src/openxr/graphics.rs
Normal file
@@ -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<T: GraphicsExt>;
|
||||
type Func: Fn();
|
||||
}
|
||||
|
||||
pub trait GraphicsExt: openxr::Graphics {
|
||||
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>;
|
||||
fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
|
||||
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<wgpu::Texture>;
|
||||
fn required_exts() -> XrExtensions;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum GraphicsWrap<T: GraphicsType> {
|
||||
Vulkan(T::Inner<openxr::Vulkan>),
|
||||
}
|
||||
|
||||
impl<T: GraphicsType> GraphicsWrap<T> {
|
||||
/// 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::<Api>()
|
||||
)
|
||||
}
|
||||
|
||||
fn graphics_type(&self) -> std::any::TypeId {
|
||||
graphics_match!(
|
||||
self;
|
||||
_ => std::any::TypeId::of::<Api>()
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks if this struct is using the wanted graphics api.
|
||||
pub fn using_graphics<G: GraphicsExt + 'static>(&self) -> bool {
|
||||
self.graphics_type() == std::any::TypeId::of::<G>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GraphicsType {
|
||||
type Inner<G: GraphicsExt>;
|
||||
}
|
||||
|
||||
impl GraphicsType for () {
|
||||
type Inner<G: GraphicsExt> = ();
|
||||
}
|
||||
|
||||
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<openxr::EnvironmentBlendMode> 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<BlendMode> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::Vulkan>,
|
||||
) -> openxr::Result<Vec<wgpu::TextureFormat>> {
|
||||
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::AnyGraphics>,
|
||||
openxr::FrameWaiter,
|
||||
FrameStreamInner,
|
||||
),
|
||||
XrInitError,
|
||||
> {
|
||||
let reqs = instance.graphics_requirements::<openxr::Vulkan>(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 =
|
||||
<Vulkan as Api>::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 {
|
||||
<Vulkan as Api>::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_hal::api::Vulkan>(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::<openxr::Vulkan>(
|
||||
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<Self::Format> {
|
||||
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::<openxr::Vulkan>(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 = <Vulkan as Api>::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 {
|
||||
<Vulkan as Api>::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_hal::api::Vulkan>(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::<openxr::Vulkan>(
|
||||
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::AnyGraphics>,
|
||||
openxr::FrameWaiter,
|
||||
FrameStreamInner,
|
||||
),
|
||||
XrInitError,
|
||||
> {
|
||||
create_session(app_info, instance, system_id, format, resolution)
|
||||
) -> Result<wgpu::Texture> {
|
||||
let color_image = ash::vk::Image::from_raw(color_image);
|
||||
let wgpu_hal_texture = unsafe {
|
||||
<wgpu_hal::vulkan::Api as wgpu_hal::Api>::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::vulkan::Api>(
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
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<Self::Format>;
|
||||
fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
|
||||
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::AnyGraphics>,
|
||||
openxr::FrameWaiter,
|
||||
FrameStreamInner,
|
||||
),
|
||||
XrInitError,
|
||||
>;
|
||||
}
|
||||
|
||||
fn xr_entry() -> Result<openxr::Entry, XrInitError> {
|
||||
#[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::AnyGraphics>,
|
||||
// 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<openxr::EnvironmentBlendMode> 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<BlendMode> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> = std::result::Result<T, XrInitError>;
|
||||
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<XrInstance> {
|
||||
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<Vec<GraphicsBackend>> {
|
||||
@@ -65,187 +58,148 @@ impl From<openxr::Entry> 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::AnyGraphics>,
|
||||
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<G: GraphicsExt> = openxr::Session<G>;
|
||||
}
|
||||
|
||||
#[derive(Resource, Deref, Clone)]
|
||||
pub struct XrSession(
|
||||
#[deref] pub(crate) openxr::Session<AnyGraphics>,
|
||||
pub(crate) TypedSession,
|
||||
pub(crate) GraphicsWrap<Self>,
|
||||
);
|
||||
|
||||
impl XrSession {
|
||||
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
|
||||
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<XrSwapchain> {
|
||||
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<Vulkan>),
|
||||
pub struct XrFrameStream(pub(crate) GraphicsWrap<Self>);
|
||||
|
||||
impl GraphicsType for XrFrameStream {
|
||||
type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
|
||||
}
|
||||
|
||||
impl TypedSession {
|
||||
pub fn into_any_graphics(&self) -> openxr::Session<AnyGraphics> {
|
||||
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<Vec<wgpu::TextureFormat>> {
|
||||
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<XrSwapchain> {
|
||||
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::<Api>() {
|
||||
warn!(
|
||||
"composition layer {i} is using graphics api '{}', expected graphics api '{}'. Excluding layer from frame submission.",
|
||||
swapchain.0.graphics_name(),
|
||||
std::any::type_name::<Api>(),
|
||||
);
|
||||
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<wgpu::Texture>);
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct XrFrameWaiter(pub openxr::FrameWaiter);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Swapchain {
|
||||
pub inner: Arc<Mutex<XrSwapchain>>,
|
||||
pub format: wgpu::TextureFormat,
|
||||
pub resolution: UVec2,
|
||||
}
|
||||
pub struct XrSwapchain(pub(crate) GraphicsWrap<Self>);
|
||||
|
||||
pub enum FrameStreamInner {
|
||||
Vulkan(openxr::FrameStream<openxr::Vulkan>),
|
||||
}
|
||||
|
||||
pub enum XrSwapchain {
|
||||
Vulkan(openxr::Swapchain<openxr::Vulkan>),
|
||||
impl GraphicsType for XrSwapchain {
|
||||
type Inner<G: GraphicsExt> = openxr::Swapchain<G>;
|
||||
}
|
||||
|
||||
impl XrSwapchain {
|
||||
pub fn acquire_image(&mut self) -> Result<u32> {
|
||||
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<Vec<wgpu::Texture>> {
|
||||
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<Vec<wgpu::Texture>>;
|
||||
}
|
||||
|
||||
impl EnumerateImages for openxr::Swapchain<openxr::Vulkan> {
|
||||
fn enumerate_imgs(
|
||||
&mut self,
|
||||
device: wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
resolution: UVec2,
|
||||
) -> Result<Vec<wgpu::Texture>> {
|
||||
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 {
|
||||
<wgpu_hal::vulkan::Api as wgpu_hal::Api>::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::vulkan::Api>(
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> = std::result::Result<T, XrError>;
|
||||
|
||||
#[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> {
|
||||
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<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInfo<G> {
|
||||
type Error = XrInitError;
|
||||
type Error = XrError;
|
||||
|
||||
fn try_from(value: SwapchainCreateInfo) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: SwapchainCreateInfo) -> Result<Self> {
|
||||
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<G: GraphicsExt> TryFrom<SwapchainCreateInfo> 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<sys::CompositionLayerProjectionView>,
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user