Skip to main content

blade_graphics/vulkan/
surface.rs

1use ash::vk::{self, Handle as _};
2use openxr as xr;
3use std::mem;
4
5impl super::Surface {
6    pub fn info(&self) -> crate::SurfaceInfo {
7        crate::SurfaceInfo {
8            format: self.swapchain.format,
9            alpha: self.swapchain.alpha,
10        }
11    }
12
13    unsafe fn deinit_swapchain(&mut self, raw_device: &ash::Device) {
14        unsafe {
15            let _ = raw_device.device_wait_idle();
16            self.device
17                .destroy_swapchain(mem::take(&mut self.swapchain.raw), None);
18        }
19        for frame in self.frames.drain(..) {
20            for view in frame.xr_views {
21                if view != vk::ImageView::null() {
22                    unsafe { raw_device.destroy_image_view(view, None) };
23                }
24            }
25            unsafe {
26                raw_device.destroy_image_view(frame.view, None);
27                raw_device.destroy_semaphore(frame.acquire_semaphore, None);
28                raw_device.destroy_semaphore(frame.present_semaphore, None);
29            }
30        }
31    }
32
33    pub fn acquire_frame(&mut self) -> super::Frame {
34        let acquire_semaphore = self.next_semaphore;
35        match unsafe {
36            self.device.acquire_next_image(
37                self.swapchain.raw,
38                !0,
39                acquire_semaphore,
40                vk::Fence::null(),
41            )
42        } {
43            Ok((index, _suboptimal)) => {
44                self.next_semaphore = mem::replace(
45                    &mut self.frames[index as usize].acquire_semaphore,
46                    acquire_semaphore,
47                );
48                super::Frame {
49                    internal: self.frames[index as usize],
50                    swapchain: self.swapchain,
51                    image_index: Some(index),
52                    xr_swapchain: 0,
53                    xr_view_count: 0,
54                    xr_views: [super::XrView::default(); super::MAX_XR_EYES],
55                }
56            }
57            Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
58                log::warn!("Acquire failed because the surface is out of date");
59                super::Frame {
60                    internal: self.frames[0],
61                    swapchain: self.swapchain,
62                    image_index: None,
63                    xr_swapchain: 0,
64                    xr_view_count: 0,
65                    xr_views: [super::XrView::default(); super::MAX_XR_EYES],
66                }
67            }
68            Err(other) => {
69                log::error!("Acquire image error: {}", other);
70                super::Frame {
71                    internal: self.frames[0],
72                    swapchain: self.swapchain,
73                    image_index: None,
74                    xr_swapchain: 0,
75                    xr_view_count: 0,
76                    xr_views: [super::XrView::default(); super::MAX_XR_EYES],
77                }
78            }
79        }
80    }
81}
82
83impl super::XrSurface {
84    pub fn acquire_frame(&mut self, context: &super::Context) -> Option<super::Frame> {
85        let xr_state = context.xr.as_ref()?;
86        {
87            let mut xr = xr_state.lock().unwrap();
88            let frame_state = xr.frame_wait.wait().ok()?;
89            xr.frame_stream.begin().ok()?;
90            xr.predicted_display_time = Some(frame_state.predicted_display_time);
91            if !frame_state.should_render {
92                xr.predicted_display_time = None;
93                let environment_blend_mode = xr.environment_blend_mode;
94                xr.frame_stream
95                    .end(
96                        frame_state.predicted_display_time,
97                        environment_blend_mode,
98                        &[],
99                    )
100                    .ok()?;
101                return None;
102            }
103        }
104
105        let image_index = self.raw.acquire_image().ok()?;
106        self.raw.wait_image(xr::Duration::INFINITE).ok()?;
107        let mut xr_views = [super::XrView::default(); super::MAX_XR_EYES];
108        let xr_view_count = {
109            let xr = xr_state.lock().unwrap();
110            let predicted_display_time = xr.predicted_display_time?;
111            let space = xr.space.as_ref()?;
112            let (_, views) = xr
113                .session
114                .locate_views(xr.view_type, predicted_display_time, space)
115                .ok()?;
116            let count = views.len().min(self.view_count as usize);
117            if views.len() > self.view_count as usize {
118                log::warn!(
119                    "OpenXR returned {} views, truncating to {}",
120                    views.len(),
121                    self.view_count
122                );
123            }
124            for (i, view) in views.iter().take(count).enumerate() {
125                xr_views[i] = super::XrView {
126                    pose: super::XrPose {
127                        orientation: [
128                            view.pose.orientation.x,
129                            view.pose.orientation.y,
130                            view.pose.orientation.z,
131                            view.pose.orientation.w,
132                        ],
133                        position: [
134                            view.pose.position.x,
135                            view.pose.position.y,
136                            view.pose.position.z,
137                        ],
138                    },
139                    fov: super::XrFov {
140                        angle_left: view.fov.angle_left,
141                        angle_right: view.fov.angle_right,
142                        angle_up: view.fov.angle_up,
143                        angle_down: view.fov.angle_down,
144                    },
145                };
146            }
147            count as u32
148        };
149        Some(super::Frame {
150            internal: self.frames[image_index as usize],
151            swapchain: self.swapchain,
152            image_index: Some(image_index),
153            xr_swapchain: (&mut self.raw as *mut xr::Swapchain<xr::Vulkan>) as usize,
154            xr_view_count,
155            xr_views,
156        })
157    }
158
159    pub fn release_frame(&mut self) {
160        self.raw.release_image().unwrap();
161    }
162
163    pub fn extent(&self) -> crate::Extent {
164        crate::Extent {
165            width: self.swapchain.target_size[0] as u32,
166            height: self.swapchain.target_size[1] as u32,
167            depth: 1,
168        }
169    }
170
171    pub fn view_count(&self) -> u32 {
172        self.view_count
173    }
174
175    pub fn format(&self) -> crate::TextureFormat {
176        self.swapchain.format
177    }
178
179    pub fn swapchain(&self) -> &xr::Swapchain<xr::Vulkan> {
180        &self.raw
181    }
182}
183
184impl super::Context {
185    pub fn create_surface<
186        I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle,
187    >(
188        &self,
189        window: &I,
190    ) -> Result<super::Surface, crate::NotSupportedError> {
191        let khr_swapchain = self
192            .device
193            .swapchain
194            .clone()
195            .ok_or(crate::NotSupportedError::NoSupportedDeviceFound)?;
196
197        let raw = unsafe {
198            ash_window::create_surface(
199                &self.inner.entry,
200                &self.inner.instance.core,
201                window.display_handle().unwrap().as_raw(),
202                window.window_handle().unwrap().as_raw(),
203                None,
204            )
205            .map_err(crate::PlatformError::init)?
206        };
207
208        let khr_surface = self
209            .inner
210            .instance
211            .surface
212            .as_ref()
213            .ok_or(crate::NotSupportedError::PlatformNotSupported)?;
214        if unsafe {
215            khr_surface.get_physical_device_surface_support(
216                self.physical_device,
217                self.queue_family_index,
218                raw,
219            ) != Ok(true)
220        } {
221            log::warn!("Rejected for not presenting to the window surface");
222            return Err(crate::NotSupportedError::PlatformNotSupported);
223        }
224
225        let mut surface_info = vk::PhysicalDeviceSurfaceInfo2KHR {
226            surface: raw,
227            ..Default::default()
228        };
229        let mut fullscreen_exclusive_win32 = vk::SurfaceFullScreenExclusiveWin32InfoEXT::default();
230        surface_info = surface_info.push_next(&mut fullscreen_exclusive_win32);
231        let mut fullscreen_exclusive_ext = vk::SurfaceCapabilitiesFullScreenExclusiveEXT::default();
232        let mut capabilities2_khr =
233            vk::SurfaceCapabilities2KHR::default().push_next(&mut fullscreen_exclusive_ext);
234        let _ = unsafe {
235            self.inner
236                .instance
237                .get_surface_capabilities2
238                .as_ref()
239                .unwrap()
240                .get_physical_device_surface_capabilities2(
241                    self.physical_device,
242                    &surface_info,
243                    &mut capabilities2_khr,
244                )
245        };
246        log::debug!("{:?}", capabilities2_khr.surface_capabilities);
247
248        let semaphore_create_info = vk::SemaphoreCreateInfo::default();
249        let next_semaphore = unsafe {
250            self.device
251                .core
252                .create_semaphore(&semaphore_create_info, None)
253                .unwrap()
254        };
255
256        Ok(super::Surface {
257            device: khr_swapchain,
258            raw,
259            frames: Vec::new(),
260            next_semaphore,
261            swapchain: super::Swapchain {
262                raw: vk::SwapchainKHR::null(),
263                format: crate::TextureFormat::Rgba8Unorm,
264                alpha: crate::AlphaMode::Ignored,
265                target_size: [0; 2],
266            },
267            full_screen_exclusive: fullscreen_exclusive_ext.full_screen_exclusive_supported != 0,
268        })
269    }
270
271    pub fn destroy_surface(&self, surface: &mut super::Surface) {
272        unsafe {
273            surface.deinit_swapchain(&self.device.core);
274            self.device
275                .core
276                .destroy_semaphore(surface.next_semaphore, None)
277        };
278        if let Some(ref surface_instance) = self.inner.instance.surface {
279            unsafe { surface_instance.destroy_surface(surface.raw, None) };
280        }
281    }
282
283    pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) {
284        let khr_surface = self.inner.instance.surface.as_ref().unwrap();
285
286        let capabilities = unsafe {
287            khr_surface
288                .get_physical_device_surface_capabilities(self.physical_device, surface.raw)
289                .unwrap()
290        };
291        if config.size.width < capabilities.min_image_extent.width
292            || config.size.width > capabilities.max_image_extent.width
293            || config.size.height < capabilities.min_image_extent.height
294            || config.size.height > capabilities.max_image_extent.height
295        {
296            log::warn!(
297                "Requested size {}x{} is outside of surface capabilities",
298                config.size.width,
299                config.size.height
300            );
301        }
302
303        let (alpha, composite_alpha) = if config.transparent {
304            if capabilities
305                .supported_composite_alpha
306                .contains(vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED)
307            {
308                (
309                    crate::AlphaMode::PostMultiplied,
310                    vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED,
311                )
312            } else if capabilities
313                .supported_composite_alpha
314                .contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED)
315            {
316                (
317                    crate::AlphaMode::PreMultiplied,
318                    vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
319                )
320            } else {
321                log::error!(
322                    "No composite alpha flag for transparency: {:?}",
323                    capabilities.supported_composite_alpha
324                );
325                (
326                    crate::AlphaMode::Ignored,
327                    vk::CompositeAlphaFlagsKHR::OPAQUE,
328                )
329            }
330        } else {
331            (
332                crate::AlphaMode::Ignored,
333                vk::CompositeAlphaFlagsKHR::OPAQUE,
334            )
335        };
336
337        let (requested_frame_count, mode_preferences) = match config.display_sync {
338            crate::DisplaySync::Block => (3, [vk::PresentModeKHR::FIFO].as_slice()),
339            crate::DisplaySync::Recent => (
340                3,
341                [
342                    vk::PresentModeKHR::MAILBOX,
343                    vk::PresentModeKHR::FIFO_RELAXED,
344                    vk::PresentModeKHR::IMMEDIATE,
345                ]
346                .as_slice(),
347            ),
348            crate::DisplaySync::Tear => (2, [vk::PresentModeKHR::IMMEDIATE].as_slice()),
349        };
350        let effective_frame_count = requested_frame_count.max(capabilities.min_image_count);
351
352        let present_modes = unsafe {
353            khr_surface
354                .get_physical_device_surface_present_modes(self.physical_device, surface.raw)
355                .unwrap()
356        };
357        let present_mode = *mode_preferences
358            .iter()
359            .find(|mode| present_modes.contains(mode))
360            .unwrap();
361        log::info!("Using surface present mode {:?}", present_mode);
362
363        let queue_families = [self.queue_family_index];
364
365        let mut supported_formats = Vec::new();
366        let (format, surface_format) = if surface.swapchain.target_size[0] > 0 {
367            let format = surface.swapchain.format;
368            log::info!("Retaining current format: {:?}", format);
369            let vk_color_space = match (format, config.color_space) {
370                (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Srgb) => {
371                    vk::ColorSpaceKHR::SRGB_NONLINEAR
372                }
373                (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Linear) => {
374                    vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
375                }
376                (crate::TextureFormat::Bgra8UnormSrgb, crate::ColorSpace::Linear) => {
377                    vk::ColorSpaceKHR::default()
378                }
379                _ => panic!(
380                    "Unexpected format {:?} under color space {:?}",
381                    format, config.color_space
382                ),
383            };
384            (
385                format,
386                vk::SurfaceFormatKHR {
387                    format: super::map_texture_format(format),
388                    color_space: vk_color_space,
389                },
390            )
391        } else {
392            supported_formats = unsafe {
393                khr_surface
394                    .get_physical_device_surface_formats(self.physical_device, surface.raw)
395                    .unwrap()
396            };
397            match config.color_space {
398                crate::ColorSpace::Linear => {
399                    let surface_format = vk::SurfaceFormatKHR {
400                        format: vk::Format::B8G8R8A8_UNORM,
401                        color_space: vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT,
402                    };
403                    if supported_formats.contains(&surface_format) {
404                        log::info!("Using linear SRGB color space");
405                        (crate::TextureFormat::Bgra8Unorm, surface_format)
406                    } else {
407                        (
408                            crate::TextureFormat::Bgra8UnormSrgb,
409                            vk::SurfaceFormatKHR {
410                                format: vk::Format::B8G8R8A8_SRGB,
411                                color_space: vk::ColorSpaceKHR::default(),
412                            },
413                        )
414                    }
415                }
416                crate::ColorSpace::Srgb => (
417                    crate::TextureFormat::Bgra8Unorm,
418                    vk::SurfaceFormatKHR {
419                        format: vk::Format::B8G8R8A8_UNORM,
420                        color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
421                    },
422                ),
423            }
424        };
425        if !supported_formats.is_empty() && !supported_formats.contains(&surface_format) {
426            log::error!("Surface formats are incompatible: {:?}", supported_formats);
427        }
428
429        let vk_usage = super::resource::map_texture_usage(config.usage, crate::TexelAspects::COLOR);
430        if !capabilities.supported_usage_flags.contains(vk_usage) {
431            log::error!(
432                "Surface usages are incompatible: {:?}",
433                capabilities.supported_usage_flags
434            );
435        }
436
437        let mut full_screen_exclusive_info = vk::SurfaceFullScreenExclusiveInfoEXT {
438            full_screen_exclusive: if config.allow_exclusive_full_screen {
439                vk::FullScreenExclusiveEXT::ALLOWED
440            } else {
441                vk::FullScreenExclusiveEXT::DISALLOWED
442            },
443            ..Default::default()
444        };
445
446        let mut create_info = vk::SwapchainCreateInfoKHR {
447            surface: surface.raw,
448            min_image_count: effective_frame_count,
449            image_format: surface_format.format,
450            image_color_space: surface_format.color_space,
451            image_extent: vk::Extent2D {
452                width: config.size.width,
453                height: config.size.height,
454            },
455            image_array_layers: 1,
456            image_usage: vk_usage,
457            pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY,
458            composite_alpha,
459            present_mode,
460            old_swapchain: surface.swapchain.raw,
461            ..Default::default()
462        }
463        .queue_family_indices(&queue_families);
464
465        if surface.full_screen_exclusive {
466            assert!(self.device.full_screen_exclusive.is_some());
467            create_info = create_info.push_next(&mut full_screen_exclusive_info);
468            log::info!(
469                "Configuring exclusive full screen: {}",
470                config.allow_exclusive_full_screen
471            );
472        }
473        let raw_swapchain = unsafe { surface.device.create_swapchain(&create_info, None).unwrap() };
474
475        unsafe {
476            surface.deinit_swapchain(&self.device.core);
477        }
478
479        let images = unsafe { surface.device.get_swapchain_images(raw_swapchain).unwrap() };
480        let target_size = [config.size.width as u16, config.size.height as u16];
481        let subresource_range = vk::ImageSubresourceRange {
482            aspect_mask: vk::ImageAspectFlags::COLOR,
483            base_mip_level: 0,
484            level_count: 1,
485            base_array_layer: 0,
486            layer_count: 1,
487        };
488        for image in images {
489            let view_create_info = vk::ImageViewCreateInfo {
490                image,
491                view_type: vk::ImageViewType::TYPE_2D,
492                format: surface_format.format,
493                subresource_range,
494                ..Default::default()
495            };
496            let view = unsafe {
497                self.device
498                    .core
499                    .create_image_view(&view_create_info, None)
500                    .unwrap()
501            };
502            let semaphore_create_info = vk::SemaphoreCreateInfo::default();
503            let acquire_semaphore = unsafe {
504                self.device
505                    .core
506                    .create_semaphore(&semaphore_create_info, None)
507                    .unwrap()
508            };
509            let present_semaphore = unsafe {
510                self.device
511                    .core
512                    .create_semaphore(&semaphore_create_info, None)
513                    .unwrap()
514            };
515            surface.frames.push(super::InternalFrame {
516                acquire_semaphore,
517                present_semaphore,
518                image,
519                view,
520                xr_views: [vk::ImageView::null(); super::MAX_XR_EYES],
521            });
522        }
523        surface.swapchain = super::Swapchain {
524            raw: raw_swapchain,
525            format,
526            alpha,
527            target_size,
528        };
529    }
530
531    fn xr_recommended_surface_config(
532        &self,
533        view_type: xr::ViewConfigurationType,
534    ) -> Option<crate::XrSurfaceConfig> {
535        let xr = self.xr.as_ref()?;
536        let xr = xr.lock().unwrap();
537        let views = xr
538            .instance
539            .enumerate_view_configuration_views(xr.system_id, view_type)
540            .ok()?;
541        let first = *views.first()?;
542        let view_count = (views.len() as u32).min(super::MAX_XR_EYES as u32);
543        Some(crate::XrSurfaceConfig {
544            size: crate::Extent {
545                width: first.recommended_image_rect_width,
546                height: first.recommended_image_rect_height,
547                depth: 1,
548            },
549            usage: crate::TextureUsage::TARGET,
550            color_space: crate::ColorSpace::Linear,
551            view_count,
552        })
553    }
554
555    pub fn create_xr_surface(&self) -> Option<super::XrSurface> {
556        let config =
557            self.xr_recommended_surface_config(xr::ViewConfigurationType::PRIMARY_STEREO)?;
558        self.create_xr_surface_configured(config)
559    }
560
561    fn create_xr_surface_configured(
562        &self,
563        config: crate::XrSurfaceConfig,
564    ) -> Option<super::XrSurface> {
565        let xr = self.xr.as_ref()?;
566        let mut surface = {
567            let xr = xr.lock().unwrap();
568            let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
569            let raw = xr
570                .session
571                .create_swapchain(&xr::SwapchainCreateInfo {
572                    create_flags: xr::SwapchainCreateFlags::EMPTY,
573                    usage_flags: xr_swapchain_usage(config.usage),
574                    format: raw_format,
575                    sample_count: 1,
576                    width: config.size.width,
577                    height: config.size.height,
578                    face_count: 1,
579                    array_size: config.view_count.max(1),
580                    mip_count: 1,
581                })
582                .ok()?;
583            super::XrSurface {
584                raw,
585                frames: Vec::new(),
586                swapchain: super::Swapchain {
587                    raw: vk::SwapchainKHR::null(),
588                    format,
589                    alpha: crate::AlphaMode::Ignored,
590                    target_size: [config.size.width as u16, config.size.height as u16],
591                },
592                view_count: config.view_count.max(1),
593            }
594        };
595        self.reconfigure_xr_surface(&mut surface, config);
596        Some(surface)
597    }
598
599    pub fn destroy_xr_surface(&self, surface: &mut super::XrSurface) {
600        for frame in surface.frames.drain(..) {
601            for view in frame.xr_views {
602                if view != vk::ImageView::null() {
603                    unsafe { self.device.core.destroy_image_view(view, None) };
604                }
605            }
606            unsafe {
607                self.device.core.destroy_image_view(frame.view, None);
608                self.device
609                    .core
610                    .destroy_semaphore(frame.acquire_semaphore, None);
611                self.device
612                    .core
613                    .destroy_semaphore(frame.present_semaphore, None);
614            }
615        }
616    }
617
618    fn reconfigure_xr_surface(
619        &self,
620        surface: &mut super::XrSurface,
621        config: crate::XrSurfaceConfig,
622    ) {
623        self.destroy_xr_surface(surface);
624        let xr = self.xr.as_ref().expect("XR is not enabled in this context");
625        let xr = xr.lock().unwrap();
626        assert!(
627            config.view_count as usize <= super::MAX_XR_EYES,
628            "XR view count {} exceeds MAX_XR_EYES={}",
629            config.view_count,
630            super::MAX_XR_EYES
631        );
632        let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
633
634        let new_handle = xr
635            .session
636            .create_swapchain(&xr::SwapchainCreateInfo {
637                create_flags: xr::SwapchainCreateFlags::EMPTY,
638                usage_flags: xr_swapchain_usage(config.usage),
639                format: raw_format,
640                sample_count: 1,
641                width: config.size.width,
642                height: config.size.height,
643                face_count: 1,
644                array_size: config.view_count.max(1),
645                mip_count: 1,
646            })
647            .unwrap();
648        surface.raw = new_handle;
649
650        let target_size = [config.size.width as u16, config.size.height as u16];
651        let view_type = if config.view_count > 1 {
652            vk::ImageViewType::TYPE_2D_ARRAY
653        } else {
654            vk::ImageViewType::TYPE_2D
655        };
656        let subresource_range = vk::ImageSubresourceRange {
657            aspect_mask: vk::ImageAspectFlags::COLOR,
658            base_mip_level: 0,
659            level_count: 1,
660            base_array_layer: 0,
661            layer_count: config.view_count.max(1),
662        };
663
664        for raw_image in surface.raw.enumerate_images().unwrap() {
665            let image = vk::Image::from_raw(raw_image);
666            let view_create_info = vk::ImageViewCreateInfo {
667                image,
668                view_type,
669                format: super::map_texture_format(format),
670                subresource_range,
671                ..Default::default()
672            };
673            let view = unsafe {
674                self.device
675                    .core
676                    .create_image_view(&view_create_info, None)
677                    .unwrap()
678            };
679            let mut xr_views = [vk::ImageView::null(); super::MAX_XR_EYES];
680            for eye in 0..config.view_count.max(1) {
681                let xr_view_info = vk::ImageViewCreateInfo {
682                    image,
683                    view_type: vk::ImageViewType::TYPE_2D,
684                    format: super::map_texture_format(format),
685                    subresource_range: vk::ImageSubresourceRange {
686                        aspect_mask: vk::ImageAspectFlags::COLOR,
687                        base_mip_level: 0,
688                        level_count: 1,
689                        base_array_layer: eye,
690                        layer_count: 1,
691                    },
692                    ..Default::default()
693                };
694                let xr_view = unsafe {
695                    self.device
696                        .core
697                        .create_image_view(&xr_view_info, None)
698                        .unwrap()
699                };
700                xr_views[eye as usize] = xr_view;
701            }
702            let semaphore_create_info = vk::SemaphoreCreateInfo::default();
703            let acquire_semaphore = unsafe {
704                self.device
705                    .core
706                    .create_semaphore(&semaphore_create_info, None)
707                    .unwrap()
708            };
709            let present_semaphore = unsafe {
710                self.device
711                    .core
712                    .create_semaphore(&semaphore_create_info, None)
713                    .unwrap()
714            };
715            surface.frames.push(super::InternalFrame {
716                acquire_semaphore,
717                present_semaphore,
718                image,
719                view,
720                xr_views,
721            });
722        }
723
724        surface.swapchain = super::Swapchain {
725            raw: vk::SwapchainKHR::null(),
726            format,
727            alpha: crate::AlphaMode::Ignored,
728            target_size,
729        };
730        surface.view_count = config.view_count.max(1);
731    }
732}
733
734fn xr_swapchain_usage(usage: crate::TextureUsage) -> xr::SwapchainUsageFlags {
735    let mut out = xr::SwapchainUsageFlags::EMPTY;
736    if usage.contains(crate::TextureUsage::TARGET) {
737        out |= xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
738    }
739    if usage.contains(crate::TextureUsage::RESOURCE) {
740        out |= xr::SwapchainUsageFlags::SAMPLED;
741    }
742    if usage.contains(crate::TextureUsage::STORAGE) {
743        out |= xr::SwapchainUsageFlags::UNORDERED_ACCESS;
744    }
745    if out.is_empty() {
746        out = xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
747    }
748    out
749}
750
751fn texture_format_from_xr_raw(raw: u32) -> Option<crate::TextureFormat> {
752    let format = vk::Format::from_raw(raw as i32);
753    Some(match format {
754        vk::Format::R8G8B8A8_UNORM => crate::TextureFormat::Rgba8Unorm,
755        vk::Format::R8G8B8A8_SRGB => crate::TextureFormat::Rgba8UnormSrgb,
756        vk::Format::B8G8R8A8_UNORM => crate::TextureFormat::Bgra8Unorm,
757        vk::Format::B8G8R8A8_SRGB => crate::TextureFormat::Bgra8UnormSrgb,
758        _ => return None,
759    })
760}
761
762fn select_xr_swapchain_format(
763    session: &xr::Session<xr::Vulkan>,
764    color_space: crate::ColorSpace,
765) -> (u32, crate::TextureFormat) {
766    let formats = session.enumerate_swapchain_formats().unwrap();
767    let mut linear_candidate = None;
768    let mut srgb_candidate = None;
769    for raw in formats {
770        if let Some(format) = texture_format_from_xr_raw(raw) {
771            match format {
772                crate::TextureFormat::Rgba8Unorm | crate::TextureFormat::Bgra8Unorm => {
773                    if linear_candidate.is_none() {
774                        linear_candidate = Some((raw, format));
775                    }
776                }
777                crate::TextureFormat::Rgba8UnormSrgb | crate::TextureFormat::Bgra8UnormSrgb => {
778                    if srgb_candidate.is_none() {
779                        srgb_candidate = Some((raw, format));
780                    }
781                }
782                _ => {}
783            }
784        }
785    }
786    match color_space {
787        crate::ColorSpace::Linear => linear_candidate.or(srgb_candidate),
788        crate::ColorSpace::Srgb => srgb_candidate.or(linear_candidate),
789    }
790    .expect("No compatible XR swapchain format available")
791}