rendering code and api crate

This commit is contained in:
awtterpip
2024-03-10 19:59:19 -05:00
parent 47eba9215f
commit 91eb263b4f
31 changed files with 1597 additions and 1775 deletions

View File

@@ -1,130 +0,0 @@
pub mod oculus_touch;
mod private {
use bevy::math::Vec2;
use crate::types::{Haptic, Pose};
pub trait Sealed {}
impl Sealed for bool {}
impl Sealed for f32 {}
impl Sealed for Vec2 {}
impl Sealed for Pose {}
impl Sealed for Haptic {}
}
use std::borrow::Cow;
use std::marker::PhantomData;
pub trait ActionType: private::Sealed {}
impl<T: private::Sealed> ActionType for T {}
pub trait ActionPathTrait {
type PathType: ActionType;
fn path(&self) -> Cow<'_, str>;
fn name(&self) -> Cow<'_, str>;
}
pub struct ActionPath<T: ActionType> {
pub path: &'static str,
pub name: &'static str,
_marker: PhantomData<T>,
}
macro_rules! actions {
// create path struct
(
$($subpath:literal),*
$id:ident {
path: $path:literal;
}
) => {};
// handle action path attrs
(
$($subpath:literal),*
$id:ident {
path: $path:literal;
name: $name:literal;
path_type: $path_type:ty;
}
) => {
paste::paste! {
pub const [<$id:snake:upper>]: crate::action_paths::ActionPath<$path_type> = crate::action_paths::ActionPath {
path: concat!($($subpath,)* $path),
name: $name,
_marker: std::marker::PhantomData,
};
}
};
// handle action path attrs
(
$($subpath:literal),*
$id:ident {
path: $path:literal;
name: $name:literal;
path_type: $path_type:ty;
$($children:tt)*
}
) => {
crate::action_paths::actions! {
$($subpath),*
$id {
path: $path;
name: $name;
path_type: $path_type;
}
}
crate::action_paths::actions! {
$($subpath),*
$id {
path: $path;
$($children)*
}
}
};
// handle children
(
$($subpath:literal),*
$id:ident {
path: $path:literal;
$($children:tt)*
}
) => {
pub mod $id {
crate::action_paths::actions! {
$($subpath,)* $path
$($children)*
}
}
};
// handle siblings
(
$($subpath:literal),*
$id:ident {
path: $path:literal;
$($attrs:tt)*
}
$($siblings:tt)*
) => {
crate::action_paths::actions! {
$($subpath),*
$id {
path: $path;
$($attrs)*
}
}
crate::action_paths::actions! {
$($subpath),*
$($siblings)*
}
};
}
pub(crate) use actions;

View File

@@ -1,238 +0,0 @@
super::actions! {
"/user"
hand {
path: "/hand";
left {
path: "/left";
input {
path: "/input";
x {
path: "/x";
click {
path: "/click";
name: "x_click";
path_type: bool;
}
touch {
path: "/touch";
name: "x_touch";
path_type: bool;
}
}
y {
path: "/y";
click {
path: "/click";
name: "y_click";
path_type: bool;
}
touch {
path: "/touch";
name: "y_touch";
path_type: bool;
}
}
menu {
path: "/menu";
click {
path: "/click";
name: "menu_click";
path_type: bool;
}
}
squeeze {
path: "/squeeze";
value {
path: "/value";
name: "left_grip_val";
path_type: f32;
}
}
trigger {
path: "/trigger";
value {
path: "/value";
name: "left_trigger_val";
path_type: f32;
}
touch {
path: "/touch";
name: "left_trigger_touch";
path_type: bool;
}
}
thumbstick {
path: "/thumbstick";
x {
path: "/x";
name: "left_thumbstick_x";
path_type: f32;
}
y {
path: "/y";
name: "left_thumbstick_y";
path_type: f32;
}
click {
path: "/click";
name: "left_thumbstick_click";
path_type: bool;
}
touch {
path: "/touch";
name: "left_thumbstick_touch";
path_type: bool;
}
}
thumbrest {
path: "/thumbrest";
touch {
path: "/touch";
name: "left_thumbrest_touch";
path_type: bool;
}
}
grip {
path: "/grip";
pose {
path: "/pose";
name: "left_grip_pose";
path_type: crate::types::Pose;
}
}
aim {
path: "/aim";
pose {
path: "/pose";
name: "left_aim_pose";
path_type: crate::types::Pose;
}
}
}
output {
path: "/output";
haptic {
path: "/haptic";
name: "left_controller_haptic";
path_type: crate::types::Haptic;
}
}
}
right {
path: "/right";
input {
path: "/input";
a {
path: "/a";
click {
path: "/click";
name: "a_click";
path_type: bool;
}
touch {
path: "/touch";
name: "a_touch";
path_type: bool;
}
}
b {
path: "/b";
click {
path: "/click";
name: "b_click";
path_type: bool;
}
touch {
path: "/touch";
name: "b_touch";
path_type: bool;
}
}
system {
path: "/system";
click {
path: "/click";
name: "system_click";
path_type: bool;
}
}
squeeze {
path: "/squeeze";
value {
path: "/value";
name: "right_grip_val";
path_type: f32;
}
}
trigger {
path: "/trigger";
value {
path: "/value";
name: "right_trigger_val";
path_type: f32;
}
touch {
path: "/touch";
name: "right_trigger_touch";
path_type: bool;
}
}
thumbstick {
path: "/thumbstick";
x {
path: "/x";
name: "right_thumbstick_x";
path_type: f32;
}
y {
path: "/y";
name: "right_thumbstick_y";
path_type: f32;
}
click {
path: "/click";
name: "right_thumbstick_click";
path_type: bool;
}
touch {
path: "/touch";
name: "right_thumbstick_touch";
path_type: bool;
}
}
thumbrest {
path: "/thumbrest";
touch {
path: "/touch";
name: "right_thumbrest_touch";
path_type: bool;
}
}
grip {
path: "/grip";
pose {
path: "/pose";
name: "right_grip_pose";
path_type: crate::types::Pose;
}
}
aim {
path: "/aim";
pose {
path: "/pose";
name: "right_aim_pose";
path_type: crate::types::Pose;
}
}
}
output {
path: "/output";
haptic {
path: "/haptic";
name: "right_controller_haptic";
path_type: crate::types::Haptic;
}
}
}
}
}

View File

@@ -1,23 +0,0 @@
use std::{borrow::Cow, marker::PhantomData};
use bevy::prelude::*;
pub use crate::action_paths::*;
#[derive(Event)]
pub struct XrCreateActionSet {
pub handle: Handle<XrActionSet>,
pub name: String,
}
pub struct XrAction<'a, T: ActionType> {
pub name: Cow<'a, str>,
pub pretty_name: Cow<'a, str>,
pub action_set: Handle<XrActionSet>,
_marker: PhantomData<T>,
}
#[derive(TypePath, Asset)]
pub struct XrActionSet {
pub name: String,
}

View File

@@ -1,8 +1,2 @@
mod action_paths;
pub mod actions;
#[cfg(not(target_family = "wasm"))]
pub mod openxr;
pub mod render;
pub mod types;
#[cfg(target_family = "wasm")]
pub mod webxr;
pub use bevy_openxr;
pub use bevy_xr;

View File

@@ -1,334 +0,0 @@
mod extensions;
pub mod graphics;
pub mod render;
mod resources;
pub mod types;
use bevy::ecs::schedule::common_conditions::resource_equals;
use bevy::ecs::system::{Res, ResMut};
use bevy::math::{uvec2, UVec2};
use bevy::render::extract_resource::ExtractResourcePlugin;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderInstance, RenderQueue};
use bevy::render::settings::RenderCreation;
use bevy::render::{RenderApp, RenderPlugin};
use bevy::utils::default;
use bevy::DefaultPlugins;
pub use resources::*;
pub use types::*;
use bevy::app::{App, First, Plugin, PluginGroup};
use bevy::log::{error, info, warn};
pub fn xr_entry() -> Result<XrEntry> {
#[cfg(windows)]
let entry = openxr::Entry::linked();
#[cfg(not(windows))]
let entry = unsafe { openxr::Entry::load()? };
Ok(XrEntry(entry))
}
pub struct XrInitPlugin {
/// 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: XrExtensions,
/// 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,
}
impl Plugin for XrInitPlugin {
fn build(&self, app: &mut App) {
if let Err(e) = init_xr(self, app) {
panic!("Encountered an error while trying to initialize XR: {e}");
}
app.add_systems(First, poll_events);
}
}
fn init_xr(config: &XrInitPlugin, app: &mut App) -> Result<()> {
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(&config.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) = &config.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(XrError::NoAvailableBackend)?;
let exts = config.exts.clone() & available_exts;
let instance = entry.create_instance(config.app_info.clone(), exts, 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
}
);
// 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(XrError::NoAvailableViewConfiguration);
}
let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO;
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) = &config.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(XrError::NoAvailableBackend)?;
let view_configuration_views =
instance.enumerate_view_configuration_views(system_id, view_configuration_type)?;
let (resolution, _view) = if let Some(resolutions) = &config.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(
config.recommended_image_rect_width,
config.recommended_image_rect_height,
),
*config,
))
} else {
None
}
}
.ok_or(XrError::NoAvailableViewConfiguration)?;
let (WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance), create_info) =
instance.init_graphics(system_id)?;
let (session, frame_waiter, frame_stream) =
unsafe { instance.create_session(system_id, create_info)? };
let available_formats = session.enumerate_swapchain_formats()?;
let format = if let Some(formats) = &config.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(XrError::NoAvailableFormat)?;
let mut 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 stage = XrStage(
session
.create_reference_space(openxr::ReferenceSpaceType::STAGE, openxr::Posef::IDENTITY)?
.into(),
);
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: config.synchronous_pipeline_compilation,
},
ExtractResourcePlugin::<XrTime>::default(),
ExtractResourcePlugin::<XrStatus>::default(),
));
let graphics_info = XrGraphicsInfo {
blend_mode,
swapchain_resolution: resolution,
swapchain_format: format,
};
app.insert_resource(instance.clone())
.insert_resource(session.clone())
.insert_resource(frame_waiter)
.insert_resource(images.clone())
.insert_resource(graphics_info)
.insert_resource(stage.clone())
.init_resource::<XrStatus>();
app.sub_app_mut(RenderApp)
.insert_resource(instance)
.insert_resource(session)
.insert_resource(frame_stream)
.insert_resource(swapchain)
.insert_resource(images)
.insert_resource(graphics_info)
.insert_resource(stage)
.init_resource::<XrStatus>();
Ok(())
}
pub fn session_running() -> impl FnMut(Res<XrStatus>) -> bool {
resource_equals(XrStatus::Enabled)
}
pub fn poll_events(
instance: Res<XrInstance>,
session: Res<XrSession>,
mut xr_status: ResMut<XrStatus>,
) {
while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() {
use openxr::Event::*;
match event {
SessionStateChanged(e) => {
// Session state change is where we can begin and end sessions, as well as
// find quit messages!
info!("entered XR state {:?}", e.state());
match e.state() {
openxr::SessionState::READY => {
info!("Calling Session begin :3");
session
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
.unwrap();
*xr_status = XrStatus::Enabled;
}
openxr::SessionState::STOPPING => {
session.end().unwrap();
*xr_status = XrStatus::Disabled;
}
// openxr::SessionState::EXITING => {
// if *exit_type == ExitAppOnSessionExit::Always
// || *exit_type == ExitAppOnSessionExit::OnlyOnExit
// {
// app_exit.send_default();
// }
// }
// openxr::SessionState::LOSS_PENDING => {
// if *exit_type == ExitAppOnSessionExit::Always {
// app_exit.send_default();
// }
// if *exit_type == ExitAppOnSessionExit::OnlyOnExit {
// start_session.send_default();
// }
// }
_ => {}
}
}
// InstanceLossPending(_) => {
// app_exit.send_default();
// }
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
}
}
}
pub struct DefaultXrPlugins;
impl PluginGroup for DefaultXrPlugins {
fn build(self) -> bevy::app::PluginGroupBuilder {
DefaultPlugins
.build()
.disable::<RenderPlugin>()
.add_before::<RenderPlugin, _>(XrInitPlugin {
app_info: default(),
exts: default(),
blend_modes: default(),
backends: default(),
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
resolutions: default(),
synchronous_pipeline_compilation: default(),
})
.add(render::XrRenderPlugin)
}
}

View File

@@ -1,291 +0,0 @@
use bevy::prelude::{Deref, DerefMut};
use openxr::ExtensionSet;
#[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut)]
pub struct XrExtensions(ExtensionSet);
impl XrExtensions {
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: &XrExtensions) -> bool {
self.clone() & available_exts.clone() == *self
}
}
impl From<ExtensionSet> for XrExtensions {
fn from(value: ExtensionSet) -> Self {
Self(value)
}
}
impl From<XrExtensions> for ExtensionSet {
fn from(val: XrExtensions) -> Self {
val.0
}
}
impl Default for XrExtensions {
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(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;
$(
$(
#[$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! {
XrExtensions;
almalence_digital_lens_control,
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,
extx_overlay,
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_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_space_warp,
fb_scene,
fb_spatial_entity_container,
fb_passthrough_keyboard_hands,
fb_composition_layer_settings,
htc_vive_cosmos_controller_interaction,
htc_facial_tracking,
htc_vive_focus3_controller_interaction,
htc_hand_interaction,
htc_vive_wrist_tracker_interaction,
htcx_vive_tracker_interaction,
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_vulkan_swapchain_create_info,
meta_performance_metrics,
ml_ml2_controller_interaction,
mnd_headless,
mnd_swapchain_usage_input_attachment_bit,
mndx_egl_enable,
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,
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,
}
)*
};
}
impl_ext!(bitor, bitand, unavailable_exts);

View File

@@ -1,132 +0,0 @@
pub mod vulkan;
use std::any::TypeId;
use bevy::math::UVec2;
use crate::openxr::types::{AppInfo, Result, XrError};
use crate::types::BlendMode;
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>;
/// 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 to_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
/// Initialize graphics for this backend
fn init_graphics(
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
/// Convert a swapchain function
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) -> 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()
}
}
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::{WgpuGraphics, 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

@@ -1,677 +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 crate::openxr::types::Result;
use crate::openxr::{extensions::XrExtensions, WgpuGraphics};
use super::{AppInfo, GraphicsExt, XrError};
#[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 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 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(XrError::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(XrError::FailedGraphicsRequirements);
}
let wgpu_vk_instance = unsafe {
<Vulkan as Api>::Instance::from_raw(
vk_entry.clone(),
vk_instance.clone(),
VK_TARGET_VERSION_ASH,
0,
None,
extensions,
flags,
false,
Some(Box::new(())),
)?
};
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::MULTIVIEW
| wgpu::Features::MULTI_DRAW_INDIRECT_COUNT
| wgpu::Features::MULTI_DRAW_INDIRECT;
let Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else {
error!("WGPU failed to provide an adapter");
return Err(XrError::FailedGraphicsRequirements);
};
let enabled_extensions = wgpu_exposed_adapter
.adapter
.required_device_extensions(wgpu_features);
let (wgpu_open_device, vk_device_ptr, queue_family_index) = {
let extensions_cchar: Vec<_> = device_extensions.iter().map(|s| s.as_ptr()).collect();
let mut enabled_phd_features = wgpu_exposed_adapter
.adapter
.physical_device_features(&enabled_extensions, wgpu_features);
let family_index = 0;
let family_info = ash::vk::DeviceQueueCreateInfo::builder()
.queue_family_index(family_index)
.queue_priorities(&[1.0])
.build();
let family_infos = [family_info];
let info = enabled_phd_features
.add_to_device_create_builder(
ash::vk::DeviceCreateInfo::builder()
.queue_create_infos(&family_infos)
.push_next(&mut ash::vk::PhysicalDeviceMultiviewFeatures {
multiview: ash::vk::TRUE,
..Default::default()
}),
)
.enabled_extension_names(&extensions_cchar)
.build();
let vk_device = unsafe {
let vk_device = instance
.create_vulkan_device(
system_id,
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
vk_physical_device.as_raw() as _,
&info as *const _ as *const _,
)?
.map_err(ash::vk::Result::from_raw)?;
ash::Device::load(
vk_instance.fp_v1_0(),
ash::vk::Device::from_raw(vk_device as _),
)
};
let vk_device_ptr = vk_device.handle().as_raw() as *const c_void;
let wgpu_open_device = unsafe {
wgpu_exposed_adapter.adapter.device_from_raw(
vk_device,
true,
&enabled_extensions,
wgpu_features,
family_info.queue_family_index,
0,
)
}?;
(
wgpu_open_device,
vk_device_ptr,
family_info.queue_family_index,
)
};
let wgpu_instance =
unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Vulkan>(wgpu_vk_instance) };
let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) };
let (wgpu_device, wgpu_queue) = unsafe {
wgpu_adapter.create_device_from_hal(
wgpu_open_device,
&wgpu::DeviceDescriptor {
label: None,
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,
},
))
}
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 required_exts() -> XrExtensions {
let mut extensions = openxr::ExtensionSet::default();
extensions.khr_vulkan_enable2 = true;
extensions.into()
}
fn wrap<T: super::GraphicsType>(item: T::Inner<Self>) -> super::GraphicsWrap<T> {
super::GraphicsWrap::Vulkan(item)
}
}
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,176 +0,0 @@
use bevy::{
prelude::*,
render::{
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews},
Render, RenderApp, RenderSet,
},
};
use openxr::CompositionLayerFlags;
use crate::openxr::resources::*;
use crate::openxr::types::*;
use crate::openxr::XrTime;
use super::{poll_events, session_running};
pub struct XrRenderPlugin;
impl Plugin for XrRenderPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
First,
wait_frame.after(poll_events).run_if(session_running()),
)
.add_systems(Startup, init_texture_views);
app.sub_app_mut(RenderApp).add_systems(
Render,
(
(begin_frame, insert_texture_views)
.chain()
.in_set(RenderSet::PrepareAssets),
end_frame.in_set(RenderSet::Cleanup),
)
.run_if(session_running()),
);
}
}
pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591);
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
fn init_texture_views(
graphics_info: Res<XrGraphicsInfo>,
mut manual_texture_views: ResMut<ManualTextureViews>,
swapchain_images: Res<SwapchainImages>,
) {
let temp_tex = swapchain_images.first().unwrap();
let left = temp_tex.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
..Default::default()
});
let right = temp_tex.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
base_array_layer: 1,
..Default::default()
});
let resolution = graphics_info.swapchain_resolution;
let format = graphics_info.swapchain_format;
let left = ManualTextureView {
texture_view: left.into(),
size: resolution,
format: format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: resolution,
format: format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
}
fn wait_frame(mut frame_waiter: ResMut<XrFrameWaiter>, mut commands: Commands) {
let state = frame_waiter.wait().expect("Failed to wait frame");
commands.insert_resource(XrTime(openxr::Time::from_nanos(
state.predicted_display_time.as_nanos() + state.predicted_display_period.as_nanos(),
)));
}
pub fn begin_frame(mut frame_stream: ResMut<XrFrameStream>) {
frame_stream.begin().expect("Failed to begin frame");
}
fn insert_texture_views(
swapchain_images: Res<SwapchainImages>,
mut swapchain: ResMut<XrSwapchain>,
mut manual_texture_views: ResMut<ManualTextureViews>,
graphics_info: Res<XrGraphicsInfo>,
) {
let index = swapchain.acquire_image().expect("Failed to acquire image");
swapchain
.wait_image(openxr::Duration::INFINITE)
.expect("Failed to wait image");
let image = &swapchain_images[index as usize];
let left = image.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
..Default::default()
});
let right = image.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
array_layer_count: Some(1),
base_array_layer: 1,
..Default::default()
});
let resolution = graphics_info.swapchain_resolution;
let format = graphics_info.swapchain_format;
let left = ManualTextureView {
texture_view: left.into(),
size: resolution,
format: format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: resolution,
format: format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
}
fn end_frame(
mut frame_stream: ResMut<XrFrameStream>,
session: Res<XrSession>,
mut swapchain: ResMut<XrSwapchain>,
stage: Res<XrStage>,
display_time: Res<XrTime>,
graphics_info: Res<XrGraphicsInfo>,
) {
swapchain.release_image().unwrap();
let (_flags, views) = session
.locate_views(
openxr::ViewConfigurationType::PRIMARY_STEREO,
**display_time,
&stage,
)
.expect("Failed to locate views");
let rect = openxr::Rect2Di {
offset: openxr::Offset2Di { x: 0, y: 0 },
extent: openxr::Extent2Di {
width: graphics_info.swapchain_resolution.x as _,
height: graphics_info.swapchain_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(views[0].pose)
.fov(views[0].fov)
.sub_image(
SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(0)
.image_rect(rect),
),
CompositionLayerProjectionView::new()
.pose(views[0].pose)
.fov(views[0].fov)
.sub_image(
SwapchainSubImage::new()
.swapchain(&swapchain)
.image_array_index(0)
.image_rect(rect),
),
])],
)
.expect("Failed to end stream");
}

View File

@@ -1,262 +0,0 @@
use std::sync::Arc;
use bevy::prelude::*;
use bevy::render::extract_resource::ExtractResource;
use openxr::AnyGraphics;
use super::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap};
use super::types::*;
#[derive(Deref, Clone)]
pub struct XrEntry(pub openxr::Entry);
impl XrEntry {
pub fn enumerate_extensions(&self) -> Result<XrExtensions> {
Ok(self.0.enumerate_extensions().map(Into::into)?)
}
pub fn create_instance(
&self,
app_info: AppInfo,
exts: XrExtensions,
backend: GraphicsBackend,
) -> Result<XrInstance> {
let available_exts = self.enumerate_extensions()?;
if !backend.is_available(&available_exts) {
return Err(XrError::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(),
&[],
)?;
Ok(XrInstance(instance, backend, app_info))
}
pub fn available_backends(&self) -> Result<Vec<GraphicsBackend>> {
Ok(GraphicsBackend::available_backends(
&self.enumerate_extensions()?,
))
}
}
#[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)]
pub struct SystemId(pub openxr::SystemId);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Resource)]
pub struct XrGraphicsInfo {
pub blend_mode: EnvironmentBlendMode,
pub swapchain_resolution: UVec2,
pub swapchain_format: wgpu::TextureFormat,
}
#[derive(Resource, Deref, Clone)]
pub struct XrInstance(
#[deref] pub openxr::Instance,
pub(crate) GraphicsBackend,
pub(crate) AppInfo,
);
impl XrInstance {
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))))
}
)
}
/// # Safety
///
/// `info` must contain valid handles for the graphics api
pub unsafe fn create_session(
&self,
system_id: openxr::SystemId,
info: SessionCreateInfo,
) -> Result<(XrSession, XrFrameWaiter, XrFrameStream)> {
if !info.0.using_graphics_of_val(&self.1) {
return Err(XrError::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(), XrFrameWaiter(frame_waiter), XrFrameStream(Api::wrap(frame_stream))))
}
)
}
}
pub struct SessionCreateInfo(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for SessionCreateInfo {
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
}
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) GraphicsWrap<Self>,
);
impl<G: GraphicsExt> From<openxr::Session<G>> for XrSession {
fn from(value: openxr::Session<G>) -> Self {
Self(value.clone().into_any_graphics(), G::wrap(value))
}
}
impl XrSession {
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::to_wgpu_format).collect())
)
}
pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<XrSwapchain> {
Ok(XrSwapchain(graphics_match!(
&self.1;
session => session.create_swapchain(&info.try_into()?)? => XrSwapchain
)))
}
}
#[derive(Resource)]
pub struct XrFrameStream(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for XrFrameStream {
type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
}
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>() {
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())?)
}
)
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct XrFrameWaiter(pub openxr::FrameWaiter);
#[derive(Resource)]
pub struct XrSwapchain(pub(crate) GraphicsWrap<Self>);
impl GraphicsType for XrSwapchain {
type Inner<G: GraphicsExt> = openxr::Swapchain<G>;
}
impl XrSwapchain {
pub fn acquire_image(&mut self) -> Result<u32> {
graphics_match!(
&mut self.0;
swap => Ok(swap.acquire_image()?)
)
}
pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> {
graphics_match!(
&mut self.0;
swap => Ok(swap.wait_image(timeout)?)
)
}
pub fn release_image(&mut self) -> Result<()> {
graphics_match!(
&mut self.0;
swap => Ok(swap.release_image()?)
)
}
pub fn enumerate_images(
&mut self,
device: &wgpu::Device,
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<SwapchainImages> {
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(SwapchainImages(images.into()))
}
)
}
}
#[derive(Deref, Clone, Resource)]
pub struct XrStage(pub Arc<openxr::Space>);
#[derive(Debug, Deref, Resource, Clone)]
pub struct SwapchainImages(pub Arc<Vec<wgpu::Texture>>);
#[derive(Copy, Clone, Eq, PartialEq, Deref, DerefMut, Resource, ExtractResource)]
pub struct XrTime(pub openxr::Time);
#[derive(Clone, Copy, Eq, PartialEq, Default, Resource, ExtractResource)]
pub enum XrStatus {
Enabled,
#[default]
Disabled,
}

View File

@@ -1,348 +0,0 @@
use std::borrow::Cow;
use super::graphics::{graphics_match, GraphicsExt, GraphicsWrap};
pub use super::extensions::XrExtensions;
pub use openxr::{
EnvironmentBlendMode, Extent2Di, FormFactor, Graphics, Offset2Di, Rect2Di,
SwapchainCreateFlags, SwapchainUsageFlags,
};
pub type Result<T> = std::result::Result<T, XrError>;
pub struct WgpuGraphics(
pub wgpu::Device,
pub wgpu::Queue,
pub wgpu::AdapterInfo,
pub wgpu::Adapter,
pub wgpu::Instance,
);
#[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, PartialEq)]
pub struct AppInfo {
pub name: Cow<'static, str>,
pub version: Version,
}
impl Default for AppInfo {
fn default() -> Self {
Self {
name: "Bevy".into(),
version: Version::BEVY,
}
}
}
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()
)
}
}
mod error {
use super::GraphicsBackend;
use std::borrow::Cow;
use std::fmt;
use thiserror::Error;
#[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("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),
}
impl From<Vec<Cow<'static, str>>> for XrError {
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(())
}
}
}
pub use error::XrError;
#[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 = XrError;
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(XrError::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,
})
}
}
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()
}
}
}

View File

@@ -1,19 +0,0 @@
use bevy::ecs::system::Resource;
use bevy::math::Mat4;
use bevy::prelude::{Deref, DerefMut};
use bevy::render::camera::{RenderTarget, Viewport};
use crate::types::Pose;
pub const XR_TEXTURE_VIEW_INDEX: u32 = 1208214591;
#[derive(Debug, Clone)]
pub struct XrView {
pub projection_matrix: Mat4,
pub pose: Pose,
pub render_target: RenderTarget,
pub view_port: Option<Viewport>,
}
#[derive(Deref, DerefMut, Default, Debug, Clone, Resource)]
pub struct XrViews(#[deref] pub Vec<XrView>);

View File

@@ -1,24 +0,0 @@
use bevy::ecs::component::Component;
use bevy::math::{Quat, Vec3};
use bevy::render::camera::ManualTextureViewHandle;
#[derive(Debug, Clone)]
pub struct Pose {
pub translation: Vec3,
pub rotation: Quat,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Component)]
pub struct XrView {
pub view_handle: ManualTextureViewHandle,
pub view_index: usize,
}
pub struct Haptic;
#[derive(Clone, Copy, Debug)]
pub enum BlendMode {
Opaque,
Additive,
AlphaBlend,
}

View File

@@ -1,178 +0,0 @@
pub mod render;
mod resources;
pub use resources::*;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Mutex;
use std::time::Duration;
use bevy::app::{App, Plugin, PluginsState};
use bevy::ecs::entity::Entity;
use bevy::ecs::query::With;
use bevy::ecs::world::World;
use bevy::log::{error, info};
use bevy::render::RenderApp;
use bevy::window::PrimaryWindow;
use bevy::winit::WinitWindows;
use js_sys::Object;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{
HtmlCanvasElement, WebGl2RenderingContext, XrReferenceSpaceType, XrRenderStateInit,
XrSessionMode,
};
use winit::platform::web::WindowExtWebSys;
#[derive(Clone)]
struct FutureXrSession(Rc<Mutex<Option<Result<(XrSession, XrReferenceSpace), JsValue>>>>);
pub struct XrInitPlugin;
impl Plugin for XrInitPlugin {
fn build(&self, app: &mut App) {
let canvas = get_canvas(&mut app.world).unwrap();
let future_session = FutureXrSession(Default::default());
app.set_runner(webxr_runner);
app.insert_non_send_resource(future_session.clone());
bevy::tasks::IoTaskPool::get().spawn_local(async move {
let result =
init_webxr(canvas, XrSessionMode::Inline, XrReferenceSpaceType::Viewer).await;
*future_session.0.lock().unwrap() = Some(result);
});
}
fn ready(&self, app: &App) -> bool {
app.world
.get_non_send_resource::<FutureXrSession>()
.and_then(|fxr| fxr.0.try_lock().map(|locked| locked.is_some()).ok())
.unwrap_or(true)
}
fn finish(&self, app: &mut App) {
info!("finishing");
if let Some(result) = app
.world
.remove_non_send_resource::<FutureXrSession>()
.and_then(|fxr| fxr.0.lock().unwrap().take())
{
let (session, reference_space) = result.unwrap();
app.insert_non_send_resource(session.clone())
.insert_non_send_resource(reference_space.clone());
app.sub_app_mut(RenderApp)
.insert_non_send_resource(session)
.insert_non_send_resource(reference_space);
}
}
}
fn webxr_runner(mut app: App) {
fn set_timeout(f: &Closure<dyn FnMut()>, dur: Duration) {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
f.as_ref().unchecked_ref(),
dur.as_millis() as i32,
)
.expect("Should register `setTimeout`.");
}
let run_xr_inner = Rc::new(RefCell::new(None));
let run_xr: Rc<RefCell<Option<Closure<dyn FnMut()>>>> = run_xr_inner.clone();
*run_xr.borrow_mut() = Some(Closure::new(move || {
let app = &mut app;
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
run_xr_app(std::mem::take(app));
} else {
set_timeout(
run_xr_inner.borrow().as_ref().unwrap(),
Duration::from_millis(1),
);
}
}));
set_timeout(run_xr.borrow().as_ref().unwrap(), Duration::from_millis(1));
}
fn run_xr_app(mut app: App) {
let session = app.world.non_send_resource::<XrSession>().clone();
let inner_closure: Rc<RefCell<Option<Closure<dyn FnMut(f64, web_sys::XrFrame)>>>> =
Rc::new(RefCell::new(None));
let closure = inner_closure.clone();
*closure.borrow_mut() = Some(Closure::new(move |_time, frame: web_sys::XrFrame| {
let session = frame.session();
app.insert_non_send_resource(XrFrame(frame.clone()));
app.sub_app_mut(RenderApp)
.insert_non_send_resource(XrFrame(frame));
app.update();
session.request_animation_frame(
inner_closure
.borrow()
.as_ref()
.unwrap()
.as_ref()
.unchecked_ref(),
);
}));
session.request_animation_frame(closure.borrow().as_ref().unwrap().as_ref().unchecked_ref());
}
fn get_canvas(world: &mut World) -> Option<HtmlCanvasElement> {
let window_entity = world
.query_filtered::<Entity, With<PrimaryWindow>>()
.get_single(world)
.ok()?;
let windows = world.get_non_send_resource::<WinitWindows>()?;
Some(windows.get_window(window_entity)?.canvas())
}
async fn init_webxr(
canvas: HtmlCanvasElement,
mode: XrSessionMode,
reference_type: XrReferenceSpaceType,
) -> Result<(XrSession, XrReferenceSpace), JsValue> {
let xr = web_sys::window().unwrap().navigator().xr();
let supports_session = JsFuture::from(xr.is_session_supported(mode)).await?;
if supports_session == false {
error!("XR session {:?} not supported", mode);
return Err(JsValue::from_str(&format!(
"XR session {:?} not supported",
mode
)));
}
info!("creating session");
let session: web_sys::XrSession = JsFuture::from(xr.request_session(mode)).await?.into();
info!("creating gl");
let gl: WebGl2RenderingContext = {
let gl_attribs = Object::new();
js_sys::Reflect::set(
&gl_attribs,
&JsValue::from_str("xrCompatible"),
&JsValue::TRUE,
)?;
canvas
.get_context_with_context_options("webgl2", &gl_attribs)?
.ok_or(JsValue::from_str(
"Unable to create WebGL rendering context",
))?
.dyn_into()?
};
let xr_gl_layer = web_sys::XrWebGlLayer::new_with_web_gl2_rendering_context(&session, &gl)?;
let mut render_state_init = XrRenderStateInit::new();
render_state_init.base_layer(Some(&xr_gl_layer));
session.update_render_state_with_state(&render_state_init);
info!("creating ref space");
let reference_space = JsFuture::from(session.request_reference_space(reference_type))
.await?
.into();
info!("finished");
Ok((XrSession(session), XrReferenceSpace(reference_space)))
}

View File

@@ -1,154 +0,0 @@
use crate::render::{XrView, XrViews};
use crate::types::Pose;
use super::resources::*;
use bevy::app::{App, Plugin, PreUpdate};
use bevy::ecs::schedule::IntoSystemConfigs;
use bevy::ecs::system::{NonSend, Res, ResMut};
use bevy::ecs::world::World;
use bevy::math::{quat, uvec2, vec3, Mat4};
use bevy::render::camera::{
ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget, Viewport,
};
use bevy::render::renderer::RenderDevice;
use bevy::utils::default;
pub const XR_TEXTURE_VIEW_HANDLE: ManualTextureViewHandle =
ManualTextureViewHandle(crate::render::XR_TEXTURE_VIEW_INDEX);
pub struct XrRenderingPlugin;
impl Plugin for XrRenderingPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<XrViews>();
app.add_systems(
PreUpdate,
(insert_gl_layer, update_manual_texture_views, insert_views).chain(),
);
}
}
pub fn insert_gl_layer(world: &mut World) {
let gl_layer = world
.non_send_resource::<XrFrame>()
.session()
.render_state()
.base_layer()
.unwrap();
world.insert_non_send_resource(XrWebGlLayer(gl_layer));
}
pub fn update_manual_texture_views(
gl_layer: NonSend<XrWebGlLayer>,
render_device: Res<RenderDevice>,
mut manual_tex_view: ResMut<ManualTextureViews>,
) {
let dest_texture = create_framebuffer_texture(render_device.wgpu_device(), &gl_layer);
let view = dest_texture.create_view(&default());
manual_tex_view.insert(
XR_TEXTURE_VIEW_HANDLE,
ManualTextureView::with_default_format(
view.into(),
uvec2(gl_layer.framebuffer_width(), gl_layer.framebuffer_height()),
),
);
}
pub fn insert_views(
gl_layer: NonSend<XrWebGlLayer>,
reference_space: NonSend<XrReferenceSpace>,
frame: NonSend<XrFrame>,
mut xr_views: ResMut<XrViews>,
) {
let Some(viewer_pose) = frame.get_viewer_pose(&reference_space) else {
return;
};
let views = viewer_pose
.views()
.into_iter()
.map(Into::<web_sys::XrView>::into)
.map(|view| {
let transform = view.transform();
let position = transform.position();
let orientation = transform.orientation();
let viewport = gl_layer
.get_viewport(&view)
.map(|viewport| Viewport {
physical_position: uvec2(viewport.x() as u32, viewport.y() as u32),
physical_size: uvec2(viewport.width() as u32, viewport.height() as u32),
..Default::default()
})
.unwrap();
XrView {
projection_matrix: Mat4::from_cols_array(
&view.projection_matrix().try_into().unwrap(),
),
pose: Pose {
translation: vec3(
position.x() as f32,
position.y() as f32,
position.z() as f32,
),
rotation: quat(
orientation.x() as f32,
orientation.y() as f32,
orientation.z() as f32,
orientation.w() as f32,
),
},
render_target: RenderTarget::TextureView(XR_TEXTURE_VIEW_HANDLE),
view_port: Some(viewport),
}
})
.collect();
xr_views.0 = views;
}
pub fn create_framebuffer_texture(device: &wgpu::Device, gl_layer: &XrWebGlLayer) -> wgpu::Texture {
unsafe {
device.create_texture_from_hal::<wgpu_hal::gles::Api>(
wgpu_hal::gles::Texture {
inner: wgpu_hal::gles::TextureInner::ExternalFramebuffer {
// inner: framebuffer,
inner: gl_layer.framebuffer_unwrapped(),
// inner: framebuffer.as_ref().unwrap().clone(),
},
mip_level_count: 1,
array_layer_count: 1,
format: wgpu::TextureFormat::Rgba8Unorm, //TODO check this is ok, different from bevy default
format_desc: wgpu_hal::gles::TextureFormatDesc {
internal: glow::RGBA,
external: glow::RGBA,
data_type: glow::UNSIGNED_BYTE,
},
copy_size: wgpu_hal::CopyExtent {
width: gl_layer.framebuffer_width(),
height: gl_layer.framebuffer_height(),
depth: 1,
},
drop_guard: None,
is_cubemap: false,
},
&wgpu::TextureDescriptor {
label: Some("framebuffer (color)"),
size: wgpu::Extent3d {
width: gl_layer.framebuffer_width(),
height: gl_layer.framebuffer_height(),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
// | wgpu::TextureUsages::COPY_SRC,
// | wgpu::TextureUsages::COPY_DST,
},
)
}
}

View File

@@ -1,25 +0,0 @@
use bevy::prelude::{Deref, DerefMut};
use web_sys::WebGlFramebuffer;
#[derive(Deref, DerefMut, Clone)]
pub struct XrSession(#[deref] pub(crate) web_sys::XrSession);
#[derive(Deref, DerefMut, Clone)]
pub struct XrReferenceSpace(#[deref] pub(crate) web_sys::XrReferenceSpace);
#[derive(Deref, DerefMut, Clone)]
pub struct XrFrame(#[deref] pub(crate) web_sys::XrFrame);
#[derive(Deref, DerefMut, Clone)]
pub struct XrWebGlLayer(#[deref] pub(crate) web_sys::XrWebGlLayer);
impl XrWebGlLayer {
pub(crate) fn framebuffer_unwrapped(&self) -> WebGlFramebuffer {
js_sys::Reflect::get(&self, &"framebuffer".into())
.unwrap()
.into()
}
}
#[derive(Clone)]
pub struct XrView(pub(crate) web_sys::XrView);