rendy_wsi/
lib.rs

1//! Window system integration.
2
3#![warn(
4    missing_debug_implementations,
5    missing_copy_implementations,
6    missing_docs,
7    trivial_casts,
8    trivial_numeric_casts,
9    unused_extern_crates,
10    unused_import_braces,
11    unused_qualifications
12)]
13
14use {
15    rendy_core::hal::{
16        device::Device as _,
17        format::Format,
18        window::{Extent2D, Surface as _, SurfaceCapabilities},
19        Backend, Instance as _,
20    },
21    rendy_core::{
22        device_owned, instance_owned, Device, DeviceId, HasRawWindowHandle, Instance, InstanceId,
23    },
24    rendy_resource::{Image, ImageInfo},
25};
26
27/// Error creating a new swapchain.
28#[derive(Debug)]
29pub enum SwapchainError {
30    /// Internal error in gfx-hal.
31    Create(rendy_core::hal::window::CreationError),
32    /// Present mode is not supported.
33    BadPresentMode(rendy_core::hal::window::PresentMode),
34    /// Image count is not supported.
35    BadImageCount(rendy_core::hal::window::SwapImageIndex),
36}
37
38/// Rendering target bound to window.
39pub struct Surface<B: Backend> {
40    raw: B::Surface,
41    instance: InstanceId,
42}
43
44impl<B> std::fmt::Debug for Surface<B>
45where
46    B: Backend,
47{
48    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        fmt.debug_struct("Surface")
50            .field("instance", &self.instance)
51            .finish()
52    }
53}
54
55instance_owned!(Surface<B>);
56
57impl<B> Surface<B>
58where
59    B: Backend,
60{
61    /// Create surface for the window.
62    pub fn new(
63        instance: &Instance<B>,
64        handle: &impl HasRawWindowHandle,
65    ) -> Result<Self, rendy_core::hal::window::InitError> {
66        let raw = unsafe { instance.create_surface(handle) }?;
67        Ok(Surface {
68            raw,
69            instance: instance.id(),
70        })
71    }
72
73    /// Create surface from `instance`.
74    ///
75    /// # Safety
76    ///
77    /// Closure must return surface object created from raw instance provided as closure argument.
78    pub unsafe fn new_with(
79        instance: &Instance<B>,
80        f: impl FnOnce(&B::Instance) -> B::Surface,
81    ) -> Self {
82        Surface {
83            raw: f(instance.raw()),
84            instance: instance.id(),
85        }
86    }
87
88    /// Create surface from raw parts.
89    pub unsafe fn from_raw(surface: B::Surface, instance: InstanceId) -> Self {
90        Surface {
91            raw: surface,
92            instance,
93        }
94    }
95}
96
97impl<B> Surface<B>
98where
99    B: Backend,
100{
101    /// Get raw `B::Surface` reference
102    pub fn raw(&self) -> &B::Surface {
103        &self.raw
104    }
105
106    /// Get current extent of the surface.
107    pub unsafe fn extent(&self, physical_device: &B::PhysicalDevice) -> Option<Extent2D> {
108        self.capabilities(physical_device).current_extent
109    }
110
111    /// Get surface ideal format.
112    pub unsafe fn format(&self, physical_device: &B::PhysicalDevice) -> Format {
113        if let Some(formats) = self.raw.supported_formats(physical_device) {
114            *formats
115                .iter()
116                .max_by_key(|format| {
117                    let base = format.base_format();
118                    let desc = base.0.desc();
119                    (
120                        !desc.is_compressed(),
121                        base.1 == rendy_core::hal::format::ChannelType::Srgb,
122                        desc.bits,
123                    )
124                })
125                .expect("At least one format must be supported by the surface")
126        } else {
127            Format::Rgba8Srgb
128        }
129    }
130
131    /// Get formats supported by surface
132    ///
133    /// ## Safety
134    ///
135    /// - `physical_device` must be created from same `Instance` as the `Surface`
136    pub unsafe fn supported_formats(
137        &self,
138        physical_device: &B::PhysicalDevice,
139    ) -> Option<Vec<Format>> {
140        self.raw.supported_formats(physical_device)
141    }
142
143    /// Get formats supported by surface
144    ///
145    /// ## Safety
146    ///
147    /// - `physical_device` must be created from same `Instance` as the `Surface`
148    pub unsafe fn capabilities(&self, physical_device: &B::PhysicalDevice) -> SurfaceCapabilities {
149        self.raw.capabilities(physical_device)
150    }
151
152    /// Cast surface into render target.
153    pub unsafe fn into_target(
154        mut self,
155        physical_device: &B::PhysicalDevice,
156        device: &Device<B>,
157        suggest_extent: Extent2D,
158        image_count: u32,
159        present_mode: rendy_core::hal::window::PresentMode,
160        usage: rendy_core::hal::image::Usage,
161    ) -> Result<Target<B>, SwapchainError> {
162        assert_eq!(
163            device.id().instance,
164            self.instance,
165            "Resource is not owned by specified instance"
166        );
167
168        let (swapchain, backbuffer, extent) = create_swapchain(
169            &mut self,
170            physical_device,
171            device,
172            suggest_extent,
173            image_count,
174            present_mode,
175            usage,
176        )?;
177
178        Ok(Target {
179            device: device.id(),
180            relevant: relevant::Relevant,
181            surface: self,
182            swapchain: Some(swapchain),
183            backbuffer: Some(backbuffer),
184            extent,
185            present_mode,
186            usage,
187        })
188    }
189}
190
191unsafe fn create_swapchain<B: Backend>(
192    surface: &mut Surface<B>,
193    physical_device: &B::PhysicalDevice,
194    device: &Device<B>,
195    suggest_extent: Extent2D,
196    image_count: u32,
197    present_mode: rendy_core::hal::window::PresentMode,
198    usage: rendy_core::hal::image::Usage,
199) -> Result<(B::Swapchain, Vec<Image<B>>, Extent2D), SwapchainError> {
200    let capabilities = surface.capabilities(physical_device);
201    let format = surface.format(physical_device);
202
203    if !capabilities.present_modes.contains(present_mode) {
204        log::warn!(
205            "Present mode is not supported. Supported: {:#?}, requested: {:#?}",
206            capabilities.present_modes,
207            present_mode,
208        );
209        return Err(SwapchainError::BadPresentMode(present_mode));
210    }
211
212    log::trace!(
213        "Surface present modes: {:#?}. Pick {:#?}",
214        capabilities.present_modes,
215        present_mode
216    );
217
218    log::trace!("Surface chosen format {:#?}", format);
219
220    if image_count < *capabilities.image_count.start()
221        || image_count > *capabilities.image_count.end()
222    {
223        log::warn!(
224            "Image count not supported. Supported: {:#?}, requested: {:#?}",
225            capabilities.image_count,
226            image_count
227        );
228        return Err(SwapchainError::BadImageCount(image_count));
229    }
230
231    log::trace!(
232        "Surface capabilities: {:#?}. Pick {} images",
233        capabilities.image_count,
234        image_count
235    );
236
237    assert!(
238        capabilities.usage.contains(usage),
239        "Surface supports {:?}, but {:?} was requested",
240        capabilities.usage,
241        usage
242    );
243
244    let extent = capabilities.current_extent.unwrap_or(suggest_extent);
245
246    let (swapchain, images) = device
247        .create_swapchain(
248            &mut surface.raw,
249            rendy_core::hal::window::SwapchainConfig {
250                present_mode,
251                format,
252                extent,
253                image_count,
254                image_layers: 1,
255                image_usage: usage,
256                composite_alpha_mode: [
257                    rendy_core::hal::window::CompositeAlphaMode::INHERIT,
258                    rendy_core::hal::window::CompositeAlphaMode::OPAQUE,
259                    rendy_core::hal::window::CompositeAlphaMode::PREMULTIPLIED,
260                    rendy_core::hal::window::CompositeAlphaMode::POSTMULTIPLIED,
261                ]
262                .iter()
263                .cloned()
264                .find(|&bit| capabilities.composite_alpha_modes.contains(bit))
265                .expect("No CompositeAlphaMode modes supported"),
266            },
267            None,
268        )
269        .map_err(SwapchainError::Create)?;
270
271    let backbuffer = images
272        .into_iter()
273        .map(|image| {
274            Image::create_from_swapchain(
275                device.id(),
276                ImageInfo {
277                    kind: rendy_core::hal::image::Kind::D2(extent.width, extent.height, 1, 1),
278                    levels: 1,
279                    format,
280                    tiling: rendy_core::hal::image::Tiling::Optimal,
281                    view_caps: rendy_core::hal::image::ViewCapabilities::empty(),
282                    usage,
283                },
284                image,
285            )
286        })
287        .collect();
288
289    Ok((swapchain, backbuffer, extent))
290}
291
292/// Rendering target bound to window.
293/// With swapchain created.
294pub struct Target<B: Backend> {
295    device: DeviceId,
296    surface: Surface<B>,
297    swapchain: Option<B::Swapchain>,
298    backbuffer: Option<Vec<Image<B>>>,
299    extent: Extent2D,
300    present_mode: rendy_core::hal::window::PresentMode,
301    usage: rendy_core::hal::image::Usage,
302    relevant: relevant::Relevant,
303}
304
305device_owned!(Target<B>);
306
307impl<B> std::fmt::Debug for Target<B>
308where
309    B: Backend,
310{
311    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        fmt.debug_struct("Target")
313            .field("backbuffer", &self.backbuffer)
314            .finish()
315    }
316}
317
318impl<B> Target<B>
319where
320    B: Backend,
321{
322    /// Dispose of target.
323    ///
324    /// # Safety
325    ///
326    /// Swapchain must be not in use.
327    pub unsafe fn dispose(mut self, device: &Device<B>) -> Surface<B> {
328        self.assert_device_owner(device);
329
330        match self.backbuffer {
331            Some(images) => {
332                images
333                    .into_iter()
334                    .for_each(|image| image.dispose_swapchain_image(device.id()));
335            }
336            _ => {}
337        };
338
339        self.relevant.dispose();
340        self.swapchain.take().map(|s| device.destroy_swapchain(s));
341        self.surface
342    }
343
344    /// Get raw surface handle.
345    pub fn surface(&self) -> &Surface<B> {
346        &self.surface
347    }
348
349    /// Get raw surface handle.
350    pub fn swapchain(&self) -> &B::Swapchain {
351        self.swapchain.as_ref().expect("Swapchain already disposed")
352    }
353
354    /// Recreate swapchain.
355    ///
356    /// #Safety
357    ///
358    /// Current swapchain must be not in use.
359    pub unsafe fn recreate(
360        &mut self,
361        physical_device: &B::PhysicalDevice,
362        device: &Device<B>,
363        suggest_extent: Extent2D,
364    ) -> Result<(), SwapchainError> {
365        self.assert_device_owner(device);
366
367        let image_count = match self.backbuffer.take() {
368            Some(images) => {
369                let count = images.len();
370                images
371                    .into_iter()
372                    .for_each(|image| image.dispose_swapchain_image(device.id()));
373                count
374            }
375            None => 0,
376        };
377
378        self.swapchain.take().map(|s| device.destroy_swapchain(s));
379
380        let (swapchain, backbuffer, extent) = create_swapchain(
381            &mut self.surface,
382            physical_device,
383            device,
384            suggest_extent,
385            image_count as u32,
386            self.present_mode,
387            self.usage,
388        )?;
389
390        self.swapchain.replace(swapchain);
391        self.backbuffer.replace(backbuffer);
392        self.extent = extent;
393
394        Ok(())
395    }
396
397    /// Get swapchain impl trait.
398    ///
399    /// # Safety
400    ///
401    /// Trait usage should not violate this type valid usage.
402    pub unsafe fn swapchain_mut(&mut self) -> &mut impl rendy_core::hal::window::Swapchain<B> {
403        self.swapchain.as_mut().expect("Swapchain already disposed")
404    }
405
406    /// Get raw handlers for the swapchain images.
407    pub fn backbuffer(&self) -> &Vec<Image<B>> {
408        self.backbuffer
409            .as_ref()
410            .expect("Swapchain already disposed")
411    }
412
413    /// Get render target size.
414    pub fn extent(&self) -> Extent2D {
415        self.extent
416    }
417
418    /// Get image usage flags.
419    pub fn usage(&self) -> rendy_core::hal::image::Usage {
420        self.usage
421    }
422
423    /// Acquire next image.
424    pub unsafe fn next_image(
425        &mut self,
426        signal: &B::Semaphore,
427    ) -> Result<NextImages<'_, B>, rendy_core::hal::window::AcquireError> {
428        let index = rendy_core::hal::window::Swapchain::acquire_image(
429            // Missing swapchain is equivalent to OutOfDate, as it has to be recreated anyway.
430            self.swapchain
431                .as_mut()
432                .ok_or(rendy_core::hal::window::AcquireError::OutOfDate)?,
433            !0,
434            Some(signal),
435            None,
436        )?
437        .0;
438
439        Ok(NextImages {
440            targets: std::iter::once((&*self, index)).collect(),
441        })
442    }
443}
444
445/// Represents acquire frames that will be presented next.
446#[derive(Debug)]
447pub struct NextImages<'a, B: Backend> {
448    targets: smallvec::SmallVec<[(&'a Target<B>, u32); 8]>,
449}
450
451impl<'a, B> NextImages<'a, B>
452where
453    B: Backend,
454{
455    /// Get indices.
456    pub fn indices(&self) -> impl IntoIterator<Item = u32> + '_ {
457        self.targets.iter().map(|(_s, i)| *i)
458    }
459
460    /// Present images by the queue.
461    ///
462    /// # TODO
463    ///
464    /// Use specific presentation error type.
465    pub unsafe fn present<'b>(
466        self,
467        queue: &mut impl rendy_core::hal::queue::CommandQueue<B>,
468        wait: impl IntoIterator<Item = &'b (impl std::borrow::Borrow<B::Semaphore> + 'b)>,
469    ) -> Result<Option<rendy_core::hal::window::Suboptimal>, rendy_core::hal::window::PresentError>
470    where
471        'a: 'b,
472    {
473        queue.present(
474            self.targets.iter().map(|(target, index)| {
475                (
476                    target
477                        .swapchain
478                        .as_ref()
479                        .expect("Swapchain already disposed"),
480                    *index,
481                )
482            }),
483            wait,
484        )
485    }
486}
487
488impl<'a, B> std::ops::Index<usize> for NextImages<'a, B>
489where
490    B: Backend,
491{
492    type Output = u32;
493
494    fn index(&self, index: usize) -> &u32 {
495        &self.targets[index].1
496    }
497}