feat: allow manually defining additional vulkan extensions

Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
Schmarni
2025-07-10 00:48:40 +02:00
parent a5e3fb2580
commit a60db3da6c
5 changed files with 387 additions and 194 deletions

View File

@@ -0,0 +1,57 @@
//! A simple 3D scene with light shining over a cube sitting on a plane.
use bevy::{prelude::*, render::pipelined_rendering::PipelinedRenderingPlugin};
use bevy_mod_openxr::{
add_xr_plugins,
graphics::{GraphicsBackend, OxrManualGraphicsConfig},
};
fn main() -> AppExit {
App::new()
.insert_resource(OxrManualGraphicsConfig {
fallback_backend: GraphicsBackend::Vulkan(()),
vk_instance_exts: vec![],
vk_device_exts: vec![ash::khr::external_memory::NAME],
})
.add_plugins(add_xr_plugins(
// Disabling Pipelined Rendering should reduce latency a little bit for button inputs
// and increase accuracy for hand tracking, controller positions and similar,
// the views are updated right before rendering so they are as accurate as possible
DefaultPlugins.build().disable::<PipelinedRenderingPlugin>(),
))
.add_plugins(bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin)
.add_systems(Startup, setup)
.run()
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// circular base
commands.spawn((
Mesh3d(meshes.add(Circle::new(4.0))),
MeshMaterial3d(materials.add(Color::WHITE)),
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
));
// cube
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
Transform::from_xyz(0.0, 0.5, 0.0),
));
// light
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}

View File

@@ -3,9 +3,9 @@
#[cfg(feature = "vulkan")]
pub mod vulkan;
use std::any::TypeId;
use std::{any::TypeId, ffi::CStr};
use bevy::math::UVec2;
use bevy::{ecs::resource::Resource, math::UVec2};
use openxr::{FrameStream, FrameWaiter, Session};
use crate::{
@@ -39,6 +39,7 @@ pub unsafe trait GraphicsExt: openxr::Graphics {
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
cfg: Option<&OxrManualGraphicsConfig>,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
unsafe fn create_session(
instance: &openxr::Instance,
@@ -46,6 +47,17 @@ pub unsafe trait GraphicsExt: openxr::Graphics {
info: &Self::SessionCreateInfo,
session_create_info_chain: &mut OxrSessionCreateNextChain,
) -> openxr::Result<(Session<Self>, FrameWaiter, FrameStream<Self>)>;
fn init_fallback_graphics(
app_info: &AppInfo,
cfg: &OxrManualGraphicsConfig,
) -> Result<WgpuGraphics>;
}
#[derive(Resource)]
pub struct OxrManualGraphicsConfig {
pub fallback_backend: GraphicsBackend,
pub vk_instance_exts: Vec<&'static CStr>,
pub vk_device_exts: Vec<&'static CStr>,
}
/// A type that can be used in [`GraphicsWrap`].

View File

@@ -1,13 +1,15 @@
use std::ffi::{c_void, CString};
use std::ffi::{c_void, CStr, CString};
use std::str::FromStr;
use ash::vk::Handle;
use ash::vk::{self, Handle};
use bevy::log::{debug, error};
use bevy::math::UVec2;
use openxr::{sys, Version};
use wgpu::InstanceFlags;
use wgpu_hal::api::Vulkan;
use wgpu_hal::Api;
use super::{GraphicsExt, GraphicsType, GraphicsWrap};
use super::{GraphicsExt, GraphicsType, GraphicsWrap, OxrManualGraphicsConfig};
use crate::error::OxrError;
use crate::session::OxrSessionCreateNextChain;
use crate::types::{AppInfo, OxrExtensions, Result, WgpuGraphics};
@@ -17,7 +19,7 @@ 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(
const VK_TARGET_VERSION_ASH: u32 = vk::make_api_version(
0,
VK_TARGET_VERSION.major() as u32,
VK_TARGET_VERSION.minor() as u32,
@@ -40,7 +42,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
}
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
vulkan_to_wgpu(vk::Format::from_raw(format as _))
}
unsafe fn to_wgpu_img(
@@ -49,7 +51,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
format: wgpu::TextureFormat,
resolution: UVec2,
) -> Result<wgpu::Texture> {
let color_image = ash::vk::Image::from_raw(color_image);
let color_image = 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,
@@ -97,6 +99,7 @@ unsafe impl GraphicsExt for openxr::Vulkan {
app_info: &AppInfo,
instance: &openxr::Instance,
system_id: openxr::SystemId,
cfg: Option<&OxrManualGraphicsConfig>,
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)> {
let reqs = instance.graphics_requirements::<openxr::Vulkan>(system_id)?;
if VK_TARGET_VERSION < reqs.min_api_version_supported
@@ -110,26 +113,12 @@ unsafe impl GraphicsExt for openxr::Vulkan {
return Err(OxrError::FailedGraphicsRequirements);
};
let vk_entry = unsafe { ash::Entry::load() }?;
let flags = wgpu::InstanceFlags::default().with_env();
let extensions =
<Vulkan as Api>::Instance::desired_extensions(&vk_entry, VK_TARGET_VERSION_ASH, flags)?;
let device_extensions = [
ash::khr::swapchain::NAME,
ash::khr::draw_indirect_count::NAME,
ash::khr::timeline_semaphore::NAME,
ash::khr::imageless_framebuffer::NAME,
ash::khr::image_format_list::NAME,
#[cfg(target_os = "macos")]
ash::khr::portability_subset::NAME,
#[cfg(target_os = "macos")]
ash::ext::metal_objects::NAME,
];
let (flags, instance_exts, device_exts) = get_extensions(cfg, &vk_entry)?;
let vk_instance = unsafe {
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
let extensions_cchar: Vec<_> = instance_exts.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::default()
let vk_app_info = vk::ApplicationInfo::default()
.application_name(&app_name)
.application_version(1)
.engine_name(&app_name)
@@ -141,18 +130,182 @@ unsafe impl GraphicsExt for openxr::Vulkan {
system_id,
#[allow(clippy::missing_transmute_annotations)]
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
&ash::vk::InstanceCreateInfo::default()
&vk::InstanceCreateInfo::default()
.application_info(&vk_app_info)
.enabled_extension_names(&extensions_cchar) as *const _
as *const _,
)?
.map_err(ash::vk::Result::from_raw)?;
.map_err(vk::Result::from_raw)?;
ash::Instance::load(
vk_entry.static_fn(),
ash::vk::Instance::from_raw(vk_instance as _),
vk::Instance::from_raw(vk_instance as _),
)
};
let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe {
instance.vulkan_graphics_device(system_id, vk_instance.handle().as_raw() as _)? as _
});
init_from_instance_and_dev(
vk_entry.clone(),
vk_instance.clone(),
vk_physical_device,
instance_exts,
flags,
device_exts.into(),
|info| unsafe {
let vk_device = instance
.create_vulkan_device(
system_id,
#[allow(clippy::missing_transmute_annotations)]
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
vk_physical_device.as_raw() as _,
info as *const _ as *const _,
)?
.map_err(vk::Result::from_raw)?;
Ok(ash::Device::load(
vk_instance.fp_v1_0(),
vk::Device::from_raw(vk_device as _),
))
},
)
}
unsafe fn create_session(
instance: &openxr::Instance,
system_id: openxr::SystemId,
info: &Self::SessionCreateInfo,
session_create_info_chain: &mut OxrSessionCreateNextChain,
) -> openxr::Result<(
openxr::Session<Self>,
openxr::FrameWaiter,
openxr::FrameStream<Self>,
)> {
let next_ptr = session_create_info_chain.chain_pointer();
let binding = sys::GraphicsBindingVulkanKHR {
ty: sys::GraphicsBindingVulkanKHR::TYPE,
next: next_ptr,
instance: info.instance,
physical_device: info.physical_device,
device: info.device,
queue_family_index: info.queue_family_index,
queue_index: info.queue_index,
};
let info = sys::SessionCreateInfo {
ty: sys::SessionCreateInfo::TYPE,
next: &binding as *const _ as *const _,
create_flags: Default::default(),
system_id,
};
let mut out = sys::Session::NULL;
cvt((instance.fp().create_session)(
instance.as_raw(),
&info,
&mut out,
))?;
Ok(openxr::Session::from_raw(
instance.clone(),
out,
Box::new(()),
))
}
fn init_fallback_graphics(
app_info: &AppInfo,
cfg: &OxrManualGraphicsConfig,
) -> Result<WgpuGraphics> {
let vk_entry = unsafe { ash::Entry::load() }?;
let (instance_flags, instance_exts, device_exts) = get_extensions(Some(cfg), &vk_entry)?;
let vk_instance = unsafe {
let extensions_cchar: Vec<_> = instance_exts.iter().map(|s| s.as_ptr()).collect();
let app_name = CString::from_str(&app_info.name)?;
let vk_app_info = ash::vk::ApplicationInfo::default()
.application_name(&app_name)
.application_version(1)
.engine_name(c"bevy")
.engine_version(16)
.api_version(VK_TARGET_VERSION_ASH);
vk_entry.create_instance(
&ash::vk::InstanceCreateInfo::default()
.application_info(&vk_app_info)
.enabled_extension_names(&extensions_cchar),
None,
)?
};
let vk_physical_device = {
let mut devices = unsafe { vk_instance.enumerate_physical_devices()? };
devices.sort_by_key(|physical_device| {
match unsafe {
vk_instance
.get_physical_device_properties(*physical_device)
.device_type
} {
vk::PhysicalDeviceType::DISCRETE_GPU => 1,
vk::PhysicalDeviceType::INTEGRATED_GPU => 2,
vk::PhysicalDeviceType::OTHER => 3,
vk::PhysicalDeviceType::VIRTUAL_GPU => 4,
vk::PhysicalDeviceType::CPU => 5,
_ => 6,
}
});
let Some(phys_dev) = devices.into_iter().next() else {
return Err(OxrError::NoAvailableBackend);
};
phys_dev
};
init_from_instance_and_dev(
vk_entry.clone(),
vk_instance.clone(),
vk_physical_device,
instance_exts,
instance_flags,
device_exts.into(),
|info| unsafe { Ok(vk_instance.create_device(vk_physical_device, &info, None)?) },
)
.map(|v| v.0)
}
}
fn get_extensions(
cfg: Option<&OxrManualGraphicsConfig>,
vk_entry: &ash::Entry,
) -> Result<(wgpu::InstanceFlags, Vec<&'static CStr>, Vec<&'static CStr>)> {
let flags = wgpu::InstanceFlags::default().with_env();
let mut instance_exts =
<Vulkan as Api>::Instance::desired_extensions(&vk_entry, VK_TARGET_VERSION_ASH, flags)?;
let mut device_exts = vec![
ash::khr::swapchain::NAME,
ash::khr::draw_indirect_count::NAME,
ash::khr::timeline_semaphore::NAME,
ash::khr::imageless_framebuffer::NAME,
ash::khr::image_format_list::NAME,
#[cfg(target_os = "macos")]
ash::khr::portability_subset::NAME,
#[cfg(target_os = "macos")]
ash::ext::metal_objects::NAME,
];
if let Some(cfg) = cfg {
instance_exts.extend(&cfg.vk_instance_exts);
device_exts.extend(&cfg.vk_device_exts);
}
instance_exts.dedup();
device_exts.dedup();
Ok((flags, instance_exts, device_exts))
}
fn init_from_instance_and_dev(
vk_entry: ash::Entry,
vk_instance: ash::Instance,
vk_physical_device: vk::PhysicalDevice,
instance_exts: Vec<&'static CStr>,
instance_flags: InstanceFlags,
device_exts: Vec<&'static CStr>,
create_dev: impl for<'a> FnOnce(&'a vk::DeviceCreateInfo) -> Result<ash::Device>,
) -> Result<(WgpuGraphics, openxr::vulkan::SessionCreateInfo)> {
let api_layers = unsafe { vk_entry.enumerate_instance_layer_properties()? };
let has_nv_optimus = api_layers.iter().any(|v| {
v.layer_name_as_c_str()
@@ -161,11 +314,10 @@ unsafe impl GraphicsExt for openxr::Vulkan {
drop(api_layers);
// these to my knowledge aren't pointers, idk why openxr_sys says that they are,
// they're just ids
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 =
@@ -216,8 +368,8 @@ unsafe impl GraphicsExt for openxr::Vulkan {
vk_device_properties.api_version,
android_sdk_version,
None,
extensions,
flags,
instance_exts,
instance_flags,
has_nv_optimus,
None,
)?
@@ -235,42 +387,27 @@ unsafe impl GraphicsExt for openxr::Vulkan {
.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 extensions_cchar: Vec<_> = device_exts.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::default()
let family_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(family_index)
.queue_priorities(&[1.0]);
let family_infos = [family_info];
let mut physical_device_multiview_features = ash::vk::PhysicalDeviceMultiviewFeatures {
multiview: ash::vk::TRUE,
let mut physical_device_multiview_features = vk::PhysicalDeviceMultiviewFeatures {
multiview: vk::TRUE,
..Default::default()
};
let info = enabled_phd_features
.add_to_device_create(
ash::vk::DeviceCreateInfo::default()
vk::DeviceCreateInfo::default()
.queue_create_infos(&family_infos)
.push_next(&mut physical_device_multiview_features),
)
.enabled_extension_names(&extensions_cchar);
let vk_device = unsafe {
let vk_device = instance
.create_vulkan_device(
system_id,
#[allow(clippy::missing_transmute_annotations)]
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 = create_dev(&info)?;
let vk_device_ptr = vk_device.handle().as_raw() as *const c_void;
let wgpu_open_device = unsafe {
@@ -309,7 +446,6 @@ unsafe impl GraphicsExt for openxr::Vulkan {
None,
)
}?;
Ok((
WgpuGraphics(
wgpu_device,
@@ -328,46 +464,6 @@ unsafe impl GraphicsExt for openxr::Vulkan {
))
}
unsafe fn create_session(
instance: &openxr::Instance,
system_id: openxr::SystemId,
info: &Self::SessionCreateInfo,
session_create_info_chain: &mut OxrSessionCreateNextChain,
) -> openxr::Result<(
openxr::Session<Self>,
openxr::FrameWaiter,
openxr::FrameStream<Self>,
)> {
let next_ptr = session_create_info_chain.chain_pointer();
let binding = sys::GraphicsBindingVulkanKHR {
ty: sys::GraphicsBindingVulkanKHR::TYPE,
next: next_ptr,
instance: info.instance,
physical_device: info.physical_device,
device: info.device,
queue_family_index: info.queue_family_index,
queue_index: info.queue_index,
};
let info = sys::SessionCreateInfo {
ty: sys::SessionCreateInfo::TYPE,
next: &binding as *const _ as *const _,
create_flags: Default::default(),
system_id,
};
let mut out = sys::Session::NULL;
cvt((instance.fp().create_session)(
instance.as_raw(),
&info,
&mut out,
))?;
Ok(openxr::Session::from_raw(
instance.clone(),
out,
Box::new(()),
))
}
}
fn cvt(x: sys::Result) -> openxr::Result<sys::Result> {
if x.into_raw() >= 0 {
Ok(x)
@@ -376,7 +472,7 @@ fn cvt(x: sys::Result) -> openxr::Result<sys::Result> {
}
}
fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
fn vulkan_to_wgpu(format: vk::Format) -> Option<wgpu::TextureFormat> {
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
@@ -624,7 +720,7 @@ fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
})
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<ash::vk::Format> {
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<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

View File

@@ -81,7 +81,8 @@ impl Plugin for OxrInitPlugin {
fn build(&self, app: &mut App) {
app.add_event::<OxrInteractionProfileChanged>();
app.init_resource::<OxrSessionConfig>();
match self.init_xr() {
let cfg = app.world_mut().remove_resource::<OxrManualGraphicsConfig>();
match self.init_xr(cfg.as_ref()) {
Ok((
instance,
system_id,
@@ -160,6 +161,31 @@ impl Plugin for OxrInitPlugin {
}
Err(e) => {
error!("Failed to initialize openxr: {e}");
if let Some(cfg) = cfg {
if let Ok(WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance)) =
graphics_match!(
cfg.fallback_backend;
_ => Api::init_fallback_graphics(&self.app_info ,&cfg)
)
.inspect_err(|err| {
error!("Failed to initialize custom fallback graphics: {err}")
})
{
app.add_plugins(RenderPlugin {
render_creation: RenderCreation::manual(
device.into(),
RenderQueue(Arc::new(WgpuWrapper::new(queue))),
RenderAdapterInfo(WgpuWrapper::new(adapter_info)),
RenderAdapter(Arc::new(WgpuWrapper::new(adapter))),
RenderInstance(Arc::new(WgpuWrapper::new(wgpu_instance))),
),
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
debug_flags: self.render_debug_flags,
})
.insert_resource(XrState::Unavailable);
return;
}
}
app.add_plugins(RenderPlugin::default())
.insert_resource(XrState::Unavailable);
}
@@ -203,6 +229,7 @@ fn detect_session_destroyed(
impl OxrInitPlugin {
fn init_xr(
&self,
cfg: Option<&OxrManualGraphicsConfig>,
) -> OxrResult<(
OxrInstance,
OxrSystemId,
@@ -272,7 +299,7 @@ impl OxrInitPlugin {
}
);
let (graphics, graphics_info) = instance.init_graphics(system_id)?;
let (graphics, graphics_info) = instance.init_graphics(system_id, cfg)?;
Ok((
instance,

View File

@@ -106,11 +106,12 @@ impl OxrInstance {
pub fn init_graphics(
&self,
system_id: openxr::SystemId,
manual_config: Option<&OxrManualGraphicsConfig>,
) -> OxrResult<(WgpuGraphics, SessionGraphicsCreateInfo)> {
graphics_match!(
self.1;
_ => {
let (graphics, session_info) = Api::init_graphics(&self.2, self, system_id)?;
let (graphics, session_info) = Api::init_graphics(&self.2, self, system_id, manual_config)?;
Ok((graphics, SessionGraphicsCreateInfo(Api::wrap(session_info))))
}