rendy_graph/node/
present.rs

1//! Defines present node.
2
3use crate::{
4    command::{
5        CommandBuffer, CommandPool, ExecutableState, Families, Family, FamilyId, Fence, MultiShot,
6        PendingState, Queue, SimultaneousUse, Submission, Submit,
7    },
8    factory::Factory,
9    frame::Frames,
10    graph::GraphContext,
11    node::{
12        gfx_acquire_barriers, gfx_release_barriers, BufferAccess, DynNode, ImageAccess, NodeBuffer,
13        NodeBuildError, NodeBuilder, NodeImage,
14    },
15    wsi::{Surface, Target},
16    BufferId, ImageId, NodeId,
17};
18
19#[derive(Debug)]
20struct ForImage<B: rendy_core::hal::Backend> {
21    acquire: B::Semaphore,
22    release: B::Semaphore,
23    submit: Submit<B, SimultaneousUse>,
24    buffer: CommandBuffer<
25        B,
26        rendy_core::hal::queue::QueueType,
27        PendingState<ExecutableState<MultiShot<SimultaneousUse>>>,
28    >,
29}
30
31impl<B: rendy_core::hal::Backend> ForImage<B> {
32    unsafe fn dispose(
33        self,
34        factory: &Factory<B>,
35        pool: &mut CommandPool<B, rendy_core::hal::queue::QueueType>,
36    ) {
37        drop(self.submit);
38        factory.destroy_semaphore(self.acquire);
39        factory.destroy_semaphore(self.release);
40        pool.free_buffers(Some(self.buffer.mark_complete()));
41    }
42}
43
44/// Node that presents images to the surface.
45#[derive(Debug)]
46pub struct PresentNode<B: rendy_core::hal::Backend> {
47    per_image: Vec<ForImage<B>>,
48    free_acquire: B::Semaphore,
49    target: Target<B>,
50    pool: CommandPool<B, rendy_core::hal::queue::QueueType>,
51    input_image: NodeImage,
52    blit_filter: rendy_core::hal::image::Filter,
53}
54
55// Raw pointer destroys Send/Sync autoimpl, but it's always from the same graph.
56unsafe impl<B: rendy_core::hal::Backend> Sync for PresentNode<B> {}
57unsafe impl<B: rendy_core::hal::Backend> Send for PresentNode<B> {}
58
59impl<B> PresentNode<B>
60where
61    B: rendy_core::hal::Backend,
62{
63    /// Node builder.
64    /// By default attempts to use 3 images in the swapchain with present mode priority:
65    ///
66    /// Mailbox > Fifo > Relaxed > Immediate.
67    ///
68    /// You can query the real image count and present mode which will be used with
69    /// `PresentBuilder::image_count()` and `PresentBuilder::present_mode()`.
70    pub fn builder(factory: &Factory<B>, surface: Surface<B>, image: ImageId) -> PresentBuilder<B> {
71        use rendy_core::hal::window::PresentMode;
72
73        let caps = factory.get_surface_capabilities(&surface);
74        let image_count = 3
75            .min(*caps.image_count.end())
76            .max(*caps.image_count.start());
77
78        let present_mode = match () {
79            _ if caps.present_modes.contains(PresentMode::FIFO) => PresentMode::FIFO,
80            _ if caps.present_modes.contains(PresentMode::MAILBOX) => PresentMode::MAILBOX,
81            _ if caps.present_modes.contains(PresentMode::RELAXED) => PresentMode::RELAXED,
82            _ if caps.present_modes.contains(PresentMode::IMMEDIATE) => PresentMode::IMMEDIATE,
83            _ => panic!("No known present modes found"),
84        };
85
86        PresentBuilder {
87            surface,
88            image,
89            dependencies: Vec::new(),
90            image_count,
91            present_mode,
92            caps,
93            blit_filter: rendy_core::hal::image::Filter::Nearest,
94        }
95    }
96}
97
98fn create_per_image_data<B: rendy_core::hal::Backend>(
99    ctx: &GraphContext<B>,
100    input_image: &NodeImage,
101    pool: &mut CommandPool<B, rendy_core::hal::queue::QueueType>,
102    factory: &Factory<B>,
103    target: &Target<B>,
104    blit_filter: rendy_core::hal::image::Filter,
105) -> Vec<ForImage<B>> {
106    let input_image_res = ctx.get_image(input_image.id).expect("Image does not exist");
107
108    let target_images = target.backbuffer();
109    let buffers = pool.allocate_buffers(target_images.len());
110    target_images
111        .iter()
112        .zip(buffers)
113        .map(|(target_image, buf_initial)| {
114            let mut buf_recording = buf_initial.begin(MultiShot(SimultaneousUse), ());
115            let mut encoder = buf_recording.encoder();
116            let (mut stages, mut barriers) =
117                gfx_acquire_barriers(ctx, None, Some(input_image));
118            stages.start |= rendy_core::hal::pso::PipelineStage::TRANSFER;
119            stages.end |= rendy_core::hal::pso::PipelineStage::TRANSFER;
120            barriers.push(rendy_core::hal::memory::Barrier::Image {
121                states: (
122                    rendy_core::hal::image::Access::empty(),
123                    rendy_core::hal::image::Layout::Undefined,
124                )
125                    ..(
126                        rendy_core::hal::image::Access::TRANSFER_WRITE,
127                        rendy_core::hal::image::Layout::TransferDstOptimal,
128                    ),
129                families: None,
130                target: target_image.raw(),
131                range: rendy_core::hal::image::SubresourceRange {
132                    aspects: rendy_core::hal::format::Aspects::COLOR,
133                    levels: 0..1,
134                    layers: 0..1,
135                },
136            });
137            log::trace!("Acquire {:?} : {:#?}", stages, barriers);
138            unsafe {
139                encoder.pipeline_barrier(
140                    stages,
141                    rendy_core::hal::memory::Dependencies::empty(),
142                    barriers,
143                );
144            }
145
146            let extents_differ = target_image.kind().extent() != input_image_res.kind().extent();
147            let formats_differ = target_image.format() != input_image_res.format();
148
149            if extents_differ || formats_differ
150            {
151                if formats_differ {
152                    log::debug!("Present node is blitting because target format {:?} doesnt match image format {:?}", target_image.format(), input_image_res.format());
153                }
154                if extents_differ {
155                    log::debug!("Present node is blitting because target extent {:?} doesnt match image extent {:?}", target_image.kind().extent(), input_image_res.kind().extent());
156                }
157                unsafe {
158                    encoder.blit_image(
159                        input_image_res.raw(),
160                        input_image.layout,
161                        target_image.raw(),
162                        rendy_core::hal::image::Layout::TransferDstOptimal,
163                        blit_filter,
164                        Some(rendy_core::hal::command::ImageBlit {
165                            src_subresource: rendy_core::hal::image::SubresourceLayers {
166                                aspects: input_image.range.aspects,
167                                level: 0,
168                                layers: input_image.range.layers.start..input_image.range.layers.start + 1,
169                            },
170                            src_bounds: rendy_core::hal::image::Offset::ZERO
171                                .into_bounds(&input_image_res.kind().extent()),
172                            dst_subresource: rendy_core::hal::image::SubresourceLayers {
173                                aspects: rendy_core::hal::format::Aspects::COLOR,
174                                level: 0,
175                                layers: 0..1,
176                            },
177                            dst_bounds: rendy_core::hal::image::Offset::ZERO
178                                .into_bounds(&target_image.kind().extent()),
179                        }),
180                    );
181                }
182            } else {
183                log::debug!("Present node is copying");
184                unsafe {
185                    encoder.copy_image(
186                        input_image_res.raw(),
187                        input_image.layout,
188                        target_image.raw(),
189                        rendy_core::hal::image::Layout::TransferDstOptimal,
190                        Some(rendy_core::hal::command::ImageCopy {
191                            src_subresource: rendy_core::hal::image::SubresourceLayers {
192                                aspects: input_image.range.aspects,
193                                level: 0,
194                                layers: input_image.range.layers.start..input_image.range.layers.start + 1,
195                            },
196                            src_offset: rendy_core::hal::image::Offset::ZERO,
197                            dst_subresource: rendy_core::hal::image::SubresourceLayers {
198                                aspects: rendy_core::hal::format::Aspects::COLOR,
199                                level: 0,
200                                layers: 0..1,
201                            },
202                            dst_offset: rendy_core::hal::image::Offset::ZERO,
203                            extent: rendy_core::hal::image::Extent {
204                                width: target_image.kind().extent().width,
205                                height: target_image.kind().extent().height,
206                                depth: 1,
207                            },
208                        }),
209                    );
210                }
211            }
212
213            {
214                let (mut stages, mut barriers) =
215                    gfx_release_barriers(ctx, None, Some(input_image));
216                stages.start |= rendy_core::hal::pso::PipelineStage::TRANSFER;
217                stages.end |= rendy_core::hal::pso::PipelineStage::BOTTOM_OF_PIPE;
218                barriers.push(rendy_core::hal::memory::Barrier::Image {
219                    states: (
220                        rendy_core::hal::image::Access::TRANSFER_WRITE,
221                        rendy_core::hal::image::Layout::TransferDstOptimal,
222                    )
223                        ..(
224                            rendy_core::hal::image::Access::empty(),
225                            rendy_core::hal::image::Layout::Present,
226                        ),
227                    families: None,
228                    target: target_image.raw(),
229                    range: rendy_core::hal::image::SubresourceRange {
230                        aspects: rendy_core::hal::format::Aspects::COLOR,
231                        levels: 0..1,
232                        layers: 0..1,
233                    },
234                });
235
236                log::trace!("Release {:?} : {:#?}", stages, barriers);
237                unsafe {
238                    encoder.pipeline_barrier(
239                        stages,
240                        rendy_core::hal::memory::Dependencies::empty(),
241                        barriers,
242                    );
243                }
244            }
245
246            let (submit, buffer) = buf_recording.finish().submit();
247
248            ForImage {
249                submit,
250                buffer,
251                acquire: factory.create_semaphore().unwrap(),
252                release: factory.create_semaphore().unwrap(),
253            }
254        })
255        .collect()
256}
257
258/// Presentation node description.
259#[derive(Debug)]
260pub struct PresentBuilder<B: rendy_core::hal::Backend> {
261    surface: Surface<B>,
262    image: ImageId,
263    image_count: u32,
264    present_mode: rendy_core::hal::window::PresentMode,
265    caps: rendy_core::hal::window::SurfaceCapabilities,
266    dependencies: Vec<NodeId>,
267    blit_filter: rendy_core::hal::image::Filter,
268}
269
270impl<B> PresentBuilder<B>
271where
272    B: rendy_core::hal::Backend,
273{
274    /// Add dependency.
275    /// Node will be placed after its dependencies.
276    pub fn add_dependency(&mut self, dependency: NodeId) -> &mut Self {
277        self.dependencies.push(dependency);
278        self
279    }
280
281    /// Add dependency.
282    /// Node will be placed after its dependencies.
283    pub fn with_dependency(mut self, dependency: NodeId) -> Self {
284        self.add_dependency(dependency);
285        self
286    }
287
288    /// Request a number of images in the swapchain. This is not guaranteed
289    /// to be the final image count, but it will be if supported by the hardware.
290    ///
291    /// Check `PresentBuilder::image_count()` after calling this function but before
292    /// building to see the final image count.
293    pub fn with_image_count(mut self, image_count: u32) -> Self {
294        let image_count = image_count
295            .min(*self.caps.image_count.end())
296            .max(*self.caps.image_count.start());
297        self.image_count = image_count;
298        self
299    }
300
301    /// Set up filter used for resizing when backbuffer size does not match source image size.
302    ///
303    /// Default is `Nearest`.
304    pub fn with_blit_filter(mut self, filter: rendy_core::hal::image::Filter) -> Self {
305        self.blit_filter = filter;
306        self
307    }
308
309    /// Request a priority of present modes when creating the swapchain for the
310    /// PresentNode. Lower index means higher priority.
311    ///
312    /// Check `PresentBuilder::present_mode()` after calling this function but before
313    /// building to see the final present mode.
314    ///
315    /// ## Parameters
316    /// - present_modes_priority: A function which takes a `rendy_core::hal::PresentMode` and returns
317    /// an `Option<usize>`. `None` indicates not to use this mode, and a higher number returned
318    /// indicates a higher prioirity for that mode.
319    ///
320    /// ## Panics
321    /// - Panics if none of the provided `PresentMode`s are supported.
322    pub fn with_present_modes_priority<PF>(mut self, present_modes_priority: PF) -> Self
323    where
324        PF: Fn(rendy_core::hal::window::PresentMode) -> Option<usize>,
325    {
326        use rendy_core::hal::window::PresentMode;
327
328        let priority_mode = [
329            PresentMode::FIFO,
330            PresentMode::MAILBOX,
331            PresentMode::RELAXED,
332            PresentMode::IMMEDIATE,
333        ]
334        .iter()
335        .cloned()
336        .filter(|&mode| self.caps.present_modes.contains(mode))
337        .filter_map(|mode| present_modes_priority(mode).map(|p| (p, mode)))
338        .max_by_key(|&(p, _)| p);
339
340        if let Some((_, mode)) = priority_mode {
341            self.present_mode = mode;
342        } else {
343            panic!(
344                "No desired PresentModes are supported. Supported: {:#?}",
345                self.caps.present_modes
346            );
347        }
348
349        self
350    }
351
352    /// Get image count in presentable swapchain.
353    pub fn image_count(&self) -> u32 {
354        self.image_count
355    }
356
357    /// Get present mode used by node.
358    pub fn present_mode(&self) -> rendy_core::hal::window::PresentMode {
359        self.present_mode
360    }
361}
362
363impl<B, T> NodeBuilder<B, T> for PresentBuilder<B>
364where
365    B: rendy_core::hal::Backend,
366    T: ?Sized,
367{
368    fn family(&self, factory: &mut Factory<B>, families: &Families<B>) -> Option<FamilyId> {
369        // Find correct queue family.
370        families.find(|family| factory.surface_support(family.id(), &self.surface))
371    }
372
373    fn buffers(&self) -> Vec<(BufferId, BufferAccess)> {
374        Vec::new()
375    }
376
377    fn images(&self) -> Vec<(ImageId, ImageAccess)> {
378        vec![(
379            self.image,
380            ImageAccess {
381                access: rendy_core::hal::image::Access::TRANSFER_READ,
382                layout: rendy_core::hal::image::Layout::TransferSrcOptimal,
383                usage: rendy_core::hal::image::Usage::TRANSFER_SRC,
384                stages: rendy_core::hal::pso::PipelineStage::TRANSFER,
385            },
386        )]
387    }
388
389    fn dependencies(&self) -> Vec<NodeId> {
390        self.dependencies.clone()
391    }
392
393    fn build<'a>(
394        self: Box<Self>,
395        ctx: &GraphContext<B>,
396        factory: &mut Factory<B>,
397        family: &mut Family<B>,
398        _queue: usize,
399        _aux: &T,
400        buffers: Vec<NodeBuffer>,
401        images: Vec<NodeImage>,
402    ) -> Result<Box<dyn DynNode<B, T>>, NodeBuildError> {
403        assert_eq!(buffers.len(), 0);
404        assert_eq!(images.len(), 1);
405
406        let input_image = images.into_iter().next().unwrap();
407        let extent = ctx
408            .get_image(input_image.id)
409            .expect("Context must contain node's image")
410            .kind()
411            .extent()
412            .into();
413
414        if !factory.surface_support(family.id(), &self.surface) {
415            log::warn!(
416                "Surface {:?} presentation is unsupported by family {:?} bound to the node",
417                self.surface,
418                family
419            );
420            return Err(NodeBuildError::QueueFamily(family.id()));
421        }
422
423        let target = factory
424            .create_target(
425                self.surface,
426                extent,
427                self.image_count,
428                self.present_mode,
429                rendy_core::hal::image::Usage::TRANSFER_DST,
430            )
431            .map_err(NodeBuildError::Swapchain)?;
432
433        let mut pool = factory
434            .create_command_pool(family)
435            .map_err(NodeBuildError::OutOfMemory)?;
436
437        let per_image = create_per_image_data(
438            ctx,
439            &input_image,
440            &mut pool,
441            factory,
442            &target,
443            self.blit_filter,
444        );
445
446        Ok(Box::new(PresentNode {
447            free_acquire: factory.create_semaphore().unwrap(),
448            pool,
449            target,
450            per_image,
451            input_image,
452            blit_filter: self.blit_filter,
453        }))
454    }
455}
456
457impl<B, T> DynNode<B, T> for PresentNode<B>
458where
459    B: rendy_core::hal::Backend,
460    T: ?Sized,
461{
462    unsafe fn run<'a>(
463        &mut self,
464        ctx: &GraphContext<B>,
465        factory: &Factory<B>,
466        queue: &mut Queue<B>,
467        _aux: &T,
468        _frames: &Frames<B>,
469        waits: &[(&'a B::Semaphore, rendy_core::hal::pso::PipelineStage)],
470        signals: &[&'a B::Semaphore],
471        mut fence: Option<&mut Fence<B>>,
472    ) {
473        loop {
474            match self.target.next_image(&self.free_acquire) {
475                Ok(next) => {
476                    log::trace!("Present: {:#?}", next);
477                    let ref mut for_image = self.per_image[next[0] as usize];
478                    core::mem::swap(&mut for_image.acquire, &mut self.free_acquire);
479
480                    queue.submit(
481                        Some(
482                            Submission::new()
483                                .submits(Some(&for_image.submit))
484                                .wait(waits.iter().cloned().chain(Some((
485                                    &for_image.acquire,
486                                    rendy_core::hal::pso::PipelineStage::TRANSFER,
487                                ))))
488                                .signal(signals.iter().cloned().chain(Some(&for_image.release))),
489                        ),
490                        fence.take(),
491                    );
492
493                    match next.present(queue.raw(), Some(&for_image.release)) {
494                        Ok(_) => break,
495                        Err(e) => {
496                            log::debug!(
497                                "Swapchain present error after next_image is acquired: {:?}",
498                                e
499                            );
500                            // recreate swapchain on next frame.
501                            break;
502                        }
503                    }
504                }
505                Err(rendy_core::hal::window::AcquireError::OutOfDate) => {
506                    // recreate swapchain and try again.
507                }
508                e => {
509                    e.unwrap();
510                    break;
511                }
512            }
513            // Recreate swapchain when OutOfDate
514            // The code has to execute after match due to mutable aliasing issues.
515
516            // TODO: use retired swapchains once available in hal and remove that wait
517            factory.wait_idle().unwrap();
518
519            let extent = ctx
520                .get_image(self.input_image.id)
521                .expect("Context must contain node's image")
522                .kind()
523                .extent()
524                .into();
525
526            self.target
527                .recreate(factory.physical(), factory.device(), extent)
528                .expect("Failed recreating swapchain");
529
530            for data in self.per_image.drain(..) {
531                data.dispose(factory, &mut self.pool);
532            }
533
534            self.per_image = create_per_image_data(
535                ctx,
536                &self.input_image,
537                &mut self.pool,
538                factory,
539                &self.target,
540                self.blit_filter,
541            );
542        }
543    }
544
545    unsafe fn dispose(mut self: Box<Self>, factory: &mut Factory<B>, _aux: &T) {
546        for data in self.per_image {
547            data.dispose(factory, &mut self.pool);
548        }
549
550        factory.destroy_semaphore(self.free_acquire);
551        factory.destroy_command_pool(self.pool);
552        factory.destroy_target(self.target);
553    }
554}