use ash::vk::{self, Handle as _};
use openxr as xr;
use std::mem;
impl super::Surface {
pub fn info(&self) -> crate::SurfaceInfo {
crate::SurfaceInfo {
format: self.swapchain.format,
alpha: self.swapchain.alpha,
}
}
unsafe fn deinit_swapchain(&mut self, raw_device: &ash::Device) {
unsafe {
let _ = raw_device.device_wait_idle();
self.device
.destroy_swapchain(mem::take(&mut self.swapchain.raw), None);
}
for frame in self.frames.drain(..) {
for view in frame.xr_views {
if view != vk::ImageView::null() {
unsafe { raw_device.destroy_image_view(view, None) };
}
}
unsafe {
raw_device.destroy_image_view(frame.view, None);
raw_device.destroy_semaphore(frame.acquire_semaphore, None);
raw_device.destroy_semaphore(frame.present_semaphore, None);
}
}
}
pub fn acquire_frame(&mut self) -> super::Frame {
let acquire_semaphore = self.next_semaphore;
match unsafe {
self.device.acquire_next_image(
self.swapchain.raw,
!0,
acquire_semaphore,
vk::Fence::null(),
)
} {
Ok((index, _suboptimal)) => {
self.next_semaphore = mem::replace(
&mut self.frames[index as usize].acquire_semaphore,
acquire_semaphore,
);
super::Frame {
internal: self.frames[index as usize],
swapchain: self.swapchain,
image_index: Some(index),
xr_swapchain: 0,
xr_view_count: 0,
xr_views: [super::XrView::default(); super::MAX_XR_EYES],
}
}
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
log::warn!("Acquire failed because the surface is out of date");
super::Frame {
internal: self.frames[0],
swapchain: self.swapchain,
image_index: None,
xr_swapchain: 0,
xr_view_count: 0,
xr_views: [super::XrView::default(); super::MAX_XR_EYES],
}
}
Err(other) => {
log::error!("Acquire image error: {}", other);
super::Frame {
internal: self.frames[0],
swapchain: self.swapchain,
image_index: None,
xr_swapchain: 0,
xr_view_count: 0,
xr_views: [super::XrView::default(); super::MAX_XR_EYES],
}
}
}
}
}
impl super::XrSurface {
pub fn acquire_frame(&mut self, context: &super::Context) -> Option<super::Frame> {
let xr_state = context.xr.as_ref()?;
{
let mut xr = xr_state.lock().unwrap();
let frame_state = xr.frame_wait.wait().ok()?;
xr.frame_stream.begin().ok()?;
xr.predicted_display_time = Some(frame_state.predicted_display_time);
if !frame_state.should_render {
xr.predicted_display_time = None;
let environment_blend_mode = xr.environment_blend_mode;
xr.frame_stream
.end(
frame_state.predicted_display_time,
environment_blend_mode,
&[],
)
.ok()?;
return None;
}
}
let image_index = self.raw.acquire_image().ok()?;
self.raw.wait_image(xr::Duration::INFINITE).ok()?;
let mut xr_views = [super::XrView::default(); super::MAX_XR_EYES];
let xr_view_count = {
let xr = xr_state.lock().unwrap();
let predicted_display_time = xr.predicted_display_time?;
let space = xr.space.as_ref()?;
let (_, views) = xr
.session
.locate_views(xr.view_type, predicted_display_time, space)
.ok()?;
let count = views.len().min(self.view_count as usize);
if views.len() > self.view_count as usize {
log::warn!(
"OpenXR returned {} views, truncating to {}",
views.len(),
self.view_count
);
}
for (i, view) in views.iter().take(count).enumerate() {
xr_views[i] = super::XrView {
pose: super::XrPose {
orientation: [
view.pose.orientation.x,
view.pose.orientation.y,
view.pose.orientation.z,
view.pose.orientation.w,
],
position: [
view.pose.position.x,
view.pose.position.y,
view.pose.position.z,
],
},
fov: super::XrFov {
angle_left: view.fov.angle_left,
angle_right: view.fov.angle_right,
angle_up: view.fov.angle_up,
angle_down: view.fov.angle_down,
},
};
}
count as u32
};
Some(super::Frame {
internal: self.frames[image_index as usize],
swapchain: self.swapchain,
image_index: Some(image_index),
xr_swapchain: (&mut self.raw as *mut xr::Swapchain<xr::Vulkan>) as usize,
xr_view_count,
xr_views,
})
}
pub fn release_frame(&mut self) {
self.raw.release_image().unwrap();
}
pub fn extent(&self) -> crate::Extent {
crate::Extent {
width: self.swapchain.target_size[0] as u32,
height: self.swapchain.target_size[1] as u32,
depth: 1,
}
}
pub fn view_count(&self) -> u32 {
self.view_count
}
pub fn format(&self) -> crate::TextureFormat {
self.swapchain.format
}
pub fn swapchain(&self) -> &xr::Swapchain<xr::Vulkan> {
&self.raw
}
}
impl super::Context {
pub fn create_surface<
I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle,
>(
&self,
window: &I,
) -> Result<super::Surface, crate::NotSupportedError> {
let khr_swapchain = self
.device
.swapchain
.clone()
.ok_or(crate::NotSupportedError::NoSupportedDeviceFound)?;
let raw = unsafe {
ash_window::create_surface(
&self.entry,
&self.instance.core,
window.display_handle().unwrap().as_raw(),
window.window_handle().unwrap().as_raw(),
None,
)
.map_err(crate::PlatformError::init)?
};
let khr_surface = self
.instance
.surface
.as_ref()
.ok_or(crate::NotSupportedError::PlatformNotSupported)?;
if unsafe {
khr_surface.get_physical_device_surface_support(
self.physical_device,
self.queue_family_index,
raw,
) != Ok(true)
} {
log::warn!("Rejected for not presenting to the window surface");
return Err(crate::NotSupportedError::PlatformNotSupported);
}
let mut surface_info = vk::PhysicalDeviceSurfaceInfo2KHR {
surface: raw,
..Default::default()
};
let mut fullscreen_exclusive_win32 = vk::SurfaceFullScreenExclusiveWin32InfoEXT::default();
surface_info = surface_info.push_next(&mut fullscreen_exclusive_win32);
let mut fullscreen_exclusive_ext = vk::SurfaceCapabilitiesFullScreenExclusiveEXT::default();
let mut capabilities2_khr =
vk::SurfaceCapabilities2KHR::default().push_next(&mut fullscreen_exclusive_ext);
let _ = unsafe {
self.instance
.get_surface_capabilities2
.as_ref()
.unwrap()
.get_physical_device_surface_capabilities2(
self.physical_device,
&surface_info,
&mut capabilities2_khr,
)
};
log::debug!("{:?}", capabilities2_khr.surface_capabilities);
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
let next_semaphore = unsafe {
self.device
.core
.create_semaphore(&semaphore_create_info, None)
.unwrap()
};
Ok(super::Surface {
device: khr_swapchain,
raw,
frames: Vec::new(),
next_semaphore,
swapchain: super::Swapchain {
raw: vk::SwapchainKHR::null(),
format: crate::TextureFormat::Rgba8Unorm,
alpha: crate::AlphaMode::Ignored,
target_size: [0; 2],
},
full_screen_exclusive: fullscreen_exclusive_ext.full_screen_exclusive_supported != 0,
})
}
pub fn destroy_surface(&self, surface: &mut super::Surface) {
unsafe {
surface.deinit_swapchain(&self.device.core);
self.device
.core
.destroy_semaphore(surface.next_semaphore, None)
};
if let Some(ref surface_instance) = self.instance.surface {
unsafe { surface_instance.destroy_surface(surface.raw, None) };
}
}
pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) {
let khr_surface = self.instance.surface.as_ref().unwrap();
let capabilities = unsafe {
khr_surface
.get_physical_device_surface_capabilities(self.physical_device, surface.raw)
.unwrap()
};
if config.size.width < capabilities.min_image_extent.width
|| config.size.width > capabilities.max_image_extent.width
|| config.size.height < capabilities.min_image_extent.height
|| config.size.height > capabilities.max_image_extent.height
{
log::warn!(
"Requested size {}x{} is outside of surface capabilities",
config.size.width,
config.size.height
);
}
let (alpha, composite_alpha) = if config.transparent {
if capabilities
.supported_composite_alpha
.contains(vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED)
{
(
crate::AlphaMode::PostMultiplied,
vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED,
)
} else if capabilities
.supported_composite_alpha
.contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED)
{
(
crate::AlphaMode::PreMultiplied,
vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
)
} else {
log::error!(
"No composite alpha flag for transparency: {:?}",
capabilities.supported_composite_alpha
);
(
crate::AlphaMode::Ignored,
vk::CompositeAlphaFlagsKHR::OPAQUE,
)
}
} else {
(
crate::AlphaMode::Ignored,
vk::CompositeAlphaFlagsKHR::OPAQUE,
)
};
let (requested_frame_count, mode_preferences) = match config.display_sync {
crate::DisplaySync::Block => (3, [vk::PresentModeKHR::FIFO].as_slice()),
crate::DisplaySync::Recent => (
3,
[
vk::PresentModeKHR::MAILBOX,
vk::PresentModeKHR::FIFO_RELAXED,
vk::PresentModeKHR::IMMEDIATE,
]
.as_slice(),
),
crate::DisplaySync::Tear => (2, [vk::PresentModeKHR::IMMEDIATE].as_slice()),
};
let effective_frame_count = requested_frame_count.max(capabilities.min_image_count);
let present_modes = unsafe {
khr_surface
.get_physical_device_surface_present_modes(self.physical_device, surface.raw)
.unwrap()
};
let present_mode = *mode_preferences
.iter()
.find(|mode| present_modes.contains(mode))
.unwrap();
log::info!("Using surface present mode {:?}", present_mode);
let queue_families = [self.queue_family_index];
let mut supported_formats = Vec::new();
let (format, surface_format) = if surface.swapchain.target_size[0] > 0 {
let format = surface.swapchain.format;
log::info!("Retaining current format: {:?}", format);
let vk_color_space = match (format, config.color_space) {
(crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Srgb) => {
vk::ColorSpaceKHR::SRGB_NONLINEAR
}
(crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Linear) => {
vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
}
(crate::TextureFormat::Bgra8UnormSrgb, crate::ColorSpace::Linear) => {
vk::ColorSpaceKHR::default()
}
_ => panic!(
"Unexpected format {:?} under color space {:?}",
format, config.color_space
),
};
(
format,
vk::SurfaceFormatKHR {
format: super::map_texture_format(format),
color_space: vk_color_space,
},
)
} else {
supported_formats = unsafe {
khr_surface
.get_physical_device_surface_formats(self.physical_device, surface.raw)
.unwrap()
};
match config.color_space {
crate::ColorSpace::Linear => {
let surface_format = vk::SurfaceFormatKHR {
format: vk::Format::B8G8R8A8_UNORM,
color_space: vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT,
};
if supported_formats.contains(&surface_format) {
log::info!("Using linear SRGB color space");
(crate::TextureFormat::Bgra8Unorm, surface_format)
} else {
(
crate::TextureFormat::Bgra8UnormSrgb,
vk::SurfaceFormatKHR {
format: vk::Format::B8G8R8A8_SRGB,
color_space: vk::ColorSpaceKHR::default(),
},
)
}
}
crate::ColorSpace::Srgb => (
crate::TextureFormat::Bgra8Unorm,
vk::SurfaceFormatKHR {
format: vk::Format::B8G8R8A8_UNORM,
color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
},
),
}
};
if !supported_formats.is_empty() && !supported_formats.contains(&surface_format) {
log::error!("Surface formats are incompatible: {:?}", supported_formats);
}
let vk_usage = super::resource::map_texture_usage(config.usage, crate::TexelAspects::COLOR);
if !capabilities.supported_usage_flags.contains(vk_usage) {
log::error!(
"Surface usages are incompatible: {:?}",
capabilities.supported_usage_flags
);
}
let mut full_screen_exclusive_info = vk::SurfaceFullScreenExclusiveInfoEXT {
full_screen_exclusive: if config.allow_exclusive_full_screen {
vk::FullScreenExclusiveEXT::ALLOWED
} else {
vk::FullScreenExclusiveEXT::DISALLOWED
},
..Default::default()
};
let mut create_info = vk::SwapchainCreateInfoKHR {
surface: surface.raw,
min_image_count: effective_frame_count,
image_format: surface_format.format,
image_color_space: surface_format.color_space,
image_extent: vk::Extent2D {
width: config.size.width,
height: config.size.height,
},
image_array_layers: 1,
image_usage: vk_usage,
pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY,
composite_alpha,
present_mode,
old_swapchain: surface.swapchain.raw,
..Default::default()
}
.queue_family_indices(&queue_families);
if surface.full_screen_exclusive {
assert!(self.device.full_screen_exclusive.is_some());
create_info = create_info.push_next(&mut full_screen_exclusive_info);
log::info!(
"Configuring exclusive full screen: {}",
config.allow_exclusive_full_screen
);
}
let raw_swapchain = unsafe { surface.device.create_swapchain(&create_info, None).unwrap() };
unsafe {
surface.deinit_swapchain(&self.device.core);
}
let images = unsafe { surface.device.get_swapchain_images(raw_swapchain).unwrap() };
let target_size = [config.size.width as u16, config.size.height as u16];
let subresource_range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
for image in images {
let view_create_info = vk::ImageViewCreateInfo {
image,
view_type: vk::ImageViewType::TYPE_2D,
format: surface_format.format,
subresource_range,
..Default::default()
};
let view = unsafe {
self.device
.core
.create_image_view(&view_create_info, None)
.unwrap()
};
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
let acquire_semaphore = unsafe {
self.device
.core
.create_semaphore(&semaphore_create_info, None)
.unwrap()
};
let present_semaphore = unsafe {
self.device
.core
.create_semaphore(&semaphore_create_info, None)
.unwrap()
};
surface.frames.push(super::InternalFrame {
acquire_semaphore,
present_semaphore,
image,
view,
xr_views: [vk::ImageView::null(); super::MAX_XR_EYES],
});
}
surface.swapchain = super::Swapchain {
raw: raw_swapchain,
format,
alpha,
target_size,
};
}
fn xr_recommended_surface_config(
&self,
view_type: xr::ViewConfigurationType,
) -> Option<crate::XrSurfaceConfig> {
let xr = self.xr.as_ref()?;
let xr = xr.lock().unwrap();
let views = xr
.instance
.enumerate_view_configuration_views(xr.system_id, view_type)
.ok()?;
let first = *views.first()?;
let view_count = (views.len() as u32).min(super::MAX_XR_EYES as u32);
Some(crate::XrSurfaceConfig {
size: crate::Extent {
width: first.recommended_image_rect_width,
height: first.recommended_image_rect_height,
depth: 1,
},
usage: crate::TextureUsage::TARGET,
color_space: crate::ColorSpace::Linear,
view_count,
})
}
pub fn create_xr_surface(&self) -> Option<super::XrSurface> {
let config =
self.xr_recommended_surface_config(xr::ViewConfigurationType::PRIMARY_STEREO)?;
self.create_xr_surface_configured(config)
}
fn create_xr_surface_configured(
&self,
config: crate::XrSurfaceConfig,
) -> Option<super::XrSurface> {
let xr = self.xr.as_ref()?;
let mut surface = {
let xr = xr.lock().unwrap();
let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
let raw = xr
.session
.create_swapchain(&xr::SwapchainCreateInfo {
create_flags: xr::SwapchainCreateFlags::EMPTY,
usage_flags: xr_swapchain_usage(config.usage),
format: raw_format,
sample_count: 1,
width: config.size.width,
height: config.size.height,
face_count: 1,
array_size: config.view_count.max(1),
mip_count: 1,
})
.ok()?;
super::XrSurface {
raw,
frames: Vec::new(),
swapchain: super::Swapchain {
raw: vk::SwapchainKHR::null(),
format,
alpha: crate::AlphaMode::Ignored,
target_size: [config.size.width as u16, config.size.height as u16],
},
view_count: config.view_count.max(1),
}
};
self.reconfigure_xr_surface(&mut surface, config);
Some(surface)
}
pub fn destroy_xr_surface(&self, surface: &mut super::XrSurface) {
for frame in surface.frames.drain(..) {
for view in frame.xr_views {
if view != vk::ImageView::null() {
unsafe { self.device.core.destroy_image_view(view, None) };
}
}
unsafe {
self.device.core.destroy_image_view(frame.view, None);
self.device
.core
.destroy_semaphore(frame.acquire_semaphore, None);
self.device
.core
.destroy_semaphore(frame.present_semaphore, None);
}
}
}
fn reconfigure_xr_surface(
&self,
surface: &mut super::XrSurface,
config: crate::XrSurfaceConfig,
) {
self.destroy_xr_surface(surface);
let xr = self.xr.as_ref().expect("XR is not enabled in this context");
let xr = xr.lock().unwrap();
assert!(
config.view_count as usize <= super::MAX_XR_EYES,
"XR view count {} exceeds MAX_XR_EYES={}",
config.view_count,
super::MAX_XR_EYES
);
let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
let new_handle = xr
.session
.create_swapchain(&xr::SwapchainCreateInfo {
create_flags: xr::SwapchainCreateFlags::EMPTY,
usage_flags: xr_swapchain_usage(config.usage),
format: raw_format,
sample_count: 1,
width: config.size.width,
height: config.size.height,
face_count: 1,
array_size: config.view_count.max(1),
mip_count: 1,
})
.unwrap();
surface.raw = new_handle;
let target_size = [config.size.width as u16, config.size.height as u16];
let view_type = if config.view_count > 1 {
vk::ImageViewType::TYPE_2D_ARRAY
} else {
vk::ImageViewType::TYPE_2D
};
let subresource_range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: config.view_count.max(1),
};
for raw_image in surface.raw.enumerate_images().unwrap() {
let image = vk::Image::from_raw(raw_image);
let view_create_info = vk::ImageViewCreateInfo {
image,
view_type,
format: super::map_texture_format(format),
subresource_range,
..Default::default()
};
let view = unsafe {
self.device
.core
.create_image_view(&view_create_info, None)
.unwrap()
};
let mut xr_views = [vk::ImageView::null(); super::MAX_XR_EYES];
for eye in 0..config.view_count.max(1) {
let xr_view_info = vk::ImageViewCreateInfo {
image,
view_type: vk::ImageViewType::TYPE_2D,
format: super::map_texture_format(format),
subresource_range: vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: eye,
layer_count: 1,
},
..Default::default()
};
let xr_view = unsafe {
self.device
.core
.create_image_view(&xr_view_info, None)
.unwrap()
};
xr_views[eye as usize] = xr_view;
}
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
let acquire_semaphore = unsafe {
self.device
.core
.create_semaphore(&semaphore_create_info, None)
.unwrap()
};
let present_semaphore = unsafe {
self.device
.core
.create_semaphore(&semaphore_create_info, None)
.unwrap()
};
surface.frames.push(super::InternalFrame {
acquire_semaphore,
present_semaphore,
image,
view,
xr_views,
});
}
surface.swapchain = super::Swapchain {
raw: vk::SwapchainKHR::null(),
format,
alpha: crate::AlphaMode::Ignored,
target_size,
};
surface.view_count = config.view_count.max(1);
}
}
fn xr_swapchain_usage(usage: crate::TextureUsage) -> xr::SwapchainUsageFlags {
let mut out = xr::SwapchainUsageFlags::EMPTY;
if usage.contains(crate::TextureUsage::TARGET) {
out |= xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
}
if usage.contains(crate::TextureUsage::RESOURCE) {
out |= xr::SwapchainUsageFlags::SAMPLED;
}
if usage.contains(crate::TextureUsage::STORAGE) {
out |= xr::SwapchainUsageFlags::UNORDERED_ACCESS;
}
if out.is_empty() {
out = xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
}
out
}
fn texture_format_from_xr_raw(raw: u32) -> Option<crate::TextureFormat> {
let format = vk::Format::from_raw(raw as i32);
Some(match format {
vk::Format::R8G8B8A8_UNORM => crate::TextureFormat::Rgba8Unorm,
vk::Format::R8G8B8A8_SRGB => crate::TextureFormat::Rgba8UnormSrgb,
vk::Format::B8G8R8A8_UNORM => crate::TextureFormat::Bgra8Unorm,
vk::Format::B8G8R8A8_SRGB => crate::TextureFormat::Bgra8UnormSrgb,
_ => return None,
})
}
fn select_xr_swapchain_format(
session: &xr::Session<xr::Vulkan>,
color_space: crate::ColorSpace,
) -> (u32, crate::TextureFormat) {
let formats = session.enumerate_swapchain_formats().unwrap();
let mut linear_candidate = None;
let mut srgb_candidate = None;
for raw in formats {
if let Some(format) = texture_format_from_xr_raw(raw) {
match format {
crate::TextureFormat::Rgba8Unorm | crate::TextureFormat::Bgra8Unorm => {
if linear_candidate.is_none() {
linear_candidate = Some((raw, format));
}
}
crate::TextureFormat::Rgba8UnormSrgb | crate::TextureFormat::Bgra8UnormSrgb => {
if srgb_candidate.is_none() {
srgb_candidate = Some((raw, format));
}
}
_ => {}
}
}
}
match color_space {
crate::ColorSpace::Linear => linear_candidate.or(srgb_candidate),
crate::ColorSpace::Srgb => srgb_candidate.or(linear_candidate),
}
.expect("No compatible XR swapchain format available")
}