Revert "move to single crate for webxr and openxr"

This reverts commit 5e769ef52b.
This commit is contained in:
awtterpip
2024-04-18 16:30:18 -05:00
parent 5e769ef52b
commit d98b9a4455
25 changed files with 747 additions and 576 deletions

View File

@@ -1,7 +1,2 @@
#[cfg(not(target_family = "wasm"))]
pub mod oxr;
#[cfg(target_family = "wasm")]
pub mod webxr;
#[cfg(not(target_family = "wasm"))]
pub use oxr::add_xr_plugins;
pub use bevy_openxr;
pub use bevy_xr;

View File

@@ -1,110 +0,0 @@
use std::borrow::Cow;
use std::fmt;
use super::graphics::GraphicsBackend;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum OXrError {
#[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("Graphics backend '{0:?}' is not available")]
UnavailableBackend(GraphicsBackend),
#[error("No compatible backend available")]
NoAvailableBackend,
#[error("No compatible view configuration available")]
NoAvailableViewConfiguration,
#[error("No compatible blend mode available")]
NoAvailableBlendMode,
#[error("No compatible format available")]
NoAvailableFormat,
#[error("OpenXR runtime does not support these extensions: {0}")]
UnavailableExtensions(UnavailableExts),
#[error("Could not meet graphics requirements for platform. See console for details")]
FailedGraphicsRequirements,
#[error(
"Tried to use item {item} with backend {backend}. Expected backend {expected_backend}"
)]
GraphicsBackendMismatch {
item: &'static str,
backend: &'static str,
expected_backend: &'static str,
},
#[error("Failed to create CString: {0}")]
NulError(#[from] std::ffi::NulError),
#[error("Graphics init error: {0}")]
InitError(InitError),
}
pub use init_error::InitError;
/// This module is needed because thiserror does not allow conditional compilation within enums for some reason,
/// so graphics api specific errors are implemented here.
mod init_error {
use super::OXrError;
use std::fmt;
#[derive(Debug)]
pub enum InitError {
#[cfg(feature = "vulkan")]
VulkanError(ash::vk::Result),
#[cfg(feature = "vulkan")]
VulkanLoadingError(ash::LoadingError),
}
impl fmt::Display for InitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "vulkan")]
InitError::VulkanError(error) => write!(f, "Vulkan error: {}", error),
#[cfg(feature = "vulkan")]
InitError::VulkanLoadingError(error) => {
write!(f, "Vulkan loading error: {}", error)
}
}
}
}
#[cfg(feature = "vulkan")]
impl From<ash::vk::Result> for OXrError {
fn from(value: ash::vk::Result) -> Self {
Self::InitError(InitError::VulkanError(value))
}
}
#[cfg(feature = "vulkan")]
impl From<ash::LoadingError> for OXrError {
fn from(value: ash::LoadingError) -> Self {
Self::InitError(InitError::VulkanLoadingError(value))
}
}
}
impl From<Vec<Cow<'static, str>>> for OXrError {
fn from(value: Vec<Cow<'static, str>>) -> Self {
Self::UnavailableExtensions(UnavailableExts(value))
}
}
#[derive(Debug)]
pub struct UnavailableExts(Vec<Cow<'static, str>>);
impl fmt::Display for UnavailableExts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for s in &self.0 {
write!(f, "\t{s}")?;
}
Ok(())
}
}

View File

@@ -1,322 +0,0 @@
use bevy::prelude::{Deref, DerefMut};
use openxr::ExtensionSet;
#[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut)]
pub struct OXrExtensions(ExtensionSet);
impl OXrExtensions {
pub fn raw_mut(&mut self) -> &mut ExtensionSet {
&mut self.0
}
pub fn raw(&self) -> &ExtensionSet {
&self.0
}
pub fn enable_fb_passthrough(&mut self) -> &mut Self {
self.0.fb_passthrough = true;
self
}
pub fn disable_fb_passthrough(&mut self) -> &mut Self {
self.0.fb_passthrough = false;
self
}
pub fn enable_hand_tracking(&mut self) -> &mut Self {
self.0.ext_hand_tracking = true;
self
}
pub fn disable_hand_tracking(&mut self) -> &mut Self {
self.0.ext_hand_tracking = false;
self
}
/// returns true if all of the extensions enabled are also available in `available_exts`
pub fn is_available(&self, available_exts: &OXrExtensions) -> bool {
self.clone() & available_exts.clone() == *self
}
}
impl From<ExtensionSet> for OXrExtensions {
fn from(value: ExtensionSet) -> Self {
Self(value)
}
}
impl From<OXrExtensions> for ExtensionSet {
fn from(val: OXrExtensions) -> Self {
val.0
}
}
impl Default for OXrExtensions {
fn default() -> Self {
let exts = ExtensionSet::default();
//exts.ext_hand_tracking = true;
Self(exts)
}
}
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 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;
$(
$(
#[$meta:meta]
)*
$ident:ident
),*
$(,)?
) => {
impl std::ops::BitOr for $exts {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
let mut out = ExtensionSet::default();
$(
$(
#[$meta]
)*
{
out.$ident = self.0.$ident || rhs.0.$ident;
}
)*
out.other = self.0.other;
for ext in rhs.0.other {
if !out.other.contains(&ext) {
out.other.push(ext);
}
}
Self(out)
}
}
};
}
macro_rules! bitand {
(
$exts:ty;
$(
$(
#[$meta:meta]
)*
$ident:ident
),*
$(,)?
) => {
impl std::ops::BitAnd for $exts {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
let mut out = ExtensionSet::default();
$(
$(
#[$meta]
)*
{
out.$ident = self.0.$ident && rhs.0.$ident;
}
)*
for ext in self.0.other {
if rhs.0.other.contains(&ext) {
out.other.push(ext);
}
}
Self(out)
}
}
};
}
macro_rules! impl_ext {
(
$(
$macro:ident
),*
) => {
$(
$macro! {
OXrExtensions;
almalence_digital_lens_control,
bd_controller_interaction,
epic_view_configuration_fov,
ext_performance_settings,
ext_thermal_query,
ext_debug_utils,
ext_eye_gaze_interaction,
ext_view_configuration_depth_range,
ext_conformance_automation,
ext_hand_tracking,
#[cfg(windows)]
ext_win32_appcontainer_compatible,
ext_dpad_binding,
ext_hand_joints_motion_range,
ext_samsung_odyssey_controller,
ext_hp_mixed_reality_controller,
ext_palm_pose,
ext_uuid,
ext_hand_interaction,
ext_active_action_set_priority,
ext_local_floor,
ext_hand_tracking_data_source,
ext_plane_detection,
fb_composition_layer_image_layout,
fb_composition_layer_alpha_blend,
#[cfg(target_os = "android")]
fb_android_surface_swapchain_create,
fb_swapchain_update_state,
fb_composition_layer_secure_content,
fb_body_tracking,
fb_display_refresh_rate,
fb_color_space,
fb_hand_tracking_mesh,
fb_hand_tracking_aim,
fb_hand_tracking_capsules,
fb_spatial_entity,
fb_foveation,
fb_foveation_configuration,
fb_keyboard_tracking,
fb_triangle_mesh,
fb_passthrough,
fb_render_model,
fb_spatial_entity_query,
fb_spatial_entity_storage,
fb_foveation_vulkan,
#[cfg(target_os = "android")]
fb_swapchain_update_state_android_surface,
fb_swapchain_update_state_opengl_es,
fb_swapchain_update_state_vulkan,
fb_touch_controller_pro,
fb_spatial_entity_sharing,
fb_space_warp,
fb_haptic_amplitude_envelope,
fb_scene,
fb_scene_capture,
fb_spatial_entity_container,
fb_face_tracking,
fb_eye_tracking_social,
fb_passthrough_keyboard_hands,
fb_composition_layer_settings,
fb_touch_controller_proximity,
fb_haptic_pcm,
fb_composition_layer_depth_test,
fb_spatial_entity_storage_batch,
fb_spatial_entity_user,
htc_vive_cosmos_controller_interaction,
htc_facial_tracking,
htc_vive_focus3_controller_interaction,
htc_hand_interaction,
htc_vive_wrist_tracker_interaction,
htc_passthrough,
htc_foveation,
huawei_controller_interaction,
#[cfg(target_os = "android")]
khr_android_thread_settings,
#[cfg(target_os = "android")]
khr_android_surface_swapchain,
khr_composition_layer_cube,
#[cfg(target_os = "android")]
khr_android_create_instance,
khr_composition_layer_depth,
khr_vulkan_swapchain_format_list,
khr_composition_layer_cylinder,
khr_composition_layer_equirect,
khr_opengl_enable,
khr_opengl_es_enable,
khr_vulkan_enable,
#[cfg(windows)]
khr_d3d11_enable,
#[cfg(windows)]
khr_d3d12_enable,
khr_visibility_mask,
khr_composition_layer_color_scale_bias,
#[cfg(windows)]
khr_win32_convert_performance_counter_time,
khr_convert_timespec_time,
khr_loader_init,
#[cfg(target_os = "android")]
khr_loader_init_android,
khr_vulkan_enable2,
khr_composition_layer_equirect2,
khr_binding_modification,
khr_swapchain_usage_input_attachment_bit,
meta_foveation_eye_tracked,
meta_local_dimming,
meta_passthrough_preferences,
meta_virtual_keyboard,
meta_vulkan_swapchain_create_info,
meta_performance_metrics,
meta_headset_id,
meta_passthrough_color_lut,
ml_ml2_controller_interaction,
ml_frame_end_info,
ml_global_dimmer,
ml_compat,
ml_user_calibration,
mnd_headless,
mnd_swapchain_usage_input_attachment_bit,
msft_unbounded_reference_space,
msft_spatial_anchor,
msft_spatial_graph_bridge,
msft_hand_interaction,
msft_hand_tracking_mesh,
msft_secondary_view_configuration,
msft_first_person_observer,
msft_controller_model,
#[cfg(windows)]
msft_perception_anchor_interop,
#[cfg(windows)]
msft_holographic_window_attachment,
msft_composition_layer_reprojection,
msft_spatial_anchor_persistence,
#[cfg(target_os = "android")]
oculus_android_session_state_enable,
oculus_audio_device_guid,
oculus_external_camera,
oppo_controller_interaction,
qcom_tracking_optimization_settings,
ultraleap_hand_tracking_forearm,
valve_analog_threshold,
varjo_quad_views,
varjo_foveated_rendering,
varjo_composition_layer_depth_test,
varjo_environment_depth_estimation,
varjo_marker_tracking,
varjo_view_offset,
yvr_controller_interaction,
}
)*
};
}
impl_ext!(bitor, bitand, unavailable_exts);

View File

@@ -1,180 +0,0 @@
#[cfg(feature = "vulkan")]
pub mod vulkan;
use std::any::TypeId;
use bevy::math::UVec2;
use crate::oxr::types::{AppInfo, OXrExtensions, Result, WgpuGraphics};
/// This is an extension trait to the [`Graphics`](openxr::Graphics) trait and is how the graphics API should be interacted with.
pub unsafe trait GraphicsExt: openxr::Graphics {
/// Wrap the graphics specific type into the [GraphicsWrap] enum
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T>;
/// Returns all of the required openxr extensions to use this graphics API.
fn required_exts() -> OXrExtensions;
/// Convert from wgpu format to the graphics format
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>;
/// Convert from the graphics format to wgpu format
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
/// Convert an API specific swapchain image to a [`Texture`](wgpu::Texture).
///
/// # Safety
///
/// The `image` argument must be a valid handle.
unsafe fn to_wgpu_img(
image: Self::SwapchainImage,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<wgpu::Texture>;
/// Initialize graphics for this backend and return a [`WgpuGraphics`] for bevy and an API specific [Self::SessionCreateInfo] for openxr
fn init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
}
/// A type that can be used in [`GraphicsWrap`].
///
/// # Example
///
/// ```
/// pub struct XrSession(GraphicsWrap<XrSession>);
///
/// impl GraphicsType for XrSession {
/// type Inner<G: GraphicsExt> = openxr::Session<G>;
/// }
///
/// ```
///
/// In this example, `GraphicsWrap<XrSession>` is now an enum with variants for each graphics API. The enum can be matched to get the graphics specific inner type.
pub trait GraphicsType {
type Inner<G: GraphicsExt>;
}
impl GraphicsType for () {
type Inner<G: GraphicsExt> = ();
}
/// This is a special variant of [GraphicsWrap] using the unit struct as the inner type. This is to simply represent a graphics backend without storing data.
pub type GraphicsBackend = GraphicsWrap<()>;
impl GraphicsBackend {
const ALL: &'static [Self] = &[Self::Vulkan(())];
pub fn available_backends(exts: &OXrExtensions) -> Vec<Self> {
Self::ALL
.iter()
.copied()
.filter(|backend| backend.is_available(exts))
.collect()
}
pub fn is_available(&self, exts: &OXrExtensions) -> bool {
self.required_exts().is_available(exts)
}
pub fn required_exts(&self) -> OXrExtensions {
graphics_match!(
self;
_ => Api::required_exts()
)
}
}
/// This struct is for creating agnostic objects for OpenXR graphics API specific structs.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GraphicsWrap<T: GraphicsType> {
#[cfg(feature = "vulkan")]
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) -> TypeId {
graphics_match!(
self;
_ => 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() == TypeId::of::<G>()
}
/// Checks if the two values are both using the same graphics backend
pub fn using_graphics_of_val<V: GraphicsType>(&self, other: &GraphicsWrap<V>) -> bool {
self.graphics_type() == other.graphics_type()
}
}
/// This macro can be used to quickly run the same code for every variant of [GraphicsWrap].
/// The first argument should be an expression that returns either a reference or owned value of [GraphicsWrap].
/// The second argument should be a match arm with the pattern for the inner type.
///
/// # Example
///
/// ```
/// pub struct OXrFrameStream(GraphicsWrap<XrFrameStream>);
///
/// impl GraphicsType for OXrFrameStream {
/// // Here is the inner type
/// type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
/// }
///
/// fn begin(frame_stream: &mut XrFrameStream) {
/// graphics_match! {
/// // get the inner 'GraphicsWrap' struct
/// &mut frame_stream.0;
/// // now we can handle every match arm with a single arm
/// // important: the first pattern here represents the inner type of `GraphicsWrap`
/// // it is already destructured for you.
/// stream => stream.begin();
/// }
/// }
/// ```
///
/// Additionally, if you want the type that implements `GraphicsExt` in the scope of the match body, you can access that type through the type alias `Api`.
macro_rules! graphics_match {
(
$field:expr;
$var:pat => $expr:expr $(=> $($return:tt)*)?
) => {
match $field {
#[cfg(feature = "vulkan")]
$crate::oxr::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
) => {
$crate::oxr::graphics::GraphicsWrap::<$wrap_ty>::$variant($expr)
};
(
@arm_impl
$variant:ident;
$expr:expr
) => {
$expr
};
}
pub(crate) use graphics_match;

View File

@@ -1,676 +0,0 @@
use std::ffi::{c_void, CString};
use ash::vk::Handle;
use bevy::log::error;
use bevy::math::UVec2;
use openxr::Version;
use wgpu_hal::api::Vulkan;
use wgpu_hal::Api;
use super::{GraphicsExt, GraphicsType, GraphicsWrap};
use crate::oxr::error::OXrError;
use crate::oxr::types::{AppInfo, OXrExtensions, Result, WgpuGraphics};
#[cfg(not(target_os = "android"))]
const VK_TARGET_VERSION: Version = Version::new(1, 2, 0);
#[cfg(target_os = "android")]
const VK_TARGET_VERSION: Version = Version::new(1, 1, 0);
const VK_TARGET_VERSION_ASH: u32 = ash::vk::make_api_version(
0,
VK_TARGET_VERSION.major() as u32,
VK_TARGET_VERSION.minor() as u32,
VK_TARGET_VERSION.patch() as u32,
);
unsafe impl GraphicsExt for openxr::Vulkan {
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T> {
GraphicsWrap::Vulkan(item)
}
fn required_exts() -> OXrExtensions {
let mut extensions = openxr::ExtensionSet::default();
extensions.khr_vulkan_enable2 = true;
extensions.into()
}
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> {
wgpu_to_vulkan(format).map(|f| f.as_raw() as _)
}
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
}
unsafe fn to_wgpu_img(
color_image: Self::SwapchainImage,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> 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 init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)> {
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(OXrError::FailedGraphicsRequirements);
};
let vk_entry = unsafe { ash::Entry::load() }?;
let flags = wgpu::InstanceFlags::empty();
let extensions =
<Vulkan as Api>::Instance::desired_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(OXrError::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(OXrError::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,
required_features: wgpu_features,
required_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,
)
}?;
Ok((
WgpuGraphics(
wgpu_device,
wgpu_queue,
wgpu_adapter.get_info(),
wgpu_adapter,
wgpu_instance,
),
openxr::vulkan::SessionCreateInfo {
instance: vk_instance_ptr,
physical_device: vk_physical_device_ptr,
device: vk_device_ptr,
queue_family_index,
queue_index: 0,
},
))
}
}
fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
Some(match format {
F::R8_UNORM => Tf::R8Unorm,
F::R8_SNORM => Tf::R8Snorm,
F::R8_UINT => Tf::R8Uint,
F::R8_SINT => Tf::R8Sint,
F::R16_UINT => Tf::R16Uint,
F::R16_SINT => Tf::R16Sint,
F::R16_UNORM => Tf::R16Unorm,
F::R16_SNORM => Tf::R16Snorm,
F::R16_SFLOAT => Tf::R16Float,
F::R8G8_UNORM => Tf::Rg8Unorm,
F::R8G8_SNORM => Tf::Rg8Snorm,
F::R8G8_UINT => Tf::Rg8Uint,
F::R8G8_SINT => Tf::Rg8Sint,
F::R16G16_UNORM => Tf::Rg16Unorm,
F::R16G16_SNORM => Tf::Rg16Snorm,
F::R32_UINT => Tf::R32Uint,
F::R32_SINT => Tf::R32Sint,
F::R32_SFLOAT => Tf::R32Float,
F::R16G16_UINT => Tf::Rg16Uint,
F::R16G16_SINT => Tf::Rg16Sint,
F::R16G16_SFLOAT => Tf::Rg16Float,
F::R8G8B8A8_UNORM => Tf::Rgba8Unorm,
F::R8G8B8A8_SRGB => Tf::Rgba8UnormSrgb,
F::B8G8R8A8_SRGB => Tf::Bgra8UnormSrgb,
F::R8G8B8A8_SNORM => Tf::Rgba8Snorm,
F::B8G8R8A8_UNORM => Tf::Bgra8Unorm,
F::R8G8B8A8_UINT => Tf::Rgba8Uint,
F::R8G8B8A8_SINT => Tf::Rgba8Sint,
F::A2B10G10R10_UINT_PACK32 => Tf::Rgb10a2Uint,
F::A2B10G10R10_UNORM_PACK32 => Tf::Rgb10a2Unorm,
F::B10G11R11_UFLOAT_PACK32 => Tf::Rg11b10Float,
F::R32G32_UINT => Tf::Rg32Uint,
F::R32G32_SINT => Tf::Rg32Sint,
F::R32G32_SFLOAT => Tf::Rg32Float,
F::R16G16B16A16_UINT => Tf::Rgba16Uint,
F::R16G16B16A16_SINT => Tf::Rgba16Sint,
F::R16G16B16A16_UNORM => Tf::Rgba16Unorm,
F::R16G16B16A16_SNORM => Tf::Rgba16Snorm,
F::R16G16B16A16_SFLOAT => Tf::Rgba16Float,
F::R32G32B32A32_UINT => Tf::Rgba32Uint,
F::R32G32B32A32_SINT => Tf::Rgba32Sint,
F::R32G32B32A32_SFLOAT => Tf::Rgba32Float,
F::D32_SFLOAT => Tf::Depth32Float,
F::D32_SFLOAT_S8_UINT => Tf::Depth32FloatStencil8,
F::D16_UNORM => Tf::Depth16Unorm,
F::G8_B8R8_2PLANE_420_UNORM => Tf::NV12,
F::E5B9G9R9_UFLOAT_PACK32 => Tf::Rgb9e5Ufloat,
F::BC1_RGBA_UNORM_BLOCK => Tf::Bc1RgbaUnorm,
F::BC1_RGBA_SRGB_BLOCK => Tf::Bc1RgbaUnormSrgb,
F::BC2_UNORM_BLOCK => Tf::Bc2RgbaUnorm,
F::BC2_SRGB_BLOCK => Tf::Bc2RgbaUnormSrgb,
F::BC3_UNORM_BLOCK => Tf::Bc3RgbaUnorm,
F::BC3_SRGB_BLOCK => Tf::Bc3RgbaUnormSrgb,
F::BC4_UNORM_BLOCK => Tf::Bc4RUnorm,
F::BC4_SNORM_BLOCK => Tf::Bc4RSnorm,
F::BC5_UNORM_BLOCK => Tf::Bc5RgUnorm,
F::BC5_SNORM_BLOCK => Tf::Bc5RgSnorm,
F::BC6H_UFLOAT_BLOCK => Tf::Bc6hRgbUfloat,
F::BC6H_SFLOAT_BLOCK => Tf::Bc6hRgbFloat,
F::BC7_UNORM_BLOCK => Tf::Bc7RgbaUnorm,
F::BC7_SRGB_BLOCK => Tf::Bc7RgbaUnormSrgb,
F::ETC2_R8G8B8_UNORM_BLOCK => Tf::Etc2Rgb8Unorm,
F::ETC2_R8G8B8_SRGB_BLOCK => Tf::Etc2Rgb8UnormSrgb,
F::ETC2_R8G8B8A1_UNORM_BLOCK => Tf::Etc2Rgb8A1Unorm,
F::ETC2_R8G8B8A1_SRGB_BLOCK => Tf::Etc2Rgb8A1UnormSrgb,
F::ETC2_R8G8B8A8_UNORM_BLOCK => Tf::Etc2Rgba8Unorm,
F::ETC2_R8G8B8A8_SRGB_BLOCK => Tf::Etc2Rgba8UnormSrgb,
F::EAC_R11_UNORM_BLOCK => Tf::EacR11Unorm,
F::EAC_R11_SNORM_BLOCK => Tf::EacR11Snorm,
F::EAC_R11G11_UNORM_BLOCK => Tf::EacRg11Unorm,
F::EAC_R11G11_SNORM_BLOCK => Tf::EacRg11Snorm,
F::ASTC_4X4_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::Unorm,
},
F::ASTC_5X4_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::Unorm,
},
F::ASTC_5X5_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B5x5,
channel: AstcChannel::Unorm,
},
F::ASTC_6X5_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B6x5,
channel: AstcChannel::Unorm,
},
F::ASTC_6X6_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::Unorm,
},
F::ASTC_8X5_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B8x5,
channel: AstcChannel::Unorm,
},
F::ASTC_8X6_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B8x6,
channel: AstcChannel::Unorm,
},
F::ASTC_8X8_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B8x8,
channel: AstcChannel::Unorm,
},
F::ASTC_10X5_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B10x5,
channel: AstcChannel::Unorm,
},
F::ASTC_10X6_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B10x6,
channel: AstcChannel::Unorm,
},
F::ASTC_10X8_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B10x8,
channel: AstcChannel::Unorm,
},
F::ASTC_10X10_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B10x10,
channel: AstcChannel::Unorm,
},
F::ASTC_12X10_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B12x10,
channel: AstcChannel::Unorm,
},
F::ASTC_12X12_UNORM_BLOCK => Tf::Astc {
block: AstcBlock::B12x12,
channel: AstcChannel::Unorm,
},
F::ASTC_4X4_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_5X4_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_5X5_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B5x5,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_6X5_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B6x5,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_6X6_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_8X5_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B8x5,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_8X6_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B8x6,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_8X8_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B8x8,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_10X5_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B10x5,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_10X6_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B10x6,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_10X8_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B10x8,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_10X10_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B10x10,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_12X10_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B12x10,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_12X12_SRGB_BLOCK => Tf::Astc {
block: AstcBlock::B12x12,
channel: AstcChannel::UnormSrgb,
},
F::ASTC_4X4_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B4x4,
channel: AstcChannel::Hdr,
},
F::ASTC_5X4_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B5x4,
channel: AstcChannel::Hdr,
},
F::ASTC_5X5_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B5x5,
channel: AstcChannel::Hdr,
},
F::ASTC_6X5_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B6x5,
channel: AstcChannel::Hdr,
},
F::ASTC_6X6_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B6x6,
channel: AstcChannel::Hdr,
},
F::ASTC_8X5_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B8x5,
channel: AstcChannel::Hdr,
},
F::ASTC_8X6_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B8x6,
channel: AstcChannel::Hdr,
},
F::ASTC_8X8_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B8x8,
channel: AstcChannel::Hdr,
},
F::ASTC_10X5_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B10x5,
channel: AstcChannel::Hdr,
},
F::ASTC_10X6_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B10x6,
channel: AstcChannel::Hdr,
},
F::ASTC_10X8_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B10x8,
channel: AstcChannel::Hdr,
},
F::ASTC_10X10_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B10x10,
channel: AstcChannel::Hdr,
},
F::ASTC_12X10_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B12x10,
channel: AstcChannel::Hdr,
},
F::ASTC_12X12_SFLOAT_BLOCK_EXT => Tf::Astc {
block: AstcBlock::B12x12,
channel: AstcChannel::Hdr,
},
_ => return None,
})
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<ash::vk::Format> {
// Copied with minor modification from:
// https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151
// license: MIT OR Apache-2.0
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
Some(match format {
Tf::R8Unorm => F::R8_UNORM,
Tf::R8Snorm => F::R8_SNORM,
Tf::R8Uint => F::R8_UINT,
Tf::R8Sint => F::R8_SINT,
Tf::R16Uint => F::R16_UINT,
Tf::R16Sint => F::R16_SINT,
Tf::R16Unorm => F::R16_UNORM,
Tf::R16Snorm => F::R16_SNORM,
Tf::R16Float => F::R16_SFLOAT,
Tf::Rg8Unorm => F::R8G8_UNORM,
Tf::Rg8Snorm => F::R8G8_SNORM,
Tf::Rg8Uint => F::R8G8_UINT,
Tf::Rg8Sint => F::R8G8_SINT,
Tf::Rg16Unorm => F::R16G16_UNORM,
Tf::Rg16Snorm => F::R16G16_SNORM,
Tf::R32Uint => F::R32_UINT,
Tf::R32Sint => F::R32_SINT,
Tf::R32Float => F::R32_SFLOAT,
Tf::Rg16Uint => F::R16G16_UINT,
Tf::Rg16Sint => F::R16G16_SINT,
Tf::Rg16Float => F::R16G16_SFLOAT,
Tf::Rgba8Unorm => F::R8G8B8A8_UNORM,
Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB,
Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB,
Tf::Rgba8Snorm => F::R8G8B8A8_SNORM,
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
Tf::Rgb10a2Uint => F::A2B10G10R10_UINT_PACK32,
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
Tf::Rg32Uint => F::R32G32_UINT,
Tf::Rg32Sint => F::R32G32_SINT,
Tf::Rg32Float => F::R32G32_SFLOAT,
Tf::Rgba16Uint => F::R16G16B16A16_UINT,
Tf::Rgba16Sint => F::R16G16B16A16_SINT,
Tf::Rgba16Unorm => F::R16G16B16A16_UNORM,
Tf::Rgba16Snorm => F::R16G16B16A16_SNORM,
Tf::Rgba16Float => F::R16G16B16A16_SFLOAT,
Tf::Rgba32Uint => F::R32G32B32A32_UINT,
Tf::Rgba32Sint => F::R32G32B32A32_SINT,
Tf::Rgba32Float => F::R32G32B32A32_SFLOAT,
Tf::Depth32Float => F::D32_SFLOAT,
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
Tf::Depth16Unorm => F::D16_UNORM,
Tf::NV12 => F::G8_B8R8_2PLANE_420_UNORM,
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK,
Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK,
Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK,
Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK,
Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK,
Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK,
Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK,
Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK,
Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK,
Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK,
Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK,
Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK,
Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK,
Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK,
Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK,
Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK,
Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK,
Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK,
Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK,
Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK,
Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK,
Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK,
Tf::Astc { block, channel } => match channel {
AstcChannel::Unorm => match block {
AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK,
},
AstcChannel::UnormSrgb => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK,
AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK,
AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK,
AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK,
AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK,
AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK,
AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK,
AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK,
AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK,
AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK,
AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK,
AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK,
AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK,
AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK,
},
AstcChannel::Hdr => match block {
AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT,
AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT,
AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT,
AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT,
AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT,
AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT,
AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT,
AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT,
AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT,
},
},
})
}

View File

@@ -1,555 +0,0 @@
use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResourcePlugin;
use bevy::render::renderer::RenderAdapter;
use bevy::render::renderer::RenderAdapterInfo;
use bevy::render::renderer::RenderDevice;
use bevy::render::renderer::RenderInstance;
use bevy::render::renderer::RenderQueue;
use bevy::render::settings::RenderCreation;
use bevy::render::MainWorld;
use bevy::render::Render;
use bevy::render::RenderApp;
use bevy::render::RenderPlugin;
use bevy::render::RenderSet;
use bevy::transform::TransformSystem;
use bevy::winit::UpdateMode;
use bevy::winit::WinitSettings;
use bevy_xr_api::session::handle_session;
use bevy_xr_api::session::session_available;
use bevy_xr_api::session::session_running;
use bevy_xr_api::session::status_equals;
use bevy_xr_api::session::BeginXrSession;
use bevy_xr_api::session::CreateXrSession;
use bevy_xr_api::session::DestroyXrSession;
use bevy_xr_api::session::EndXrSession;
use bevy_xr_api::session::XrSharedStatus;
use bevy_xr_api::session::XrStatus;
use crate::oxr::error::OXrError;
use crate::oxr::graphics::*;
use crate::oxr::resources::*;
use crate::oxr::types::*;
pub fn session_started(started: Option<Res<OXrSessionStarted>>) -> bool {
started.is_some_and(|started| started.get())
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum OXrPreUpdateSet {
PollEvents,
HandleEvents,
}
pub struct OXrInitPlugin {
/// Information about the app this is being used to build.
pub app_info: AppInfo,
/// Extensions wanted for this session.
// TODO!() This should be changed to take a simpler list of features wanted that this crate supports. i.e. hand tracking
pub exts: OXrExtensions,
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
/// List of backends the openxr session can use. If [None], pick the first available backend.
pub backends: Option<Vec<GraphicsBackend>>,
/// List of formats the openxr session can use. If [None], pick the first available format
pub formats: Option<Vec<wgpu::TextureFormat>>,
/// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution.
pub resolutions: Option<Vec<UVec2>>,
/// Passed into the render plugin when added to the app.
pub synchronous_pipeline_compilation: bool,
}
#[derive(Component)]
pub struct OXrTrackingRoot;
impl Plugin for OXrInitPlugin {
fn build(&self, app: &mut App) {
match self.init_xr() {
Ok((
instance,
system_id,
WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance),
session_create_info,
)) => {
let status = XrSharedStatus::new(XrStatus::Available);
app.add_plugins((
RenderPlugin {
render_creation: RenderCreation::manual(
device.into(),
RenderQueue(queue.into()),
RenderAdapterInfo(adapter_info),
RenderAdapter(adapter.into()),
RenderInstance(wgpu_instance.into()),
),
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
},
ExtractResourcePlugin::<OXrCleanupSession>::default(),
ExtractResourcePlugin::<OXrTime>::default(),
ExtractResourcePlugin::<OXrRootTransform>::default(),
))
.add_systems(First, reset_per_frame_resources)
.add_systems(
PreUpdate,
(
poll_events
.run_if(session_available)
.in_set(OXrPreUpdateSet::PollEvents),
(
(create_xr_session, apply_deferred)
.chain()
.run_if(on_event::<CreateXrSession>())
.run_if(status_equals(XrStatus::Available)),
begin_xr_session
.run_if(on_event::<BeginXrSession>())
.run_if(status_equals(XrStatus::Ready)),
end_xr_session
.run_if(on_event::<EndXrSession>())
.run_if(status_equals(XrStatus::Stopping)),
destroy_xr_session
.run_if(on_event::<DestroyXrSession>())
.run_if(status_equals(XrStatus::Exiting)),
)
.in_set(OXrPreUpdateSet::HandleEvents),
),
)
.add_systems(
PostUpdate,
update_root_transform.after(TransformSystem::TransformPropagate),
)
.insert_resource(instance.clone())
.insert_resource(system_id)
.insert_resource(status.clone())
.insert_resource(WinitSettings {
focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::Continuous,
})
.init_resource::<OXrCleanupSession>()
.init_resource::<OXrRootTransform>()
.insert_non_send_resource(session_create_info);
app.world
.spawn((TransformBundle::default(), OXrTrackingRoot));
let render_app = app.sub_app_mut(RenderApp);
render_app
.insert_resource(instance)
.insert_resource(system_id)
.insert_resource(status)
.init_resource::<OXrRootTransform>()
.init_resource::<OXrCleanupSession>()
.add_systems(
Render,
destroy_xr_session_render
.run_if(resource_equals(OXrCleanupSession(true)))
.after(RenderSet::ExtractCommands),
)
.add_systems(
ExtractSchedule,
transfer_xr_resources.run_if(not(session_running)),
);
}
Err(e) => {
error!("Failed to initialize openxr: {e}");
let status = XrSharedStatus::new(XrStatus::Unavailable);
app.add_plugins(RenderPlugin::default())
.insert_resource(status.clone());
let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(status);
}
};
app.configure_sets(
PreUpdate,
(
OXrPreUpdateSet::PollEvents.before(handle_session),
OXrPreUpdateSet::HandleEvents.after(handle_session),
),
);
let session_started = OXrSessionStarted::default();
app.insert_resource(session_started.clone());
let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(session_started);
}
}
pub fn update_root_transform(
mut root_transform: ResMut<OXrRootTransform>,
root: Query<&GlobalTransform, With<OXrTrackingRoot>>,
) {
let transform = root.single();
root_transform.0 = *transform;
}
fn xr_entry() -> Result<OXrEntry> {
#[cfg(windows)]
let entry = openxr::Entry::linked();
#[cfg(not(windows))]
let entry = unsafe { openxr::Entry::load()? };
Ok(OXrEntry(entry))
}
impl OXrInitPlugin {
fn init_xr(&self) -> Result<(OXrInstance, OXrSystemId, WgpuGraphics, SessionConfigInfo)> {
let entry = xr_entry()?;
let available_exts = entry.enumerate_extensions()?;
// check available extensions and send a warning for any wanted extensions that aren't available.
for ext in available_exts.unavailable_exts(&self.exts) {
error!(
"Extension \"{ext}\" not available in the current OpenXR runtime. Disabling extension."
);
}
let available_backends = GraphicsBackend::available_backends(&available_exts);
// Backend selection
let backend = if let Some(wanted_backends) = &self.backends {
let mut backend = None;
for wanted_backend in wanted_backends {
if available_backends.contains(wanted_backend) {
backend = Some(*wanted_backend);
break;
}
}
backend
} else {
available_backends.first().copied()
}
.ok_or(OXrError::NoAvailableBackend)?;
let exts = self.exts.clone() & available_exts;
let instance = entry.create_instance(
self.app_info.clone(),
exts,
// &["XR_APILAYER_LUNARG_api_dump"],
&[],
backend,
)?;
let instance_props = instance.properties()?;
info!(
"Loaded OpenXR runtime: {} {}",
instance_props.runtime_name, instance_props.runtime_version
);
let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
let system_props = instance.system_properties(system_id)?;
info!(
"Using system: {}",
if system_props.system_name.is_empty() {
"<unnamed>"
} else {
&system_props.system_name
}
);
let (graphics, graphics_info) = instance.init_graphics(system_id)?;
let session_create_info = SessionConfigInfo {
blend_modes: self.blend_modes.clone(),
formats: self.formats.clone(),
resolutions: self.resolutions.clone(),
graphics_info,
};
Ok((
instance,
OXrSystemId(system_id),
graphics,
session_create_info,
))
}
}
fn init_xr_session(
device: &wgpu::Device,
instance: &OXrInstance,
system_id: openxr::SystemId,
SessionConfigInfo {
blend_modes,
formats,
resolutions,
graphics_info,
}: SessionConfigInfo,
) -> Result<(
OXrSession,
OXrFrameWaiter,
OXrFrameStream,
OXrSwapchain,
OXrSwapchainImages,
OXrGraphicsInfo,
OXrStage,
)> {
let (session, frame_waiter, frame_stream) =
unsafe { instance.create_session(system_id, graphics_info)? };
// TODO!() support other view configurations
let available_view_configurations = instance.enumerate_view_configurations(system_id)?;
if !available_view_configurations.contains(&openxr::ViewConfigurationType::PRIMARY_STEREO) {
return Err(OXrError::NoAvailableViewConfiguration);
}
let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO;
let view_configuration_views =
instance.enumerate_view_configuration_views(system_id, view_configuration_type)?;
let (resolution, _view) = if let Some(resolutions) = &resolutions {
let mut preferred = None;
for resolution in resolutions {
for view_config in view_configuration_views.iter() {
if view_config.recommended_image_rect_height == resolution.y
&& view_config.recommended_image_rect_width == resolution.x
{
preferred = Some((*resolution, *view_config));
}
}
}
if preferred.is_none() {
for resolution in resolutions {
for view_config in view_configuration_views.iter() {
if view_config.max_image_rect_height >= resolution.y
&& view_config.max_image_rect_width >= resolution.x
{
preferred = Some((*resolution, *view_config));
}
}
}
}
preferred
} else {
if let Some(config) = view_configuration_views.first() {
Some((
UVec2::new(
config.recommended_image_rect_width,
config.recommended_image_rect_height,
),
*config,
))
} else {
None
}
}
.ok_or(OXrError::NoAvailableViewConfiguration)?;
let available_formats = session.enumerate_swapchain_formats()?;
let format = if let Some(formats) = &formats {
let mut format = None;
for wanted_format in formats {
if available_formats.contains(wanted_format) {
format = Some(*wanted_format);
}
}
format
} else {
available_formats.first().copied()
}
.ok_or(OXrError::NoAvailableFormat)?;
let swapchain = session.create_swapchain(SwapchainCreateInfo {
create_flags: SwapchainCreateFlags::EMPTY,
usage_flags: SwapchainUsageFlags::COLOR_ATTACHMENT | SwapchainUsageFlags::SAMPLED,
format,
// TODO() add support for multisampling
sample_count: 1,
width: resolution.x,
height: resolution.y,
face_count: 1,
array_size: 2,
mip_count: 1,
})?;
let images = swapchain.enumerate_images(device, format, resolution)?;
let available_blend_modes =
instance.enumerate_environment_blend_modes(system_id, view_configuration_type)?;
// blend mode selection
let blend_mode = if let Some(wanted_blend_modes) = &blend_modes {
let mut blend_mode = None;
for wanted_blend_mode in wanted_blend_modes {
if available_blend_modes.contains(wanted_blend_mode) {
blend_mode = Some(*wanted_blend_mode);
break;
}
}
blend_mode
} else {
available_blend_modes.first().copied()
}
.ok_or(OXrError::NoAvailableBackend)?;
let stage = OXrStage(
session
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
.into(),
);
let graphics_info = OXrGraphicsInfo {
blend_mode,
resolution,
format,
};
Ok((
session,
frame_waiter,
frame_stream,
swapchain,
images,
graphics_info,
stage,
))
}
/// This is used solely to transport resources from the main world to the render world.
#[derive(Resource)]
struct XrRenderResources {
session: OXrSession,
frame_stream: OXrFrameStream,
swapchain: OXrSwapchain,
images: OXrSwapchainImages,
graphics_info: OXrGraphicsInfo,
stage: OXrStage,
}
pub fn create_xr_session(
device: Res<RenderDevice>,
instance: Res<OXrInstance>,
create_info: NonSend<SessionConfigInfo>,
system_id: Res<OXrSystemId>,
mut commands: Commands,
) {
match init_xr_session(
device.wgpu_device(),
&instance,
**system_id,
create_info.clone(),
) {
Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info, stage)) => {
commands.insert_resource(session.clone());
commands.insert_resource(frame_waiter);
commands.insert_resource(images.clone());
commands.insert_resource(graphics_info.clone());
commands.insert_resource(stage.clone());
commands.insert_resource(XrRenderResources {
session,
frame_stream,
swapchain,
images,
graphics_info,
stage,
});
}
Err(e) => error!("Failed to initialize XrSession: {e}"),
}
}
pub fn begin_xr_session(session: Res<OXrSession>, session_started: Res<OXrSessionStarted>) {
let _span = info_span!("xr_begin_session");
session
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
.expect("Failed to begin session");
session_started.set(true);
}
pub fn end_xr_session(session: Res<OXrSession>, session_started: Res<OXrSessionStarted>) {
let _span = info_span!("xr_end_session");
session.end().expect("Failed to end session");
session_started.set(false);
}
/// This system transfers important render resources from the main world to the render world when a session is created.
pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld>) {
let Some(XrRenderResources {
session,
frame_stream,
swapchain,
images,
graphics_info,
stage,
}) = world.remove_resource()
else {
return;
};
commands.insert_resource(session);
commands.insert_resource(frame_stream);
commands.insert_resource(swapchain);
commands.insert_resource(images);
commands.insert_resource(graphics_info);
commands.insert_resource(stage);
}
/// Polls any OpenXR events and handles them accordingly
pub fn poll_events(instance: Res<OXrInstance>, status: Res<XrSharedStatus>) {
let _span = info_span!("xr_poll_events");
let mut buffer = Default::default();
while let Some(event) = instance
.poll_event(&mut buffer)
.expect("Failed to poll event")
{
use openxr::Event::*;
match event {
SessionStateChanged(state) => {
use openxr::SessionState;
let state = state.state();
info!("entered XR state {:?}", state);
let new_status = match state {
SessionState::IDLE => XrStatus::Idle,
SessionState::READY => XrStatus::Ready,
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
XrStatus::Running
}
SessionState::STOPPING => XrStatus::Stopping,
SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting,
_ => unreachable!(),
};
status.set(new_status);
}
InstanceLossPending(_) => {}
EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()),
_ => {}
}
}
}
pub fn reset_per_frame_resources(mut cleanup: ResMut<OXrCleanupSession>) {
**cleanup = false;
}
pub fn destroy_xr_session(mut commands: Commands) {
commands.remove_resource::<OXrSession>();
commands.remove_resource::<OXrFrameWaiter>();
commands.remove_resource::<OXrSwapchainImages>();
commands.remove_resource::<OXrGraphicsInfo>();
commands.remove_resource::<OXrStage>();
commands.insert_resource(OXrCleanupSession(true));
}
pub fn destroy_xr_session_render(world: &mut World) {
world.remove_resource::<OXrSwapchain>();
world.remove_resource::<OXrFrameStream>();
world.remove_resource::<OXrStage>();
world.remove_resource::<OXrSwapchainImages>();
world.remove_resource::<OXrGraphicsInfo>();
world.remove_resource::<OXrSession>();
}

View File

@@ -1,169 +0,0 @@
use std::mem;
use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di, Space};
use crate::oxr::graphics::graphics_match;
use crate::oxr::resources::OXrSwapchain;
#[derive(Copy, Clone)]
pub struct SwapchainSubImage<'a> {
inner: sys::SwapchainSubImage,
swapchain: Option<&'a OXrSwapchain>,
}
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 OXrSwapchain) -> 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 OXrSwapchain>,
}
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 OXrSwapchain>;
fn header(&self) -> &'a sys::CompositionLayerBaseHeader;
}
#[derive(Clone)]
pub struct CompositionLayerProjection<'a> {
inner: sys::CompositionLayerProjection,
swapchain: Option<&'a OXrSwapchain>,
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 OXrSwapchain> {
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()
}
}

View File

@@ -1,56 +0,0 @@
pub mod error;
mod exts;
pub mod graphics;
pub mod init;
pub mod layer_builder;
pub mod render;
pub mod resources;
pub mod types;
// use actions::XrActionPlugin;
use bevy::{
app::{PluginGroup, PluginGroupBuilder},
render::{pipelined_rendering::PipelinedRenderingPlugin, RenderPlugin},
utils::default,
window::{PresentMode, Window, WindowPlugin},
};
use bevy_xr_api::camera::XrCameraPlugin;
use bevy_xr_api::session::XrSessionPlugin;
use init::OXrInitPlugin;
use render::XrRenderPlugin;
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
plugins
.build()
.disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(XrSessionPlugin)
.add_before::<RenderPlugin, _>(OXrInitPlugin {
app_info: default(),
exts: default(),
blend_modes: default(),
backends: default(),
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
resolutions: default(),
synchronous_pipeline_compilation: default(),
})
.add(XrRenderPlugin)
.add(XrCameraPlugin)
// .add(XrActionPlugin)
.set(WindowPlugin {
#[cfg(not(target_os = "android"))]
primary_window: Some(Window {
transparent: true,
present_mode: PresentMode::AutoNoVsync,
// title: self.app_info.name.clone(),
..default()
}),
#[cfg(target_os = "android")]
primary_window: None, // ?
#[cfg(target_os = "android")]
exit_condition: bevy::window::ExitCondition::DontExit,
#[cfg(target_os = "android")]
close_when_requested: true,
..default()
})
}

View File

@@ -1,375 +0,0 @@
use bevy::{
prelude::*,
render::{
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget},
extract_resource::ExtractResourcePlugin,
renderer::render_system,
view::ExtractedView,
Render, RenderApp, RenderSet,
},
transform::TransformSystem,
};
use bevy_xr_api::camera::{XrCamera, XrCameraBundle, XrProjection};
use openxr::{CompositionLayerFlags, ViewStateFlags};
use crate::oxr::init::{session_started, OXrPreUpdateSet};
use crate::oxr::layer_builder::*;
use crate::oxr::resources::*;
pub struct XrRenderPlugin;
impl Plugin for XrRenderPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((ExtractResourcePlugin::<OXrViews>::default(),))
.add_systems(
PreUpdate,
(
init_views.run_if(resource_added::<OXrGraphicsInfo>),
wait_frame.run_if(session_started),
locate_views.run_if(session_started),
update_views.run_if(session_started),
)
.chain()
.after(OXrPreUpdateSet::HandleEvents),
)
.add_systems(
PostUpdate,
(locate_views, update_views)
.chain()
.run_if(session_started)
.before(TransformSystem::TransformPropagate),
);
app.sub_app_mut(RenderApp).add_systems(
Render,
(
(
insert_texture_views,
locate_views,
update_views_render_world,
)
.chain()
.in_set(RenderSet::PrepareAssets),
begin_frame
.before(RenderSet::Queue)
.before(insert_texture_views),
wait_image.in_set(RenderSet::Render).before(render_system),
(end_frame).chain().in_set(RenderSet::Cleanup),
)
.run_if(session_started),
);
}
}
pub const XR_TEXTURE_INDEX: u32 = 3383858418;
// TODO: have cameras initialized externally and then recieved by this function.
/// This is needed to properly initialize the texture views so that bevy will set them to the correct resolution despite them being updated in the render world.
pub fn init_views(
graphics_info: Res<OXrGraphicsInfo>,
mut manual_texture_views: ResMut<ManualTextureViews>,
swapchain_images: Res<OXrSwapchainImages>,
mut commands: Commands,
) {
let _span = info_span!("xr_init_views");
let temp_tex = swapchain_images.first().unwrap();
// this for loop is to easily add support for quad or mono views in the future.
let mut views = vec![];
for index in 0..2 {
info!("{}", graphics_info.resolution);
let view_handle =
add_texture_view(&mut manual_texture_views, temp_tex, &graphics_info, index);
commands.spawn((
XrCameraBundle {
camera: Camera {
target: RenderTarget::TextureView(view_handle),
..Default::default()
},
view: XrCamera(index),
..Default::default()
},
// OpenXrTracker,
// XrRoot::default(),
));
views.push(default());
}
commands.insert_resource(OXrViews(views));
}
pub fn wait_frame(mut frame_waiter: ResMut<OXrFrameWaiter>, mut commands: Commands) {
let _span = info_span!("xr_wait_frame");
let state = frame_waiter.wait().expect("Failed to wait frame");
// Here we insert the predicted display time for when this frame will be displayed.
// TODO: don't add predicted_display_period if pipelined rendering plugin not enabled
commands.insert_resource(OXrTime(state.predicted_display_time));
}
pub fn locate_views(
session: Res<OXrSession>,
stage: Res<OXrStage>,
time: Res<OXrTime>,
mut openxr_views: ResMut<OXrViews>,
) {
let _span = info_span!("xr_locate_views");
let (flags, xr_views) = session
.locate_views(
openxr::ViewConfigurationType::PRIMARY_STEREO,
**time,
&stage,
)
.expect("Failed to locate views");
if openxr_views.len() != xr_views.len() {
openxr_views.resize(xr_views.len(), default());
}
match (
flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID,
flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID,
) {
(true, true) => *openxr_views = OXrViews(xr_views),
(true, false) => {
for (i, view) in openxr_views.iter_mut().enumerate() {
view.pose.orientation = xr_views[i].pose.orientation;
}
}
(false, true) => {
for (i, view) in openxr_views.iter_mut().enumerate() {
view.pose.position = xr_views[i].pose.position;
}
}
(false, false) => {}
}
}
pub fn update_views(
mut query: Query<(&mut Transform, &mut XrProjection, &XrCamera)>,
views: ResMut<OXrViews>,
) {
for (mut transform, mut projection, camera) in query.iter_mut() {
let Some(view) = views.get(camera.0 as usize) else {
continue;
};
let projection_matrix = calculate_projection(projection.near, view.fov);
projection.projection_matrix = projection_matrix;
let openxr::Quaternionf { x, y, z, w } = view.pose.orientation;
let rotation = Quat::from_xyzw(x, y, z, w);
transform.rotation = rotation;
let openxr::Vector3f { x, y, z } = view.pose.position;
let translation = Vec3::new(x, y, z);
transform.translation = translation;
}
}
pub fn update_views_render_world(
views: Res<OXrViews>,
root: Res<OXrRootTransform>,
mut query: Query<(&mut ExtractedView, &XrCamera)>,
) {
for (mut extracted_view, camera) in query.iter_mut() {
let Some(view) = views.get(camera.0 as usize) else {
continue;
};
let mut transform = Transform::IDENTITY;
let openxr::Quaternionf { x, y, z, w } = view.pose.orientation;
let rotation = Quat::from_xyzw(x, y, z, w);
transform.rotation = rotation;
let openxr::Vector3f { x, y, z } = view.pose.position;
let translation = Vec3::new(x, y, z);
transform.translation = translation;
extracted_view.transform = root.0.mul_transform(transform);
}
}
fn calculate_projection(near_z: f32, fov: openxr::Fovf) -> Mat4 {
// symmetric perspective for debugging
// let x_fov = (self.fov.angle_left.abs() + self.fov.angle_right.abs());
// let y_fov = (self.fov.angle_up.abs() + self.fov.angle_down.abs());
// return Mat4::perspective_infinite_reverse_rh(y_fov, x_fov / y_fov, self.near);
let is_vulkan_api = false; // FIXME wgpu probably abstracts this
let far_z = -1.; // use infinite proj
// let far_z = self.far;
let tan_angle_left = fov.angle_left.tan();
let tan_angle_right = fov.angle_right.tan();
let tan_angle_down = fov.angle_down.tan();
let tan_angle_up = fov.angle_up.tan();
let tan_angle_width = tan_angle_right - tan_angle_left;
// Set to tanAngleDown - tanAngleUp for a clip space with positive Y
// down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with
// positive Y up (OpenGL / D3D / Metal).
// const float tanAngleHeight =
// graphicsApi == GRAPHICS_VULKAN ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown);
let tan_angle_height = if is_vulkan_api {
tan_angle_down - tan_angle_up
} else {
tan_angle_up - tan_angle_down
};
// Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
// Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
// const float offsetZ =
// (graphicsApi == GRAPHICS_OPENGL || graphicsApi == GRAPHICS_OPENGL_ES) ? nearZ : 0;
// FIXME handle enum of graphics apis
let offset_z = 0.;
let mut cols: [f32; 16] = [0.0; 16];
if far_z <= near_z {
// place the far plane at infinity
cols[0] = 2. / tan_angle_width;
cols[4] = 0.;
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
cols[12] = 0.;
cols[1] = 0.;
cols[5] = 2. / tan_angle_height;
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
cols[13] = 0.;
cols[2] = 0.;
cols[6] = 0.;
cols[10] = -1.;
cols[14] = -(near_z + offset_z);
cols[3] = 0.;
cols[7] = 0.;
cols[11] = -1.;
cols[15] = 0.;
// bevy uses the _reverse_ infinite projection
// https://dev.theomader.com/depth-precision/
let z_reversal = Mat4::from_cols_array_2d(&[
[1f32, 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., -1., 0.],
[0., 0., 1., 1.],
]);
return z_reversal * Mat4::from_cols_array(&cols);
} else {
// normal projection
cols[0] = 2. / tan_angle_width;
cols[4] = 0.;
cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width;
cols[12] = 0.;
cols[1] = 0.;
cols[5] = 2. / tan_angle_height;
cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height;
cols[13] = 0.;
cols[2] = 0.;
cols[6] = 0.;
cols[10] = -(far_z + offset_z) / (far_z - near_z);
cols[14] = -(far_z * (near_z + offset_z)) / (far_z - near_z);
cols[3] = 0.;
cols[7] = 0.;
cols[11] = -1.;
cols[15] = 0.;
}
Mat4::from_cols_array(&cols)
}
/// # Safety
/// Images inserted into texture views here should not be written to until [`wait_image`] is ran
pub fn insert_texture_views(
swapchain_images: Res<OXrSwapchainImages>,
mut swapchain: ResMut<OXrSwapchain>,
mut manual_texture_views: ResMut<ManualTextureViews>,
graphics_info: Res<OXrGraphicsInfo>,
) {
let _span = info_span!("xr_insert_texture_views");
let index = swapchain.acquire_image().expect("Failed to acquire image");
let image = &swapchain_images[index as usize];
for i in 0..2 {
add_texture_view(&mut manual_texture_views, image, &graphics_info, i);
}
}
pub fn wait_image(mut swapchain: ResMut<OXrSwapchain>) {
swapchain
.wait_image(openxr::Duration::INFINITE)
.expect("Failed to wait image");
}
pub fn add_texture_view(
manual_texture_views: &mut ManualTextureViews,
texture: &wgpu::Texture,
info: &OXrGraphicsInfo,
index: u32,
) -> ManualTextureViewHandle {
let view = texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
base_array_layer: index,
..default()
});
let view = ManualTextureView {
texture_view: view.into(),
size: info.resolution,
format: info.format,
};
let handle = ManualTextureViewHandle(XR_TEXTURE_INDEX + index);
manual_texture_views.insert(handle, view);
handle
}
pub fn begin_frame(mut frame_stream: ResMut<OXrFrameStream>) {
frame_stream.begin().expect("Failed to begin frame")
}
pub fn end_frame(
mut frame_stream: ResMut<OXrFrameStream>,
mut swapchain: ResMut<OXrSwapchain>,
stage: Res<OXrStage>,
display_time: Res<OXrTime>,
graphics_info: Res<OXrGraphicsInfo>,
openxr_views: Res<OXrViews>,
) {
let _span = info_span!("xr_end_frame");
swapchain.release_image().unwrap();
let rect = openxr::Rect2Di {
offset: openxr::Offset2Di { x: 0, y: 0 },
extent: openxr::Extent2Di {
width: graphics_info.resolution.x as _,
height: graphics_info.resolution.y as _,
},
};
frame_stream
.end(
**display_time,
graphics_info.blend_mode,
&[&CompositionLayerProjection::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.space(&stage)
.views(&[
CompositionLayerProjectionView::new()
.pose(openxr_views.0[0].pose)
.fov(openxr_views.0[0].fov)
.sub_image(
SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(0)
.image_rect(rect),
),
CompositionLayerProjectionView::new()
.pose(openxr_views.0[1].pose)
.fov(openxr_views.0[1].fov)
.sub_image(
SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(1)
.image_rect(rect),
),
])],
)
.expect("Failed to end frame");
}

View File

@@ -1,324 +0,0 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResource;
use openxr::AnyGraphics;
use crate::oxr::error::OXrError;
use crate::oxr::graphics::*;
use crate::oxr::layer_builder::CompositionLayer;
use crate::oxr::types::*;
/// Wrapper around the entry point to the OpenXR API
#[derive(Deref, Clone)]
pub struct OXrEntry(pub openxr::Entry);
impl OXrEntry {
/// Enumerate available extensions for this OpenXR runtime.
pub fn enumerate_extensions(&self) -> Result<OXrExtensions> {
Ok(self.0.enumerate_extensions().map(Into::into)?)
}
pub fn create_instance(
&self,
app_info: AppInfo,
exts: OXrExtensions,
layers: &[&str],
backend: GraphicsBackend,
) -> Result<OXrInstance> {
let available_exts = self.enumerate_extensions()?;
if !backend.is_available(&available_exts) {
return Err(OXrError::UnavailableBackend(backend));
}
let required_exts = exts | backend.required_exts();
let instance = self.0.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(),
},
&required_exts.into(),
layers,
)?;
Ok(OXrInstance(instance, backend, app_info))
}
pub fn available_backends(&self) -> Result<Vec<GraphicsBackend>> {
Ok(GraphicsBackend::available_backends(
&self.enumerate_extensions()?,
))
}
}
/// Wrapper around [openxr::Instance] with additional data for safety.
#[derive(Resource, Deref, Clone)]
pub struct OXrInstance(
#[deref] pub openxr::Instance,
pub(crate) GraphicsBackend,
pub(crate) AppInfo,
);
impl OXrInstance {
pub fn into_inner(self) -> openxr::Instance {
self.0
}
/// Initialize graphics. This is used to create [WgpuGraphics] for the bevy app and to get the [SessionCreateInfo] to make an XR session.
pub fn init_graphics(
&self,
system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, SessionCreateInfo)> {
graphics_match!(
self.1;
_ => {
let (graphics, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
Ok((graphics, SessionCreateInfo(Api::wrap(session_info))))
}
)
}
/// Creates an [OXrSession]
///
/// # Safety
///
/// `info` must contain valid handles for the graphics api
pub unsafe fn create_session(
&self,
system_id: openxr::SystemId,
info: SessionCreateInfo,
) -> Result<(OXrSession, OXrFrameWaiter, OXrFrameStream)> {
if !info.0.using_graphics_of_val(&self.1) {
return Err(OXrError::GraphicsBackendMismatch {
item: std::any::type_name::<SessionCreateInfo>(),
backend: info.0.graphics_name(),
expected_backend: self.1.graphics_name(),
});
}
graphics_match!(
info.0;
info => {
let (session, frame_waiter, frame_stream) = self.0.create_session::<Api>(system_id, &info)?;
Ok((session.into(), OXrFrameWaiter(frame_waiter), OXrFrameStream(Api::wrap(frame_stream))))
}
)
}
}
/// Graphics agnostic wrapper around [openxr::Session]
#[derive(Resource, Deref, Clone)]
pub struct OXrSession(
#[deref] pub openxr::Session<AnyGraphics>,
pub GraphicsWrap<Self>,
);
impl GraphicsType for OXrSession {
type Inner<G: GraphicsExt> = openxr::Session<G>;
}
impl<G: GraphicsExt> From<openxr::Session<G>> for OXrSession {
fn from(session: openxr::Session<G>) -> Self {
Self::new(session)
}
}
impl OXrSession {
pub fn new<G: GraphicsExt>(session: openxr::Session<G>) -> Self {
Self(session.clone().into_any_graphics(), G::wrap(session))
}
/// Enumerate all available swapchain formats.
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
graphics_match!(
&self.1;
session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::into_wgpu_format).collect())
)
}
/// Creates an [OXrSwapchain].
pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<OXrSwapchain> {
Ok(OXrSwapchain(graphics_match!(
&self.1;
session => session.create_swapchain(&info.try_into()?)? => OXrSwapchain
)))
}
}
/// Graphics agnostic wrapper around [openxr::FrameStream]
#[derive(Resource)]
pub struct OXrFrameStream(pub GraphicsWrap<Self>);
impl GraphicsType for OXrFrameStream {
type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
}
impl OXrFrameStream {
/// Indicate that graphics device work is beginning.
pub fn begin(&mut self) -> openxr::Result<()> {
graphics_match!(
&mut self.0;
stream => stream.begin()
)
}
/// Indicate that all graphics work for the frame has been submitted
///
/// `layers` is an array of references to any type of composition layer,
/// e.g. [`CompositionLayerProjection`](crate::oxr::layer_builder::CompositionLayerProjection)
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>() {
error!(
"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())?)
}
)
}
}
/// Handle for waiting to render a frame. Check [`FrameWaiter`](openxr::FrameWaiter) for available methods.
#[derive(Resource, Deref, DerefMut)]
pub struct OXrFrameWaiter(pub openxr::FrameWaiter);
/// Graphics agnostic wrapper around [openxr::Swapchain]
#[derive(Resource)]
pub struct OXrSwapchain(pub GraphicsWrap<Self>);
impl GraphicsType for OXrSwapchain {
type Inner<G: GraphicsExt> = openxr::Swapchain<G>;
}
impl OXrSwapchain {
/// Determine the index of the next image to render to in the swapchain image array
pub fn acquire_image(&mut self) -> Result<u32> {
graphics_match!(
&mut self.0;
swap => Ok(swap.acquire_image()?)
)
}
/// Wait for the compositor to finish reading from the oldest unwaited acquired image
pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> {
graphics_match!(
&mut self.0;
swap => Ok(swap.wait_image(timeout)?)
)
}
/// Release the oldest acquired image
pub fn release_image(&mut self) -> Result<()> {
graphics_match!(
&mut self.0;
swap => Ok(swap.release_image()?)
)
}
/// Enumerates swapchain images and converts them to wgpu [`Texture`](wgpu::Texture)s.
pub fn enumerate_images(
&self,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<OXrSwapchainImages> {
graphics_match!(
&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(OXrSwapchainImages(images.into()))
}
)
}
}
/// Stores the generated swapchain images.
#[derive(Debug, Deref, Resource, Clone)]
pub struct OXrSwapchainImages(pub Arc<Vec<wgpu::Texture>>);
/// Thread safe wrapper around [openxr::Space] representing the stage.
#[derive(Deref, Clone, Resource)]
pub struct OXrStage(pub Arc<openxr::Space>);
/// Stores the latest generated [OXrViews]
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut)]
pub struct OXrViews(pub Vec<openxr::View>);
/// Wrapper around [openxr::SystemId] to allow it to be stored as a resource.
#[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)]
pub struct OXrSystemId(pub openxr::SystemId);
/// Resource storing graphics info for the currently running session.
#[derive(Clone, Copy, Resource)]
pub struct OXrGraphicsInfo {
pub blend_mode: EnvironmentBlendMode,
pub resolution: UVec2,
pub format: wgpu::TextureFormat,
}
#[derive(Clone)]
/// This is used to store information from startup that is needed to create the session after the instance has been created.
pub struct SessionConfigInfo {
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
/// List of formats the openxr session can use. If [None], pick the first available format
pub formats: Option<Vec<wgpu::TextureFormat>>,
/// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution.
pub resolutions: Option<Vec<UVec2>>,
/// Graphics info used to create a session.
pub graphics_info: SessionCreateInfo,
}
#[derive(Resource, Clone, Default)]
pub struct OXrSessionStarted(Arc<AtomicBool>);
impl OXrSessionStarted {
pub fn set(&self, val: bool) {
self.0.store(val, Ordering::SeqCst);
}
pub fn get(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
}
/// The calculated display time for the app. Passed through the pipeline.
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
pub struct OXrTime(pub openxr::Time);
/// The root transform's global position for late latching in the render world.
#[derive(ExtractResource, Resource, Clone, Copy, Default)]
pub struct OXrRootTransform(pub GlobalTransform);
#[derive(ExtractResource, Resource, Clone, Copy, Default, Deref, DerefMut, PartialEq)]
/// This is inserted into the world to signify if the session should be cleaned up.
pub struct OXrCleanupSession(pub bool);

View File

@@ -1,98 +0,0 @@
use std::borrow::Cow;
use super::error::OXrError;
use super::graphics::{GraphicsExt, GraphicsType, GraphicsWrap};
pub use crate::oxr::exts::OXrExtensions;
pub use openxr::{EnvironmentBlendMode, SwapchainCreateFlags, SwapchainUsageFlags};
pub type Result<T> = std::result::Result<T, OXrError>;
/// A container for all required graphics objects needed for a bevy app.
pub struct WgpuGraphics(
pub wgpu::Device,
pub wgpu::Queue,
pub wgpu::AdapterInfo,
pub wgpu::Adapter,
pub wgpu::Instance,
);
/// A version number that can be stored inside of a u32
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Version(pub u8, pub u8, pub u16);
impl Version {
/// Bevy's version number
pub const BEVY: Self = Self(0, 13, 0);
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
}
}
/// Info needed about an app for OpenXR
#[derive(Clone, Debug, PartialEq)]
pub struct AppInfo {
pub name: Cow<'static, str>,
pub version: Version,
}
impl AppInfo {
/// The default app info for a generic bevy app
pub const BEVY: Self = Self {
name: Cow::Borrowed("Bevy"),
version: Version::BEVY,
};
}
impl Default for AppInfo {
fn default() -> Self {
Self::BEVY
}
}
/// Info needed to create a swapchain.
/// This is an API agnostic version of [openxr::SwapchainCreateInfo] used for some of this library's functions
#[derive(Debug, Copy, Clone)]
pub struct SwapchainCreateInfo {
pub create_flags: SwapchainCreateFlags,
pub usage_flags: SwapchainUsageFlags,
pub format: wgpu::TextureFormat,
pub sample_count: u32,
pub width: u32,
pub height: u32,
pub face_count: u32,
pub array_size: u32,
pub mip_count: u32,
}
impl<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInfo<G> {
type Error = OXrError;
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(OXrError::UnsupportedTextureFormat(value.format))?,
sample_count: value.sample_count,
width: value.width,
height: value.height,
face_count: value.face_count,
array_size: value.array_size,
mip_count: value.mip_count,
})
}
}
/// Info needed to create a session. Mostly contains graphics info.
/// This is an API agnostic version of [openxr::Graphics::SessionCreateInfo] used for some of this library's functions
#[derive(Clone)]
pub struct SessionCreateInfo(pub GraphicsWrap<Self>);
impl GraphicsType for SessionCreateInfo {
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
}

View File