Skip to main content

vk_graph_window/
graphchain.rs

1//! Window graphchain creation, acquisition, and presentation helpers.
2
3use {
4    derive_builder::Builder,
5    log::{Level, log_enabled, trace, warn},
6    std::{
7        error::Error,
8        fmt::{Debug, Formatter},
9        mem::take,
10        ops::Deref,
11        slice,
12        thread::panicking,
13    },
14    vk_graph::{
15        Graph,
16        driver::{
17            DriverError,
18            ash::vk,
19            cmd_buf::{CommandBuffer, CommandBufferInfo},
20            device::Device,
21            fence::Fence,
22            surface::Surface,
23            swapchain::{
24                PresentError, PresentFailure, PresentInfo, PresentResult, Swapchain,
25                SwapchainError, SwapchainImage, SwapchainInfo, SwapchainInfoBuilder,
26            },
27        },
28        node::SwapchainImageNode,
29        pool::{Pool, SubmissionPool},
30        submission::{QueueSubmitInfo, RecordSelection, SemaphoreSubmitInfo},
31    },
32};
33
34/// A physical display interface.
35#[read_only::embed]
36pub struct Graphchain {
37    frame_idx: usize,
38    frames: Box<[FrameSlot]>,
39    image_frames: Vec<usize>,
40    last_present_queue_index: u32,
41    queue_family_index: u32,
42    recreate_pending: bool,
43    requested_info: GraphchainInfo,
44    strategy: PresentRetirementStrategy,
45
46    /// Effective runtime information for the live graphchain.
47    ///
48    /// Calls to [`set_info`](Self::set_info) update the requested configuration. This field is
49    /// updated lazily after the next successful acquire recreates the swapchain.
50    ///
51    /// _Note:_ This field is read-only.
52    #[readonly]
53    pub info: EffectiveGraphchainInfo,
54
55    /// The driver swapchain backing this graphchain.
56    ///
57    /// _Note:_ This field is read-only.
58    #[readonly]
59    pub swapchain: Swapchain,
60}
61
62impl Deref for ReadOnlyGraphchain {
63    type Target = Swapchain;
64
65    fn deref(&self) -> &Self::Target {
66        &self.swapchain
67    }
68}
69
70impl Graphchain {
71    /// Constructs a new `Graphchain` object.
72    pub fn new(surface: Surface, info: impl Into<GraphchainInfo>) -> Result<Self, DriverError> {
73        let mut info = info.into();
74
75        // Frame capacity must always be able to cover at least the requested minimum image count.
76        info.frame_capacity = info
77            .frame_capacity
78            .max(info.min_image_count as usize)
79            .max(1);
80
81        let swapchain_info: SwapchainInfo = info.into();
82        let swapchain = Swapchain::create(surface, swapchain_info)?;
83        let physical_device = &swapchain.surface.device.physical;
84        let queue_family_index = physical_device
85            .queue_families
86            .iter()
87            .enumerate()
88            .find_map(|(idx, _)| {
89                swapchain
90                    .surface
91                    .physical_device_support(idx as u32)
92                    .ok()
93                    .and_then(|supported| supported.then_some(idx as u32))
94            })
95            .ok_or(DriverError::Unsupported)?;
96
97        // Use the modern strategy only when both present-id and present-wait are supported.
98        let strategy = if swapchain
99            .surface
100            .device
101            .physical
102            .vk_khr_present_id
103            .is_some()
104            && swapchain
105                .surface
106                .device
107                .physical
108                .vk_khr_present_wait
109                .is_some()
110        {
111            PresentRetirementStrategy::PresentWait(PresentWait::default())
112        } else {
113            PresentRetirementStrategy::PerImageSemaphore(PerImageSemaphore::default())
114        };
115
116        let mut frames = Vec::with_capacity(info.frame_capacity);
117        for _ in 0..info.frame_capacity {
118            let cmd_buf = CommandBuffer::create(
119                &swapchain.surface.device,
120                CommandBufferInfo::new(queue_family_index),
121            )?;
122            let fence = Fence::create(&swapchain.surface.device, false)?;
123            let swapchain_acquired = Device::create_semaphore(&swapchain.surface.device)?;
124            Device::try_set_debug_utils_object_name(
125                &swapchain.surface.device,
126                swapchain_acquired,
127                "graphchain acquired semaphore",
128            );
129
130            frames.push(FrameSlot {
131                cmd_buf,
132                fence,
133                swapchain_acquired,
134            });
135        }
136
137        let effective_info = {
138            let GraphchainInfo {
139                acquire_timeout,
140                clipped,
141                composite_alpha,
142                frame_capacity,
143                height,
144                min_image_count,
145                present_mode,
146                surface,
147                width,
148            } = info;
149
150            EffectiveGraphchainInfo {
151                acquire_timeout,
152                clipped,
153                composite_alpha,
154                frame_capacity,
155                frame_count: frame_capacity,
156                height,
157                image_count: swapchain.info.image_count as usize,
158                min_image_count,
159                present_mode,
160                surface,
161                width,
162            }
163        };
164
165        Ok(Self {
166            frame_idx: info.frame_capacity - 1,
167            frames: frames.into_boxed_slice(),
168            image_frames: Vec::new(),
169            last_present_queue_index: 0,
170            queue_family_index,
171            recreate_pending: false,
172            requested_info: info,
173            strategy,
174            read_only: ReadOnlyGraphchain {
175                info: effective_info,
176                swapchain,
177            },
178        })
179    }
180
181    /// Acquires the next available swapchain image for rendering.
182    pub fn acquire_next_image(&mut self) -> Result<Option<SwapchainImage>, GraphchainError> {
183        if self.recreate_pending {
184            for frame in &mut self.frames {
185                if frame.fence.is_queued() {
186                    frame.fence.wait()?.reset()?;
187                }
188            }
189
190            self.strategy
191                .retire_pending(&mut self.read_only.swapchain)?;
192
193            if self.read_only.swapchain.recreate_pending {
194                self.read_only.swapchain.recreate()?;
195            }
196
197            self.strategy
198                .reset(&self.read_only.swapchain.surface.device);
199            self.image_frames.clear();
200            self.recreate_pending = false;
201        }
202
203        self.frame_idx += 1;
204        self.frame_idx %= self.frames.len();
205
206        let frame = &mut self.frames[self.frame_idx];
207
208        if frame.fence.is_queued() {
209            frame.fence.wait()?.reset()?;
210        }
211
212        self.strategy.prepare_frame(
213            &frame.cmd_buf.device,
214            &mut self.read_only.swapchain,
215            self.frame_idx,
216        )?;
217
218        let swapchain_image = match self
219            .read_only
220            .swapchain
221            .acquire_next_image(frame.swapchain_acquired)
222        {
223            Ok(acquired) => acquired,
224            Err(SwapchainError::DeviceLost) => return Err(GraphchainError::DeviceLost),
225            Err(SwapchainError::Driver(err)) => return Err(GraphchainError::Driver(err)),
226            Err(
227                SwapchainError::FullScreenExclusiveModeLost
228                | SwapchainError::NotReady
229                | SwapchainError::Timeout,
230            ) => return Ok(None),
231            Err(SwapchainError::SurfaceLost) => {
232                return Err(GraphchainError::SurfaceLost);
233            }
234        };
235
236        if swapchain_image.suboptimal {
237            self.recreate_pending = true;
238        }
239
240        let swapchain_info = self.read_only.swapchain.info;
241        self.read_only.info.acquire_timeout = swapchain_info.acquire_timeout;
242        self.read_only.info.clipped = swapchain_info.clipped;
243        self.read_only.info.composite_alpha = swapchain_info.composite_alpha;
244        self.read_only.info.frame_capacity = self.requested_info.frame_capacity;
245        self.read_only.info.height = swapchain_info.height;
246        self.read_only.info.image_count = swapchain_info.image_count as usize;
247        self.read_only.info.present_mode = swapchain_info.present_mode;
248        self.read_only.info.surface = swapchain_info.surface;
249        self.read_only.info.width = swapchain_info.width;
250
251        // Live frame count cannot be smaller than the live swapchain image count.
252        self.read_only.info.frame_count = self
253            .read_only
254            .info
255            .frame_capacity
256            .max(self.read_only.info.image_count);
257
258        self.ensure_frame_count()?;
259
260        while swapchain_image.index >= self.image_frames.len() as u32 {
261            self.image_frames.push(0);
262        }
263
264        self.image_frames[swapchain_image.index as usize] = self.frame_idx;
265        self.strategy.acquire_image(
266            &self.read_only.swapchain.surface.device,
267            self.frame_idx,
268            swapchain_image.index,
269        )?;
270
271        Ok(Some(swapchain_image))
272    }
273
274    /// Displays the given swapchain image using passes specified in `graph`, if possible.
275    #[profiling::function]
276    pub fn present_image<P>(
277        &mut self,
278        pool: &mut P,
279        graph: Graph,
280        image_node: SwapchainImageNode,
281        queue_index: u32,
282    ) -> Result<(), GraphchainError>
283    where
284        P: Pool<CommandBufferInfo, CommandBuffer> + SubmissionPool,
285    {
286        trace!("present_image");
287
288        let submission = graph.finalize();
289        let (image_idx, image_sync_info) = {
290            let image = submission.resource(image_node);
291
292            (image.index as usize, image.sync_info())
293        };
294        let frame_idx = self.image_frames[image_idx];
295        let frame = &mut self.frames[frame_idx];
296        let rendered =
297            self.strategy
298                .rendered_semaphore(&frame.cmd_buf.device, frame_idx, image_idx as u32)?;
299
300        frame.cmd_buf.begin(
301            &vk::CommandBufferBeginInfo::default()
302                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
303        )?;
304
305        let wait_signal = image_sync_info.subresources.first().map(|_| {
306            let wait = SemaphoreSubmitInfo {
307                semaphore: frame.swapchain_acquired,
308                stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
309                value: 0,
310            };
311            let signal = SemaphoreSubmitInfo {
312                semaphore: rendered,
313                stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
314                value: 0,
315            };
316
317            (wait, signal)
318        });
319
320        let (mut recording, wait, signal, record_remaining) =
321            if let Some((wait, signal)) = wait_signal {
322                (
323                    submission.record(pool, &frame.cmd_buf, image_node)?,
324                    wait,
325                    signal,
326                    true,
327                )
328            } else {
329                warn!("uninitialized swapchain image");
330
331                let wait = SemaphoreSubmitInfo {
332                    semaphore: frame.swapchain_acquired,
333                    stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
334                    value: 0,
335                };
336                let signal = SemaphoreSubmitInfo {
337                    semaphore: rendered,
338                    stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
339                    value: 0,
340                };
341
342                (
343                    submission.record(pool, &frame.cmd_buf, RecordSelection::All)?,
344                    wait,
345                    signal,
346                    false,
347                )
348            };
349
350        let supports_synchronization2 = recording.cmd_buf.device.physical.vk_khr_synchronization2;
351
352        let image = recording.resource(image_node);
353        let image_sync_info = image.sync_info();
354        let image_handle = image.handle;
355
356        if log_enabled!(Level::Trace) {
357            for sync_info in &image_sync_info.subresources {
358                trace!(
359                    "image {:?} {:?}->{:?}",
360                    image,
361                    sync_info.layout,
362                    vk::ImageLayout::PRESENT_SRC_KHR,
363                );
364            }
365        }
366
367        if supports_synchronization2 {
368            for sync_info in &image_sync_info.subresources {
369                Device::cmd_pipeline_barrier2(
370                    &recording.cmd_buf.device,
371                    recording.cmd_buf.handle,
372                    &vk::DependencyInfo::default().image_memory_barriers(slice::from_ref(
373                        &vk::ImageMemoryBarrier2::default()
374                            .src_stage_mask(vk::PipelineStageFlags2::from_raw(
375                                sync_info.stage_mask.as_raw() as u64,
376                            ))
377                            .src_access_mask(vk::AccessFlags2::from_raw(
378                                sync_info.access_mask.as_raw() as u64,
379                            ))
380                            .dst_stage_mask(vk::PipelineStageFlags2::NONE)
381                            .dst_access_mask(vk::AccessFlags2::empty())
382                            .old_layout(sync_info.layout.unwrap_or(vk::ImageLayout::UNDEFINED))
383                            .new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
384                            .src_queue_family_index(
385                                sync_info
386                                    .queue_family_index
387                                    .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
388                            )
389                            .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
390                            .image(image_handle)
391                            .subresource_range(sync_info.range),
392                    )),
393                );
394            }
395        } else {
396            for sync_info in &image_sync_info.subresources {
397                unsafe {
398                    recording.cmd_buf.device.cmd_pipeline_barrier(
399                        recording.cmd_buf.handle,
400                        sync_info.stage_mask,
401                        vk::PipelineStageFlags::BOTTOM_OF_PIPE,
402                        vk::DependencyFlags::empty(),
403                        &[],
404                        &[],
405                        slice::from_ref(
406                            &vk::ImageMemoryBarrier::default()
407                                .src_access_mask(sync_info.access_mask)
408                                .dst_access_mask(vk::AccessFlags::empty())
409                                .old_layout(sync_info.layout.unwrap_or(vk::ImageLayout::UNDEFINED))
410                                .new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
411                                .src_queue_family_index(
412                                    sync_info
413                                        .queue_family_index
414                                        .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
415                                )
416                                .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
417                                .image(image_handle)
418                                .subresource_range(sync_info.range),
419                        ),
420                    )
421                };
422            }
423        }
424
425        if record_remaining {
426            recording.record(RecordSelection::All)?;
427        }
428
429        recording.cmd_buf.end()?;
430
431        let image = unsafe { recording.resource(image_node).to_detached() };
432
433        let mut recorded = recording.finish()?;
434        let waits = slice::from_ref(&wait);
435        let signals = slice::from_ref(&signal);
436        let submit_info = QueueSubmitInfo::queue_submit(waits, signals);
437        recorded.queue_submit(&mut frame.fence, queue_index, submit_info)?;
438
439        // Only mark presentation as pending when the driver actually queued the present.
440        let present_info = PresentInfo {
441            image,
442            swapchain: &mut self.read_only.swapchain,
443            wait_semaphores: slice::from_ref(&rendered),
444        };
445
446        let present =
447            Swapchain::queue_present(self.queue_family_index, queue_index, [present_info]);
448        let present_result = match &present {
449            Ok(results) => Some(results.first().copied().expect("missing present result")),
450            Err(err) => err.results.first().copied().flatten(),
451        };
452
453        if present_result.is_some_and(PresentResult::suboptimal) {
454            self.recreate_pending = true;
455        }
456
457        if let Some(PresentResult::NotQueued(err)) = present_result {
458            match err {
459                PresentError::DeviceLost | PresentError::SurfaceLost => {}
460                PresentError::FullScreenExclusiveModeLost | PresentError::OutOfDate => {
461                    self.recreate_pending = true;
462                }
463            }
464        }
465
466        if present_result.is_some_and(PresentResult::queued) {
467            self.last_present_queue_index = queue_index;
468            self.strategy.present_image(frame_idx, image_idx as u32);
469        }
470
471        if let Err(err) = present {
472            match err.source {
473                PresentFailure::DeviceLost => return Err(GraphchainError::DeviceLost),
474                PresentFailure::SurfaceLost => return Err(GraphchainError::SurfaceLost),
475                PresentFailure::Driver(err) => return Err(GraphchainError::Driver(err)),
476                PresentFailure::FullScreenExclusiveModeLost | PresentFailure::OutOfDate => {
477                    self.recreate_pending = true;
478                }
479            }
480        }
481
482        Ok(())
483    }
484
485    /// Updates the requested information which controls the graphchain.
486    ///
487    /// Previously acquired swapchain images should be discarded after calling this function.
488    /// The live effective [`info`](Self::info) field updates [`acquire_timeout`](GraphchainInfo::acquire_timeout)
489    /// immediately. Other fields are updated lazily after the next successful acquire recreates the
490    /// swapchain and reflects the runtime values that were actually selected.
491    pub fn set_info(&mut self, info: impl Into<GraphchainInfo>) {
492        let info = info.into();
493        let swapchain_info = info.into_swapchain_info();
494        let recreate = self.requested_info.height != info.height
495            || self.requested_info.clipped != info.clipped
496            || self.requested_info.min_image_count != info.min_image_count
497            || self.requested_info.present_mode != info.present_mode
498            || self.requested_info.composite_alpha != info.composite_alpha
499            || self.requested_info.surface != info.surface
500            || self.requested_info.width != info.width;
501
502        self.read_only.swapchain.set_info(swapchain_info);
503        self.requested_info = info;
504        self.read_only.info.acquire_timeout = info.acquire_timeout;
505        self.read_only.info.clipped = info.clipped;
506        self.read_only.info.min_image_count = info.min_image_count;
507
508        if recreate {
509            self.recreate_pending = true;
510        }
511    }
512
513    fn ensure_frame_count(&mut self) -> Result<(), GraphchainError> {
514        if self.frames.len() >= self.read_only.info.frame_count {
515            return Ok(());
516        }
517
518        let device = self.read_only.swapchain.surface.device.clone();
519        let mut frames = take(&mut self.frames).into_vec();
520
521        for _ in frames.len()..self.read_only.info.frame_count {
522            let cmd_buf =
523                CommandBuffer::create(&device, CommandBufferInfo::new(self.queue_family_index))?;
524            let fence = Fence::create(&device, false)?;
525            let swapchain_acquired = Device::create_semaphore(&device)?;
526            Device::try_set_debug_utils_object_name(
527                &device,
528                swapchain_acquired,
529                "graphchain acquired semaphore",
530            );
531
532            frames.push(FrameSlot {
533                cmd_buf,
534                fence,
535                swapchain_acquired,
536            });
537        }
538
539        self.frames = frames.into_boxed_slice();
540
541        Ok(())
542    }
543}
544
545impl Debug for Graphchain {
546    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
547        f.write_str("Graphchain")
548    }
549}
550
551impl Drop for Graphchain {
552    fn drop(&mut self) {
553        if panicking() {
554            return;
555        }
556
557        let Some(frame) = self.frames.first() else {
558            return;
559        };
560        let device = frame.cmd_buf.device.clone();
561
562        for frame in &mut self.frames {
563            if frame.fence.is_queued() && frame.fence.wait().is_err() {
564                return;
565            }
566        }
567
568        if self
569            .strategy
570            .retire_pending(&mut self.read_only.swapchain)
571            .is_err()
572        {
573            return;
574        }
575
576        if Device::with_queue(
577            &device,
578            self.queue_family_index,
579            self.last_present_queue_index,
580            |queue| Device::queue_wait_idle(&device, queue),
581        )
582        .is_err()
583        {
584            return;
585        }
586
587        for frame in &mut self.frames {
588            unsafe {
589                frame
590                    .cmd_buf
591                    .device
592                    .destroy_semaphore(frame.swapchain_acquired, None);
593            }
594        }
595
596        self.strategy.destroy(&device);
597    }
598}
599
600/// Describes error conditions relating to physical displays.
601#[derive(Clone, Copy, Debug)]
602pub enum GraphchainError {
603    /// Unrecoverable device error; must destroy this device and display and start a new one.
604    DeviceLost,
605
606    /// The surface was lost and must be recreated.
607    SurfaceLost,
608
609    /// Recoverable driver error.
610    Driver(DriverError),
611}
612
613impl Error for GraphchainError {
614    fn source(&self) -> Option<&(dyn Error + 'static)> {
615        match self {
616            Self::Driver(err) => Some(err),
617            Self::DeviceLost | Self::SurfaceLost => None,
618        }
619    }
620}
621
622impl From<()> for GraphchainError {
623    fn from(_: ()) -> Self {
624        Self::DeviceLost
625    }
626}
627
628impl From<DriverError> for GraphchainError {
629    fn from(err: DriverError) -> Self {
630        Self::Driver(err)
631    }
632}
633
634impl From<SwapchainError> for GraphchainError {
635    fn from(err: SwapchainError) -> Self {
636        match err {
637            SwapchainError::DeviceLost => Self::DeviceLost,
638            SwapchainError::Driver(err) => Self::Driver(err),
639            SwapchainError::FullScreenExclusiveModeLost
640            | SwapchainError::NotReady
641            | SwapchainError::Timeout => Self::Driver(DriverError::Unsupported),
642            SwapchainError::SurfaceLost => Self::SurfaceLost,
643        }
644    }
645}
646
647impl std::fmt::Display for GraphchainError {
648    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
649        match self {
650            Self::DeviceLost => f.write_str("device lost"),
651            Self::SurfaceLost => f.write_str("surface lost"),
652            Self::Driver(err) => std::fmt::Display::fmt(err, f),
653        }
654    }
655}
656
657/// Information used to create a [`Graphchain`] instance.
658#[derive(Builder, Clone, Copy, Debug, Eq, Hash, PartialEq)]
659#[builder(
660    build_fn(private, name = "fallible_build"),
661    derive(Clone, Copy, Debug),
662    pattern = "owned"
663)]
664pub struct GraphchainInfo {
665    /// Timeout in nanoseconds used when acquiring the next image.
666    ///
667    /// Do not use `u64::MAX` on surfaces where acquire forward progress cannot be guaranteed.
668    #[builder(default = "u64::MAX")]
669    pub acquire_timeout: u64,
670
671    /// Alpha compositing mode used by the presentation engine.
672    #[builder(default = vk::CompositeAlphaFlagsKHR::OPAQUE)]
673    pub composite_alpha: vk::CompositeAlphaFlagsKHR,
674
675    /// Whether to clip pixels obscured by other windows on the native surface.
676    #[builder(default = "true")]
677    pub clipped: bool,
678
679    /// The requested frame capacity used for in-flight graphchain work.
680    ///
681    /// The effective live capacity may be raised at runtime to match the swapchain image count.
682    /// That resolved value is reflected through [`EffectiveGraphchainInfo`].
683    #[builder(default = "4")]
684    pub frame_capacity: usize,
685
686    /// The initial height of the surface.
687    #[builder(default)]
688    pub height: u32,
689
690    /// The minimum number of presentable images that the application needs.
691    #[builder(default = "2")]
692    pub min_image_count: u32,
693
694    /// `vk::PresentModeKHR` determines timing and queueing with which frames are displayed.
695    #[builder(default = vk::PresentModeKHR::MAILBOX)]
696    pub present_mode: vk::PresentModeKHR,
697
698    /// The format and color space of the surface.
699    #[builder(default)]
700    pub surface: vk::SurfaceFormatKHR,
701
702    /// The initial width of the surface.
703    #[builder(default)]
704    pub width: u32,
705}
706
707/// Effective runtime information for a live [`Graphchain`].
708#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
709pub struct EffectiveGraphchainInfo {
710    /// Timeout in nanoseconds used when acquiring the next image.
711    pub acquire_timeout: u64,
712
713    /// Active alpha compositing mode used by the presentation engine.
714    pub composite_alpha: vk::CompositeAlphaFlagsKHR,
715
716    /// Whether pixels obscured by other windows on the native surface are clipped.
717    pub clipped: bool,
718
719    frame_capacity: usize,
720
721    /// The effective number of tracked frame slots.
722    pub frame_count: usize,
723
724    /// The live height of the surface.
725    pub height: u32,
726
727    /// The live swapchain image count.
728    pub image_count: usize,
729
730    min_image_count: u32,
731
732    /// `vk::PresentModeKHR` determines timing and queueing with which frames are displayed.
733    pub present_mode: vk::PresentModeKHR,
734
735    /// The format and color space of the surface.
736    pub surface: vk::SurfaceFormatKHR,
737
738    /// The live width of the surface.
739    pub width: u32,
740}
741
742impl GraphchainInfo {
743    /// Specifies a default graphchain with the given `width`, `height` and `format` values.
744    #[inline(always)]
745    pub fn new(width: u32, height: u32, surface: vk::SurfaceFormatKHR) -> GraphchainInfo {
746        Self {
747            acquire_timeout: u64::MAX,
748            clipped: true,
749            composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
750            frame_capacity: 4,
751            height,
752            min_image_count: 2,
753            present_mode: vk::PresentModeKHR::MAILBOX,
754            surface,
755            width,
756        }
757    }
758
759    /// Creates a default `GraphchainInfoBuilder`.
760    pub fn builder() -> GraphchainInfoBuilder {
761        Default::default()
762    }
763
764    /// Converts a `GraphchainInfo` into a `GraphchainInfoBuilder`.
765    pub fn into_builder(self) -> GraphchainInfoBuilder {
766        GraphchainInfoBuilder {
767            acquire_timeout: Some(self.acquire_timeout),
768            clipped: Some(self.clipped),
769            composite_alpha: Some(self.composite_alpha),
770            frame_capacity: Some(self.frame_capacity),
771            height: Some(self.height),
772            min_image_count: Some(self.min_image_count),
773            present_mode: Some(self.present_mode),
774            surface: Some(self.surface),
775            width: Some(self.width),
776        }
777    }
778
779    /// Converts this graphchain info into low-level swapchain info.
780    pub fn into_swapchain_info(self) -> SwapchainInfo {
781        self.into()
782    }
783}
784
785impl EffectiveGraphchainInfo {
786    /// Creates a default `GraphchainInfoBuilder`.
787    pub fn builder() -> GraphchainInfoBuilder {
788        Default::default()
789    }
790
791    /// Converts an `EffectiveGraphchainInfo` into a `GraphchainInfoBuilder`.
792    pub fn into_builder(self) -> GraphchainInfoBuilder {
793        self.into_requested_info().into_builder()
794    }
795
796    /// Converts this effective runtime info back into requested graphchain info.
797    pub fn into_requested_info(self) -> GraphchainInfo {
798        let Self {
799            acquire_timeout,
800            clipped,
801            composite_alpha,
802            frame_capacity,
803            frame_count: _,
804            height,
805            image_count: _,
806            min_image_count,
807            present_mode,
808            surface,
809            width,
810        } = self;
811
812        GraphchainInfo {
813            acquire_timeout,
814            clipped,
815            composite_alpha,
816            frame_capacity,
817            height,
818            min_image_count,
819            present_mode,
820            surface,
821            width,
822        }
823    }
824}
825
826impl From<EffectiveGraphchainInfo> for GraphchainInfo {
827    fn from(info: EffectiveGraphchainInfo) -> Self {
828        info.into_requested_info()
829    }
830}
831
832impl From<GraphchainInfoBuilder> for GraphchainInfo {
833    fn from(info: GraphchainInfoBuilder) -> Self {
834        info.build()
835    }
836}
837
838impl From<GraphchainInfo> for SwapchainInfo {
839    fn from(val: GraphchainInfo) -> Self {
840        SwapchainInfoBuilder::default()
841            .acquire_timeout(val.acquire_timeout)
842            .clipped(val.clipped)
843            .composite_alpha(val.composite_alpha)
844            .height(val.height)
845            .min_image_count(val.min_image_count)
846            .present_mode(val.present_mode)
847            .surface(val.surface)
848            .width(val.width)
849            .build()
850    }
851}
852
853impl From<GraphchainInfoBuilder> for SwapchainInfo {
854    fn from(info: GraphchainInfoBuilder) -> Self {
855        info.build().into_swapchain_info()
856    }
857}
858
859impl GraphchainInfoBuilder {
860    /// Builds a new `GraphchainInfo`.
861    #[inline(always)]
862    pub fn build(self) -> GraphchainInfo {
863        self.fallible_build().expect("all fields have defaults")
864    }
865}
866
867enum PresentRetirementStrategy {
868    PresentWait(PresentWait),
869    PerImageSemaphore(PerImageSemaphore),
870}
871
872impl PresentRetirementStrategy {
873    fn prepare_frame(
874        &mut self,
875        device: &Device,
876        swapchain: &mut Swapchain,
877        frame_idx: usize,
878    ) -> Result<(), GraphchainError> {
879        match self {
880            Self::PresentWait(strategy) => strategy.prepare_frame(device, swapchain, frame_idx),
881            Self::PerImageSemaphore(strategy) => {
882                strategy.prepare_frame(device, swapchain, frame_idx)
883            }
884        }
885    }
886
887    fn acquire_image(
888        &mut self,
889        device: &Device,
890        frame_idx: usize,
891        image_idx: u32,
892    ) -> Result<(), DriverError> {
893        match self {
894            Self::PresentWait(strategy) => strategy.acquire_image(device, frame_idx, image_idx),
895            Self::PerImageSemaphore(strategy) => {
896                strategy.acquire_image(device, frame_idx, image_idx)
897            }
898        }
899    }
900
901    fn rendered_semaphore(
902        &mut self,
903        device: &Device,
904        frame_idx: usize,
905        image_idx: u32,
906    ) -> Result<vk::Semaphore, DriverError> {
907        match self {
908            Self::PresentWait(strategy) => {
909                strategy.rendered_semaphore(device, frame_idx, image_idx)
910            }
911            Self::PerImageSemaphore(strategy) => {
912                strategy.rendered_semaphore(device, frame_idx, image_idx)
913            }
914        }
915    }
916
917    fn present_image(&mut self, frame_idx: usize, image_idx: u32) {
918        match self {
919            Self::PresentWait(strategy) => strategy.present_image(frame_idx, image_idx),
920            Self::PerImageSemaphore(strategy) => strategy.present_image(frame_idx, image_idx),
921        }
922    }
923
924    fn retire_pending(&mut self, swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
925        match self {
926            Self::PresentWait(strategy) => strategy.retire_pending(swapchain),
927            Self::PerImageSemaphore(strategy) => strategy.retire_pending(swapchain),
928        }
929    }
930
931    fn reset(&mut self, device: &Device) {
932        match self {
933            Self::PresentWait(strategy) => strategy.reset(device),
934            Self::PerImageSemaphore(strategy) => strategy.reset(device),
935        }
936    }
937
938    fn destroy(&mut self, device: &Device) {
939        match self {
940            Self::PresentWait(strategy) => strategy.destroy(device),
941            Self::PerImageSemaphore(strategy) => strategy.destroy(device),
942        }
943    }
944}
945
946#[derive(Default)]
947struct PresentWait {
948    retired: Vec<vk::Semaphore>,
949    rendered: Vec<Option<vk::Semaphore>>,
950    pending_images: Vec<Option<u32>>,
951}
952
953impl PresentWait {
954    fn prepare_frame(
955        &mut self,
956        _device: &Device,
957        swapchain: &mut Swapchain,
958        frame_idx: usize,
959    ) -> Result<(), GraphchainError> {
960        while self.pending_images.len() <= frame_idx {
961            self.pending_images.push(None);
962        }
963
964        if let Some(image_idx) = self.pending_images[frame_idx].take() {
965            unsafe {
966                match swapchain.wait_for_present_image(image_idx, u64::MAX) {
967                    Ok(()) => {}
968                    Err(SwapchainError::DeviceLost) => {
969                        return Err(GraphchainError::DeviceLost);
970                    }
971                    Err(SwapchainError::Driver(err)) => {
972                        return Err(GraphchainError::Driver(err));
973                    }
974                    Err(SwapchainError::FullScreenExclusiveModeLost) => {}
975                    Err(SwapchainError::NotReady | SwapchainError::Timeout) => {
976                        return Err(GraphchainError::Driver(DriverError::Unsupported));
977                    }
978                    Err(SwapchainError::SurfaceLost) => {
979                        return Err(GraphchainError::SurfaceLost);
980                    }
981                }
982            }
983        }
984
985        Ok(())
986    }
987
988    fn acquire_image(
989        &mut self,
990        device: &Device,
991        _frame_idx: usize,
992        image_idx: u32,
993    ) -> Result<(), DriverError> {
994        self.ensure_rendered(device, image_idx).map(|_| ())
995    }
996
997    fn rendered_semaphore(
998        &mut self,
999        device: &Device,
1000        _frame_idx: usize,
1001        image_idx: u32,
1002    ) -> Result<vk::Semaphore, DriverError> {
1003        self.ensure_rendered(device, image_idx)
1004    }
1005
1006    fn ensure_rendered(
1007        &mut self,
1008        device: &Device,
1009        image_idx: u32,
1010    ) -> Result<vk::Semaphore, DriverError> {
1011        while self.rendered.len() <= image_idx as usize {
1012            self.rendered.push(None);
1013        }
1014
1015        if self.rendered[image_idx as usize].is_none() {
1016            let semaphore = Device::create_semaphore(device)?;
1017            Device::try_set_debug_utils_object_name(
1018                device,
1019                semaphore,
1020                "graphchain present-wait per-image rendered semaphore",
1021            );
1022
1023            self.rendered[image_idx as usize] = Some(semaphore);
1024        }
1025
1026        Ok(self.rendered[image_idx as usize].expect("missing rendered semaphore"))
1027    }
1028
1029    fn present_image(&mut self, frame_idx: usize, image_idx: u32) {
1030        while self.pending_images.len() <= frame_idx {
1031            self.pending_images.push(None);
1032        }
1033
1034        self.pending_images[frame_idx] = Some(image_idx);
1035    }
1036
1037    fn retire_pending(&mut self, swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
1038        for image_idx in self.pending_images.drain(..).flatten() {
1039            unsafe {
1040                match swapchain.wait_for_present_image(image_idx, u64::MAX) {
1041                    Ok(()) | Err(SwapchainError::FullScreenExclusiveModeLost) => {}
1042                    Err(SwapchainError::DeviceLost) => return Err(GraphchainError::DeviceLost),
1043                    Err(SwapchainError::Driver(err)) => return Err(GraphchainError::Driver(err)),
1044                    Err(SwapchainError::NotReady | SwapchainError::Timeout) => {
1045                        return Err(GraphchainError::Driver(DriverError::Unsupported));
1046                    }
1047                    Err(SwapchainError::SurfaceLost) => return Err(GraphchainError::SurfaceLost),
1048                }
1049            }
1050        }
1051
1052        Ok(())
1053    }
1054
1055    fn reset(&mut self, device: &Device) {
1056        self.destroy_retired(device);
1057        self.pending_images.clear();
1058        self.retired.extend(self.rendered.drain(..).flatten());
1059    }
1060
1061    fn destroy(&mut self, device: &Device) {
1062        self.destroy_retired(device);
1063
1064        for semaphore in self
1065            .rendered
1066            .drain(..)
1067            .flatten()
1068            .chain(self.retired.drain(..))
1069        {
1070            unsafe {
1071                device.destroy_semaphore(semaphore, None);
1072            }
1073        }
1074    }
1075
1076    fn destroy_retired(&mut self, device: &Device) {
1077        for semaphore in self.retired.drain(..) {
1078            unsafe {
1079                device.destroy_semaphore(semaphore, None);
1080            }
1081        }
1082    }
1083}
1084
1085#[derive(Default)]
1086struct PerImageSemaphore {
1087    retired: Vec<vk::Semaphore>,
1088    rendered: Vec<Option<vk::Semaphore>>,
1089}
1090
1091impl PerImageSemaphore {
1092    fn prepare_frame(
1093        &mut self,
1094        _device: &Device,
1095        _swapchain: &mut Swapchain,
1096        _frame_idx: usize,
1097    ) -> Result<(), GraphchainError> {
1098        Ok(())
1099    }
1100
1101    fn acquire_image(
1102        &mut self,
1103        device: &Device,
1104        _frame_idx: usize,
1105        image_idx: u32,
1106    ) -> Result<(), DriverError> {
1107        self.ensure_rendered(device, image_idx).map(|_| ())
1108    }
1109
1110    fn rendered_semaphore(
1111        &mut self,
1112        device: &Device,
1113        _frame_idx: usize,
1114        image_idx: u32,
1115    ) -> Result<vk::Semaphore, DriverError> {
1116        self.ensure_rendered(device, image_idx)
1117    }
1118
1119    fn ensure_rendered(
1120        &mut self,
1121        device: &Device,
1122        image_idx: u32,
1123    ) -> Result<vk::Semaphore, DriverError> {
1124        while self.rendered.len() <= image_idx as usize {
1125            self.rendered.push(None);
1126        }
1127
1128        if self.rendered[image_idx as usize].is_none() {
1129            let semaphore = Device::create_semaphore(device)?;
1130            Device::try_set_debug_utils_object_name(
1131                device,
1132                semaphore,
1133                "graphchain per-image rendered semaphore",
1134            );
1135
1136            self.rendered[image_idx as usize] = Some(semaphore);
1137        }
1138
1139        Ok(self.rendered[image_idx as usize].expect("missing rendered semaphore"))
1140    }
1141
1142    fn present_image(&mut self, _frame_idx: usize, _image_idx: u32) {}
1143
1144    fn retire_pending(&mut self, _swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
1145        Ok(())
1146    }
1147
1148    fn reset(&mut self, device: &Device) {
1149        self.destroy_retired(device);
1150        self.retired.extend(self.rendered.drain(..).flatten());
1151    }
1152
1153    fn destroy(&mut self, device: &Device) {
1154        self.destroy_retired(device);
1155
1156        for semaphore in self
1157            .rendered
1158            .drain(..)
1159            .flatten()
1160            .chain(self.retired.drain(..))
1161        {
1162            unsafe {
1163                device.destroy_semaphore(semaphore, None);
1164            }
1165        }
1166    }
1167
1168    fn destroy_retired(&mut self, device: &Device) {
1169        for semaphore in self.retired.drain(..) {
1170            unsafe {
1171                device.destroy_semaphore(semaphore, None);
1172            }
1173        }
1174    }
1175}
1176
1177struct FrameSlot {
1178    cmd_buf: CommandBuffer,
1179    fence: Fence,
1180    swapchain_acquired: vk::Semaphore,
1181}
1182
1183#[cfg(test)]
1184mod test {
1185    use super::*;
1186
1187    type Info = GraphchainInfo;
1188    type Builder = GraphchainInfoBuilder;
1189    type Effective = EffectiveGraphchainInfo;
1190
1191    #[test]
1192    fn graphchain_info_round_trips_through_builder() {
1193        let info = Info {
1194            acquire_timeout: 42,
1195            clipped: false,
1196            composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1197            frame_capacity: 6,
1198            height: 123,
1199            min_image_count: 3,
1200            present_mode: vk::PresentModeKHR::FIFO,
1201            surface: vk::SurfaceFormatKHR::default()
1202                .format(vk::Format::B8G8R8A8_UNORM)
1203                .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1204            width: 456,
1205        };
1206
1207        assert_eq!(info, info.into_builder().build());
1208    }
1209
1210    #[test]
1211    fn graphchain_info_builder_defaults() {
1212        assert_eq!(
1213            Builder::default().build(),
1214            Info {
1215                acquire_timeout: u64::MAX,
1216                clipped: true,
1217                composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
1218                frame_capacity: 4,
1219                height: 0,
1220                min_image_count: 2,
1221                present_mode: vk::PresentModeKHR::MAILBOX,
1222                surface: vk::SurfaceFormatKHR::default(),
1223                width: 0,
1224            }
1225        );
1226    }
1227
1228    #[test]
1229    fn effective_graphchain_info_into_requested_info() {
1230        let info = Effective {
1231            acquire_timeout: 42,
1232            clipped: false,
1233            composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1234            frame_capacity: 6,
1235            frame_count: 8,
1236            height: 123,
1237            image_count: 7,
1238            min_image_count: 3,
1239            present_mode: vk::PresentModeKHR::FIFO,
1240            surface: vk::SurfaceFormatKHR::default()
1241                .format(vk::Format::B8G8R8A8_UNORM)
1242                .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1243            width: 456,
1244        };
1245
1246        assert_eq!(
1247            info.into_requested_info(),
1248            Info {
1249                acquire_timeout: 42,
1250                clipped: false,
1251                composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1252                frame_capacity: 6,
1253                height: 123,
1254                min_image_count: 3,
1255                present_mode: vk::PresentModeKHR::FIFO,
1256                surface: vk::SurfaceFormatKHR::default()
1257                    .format(vk::Format::B8G8R8A8_UNORM)
1258                    .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1259                width: 456,
1260            }
1261        );
1262    }
1263
1264    // #[test]
1265    // fn effective_graphchain_info_from_requested_info() {
1266    //     let info = Info {
1267    //         acquire_timeout: 42,
1268    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1269    //         frame_capacity: 6,
1270    //         height: 123,
1271    //         min_image_count: 3,
1272    //         present_mode: vk::PresentModeKHR::FIFO,
1273    //         surface: vk::SurfaceFormatKHR::default()
1274    //             .format(vk::Format::B8G8R8A8_UNORM)
1275    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1276    //         width: 456,
1277    //     };
1278
1279    //     assert_eq!(
1280    //         Effective::from(info),
1281    //         Effective {
1282    //             acquire_timeout: 42,
1283    //             composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1284    //             frame_capacity: 6,
1285    //             frame_count: 6,
1286    //             height: 123,
1287    //             image_count: 0,
1288    //             present_mode: vk::PresentModeKHR::FIFO,
1289    //             surface: vk::SurfaceFormatKHR::default()
1290    //                 .format(vk::Format::B8G8R8A8_UNORM)
1291    //                 .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1292    //             width: 456,
1293    //         }
1294    //     );
1295    // }
1296
1297    // #[test]
1298    // fn effective_graphchain_info_has_only_runtime_fields() {
1299    //     let info = Effective {
1300    //         acquire_timeout: 42,
1301    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1302    //         frame_capacity: 6,
1303    //         frame_count: 8,
1304    //         height: 123,
1305    //         image_count: 7,
1306    //         present_mode: vk::PresentModeKHR::FIFO,
1307    //         surface: vk::SurfaceFormatKHR::default()
1308    //             .format(vk::Format::B8G8R8A8_UNORM)
1309    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1310    //         width: 456,
1311    //     };
1312
1313    //     assert_eq!(info.frame_count, 8);
1314    //     assert_eq!(info.image_count, 7);
1315    // }
1316
1317    // #[test]
1318    // fn effective_graphchain_info_round_trips_through_builder() {
1319    //     let info = Effective {
1320    //         acquire_timeout: 42,
1321    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1322    //         frame_capacity: 6,
1323    //         frame_count: 8,
1324    //         height: 123,
1325    //         image_count: 7,
1326    //         present_mode: vk::PresentModeKHR::FIFO,
1327    //         surface: vk::SurfaceFormatKHR::default()
1328    //             .format(vk::Format::B8G8R8A8_UNORM)
1329    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1330    //         width: 456,
1331    //     };
1332
1333    //     assert_eq!(info, info.into_builder().build());
1334    // }
1335
1336    // #[test]
1337    // fn effective_graphchain_info_builder_defaults() {
1338    //     assert_eq!(
1339    //         EffectiveBuilder::default().build(),
1340    //         Effective {
1341    //             acquire_timeout: u64::MAX,
1342    //             composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
1343    //             frame_capacity: 4,
1344    //             frame_count: 4,
1345    //             height: 0,
1346    //             image_count: 0,
1347    //             present_mode: vk::PresentModeKHR::MAILBOX,
1348    //             surface: vk::SurfaceFormatKHR::default(),
1349    //             width: 0,
1350    //         }
1351    //     );
1352    // }
1353
1354    // #[test]
1355    // fn effective_graphchain_info_is_not_requested_info() {
1356    //     let info = Info {
1357    //         acquire_timeout: 42,
1358    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1359    //         frame_capacity: 6,
1360    //         height: 123,
1361    //         min_image_count: 3,
1362    //         present_mode: vk::PresentModeKHR::FIFO,
1363    //         surface: vk::SurfaceFormatKHR::default()
1364    //             .format(vk::Format::B8G8R8A8_UNORM)
1365    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1366    //         width: 456,
1367    //     };
1368
1369    //     assert_eq!(Effective::from(info).image_count, 0);
1370    // }
1371}