Skip to main content

vk_graph/driver/
surface.rs

1//! Native platform window surface types.
2
3use {
4    super::{DriverError, device::Device, instance::Instance},
5    ash::vk,
6    ash_window::create_surface,
7    log::warn,
8    raw_window_handle::{HasDisplayHandle, HasWindowHandle},
9    std::{
10        fmt::{Debug, Formatter},
11        thread::panicking,
12    },
13};
14
15/// Smart pointer handle to a [`vk::SurfaceKHR`] object.
16///
17/// See [`VkSurfaceKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSurfaceKHR.html).
18#[read_only::cast]
19pub struct Surface {
20    /// The device which owns this surface resource.
21    ///
22    /// _Note:_ This field is read-only.
23    pub device: Device,
24
25    /// The native Vulkan resource handle of this surface.
26    ///
27    /// _Note:_ This field is read-only.
28    pub handle: vk::SurfaceKHR,
29}
30
31impl Surface {
32    /// Queries surface capabilities.
33    ///
34    /// See [`vkGetPhysicalDeviceSurfaceCapabilitiesKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkGetPhysicalDeviceSurfaceCapabilitiesKHR.html).
35    pub fn capabilities(&self) -> Result<vk::SurfaceCapabilitiesKHR, DriverError> {
36        let khr_surface = Device::expect_vk_khr_surface(&self.device);
37
38        unsafe {
39            khr_surface
40                .get_physical_device_surface_capabilities(self.device.physical.handle, self.handle)
41        }
42        .map_err(|err| {
43            warn!("unable to get surface capabilities: {err}");
44
45            DriverError::Unsupported
46        })
47    }
48
49    /// Creates a surface from a raw window display handle.
50    ///
51    /// `device` must have been created with platform specific surface extensions enabled.
52    ///
53    /// See [`VkSurfaceKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSurfaceKHR.html).
54    #[profiling::function]
55    pub fn create(
56        device: &Device,
57        display: impl HasDisplayHandle,
58        window: impl HasWindowHandle,
59    ) -> Result<Self, DriverError> {
60        let device = device.clone();
61
62        let display_handle = display.display_handle().map_err(|err| {
63            warn!("unable to get display handle: {err}");
64
65            DriverError::Unsupported
66        })?;
67        let window_handle = window.window_handle().map_err(|err| {
68            warn!("unable to get window handle: {err}");
69
70            DriverError::Unsupported
71        })?;
72
73        let handle = unsafe {
74            create_surface(
75                Instance::entry(&device.physical.instance),
76                &device.physical.instance,
77                display_handle.as_raw(),
78                window_handle.as_raw(),
79                None,
80            )
81        }
82        .map_err(|err| {
83            warn!("unable to create surface: {err}");
84
85            DriverError::Unsupported
86        })?;
87
88        Ok(Self { device, handle })
89    }
90
91    /// Lists the supported surface formats.
92    ///
93    /// See [`vkGetPhysicalDeviceSurfaceFormatsKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkGetPhysicalDeviceSurfaceFormatsKHR.html).
94    #[profiling::function]
95    pub fn formats(&self) -> Result<Vec<vk::SurfaceFormatKHR>, DriverError> {
96        let khr_surface = Device::expect_vk_khr_surface(&self.device);
97
98        unsafe {
99            khr_surface
100                .get_physical_device_surface_formats(self.device.physical.handle, self.handle)
101        }
102        .map_err(|err| {
103            warn!("unable to get surface formats: {err}");
104
105            DriverError::Unsupported
106        })
107    }
108
109    /// Helper function to automatically select the best UNORM format, if one is available.
110    #[profiling::function]
111    pub fn linear(formats: &[vk::SurfaceFormatKHR]) -> Option<vk::SurfaceFormatKHR> {
112        formats
113            .iter()
114            .find(|&&vk::SurfaceFormatKHR { format, .. }| {
115                matches!(
116                    format,
117                    vk::Format::R8G8B8A8_UNORM | vk::Format::B8G8R8A8_UNORM
118                )
119            })
120            .copied()
121    }
122
123    /// Helper function to automatically select the best UNORM format.
124    ///
125    /// **_NOTE:_** The default surface format is undefined, and although legal the results _may_
126    /// not support presentation. You should prefer to use [`Surface::linear`] and fall back to
127    /// supported values manually.
128    pub fn linear_or_default(formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
129        Self::linear(formats).unwrap_or_else(|| formats.first().copied().unwrap_or_default())
130    }
131
132    /// Selects a supported composite alpha mode, preferring `requested` and then `OPAQUE`.
133    pub fn composite_alpha_or_default(
134        supported: vk::CompositeAlphaFlagsKHR,
135        requested: vk::CompositeAlphaFlagsKHR,
136    ) -> vk::CompositeAlphaFlagsKHR {
137        if supported.contains(requested) {
138            return requested;
139        }
140
141        if supported.contains(vk::CompositeAlphaFlagsKHR::OPAQUE) {
142            return vk::CompositeAlphaFlagsKHR::OPAQUE;
143        }
144
145        [
146            vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
147            vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED,
148            vk::CompositeAlphaFlagsKHR::INHERIT,
149        ]
150        .into_iter()
151        .find(|mode| supported.contains(*mode))
152        .unwrap_or(requested)
153    }
154
155    /// Returns `true` if the given queue family supports presentation on this surface.
156    ///
157    /// See [`vkGetPhysicalDeviceSurfaceSupportKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkGetPhysicalDeviceSurfaceSupportKHR.html).
158    pub fn physical_device_support(&self, queue_family_index: u32) -> Result<bool, DriverError> {
159        let khr_surface = Device::expect_vk_khr_surface(&self.device);
160
161        unsafe {
162            khr_surface
163                .get_physical_device_surface_support(
164                    self.device.physical.handle,
165                    queue_family_index,
166                    self.handle,
167                )
168                .map_err(|err| {
169                    warn!("unable to get physical device support: {err}");
170
171                    match err {
172                        vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
173                        | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
174                        vk::Result::ERROR_SURFACE_LOST_KHR => DriverError::InvalidData,
175                        _ => DriverError::Unsupported,
176                    }
177                })
178        }
179    }
180
181    /// Queries supported presentation modes.
182    ///
183    /// See [`vkGetPhysicalDeviceSurfacePresentModesKHR`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkGetPhysicalDeviceSurfacePresentModesKHR.html).
184    pub fn present_modes(&self) -> Result<Vec<vk::PresentModeKHR>, DriverError> {
185        let khr_surface = Device::expect_vk_khr_surface(&self.device);
186
187        unsafe {
188            khr_surface
189                .get_physical_device_surface_present_modes(self.device.physical.handle, self.handle)
190                .map_err(|err| {
191                    warn!("unable to get present modes: {err}");
192
193                    DriverError::Unsupported
194                })
195        }
196    }
197
198    /// Helper function to automatically select the best sRGB format, if one is available.
199    #[profiling::function]
200    pub fn srgb(formats: &[vk::SurfaceFormatKHR]) -> Option<vk::SurfaceFormatKHR> {
201        formats
202            .iter()
203            .find(
204                |&&vk::SurfaceFormatKHR {
205                     color_space,
206                     format,
207                 }| {
208                    matches!(color_space, vk::ColorSpaceKHR::SRGB_NONLINEAR)
209                        && matches!(
210                            format,
211                            vk::Format::R8G8B8A8_SRGB | vk::Format::B8G8R8A8_SRGB
212                        )
213                },
214            )
215            .copied()
216    }
217
218    /// Helper function to automatically select the best sRGB format.
219    ///
220    /// **_NOTE:_** The default surface format is undefined, and although legal the results _may_
221    /// not support presentation. You should prefer to use [`Surface::srgb`] and fall back to
222    /// supported values manually.
223    pub fn srgb_or_default(formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
224        Self::srgb(formats).unwrap_or_else(|| formats.first().copied().unwrap_or_default())
225    }
226}
227
228impl Debug for Surface {
229    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
230        f.debug_struct(stringify!(Surface))
231            .field("handle", &self.handle)
232            .field("device", &self.device)
233            .finish_non_exhaustive()
234    }
235}
236
237impl Drop for Surface {
238    #[profiling::function]
239    fn drop(&mut self) {
240        if panicking() {
241            return;
242        }
243
244        let khr_surface = Device::expect_vk_khr_surface(&self.device);
245
246        unsafe {
247            khr_surface.destroy_surface(self.handle, None);
248        }
249    }
250}
251
252impl Eq for Surface {}
253
254impl PartialEq for Surface {
255    fn eq(&self, other: &Self) -> bool {
256        self.handle == other.handle
257    }
258}
259
260#[cfg(test)]
261mod test {
262    use super::Surface;
263    use ash::vk;
264
265    #[test]
266    fn linear_prefers_known_unorm_formats() {
267        let formats = [
268            vk::SurfaceFormatKHR {
269                format: vk::Format::R8G8B8A8_SRGB,
270                ..Default::default()
271            },
272            vk::SurfaceFormatKHR {
273                format: vk::Format::R8G8B8A8_UNORM,
274                ..Default::default()
275            },
276        ];
277
278        assert_eq!(
279            Surface::linear(&formats).unwrap().format,
280            vk::Format::R8G8B8A8_UNORM
281        );
282    }
283
284    #[test]
285    fn linear_or_default_falls_back_to_first_format() {
286        let formats = [vk::SurfaceFormatKHR {
287            format: vk::Format::R16G16B16A16_SFLOAT,
288            ..Default::default()
289        }];
290
291        assert_eq!(
292            Surface::linear_or_default(&formats).format,
293            vk::Format::R16G16B16A16_SFLOAT
294        );
295    }
296
297    #[test]
298    fn srgb_prefers_known_srgb_formats() {
299        let formats = [
300            vk::SurfaceFormatKHR {
301                color_space: vk::ColorSpaceKHR::DISPLAY_P3_NONLINEAR_EXT,
302                format: vk::Format::B8G8R8A8_SRGB,
303            },
304            vk::SurfaceFormatKHR {
305                color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
306                format: vk::Format::R8G8B8A8_SRGB,
307            },
308        ];
309
310        assert_eq!(
311            Surface::srgb(&formats).unwrap().format,
312            vk::Format::R8G8B8A8_SRGB
313        );
314    }
315
316    #[test]
317    fn srgb_or_default_falls_back_to_first_format() {
318        let formats = [vk::SurfaceFormatKHR {
319            color_space: vk::ColorSpaceKHR::DISPLAY_P3_NONLINEAR_EXT,
320            format: vk::Format::R16G16B16A16_SFLOAT,
321        }];
322
323        assert_eq!(
324            Surface::srgb_or_default(&formats).format,
325            vk::Format::R16G16B16A16_SFLOAT
326        );
327    }
328
329    #[test]
330    fn composite_alpha_prefers_requested_supported_mode() {
331        let supported =
332            vk::CompositeAlphaFlagsKHR::OPAQUE | vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED;
333
334        assert_eq!(
335            Surface::composite_alpha_or_default(
336                supported,
337                vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
338            ),
339            vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED
340        );
341    }
342
343    #[test]
344    fn composite_alpha_falls_back_to_opaque() {
345        let supported =
346            vk::CompositeAlphaFlagsKHR::OPAQUE | vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED;
347
348        assert_eq!(
349            Surface::composite_alpha_or_default(
350                supported,
351                vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
352            ),
353            vk::CompositeAlphaFlagsKHR::OPAQUE
354        );
355    }
356}