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.entry,
200                &self.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            .instance
210            .surface
211            .as_ref()
212            .ok_or(crate::NotSupportedError::PlatformNotSupported)?;
213        if unsafe {
214            khr_surface.get_physical_device_surface_support(
215                self.physical_device,
216                self.queue_family_index,
217                raw,
218            ) != Ok(true)
219        } {
220            log::warn!("Rejected for not presenting to the window surface");
221            return Err(crate::NotSupportedError::PlatformNotSupported);
222        }
223
224        let mut surface_info = vk::PhysicalDeviceSurfaceInfo2KHR {
225            surface: raw,
226            ..Default::default()
227        };
228        let mut fullscreen_exclusive_win32 = vk::SurfaceFullScreenExclusiveWin32InfoEXT::default();
229        surface_info = surface_info.push_next(&mut fullscreen_exclusive_win32);
230        let mut fullscreen_exclusive_ext = vk::SurfaceCapabilitiesFullScreenExclusiveEXT::default();
231        let mut capabilities2_khr =
232            vk::SurfaceCapabilities2KHR::default().push_next(&mut fullscreen_exclusive_ext);
233        let _ = unsafe {
234            self.instance
235                .get_surface_capabilities2
236                .as_ref()
237                .unwrap()
238                .get_physical_device_surface_capabilities2(
239                    self.physical_device,
240                    &surface_info,
241                    &mut capabilities2_khr,
242                )
243        };
244        log::debug!("{:?}", capabilities2_khr.surface_capabilities);
245
246        let semaphore_create_info = vk::SemaphoreCreateInfo::default();
247        let next_semaphore = unsafe {
248            self.device
249                .core
250                .create_semaphore(&semaphore_create_info, None)
251                .unwrap()
252        };
253
254        Ok(super::Surface {
255            device: khr_swapchain,
256            raw,
257            frames: Vec::new(),
258            next_semaphore,
259            swapchain: super::Swapchain {
260                raw: vk::SwapchainKHR::null(),
261                format: crate::TextureFormat::Rgba8Unorm,
262                alpha: crate::AlphaMode::Ignored,
263                target_size: [0; 2],
264            },
265            full_screen_exclusive: fullscreen_exclusive_ext.full_screen_exclusive_supported != 0,
266        })
267    }
268
269    pub fn destroy_surface(&self, surface: &mut super::Surface) {
270        unsafe {
271            surface.deinit_swapchain(&self.device.core);
272            self.device
273                .core
274                .destroy_semaphore(surface.next_semaphore, None)
275        };
276        if let Some(ref surface_instance) = self.instance.surface {
277            unsafe { surface_instance.destroy_surface(surface.raw, None) };
278        }
279    }
280
281    pub fn reconfigure_surface(&self, surface: &mut super::Surface, config: crate::SurfaceConfig) {
282        let khr_surface = self.instance.surface.as_ref().unwrap();
283
284        let capabilities = unsafe {
285            khr_surface
286                .get_physical_device_surface_capabilities(self.physical_device, surface.raw)
287                .unwrap()
288        };
289        if config.size.width < capabilities.min_image_extent.width
290            || config.size.width > capabilities.max_image_extent.width
291            || config.size.height < capabilities.min_image_extent.height
292            || config.size.height > capabilities.max_image_extent.height
293        {
294            log::warn!(
295                "Requested size {}x{} is outside of surface capabilities",
296                config.size.width,
297                config.size.height
298            );
299        }
300
301        let (alpha, composite_alpha) = if config.transparent {
302            if capabilities
303                .supported_composite_alpha
304                .contains(vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED)
305            {
306                (
307                    crate::AlphaMode::PostMultiplied,
308                    vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED,
309                )
310            } else if capabilities
311                .supported_composite_alpha
312                .contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED)
313            {
314                (
315                    crate::AlphaMode::PreMultiplied,
316                    vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
317                )
318            } else {
319                log::error!(
320                    "No composite alpha flag for transparency: {:?}",
321                    capabilities.supported_composite_alpha
322                );
323                (
324                    crate::AlphaMode::Ignored,
325                    vk::CompositeAlphaFlagsKHR::OPAQUE,
326                )
327            }
328        } else {
329            (
330                crate::AlphaMode::Ignored,
331                vk::CompositeAlphaFlagsKHR::OPAQUE,
332            )
333        };
334
335        let (requested_frame_count, mode_preferences) = match config.display_sync {
336            crate::DisplaySync::Block => (3, [vk::PresentModeKHR::FIFO].as_slice()),
337            crate::DisplaySync::Recent => (
338                3,
339                [
340                    vk::PresentModeKHR::MAILBOX,
341                    vk::PresentModeKHR::FIFO_RELAXED,
342                    vk::PresentModeKHR::IMMEDIATE,
343                ]
344                .as_slice(),
345            ),
346            crate::DisplaySync::Tear => (2, [vk::PresentModeKHR::IMMEDIATE].as_slice()),
347        };
348        let effective_frame_count = requested_frame_count.max(capabilities.min_image_count);
349
350        let present_modes = unsafe {
351            khr_surface
352                .get_physical_device_surface_present_modes(self.physical_device, surface.raw)
353                .unwrap()
354        };
355        let present_mode = *mode_preferences
356            .iter()
357            .find(|mode| present_modes.contains(mode))
358            .unwrap();
359        log::info!("Using surface present mode {:?}", present_mode);
360
361        let queue_families = [self.queue_family_index];
362
363        let mut supported_formats = Vec::new();
364        let (format, surface_format) = if surface.swapchain.target_size[0] > 0 {
365            let format = surface.swapchain.format;
366            log::info!("Retaining current format: {:?}", format);
367            let vk_color_space = match (format, config.color_space) {
368                (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Srgb) => {
369                    vk::ColorSpaceKHR::SRGB_NONLINEAR
370                }
371                (crate::TextureFormat::Bgra8Unorm, crate::ColorSpace::Linear) => {
372                    vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT
373                }
374                (crate::TextureFormat::Bgra8UnormSrgb, crate::ColorSpace::Linear) => {
375                    vk::ColorSpaceKHR::default()
376                }
377                _ => panic!(
378                    "Unexpected format {:?} under color space {:?}",
379                    format, config.color_space
380                ),
381            };
382            (
383                format,
384                vk::SurfaceFormatKHR {
385                    format: super::map_texture_format(format),
386                    color_space: vk_color_space,
387                },
388            )
389        } else {
390            supported_formats = unsafe {
391                khr_surface
392                    .get_physical_device_surface_formats(self.physical_device, surface.raw)
393                    .unwrap()
394            };
395            match config.color_space {
396                crate::ColorSpace::Linear => {
397                    let surface_format = vk::SurfaceFormatKHR {
398                        format: vk::Format::B8G8R8A8_UNORM,
399                        color_space: vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT,
400                    };
401                    if supported_formats.contains(&surface_format) {
402                        log::info!("Using linear SRGB color space");
403                        (crate::TextureFormat::Bgra8Unorm, surface_format)
404                    } else {
405                        (
406                            crate::TextureFormat::Bgra8UnormSrgb,
407                            vk::SurfaceFormatKHR {
408                                format: vk::Format::B8G8R8A8_SRGB,
409                                color_space: vk::ColorSpaceKHR::default(),
410                            },
411                        )
412                    }
413                }
414                crate::ColorSpace::Srgb => (
415                    crate::TextureFormat::Bgra8Unorm,
416                    vk::SurfaceFormatKHR {
417                        format: vk::Format::B8G8R8A8_UNORM,
418                        color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
419                    },
420                ),
421            }
422        };
423        if !supported_formats.is_empty() && !supported_formats.contains(&surface_format) {
424            log::error!("Surface formats are incompatible: {:?}", supported_formats);
425        }
426
427        let vk_usage = super::resource::map_texture_usage(config.usage, crate::TexelAspects::COLOR);
428        if !capabilities.supported_usage_flags.contains(vk_usage) {
429            log::error!(
430                "Surface usages are incompatible: {:?}",
431                capabilities.supported_usage_flags
432            );
433        }
434
435        let mut full_screen_exclusive_info = vk::SurfaceFullScreenExclusiveInfoEXT {
436            full_screen_exclusive: if config.allow_exclusive_full_screen {
437                vk::FullScreenExclusiveEXT::ALLOWED
438            } else {
439                vk::FullScreenExclusiveEXT::DISALLOWED
440            },
441            ..Default::default()
442        };
443
444        let mut create_info = vk::SwapchainCreateInfoKHR {
445            surface: surface.raw,
446            min_image_count: effective_frame_count,
447            image_format: surface_format.format,
448            image_color_space: surface_format.color_space,
449            image_extent: vk::Extent2D {
450                width: config.size.width,
451                height: config.size.height,
452            },
453            image_array_layers: 1,
454            image_usage: vk_usage,
455            pre_transform: vk::SurfaceTransformFlagsKHR::IDENTITY,
456            composite_alpha,
457            present_mode,
458            old_swapchain: surface.swapchain.raw,
459            ..Default::default()
460        }
461        .queue_family_indices(&queue_families);
462
463        if surface.full_screen_exclusive {
464            assert!(self.device.full_screen_exclusive.is_some());
465            create_info = create_info.push_next(&mut full_screen_exclusive_info);
466            log::info!(
467                "Configuring exclusive full screen: {}",
468                config.allow_exclusive_full_screen
469            );
470        }
471        let raw_swapchain = unsafe { surface.device.create_swapchain(&create_info, None).unwrap() };
472
473        unsafe {
474            surface.deinit_swapchain(&self.device.core);
475        }
476
477        let images = unsafe { surface.device.get_swapchain_images(raw_swapchain).unwrap() };
478        let target_size = [config.size.width as u16, config.size.height as u16];
479        let subresource_range = vk::ImageSubresourceRange {
480            aspect_mask: vk::ImageAspectFlags::COLOR,
481            base_mip_level: 0,
482            level_count: 1,
483            base_array_layer: 0,
484            layer_count: 1,
485        };
486        for image in images {
487            let view_create_info = vk::ImageViewCreateInfo {
488                image,
489                view_type: vk::ImageViewType::TYPE_2D,
490                format: surface_format.format,
491                subresource_range,
492                ..Default::default()
493            };
494            let view = unsafe {
495                self.device
496                    .core
497                    .create_image_view(&view_create_info, None)
498                    .unwrap()
499            };
500            let semaphore_create_info = vk::SemaphoreCreateInfo::default();
501            let acquire_semaphore = unsafe {
502                self.device
503                    .core
504                    .create_semaphore(&semaphore_create_info, None)
505                    .unwrap()
506            };
507            let present_semaphore = unsafe {
508                self.device
509                    .core
510                    .create_semaphore(&semaphore_create_info, None)
511                    .unwrap()
512            };
513            surface.frames.push(super::InternalFrame {
514                acquire_semaphore,
515                present_semaphore,
516                image,
517                view,
518                xr_views: [vk::ImageView::null(); super::MAX_XR_EYES],
519            });
520        }
521        surface.swapchain = super::Swapchain {
522            raw: raw_swapchain,
523            format,
524            alpha,
525            target_size,
526        };
527    }
528
529    fn xr_recommended_surface_config(
530        &self,
531        view_type: xr::ViewConfigurationType,
532    ) -> Option<crate::XrSurfaceConfig> {
533        let xr = self.xr.as_ref()?;
534        let xr = xr.lock().unwrap();
535        let views = xr
536            .instance
537            .enumerate_view_configuration_views(xr.system_id, view_type)
538            .ok()?;
539        let first = *views.first()?;
540        let view_count = (views.len() as u32).min(super::MAX_XR_EYES as u32);
541        Some(crate::XrSurfaceConfig {
542            size: crate::Extent {
543                width: first.recommended_image_rect_width,
544                height: first.recommended_image_rect_height,
545                depth: 1,
546            },
547            usage: crate::TextureUsage::TARGET,
548            color_space: crate::ColorSpace::Linear,
549            view_count,
550        })
551    }
552
553    pub fn create_xr_surface(&self) -> Option<super::XrSurface> {
554        let config =
555            self.xr_recommended_surface_config(xr::ViewConfigurationType::PRIMARY_STEREO)?;
556        self.create_xr_surface_configured(config)
557    }
558
559    fn create_xr_surface_configured(
560        &self,
561        config: crate::XrSurfaceConfig,
562    ) -> Option<super::XrSurface> {
563        let xr = self.xr.as_ref()?;
564        let mut surface = {
565            let xr = xr.lock().unwrap();
566            let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
567            let raw = xr
568                .session
569                .create_swapchain(&xr::SwapchainCreateInfo {
570                    create_flags: xr::SwapchainCreateFlags::EMPTY,
571                    usage_flags: xr_swapchain_usage(config.usage),
572                    format: raw_format,
573                    sample_count: 1,
574                    width: config.size.width,
575                    height: config.size.height,
576                    face_count: 1,
577                    array_size: config.view_count.max(1),
578                    mip_count: 1,
579                })
580                .ok()?;
581            super::XrSurface {
582                raw,
583                frames: Vec::new(),
584                swapchain: super::Swapchain {
585                    raw: vk::SwapchainKHR::null(),
586                    format,
587                    alpha: crate::AlphaMode::Ignored,
588                    target_size: [config.size.width as u16, config.size.height as u16],
589                },
590                view_count: config.view_count.max(1),
591            }
592        };
593        self.reconfigure_xr_surface(&mut surface, config);
594        Some(surface)
595    }
596
597    pub fn destroy_xr_surface(&self, surface: &mut super::XrSurface) {
598        for frame in surface.frames.drain(..) {
599            for view in frame.xr_views {
600                if view != vk::ImageView::null() {
601                    unsafe { self.device.core.destroy_image_view(view, None) };
602                }
603            }
604            unsafe {
605                self.device.core.destroy_image_view(frame.view, None);
606                self.device
607                    .core
608                    .destroy_semaphore(frame.acquire_semaphore, None);
609                self.device
610                    .core
611                    .destroy_semaphore(frame.present_semaphore, None);
612            }
613        }
614    }
615
616    fn reconfigure_xr_surface(
617        &self,
618        surface: &mut super::XrSurface,
619        config: crate::XrSurfaceConfig,
620    ) {
621        self.destroy_xr_surface(surface);
622        let xr = self.xr.as_ref().expect("XR is not enabled in this context");
623        let xr = xr.lock().unwrap();
624        assert!(
625            config.view_count as usize <= super::MAX_XR_EYES,
626            "XR view count {} exceeds MAX_XR_EYES={}",
627            config.view_count,
628            super::MAX_XR_EYES
629        );
630        let (raw_format, format) = select_xr_swapchain_format(&xr.session, config.color_space);
631
632        let new_handle = xr
633            .session
634            .create_swapchain(&xr::SwapchainCreateInfo {
635                create_flags: xr::SwapchainCreateFlags::EMPTY,
636                usage_flags: xr_swapchain_usage(config.usage),
637                format: raw_format,
638                sample_count: 1,
639                width: config.size.width,
640                height: config.size.height,
641                face_count: 1,
642                array_size: config.view_count.max(1),
643                mip_count: 1,
644            })
645            .unwrap();
646        surface.raw = new_handle;
647
648        let target_size = [config.size.width as u16, config.size.height as u16];
649        let view_type = if config.view_count > 1 {
650            vk::ImageViewType::TYPE_2D_ARRAY
651        } else {
652            vk::ImageViewType::TYPE_2D
653        };
654        let subresource_range = vk::ImageSubresourceRange {
655            aspect_mask: vk::ImageAspectFlags::COLOR,
656            base_mip_level: 0,
657            level_count: 1,
658            base_array_layer: 0,
659            layer_count: config.view_count.max(1),
660        };
661
662        for raw_image in surface.raw.enumerate_images().unwrap() {
663            let image = vk::Image::from_raw(raw_image);
664            let view_create_info = vk::ImageViewCreateInfo {
665                image,
666                view_type,
667                format: super::map_texture_format(format),
668                subresource_range,
669                ..Default::default()
670            };
671            let view = unsafe {
672                self.device
673                    .core
674                    .create_image_view(&view_create_info, None)
675                    .unwrap()
676            };
677            let mut xr_views = [vk::ImageView::null(); super::MAX_XR_EYES];
678            for eye in 0..config.view_count.max(1) {
679                let xr_view_info = vk::ImageViewCreateInfo {
680                    image,
681                    view_type: vk::ImageViewType::TYPE_2D,
682                    format: super::map_texture_format(format),
683                    subresource_range: vk::ImageSubresourceRange {
684                        aspect_mask: vk::ImageAspectFlags::COLOR,
685                        base_mip_level: 0,
686                        level_count: 1,
687                        base_array_layer: eye,
688                        layer_count: 1,
689                    },
690                    ..Default::default()
691                };
692                let xr_view = unsafe {
693                    self.device
694                        .core
695                        .create_image_view(&xr_view_info, None)
696                        .unwrap()
697                };
698                xr_views[eye as usize] = xr_view;
699            }
700            let semaphore_create_info = vk::SemaphoreCreateInfo::default();
701            let acquire_semaphore = unsafe {
702                self.device
703                    .core
704                    .create_semaphore(&semaphore_create_info, None)
705                    .unwrap()
706            };
707            let present_semaphore = unsafe {
708                self.device
709                    .core
710                    .create_semaphore(&semaphore_create_info, None)
711                    .unwrap()
712            };
713            surface.frames.push(super::InternalFrame {
714                acquire_semaphore,
715                present_semaphore,
716                image,
717                view,
718                xr_views,
719            });
720        }
721
722        surface.swapchain = super::Swapchain {
723            raw: vk::SwapchainKHR::null(),
724            format,
725            alpha: crate::AlphaMode::Ignored,
726            target_size,
727        };
728        surface.view_count = config.view_count.max(1);
729    }
730}
731
732fn xr_swapchain_usage(usage: crate::TextureUsage) -> xr::SwapchainUsageFlags {
733    let mut out = xr::SwapchainUsageFlags::EMPTY;
734    if usage.contains(crate::TextureUsage::TARGET) {
735        out |= xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
736    }
737    if usage.contains(crate::TextureUsage::RESOURCE) {
738        out |= xr::SwapchainUsageFlags::SAMPLED;
739    }
740    if usage.contains(crate::TextureUsage::STORAGE) {
741        out |= xr::SwapchainUsageFlags::UNORDERED_ACCESS;
742    }
743    if out.is_empty() {
744        out = xr::SwapchainUsageFlags::COLOR_ATTACHMENT;
745    }
746    out
747}
748
749fn texture_format_from_xr_raw(raw: u32) -> Option<crate::TextureFormat> {
750    let format = vk::Format::from_raw(raw as i32);
751    Some(match format {
752        vk::Format::R8G8B8A8_UNORM => crate::TextureFormat::Rgba8Unorm,
753        vk::Format::R8G8B8A8_SRGB => crate::TextureFormat::Rgba8UnormSrgb,
754        vk::Format::B8G8R8A8_UNORM => crate::TextureFormat::Bgra8Unorm,
755        vk::Format::B8G8R8A8_SRGB => crate::TextureFormat::Bgra8UnormSrgb,
756        _ => return None,
757    })
758}
759
760fn select_xr_swapchain_format(
761    session: &xr::Session<xr::Vulkan>,
762    color_space: crate::ColorSpace,
763) -> (u32, crate::TextureFormat) {
764    let formats = session.enumerate_swapchain_formats().unwrap();
765    let mut linear_candidate = None;
766    let mut srgb_candidate = None;
767    for raw in formats {
768        if let Some(format) = texture_format_from_xr_raw(raw) {
769            match format {
770                crate::TextureFormat::Rgba8Unorm | crate::TextureFormat::Bgra8Unorm => {
771                    if linear_candidate.is_none() {
772                        linear_candidate = Some((raw, format));
773                    }
774                }
775                crate::TextureFormat::Rgba8UnormSrgb | crate::TextureFormat::Bgra8UnormSrgb => {
776                    if srgb_candidate.is_none() {
777                        srgb_candidate = Some((raw, format));
778                    }
779                }
780                _ => {}
781            }
782        }
783    }
784    match color_space {
785        crate::ColorSpace::Linear => linear_candidate.or(srgb_candidate),
786        crate::ColorSpace::Srgb => srgb_candidate.or(linear_candidate),
787    }
788    .expect("No compatible XR swapchain format available")
789}