openxr changes

This commit is contained in:
awtterpip
2024-02-18 21:12:48 -06:00
parent 08e98dd051
commit 80d6cadadf
7 changed files with 782 additions and 659 deletions

View File

@@ -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!()
}
}

View File

@@ -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
View 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,
}
}
}

View File

@@ -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,35 +25,28 @@ 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)
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 _)
}
pub fn create_session(
app_info: AppInfo,
fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
}
fn create_session(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<
(
) -> Result<(
wgpu::Device,
wgpu::Queue,
wgpu::Adapter,
wgpu::Instance,
openxr::Session<openxr::AnyGraphics>,
openxr::FrameWaiter,
FrameStreamInner,
),
XrInitError,
> {
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()
@@ -68,12 +56,15 @@ pub fn create_session(
reqs.min_api_version_supported,
reqs.max_api_version_supported.major() + 1
);
return Err(XrInitError::FailedGraphicsRequirements);
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 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(),
@@ -84,7 +75,7 @@ pub fn create_session(
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 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)
@@ -127,7 +118,7 @@ pub fn create_session(
VK_TARGET_VERSION.minor(),
VK_TARGET_VERSION.patch()
);
return Err(XrInitError::FailedGraphicsRequirements);
return Err(XrError::FailedGraphicsRequirements);
}
let wgpu_vk_instance = unsafe {
@@ -151,7 +142,7 @@ pub fn create_session(
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);
return Err(XrError::FailedGraphicsRequirements);
};
let enabled_extensions = wgpu_exposed_adapter
@@ -255,40 +246,69 @@ pub fn create_session(
wgpu_queue,
wgpu_adapter,
wgpu_instance,
session.into_any_graphics(),
frame_wait,
FrameStreamInner::Vulkan(frame_stream),
XrSession(
session.clone().into_any_graphics(),
super::GraphicsWrap::Vulkan(session),
),
XrFrameWaiter(frame_wait),
XrFrameStream(super::GraphicsWrap::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 _)
}
fn to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
}
fn create_session(
app_info: AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
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()
}
}

View File

@@ -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,
}
}
}

View File

@@ -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 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![];
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())?)
}
)
}
}
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
Ok(match self {
TypedSession::Vulkan(session) => init::vulkan::enumerate_swapchain_formats(session),
}?)
}
#[derive(Deref, DerefMut)]
pub struct XrFrameWaiter(pub openxr::FrameWaiter);
pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<XrSwapchain> {
Ok(match self {
TypedSession::Vulkan(session) => {
XrSwapchain::Vulkan(session.create_swapchain(&info.try_into()?)?)
}
})
}
}
pub struct XrSwapchain(pub(crate) GraphicsWrap<Self>);
#[derive(Resource, Default, Deref)]
pub struct Framebuffers(pub Vec<wgpu::Texture>);
#[derive(Clone)]
pub struct Swapchain {
pub inner: Arc<Mutex<XrSwapchain>>,
pub format: wgpu::TextureFormat,
pub resolution: UVec2,
}
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),
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)
}
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())
}
}

View File

@@ -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()
}
}
}