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, TimestampQueryPool},
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<SubmissionQueryPool, 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        let submission_queries = SubmissionQueryPool {
439            timestamps: frame.fence.timestamps.clone(),
440        };
441
442        // Only mark presentation as pending when the driver actually queued the present.
443        let present_info = PresentInfo {
444            image,
445            swapchain: &mut self.read_only.swapchain,
446            wait_semaphores: slice::from_ref(&rendered),
447        };
448
449        let present =
450            Swapchain::queue_present(self.queue_family_index, queue_index, [present_info]);
451        let present_result = match &present {
452            Ok(results) => Some(results.first().copied().expect("missing present result")),
453            Err(err) => err.results.first().copied().flatten(),
454        };
455
456        if present_result.is_some_and(PresentResult::suboptimal) {
457            self.recreate_pending = true;
458        }
459
460        if let Some(PresentResult::NotQueued(err)) = present_result {
461            match err {
462                PresentError::DeviceLost | PresentError::SurfaceLost => {}
463                PresentError::FullScreenExclusiveModeLost | PresentError::OutOfDate => {
464                    self.recreate_pending = true;
465                }
466            }
467        }
468
469        if present_result.is_some_and(PresentResult::queued) {
470            self.last_present_queue_index = queue_index;
471            self.strategy.present_image(frame_idx, image_idx as u32);
472        }
473
474        if let Err(err) = present {
475            match err.source {
476                PresentFailure::DeviceLost => return Err(GraphchainError::DeviceLost),
477                PresentFailure::SurfaceLost => return Err(GraphchainError::SurfaceLost),
478                PresentFailure::Driver(err) => return Err(GraphchainError::Driver(err)),
479                PresentFailure::FullScreenExclusiveModeLost | PresentFailure::OutOfDate => {
480                    self.recreate_pending = true;
481                }
482            }
483        }
484
485        Ok(submission_queries)
486    }
487
488    /// Updates the requested information which controls the graphchain.
489    ///
490    /// Previously acquired swapchain images should be discarded after calling this function.
491    /// The live effective [`info`](Self::info) field updates [`acquire_timeout`](GraphchainInfo::acquire_timeout)
492    /// immediately. Other fields are updated lazily after the next successful acquire recreates the
493    /// swapchain and reflects the runtime values that were actually selected.
494    pub fn set_info(&mut self, info: impl Into<GraphchainInfo>) {
495        let info = info.into();
496        let swapchain_info = info.into_swapchain_info();
497        let recreate = self.requested_info.height != info.height
498            || self.requested_info.clipped != info.clipped
499            || self.requested_info.min_image_count != info.min_image_count
500            || self.requested_info.present_mode != info.present_mode
501            || self.requested_info.composite_alpha != info.composite_alpha
502            || self.requested_info.surface != info.surface
503            || self.requested_info.width != info.width;
504
505        self.read_only.swapchain.set_info(swapchain_info);
506        self.requested_info = info;
507        self.read_only.info.acquire_timeout = info.acquire_timeout;
508        self.read_only.info.clipped = info.clipped;
509        self.read_only.info.min_image_count = info.min_image_count;
510
511        if recreate {
512            self.recreate_pending = true;
513        }
514    }
515
516    fn ensure_frame_count(&mut self) -> Result<(), GraphchainError> {
517        if self.frames.len() >= self.read_only.info.frame_count {
518            return Ok(());
519        }
520
521        let device = self.read_only.swapchain.surface.device.clone();
522        let mut frames = take(&mut self.frames).into_vec();
523
524        for _ in frames.len()..self.read_only.info.frame_count {
525            let cmd_buf =
526                CommandBuffer::create(&device, CommandBufferInfo::new(self.queue_family_index))?;
527            let fence = Fence::create(&device, false)?;
528            let swapchain_acquired = Device::create_semaphore(&device)?;
529            Device::try_set_debug_utils_object_name(
530                &device,
531                swapchain_acquired,
532                "graphchain acquired semaphore",
533            );
534
535            frames.push(FrameSlot {
536                cmd_buf,
537                fence,
538                swapchain_acquired,
539            });
540        }
541
542        self.frames = frames.into_boxed_slice();
543
544        Ok(())
545    }
546}
547
548impl Debug for Graphchain {
549    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
550        f.write_str("Graphchain")
551    }
552}
553
554impl Drop for Graphchain {
555    fn drop(&mut self) {
556        if panicking() {
557            return;
558        }
559
560        let Some(frame) = self.frames.first() else {
561            return;
562        };
563        let device = frame.cmd_buf.device.clone();
564
565        for frame in &mut self.frames {
566            if frame.fence.is_queued() && frame.fence.wait().is_err() {
567                return;
568            }
569        }
570
571        if self
572            .strategy
573            .retire_pending(&mut self.read_only.swapchain)
574            .is_err()
575        {
576            return;
577        }
578
579        if Device::with_queue(
580            &device,
581            self.queue_family_index,
582            self.last_present_queue_index,
583            |queue| Device::queue_wait_idle(&device, queue),
584        )
585        .is_err()
586        {
587            return;
588        }
589
590        for frame in &mut self.frames {
591            unsafe {
592                frame
593                    .cmd_buf
594                    .device
595                    .destroy_semaphore(frame.swapchain_acquired, None);
596            }
597        }
598
599        self.strategy.destroy(&device);
600    }
601}
602
603/// Describes error conditions relating to physical displays.
604#[derive(Clone, Copy, Debug)]
605pub enum GraphchainError {
606    /// Unrecoverable device error; must destroy this device and display and start a new one.
607    DeviceLost,
608
609    /// The surface was lost and must be recreated.
610    SurfaceLost,
611
612    /// Recoverable driver error.
613    Driver(DriverError),
614}
615
616impl Error for GraphchainError {
617    fn source(&self) -> Option<&(dyn Error + 'static)> {
618        match self {
619            Self::Driver(err) => Some(err),
620            Self::DeviceLost | Self::SurfaceLost => None,
621        }
622    }
623}
624
625impl From<()> for GraphchainError {
626    fn from(_: ()) -> Self {
627        Self::DeviceLost
628    }
629}
630
631impl From<DriverError> for GraphchainError {
632    fn from(err: DriverError) -> Self {
633        Self::Driver(err)
634    }
635}
636
637impl From<SwapchainError> for GraphchainError {
638    fn from(err: SwapchainError) -> Self {
639        match err {
640            SwapchainError::DeviceLost => Self::DeviceLost,
641            SwapchainError::Driver(err) => Self::Driver(err),
642            SwapchainError::FullScreenExclusiveModeLost
643            | SwapchainError::NotReady
644            | SwapchainError::Timeout => Self::Driver(DriverError::Unsupported),
645            SwapchainError::SurfaceLost => Self::SurfaceLost,
646        }
647    }
648}
649
650impl std::fmt::Display for GraphchainError {
651    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
652        match self {
653            Self::DeviceLost => f.write_str("device lost"),
654            Self::SurfaceLost => f.write_str("surface lost"),
655            Self::Driver(err) => std::fmt::Display::fmt(err, f),
656        }
657    }
658}
659
660/// Information used to create a [`Graphchain`] instance.
661#[derive(Builder, Clone, Copy, Debug, Eq, Hash, PartialEq)]
662#[builder(
663    build_fn(private, name = "fallible_build"),
664    derive(Clone, Copy, Debug),
665    pattern = "owned"
666)]
667pub struct GraphchainInfo {
668    /// Timeout in nanoseconds used when acquiring the next image.
669    ///
670    /// Do not use `u64::MAX` on surfaces where acquire forward progress cannot be guaranteed.
671    #[builder(default = "u64::MAX")]
672    pub acquire_timeout: u64,
673
674    /// Alpha compositing mode used by the presentation engine.
675    #[builder(default = vk::CompositeAlphaFlagsKHR::OPAQUE)]
676    pub composite_alpha: vk::CompositeAlphaFlagsKHR,
677
678    /// Whether to clip pixels obscured by other windows on the native surface.
679    #[builder(default = "true")]
680    pub clipped: bool,
681
682    /// The requested frame capacity used for in-flight graphchain work.
683    ///
684    /// The effective live capacity may be raised at runtime to match the swapchain image count.
685    /// That resolved value is reflected through [`EffectiveGraphchainInfo`].
686    #[builder(default = "4")]
687    pub frame_capacity: usize,
688
689    /// The initial height of the surface.
690    #[builder(default)]
691    pub height: u32,
692
693    /// The minimum number of presentable images that the application needs.
694    #[builder(default = "2")]
695    pub min_image_count: u32,
696
697    /// `vk::PresentModeKHR` determines timing and queueing with which frames are displayed.
698    #[builder(default = vk::PresentModeKHR::MAILBOX)]
699    pub present_mode: vk::PresentModeKHR,
700
701    /// The format and color space of the surface.
702    #[builder(default)]
703    pub surface: vk::SurfaceFormatKHR,
704
705    /// The initial width of the surface.
706    #[builder(default)]
707    pub width: u32,
708}
709
710/// Effective runtime information for a live [`Graphchain`].
711#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
712pub struct EffectiveGraphchainInfo {
713    /// Timeout in nanoseconds used when acquiring the next image.
714    pub acquire_timeout: u64,
715
716    /// Active alpha compositing mode used by the presentation engine.
717    pub composite_alpha: vk::CompositeAlphaFlagsKHR,
718
719    /// Whether pixels obscured by other windows on the native surface are clipped.
720    pub clipped: bool,
721
722    frame_capacity: usize,
723
724    /// The effective number of tracked frame slots.
725    pub frame_count: usize,
726
727    /// The live height of the surface.
728    pub height: u32,
729
730    /// The live swapchain image count.
731    pub image_count: usize,
732
733    min_image_count: u32,
734
735    /// `vk::PresentModeKHR` determines timing and queueing with which frames are displayed.
736    pub present_mode: vk::PresentModeKHR,
737
738    /// The format and color space of the surface.
739    pub surface: vk::SurfaceFormatKHR,
740
741    /// The live width of the surface.
742    pub width: u32,
743}
744
745impl GraphchainInfo {
746    /// Specifies a default graphchain with the given `width`, `height` and `format` values.
747    #[inline(always)]
748    pub fn new(width: u32, height: u32, surface: vk::SurfaceFormatKHR) -> GraphchainInfo {
749        Self {
750            acquire_timeout: u64::MAX,
751            clipped: true,
752            composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
753            frame_capacity: 4,
754            height,
755            min_image_count: 2,
756            present_mode: vk::PresentModeKHR::MAILBOX,
757            surface,
758            width,
759        }
760    }
761
762    /// Creates a default `GraphchainInfoBuilder`.
763    pub fn builder() -> GraphchainInfoBuilder {
764        Default::default()
765    }
766
767    /// Converts a `GraphchainInfo` into a `GraphchainInfoBuilder`.
768    pub fn into_builder(self) -> GraphchainInfoBuilder {
769        GraphchainInfoBuilder {
770            acquire_timeout: Some(self.acquire_timeout),
771            clipped: Some(self.clipped),
772            composite_alpha: Some(self.composite_alpha),
773            frame_capacity: Some(self.frame_capacity),
774            height: Some(self.height),
775            min_image_count: Some(self.min_image_count),
776            present_mode: Some(self.present_mode),
777            surface: Some(self.surface),
778            width: Some(self.width),
779        }
780    }
781
782    /// Converts this graphchain info into low-level swapchain info.
783    pub fn into_swapchain_info(self) -> SwapchainInfo {
784        self.into()
785    }
786}
787
788impl EffectiveGraphchainInfo {
789    /// Creates a default `GraphchainInfoBuilder`.
790    pub fn builder() -> GraphchainInfoBuilder {
791        Default::default()
792    }
793
794    /// Converts an `EffectiveGraphchainInfo` into a `GraphchainInfoBuilder`.
795    pub fn into_builder(self) -> GraphchainInfoBuilder {
796        self.into_requested_info().into_builder()
797    }
798
799    /// Converts this effective runtime info back into requested graphchain info.
800    pub fn into_requested_info(self) -> GraphchainInfo {
801        let Self {
802            acquire_timeout,
803            clipped,
804            composite_alpha,
805            frame_capacity,
806            frame_count: _,
807            height,
808            image_count: _,
809            min_image_count,
810            present_mode,
811            surface,
812            width,
813        } = self;
814
815        GraphchainInfo {
816            acquire_timeout,
817            clipped,
818            composite_alpha,
819            frame_capacity,
820            height,
821            min_image_count,
822            present_mode,
823            surface,
824            width,
825        }
826    }
827}
828
829impl From<EffectiveGraphchainInfo> for GraphchainInfo {
830    fn from(info: EffectiveGraphchainInfo) -> Self {
831        info.into_requested_info()
832    }
833}
834
835impl From<GraphchainInfoBuilder> for GraphchainInfo {
836    fn from(info: GraphchainInfoBuilder) -> Self {
837        info.build()
838    }
839}
840
841impl From<GraphchainInfo> for SwapchainInfo {
842    fn from(val: GraphchainInfo) -> Self {
843        SwapchainInfoBuilder::default()
844            .acquire_timeout(val.acquire_timeout)
845            .clipped(val.clipped)
846            .composite_alpha(val.composite_alpha)
847            .height(val.height)
848            .min_image_count(val.min_image_count)
849            .present_mode(val.present_mode)
850            .surface(val.surface)
851            .width(val.width)
852            .build()
853    }
854}
855
856impl From<GraphchainInfoBuilder> for SwapchainInfo {
857    fn from(info: GraphchainInfoBuilder) -> Self {
858        info.build().into_swapchain_info()
859    }
860}
861
862impl GraphchainInfoBuilder {
863    /// Builds a new `GraphchainInfo`.
864    #[inline(always)]
865    pub fn build(self) -> GraphchainInfo {
866        self.fallible_build().expect("all fields have defaults")
867    }
868}
869
870enum PresentRetirementStrategy {
871    PresentWait(PresentWait),
872    PerImageSemaphore(PerImageSemaphore),
873}
874
875impl PresentRetirementStrategy {
876    fn prepare_frame(
877        &mut self,
878        device: &Device,
879        swapchain: &mut Swapchain,
880        frame_idx: usize,
881    ) -> Result<(), GraphchainError> {
882        match self {
883            Self::PresentWait(strategy) => strategy.prepare_frame(device, swapchain, frame_idx),
884            Self::PerImageSemaphore(strategy) => {
885                strategy.prepare_frame(device, swapchain, frame_idx)
886            }
887        }
888    }
889
890    fn acquire_image(
891        &mut self,
892        device: &Device,
893        frame_idx: usize,
894        image_idx: u32,
895    ) -> Result<(), DriverError> {
896        match self {
897            Self::PresentWait(strategy) => strategy.acquire_image(device, frame_idx, image_idx),
898            Self::PerImageSemaphore(strategy) => {
899                strategy.acquire_image(device, frame_idx, image_idx)
900            }
901        }
902    }
903
904    fn rendered_semaphore(
905        &mut self,
906        device: &Device,
907        frame_idx: usize,
908        image_idx: u32,
909    ) -> Result<vk::Semaphore, DriverError> {
910        match self {
911            Self::PresentWait(strategy) => {
912                strategy.rendered_semaphore(device, frame_idx, image_idx)
913            }
914            Self::PerImageSemaphore(strategy) => {
915                strategy.rendered_semaphore(device, frame_idx, image_idx)
916            }
917        }
918    }
919
920    fn present_image(&mut self, frame_idx: usize, image_idx: u32) {
921        match self {
922            Self::PresentWait(strategy) => strategy.present_image(frame_idx, image_idx),
923            Self::PerImageSemaphore(strategy) => strategy.present_image(frame_idx, image_idx),
924        }
925    }
926
927    fn retire_pending(&mut self, swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
928        match self {
929            Self::PresentWait(strategy) => strategy.retire_pending(swapchain),
930            Self::PerImageSemaphore(strategy) => strategy.retire_pending(swapchain),
931        }
932    }
933
934    fn reset(&mut self, device: &Device) {
935        match self {
936            Self::PresentWait(strategy) => strategy.reset(device),
937            Self::PerImageSemaphore(strategy) => strategy.reset(device),
938        }
939    }
940
941    fn destroy(&mut self, device: &Device) {
942        match self {
943            Self::PresentWait(strategy) => strategy.destroy(device),
944            Self::PerImageSemaphore(strategy) => strategy.destroy(device),
945        }
946    }
947}
948
949#[derive(Default)]
950struct PresentWait {
951    retired: Vec<vk::Semaphore>,
952    rendered: Vec<Option<vk::Semaphore>>,
953    pending_images: Vec<Option<u32>>,
954}
955
956impl PresentWait {
957    fn prepare_frame(
958        &mut self,
959        _device: &Device,
960        swapchain: &mut Swapchain,
961        frame_idx: usize,
962    ) -> Result<(), GraphchainError> {
963        while self.pending_images.len() <= frame_idx {
964            self.pending_images.push(None);
965        }
966
967        if let Some(image_idx) = self.pending_images[frame_idx].take() {
968            unsafe {
969                match swapchain.wait_for_present_image(image_idx, u64::MAX) {
970                    Ok(()) => {}
971                    Err(SwapchainError::DeviceLost) => {
972                        return Err(GraphchainError::DeviceLost);
973                    }
974                    Err(SwapchainError::Driver(err)) => {
975                        return Err(GraphchainError::Driver(err));
976                    }
977                    Err(SwapchainError::FullScreenExclusiveModeLost) => {}
978                    Err(SwapchainError::NotReady | SwapchainError::Timeout) => {
979                        return Err(GraphchainError::Driver(DriverError::Unsupported));
980                    }
981                    Err(SwapchainError::SurfaceLost) => {
982                        return Err(GraphchainError::SurfaceLost);
983                    }
984                }
985            }
986        }
987
988        Ok(())
989    }
990
991    fn acquire_image(
992        &mut self,
993        device: &Device,
994        _frame_idx: usize,
995        image_idx: u32,
996    ) -> Result<(), DriverError> {
997        self.ensure_rendered(device, image_idx).map(|_| ())
998    }
999
1000    fn rendered_semaphore(
1001        &mut self,
1002        device: &Device,
1003        _frame_idx: usize,
1004        image_idx: u32,
1005    ) -> Result<vk::Semaphore, DriverError> {
1006        self.ensure_rendered(device, image_idx)
1007    }
1008
1009    fn ensure_rendered(
1010        &mut self,
1011        device: &Device,
1012        image_idx: u32,
1013    ) -> Result<vk::Semaphore, DriverError> {
1014        while self.rendered.len() <= image_idx as usize {
1015            self.rendered.push(None);
1016        }
1017
1018        if self.rendered[image_idx as usize].is_none() {
1019            let semaphore = Device::create_semaphore(device)?;
1020            Device::try_set_debug_utils_object_name(
1021                device,
1022                semaphore,
1023                "graphchain present-wait per-image rendered semaphore",
1024            );
1025
1026            self.rendered[image_idx as usize] = Some(semaphore);
1027        }
1028
1029        Ok(self.rendered[image_idx as usize].expect("missing rendered semaphore"))
1030    }
1031
1032    fn present_image(&mut self, frame_idx: usize, image_idx: u32) {
1033        while self.pending_images.len() <= frame_idx {
1034            self.pending_images.push(None);
1035        }
1036
1037        self.pending_images[frame_idx] = Some(image_idx);
1038    }
1039
1040    fn retire_pending(&mut self, swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
1041        for image_idx in self.pending_images.drain(..).flatten() {
1042            unsafe {
1043                match swapchain.wait_for_present_image(image_idx, u64::MAX) {
1044                    Ok(()) | Err(SwapchainError::FullScreenExclusiveModeLost) => {}
1045                    Err(SwapchainError::DeviceLost) => return Err(GraphchainError::DeviceLost),
1046                    Err(SwapchainError::Driver(err)) => return Err(GraphchainError::Driver(err)),
1047                    Err(SwapchainError::NotReady | SwapchainError::Timeout) => {
1048                        return Err(GraphchainError::Driver(DriverError::Unsupported));
1049                    }
1050                    Err(SwapchainError::SurfaceLost) => return Err(GraphchainError::SurfaceLost),
1051                }
1052            }
1053        }
1054
1055        Ok(())
1056    }
1057
1058    fn reset(&mut self, device: &Device) {
1059        self.destroy_retired(device);
1060        self.pending_images.clear();
1061        self.retired.extend(self.rendered.drain(..).flatten());
1062    }
1063
1064    fn destroy(&mut self, device: &Device) {
1065        self.destroy_retired(device);
1066
1067        for semaphore in self
1068            .rendered
1069            .drain(..)
1070            .flatten()
1071            .chain(self.retired.drain(..))
1072        {
1073            unsafe {
1074                device.destroy_semaphore(semaphore, None);
1075            }
1076        }
1077    }
1078
1079    fn destroy_retired(&mut self, device: &Device) {
1080        for semaphore in self.retired.drain(..) {
1081            unsafe {
1082                device.destroy_semaphore(semaphore, None);
1083            }
1084        }
1085    }
1086}
1087
1088#[derive(Default)]
1089struct PerImageSemaphore {
1090    retired: Vec<vk::Semaphore>,
1091    rendered: Vec<Option<vk::Semaphore>>,
1092}
1093
1094impl PerImageSemaphore {
1095    fn prepare_frame(
1096        &mut self,
1097        _device: &Device,
1098        _swapchain: &mut Swapchain,
1099        _frame_idx: usize,
1100    ) -> Result<(), GraphchainError> {
1101        Ok(())
1102    }
1103
1104    fn acquire_image(
1105        &mut self,
1106        device: &Device,
1107        _frame_idx: usize,
1108        image_idx: u32,
1109    ) -> Result<(), DriverError> {
1110        self.ensure_rendered(device, image_idx).map(|_| ())
1111    }
1112
1113    fn rendered_semaphore(
1114        &mut self,
1115        device: &Device,
1116        _frame_idx: usize,
1117        image_idx: u32,
1118    ) -> Result<vk::Semaphore, DriverError> {
1119        self.ensure_rendered(device, image_idx)
1120    }
1121
1122    fn ensure_rendered(
1123        &mut self,
1124        device: &Device,
1125        image_idx: u32,
1126    ) -> Result<vk::Semaphore, DriverError> {
1127        while self.rendered.len() <= image_idx as usize {
1128            self.rendered.push(None);
1129        }
1130
1131        if self.rendered[image_idx as usize].is_none() {
1132            let semaphore = Device::create_semaphore(device)?;
1133            Device::try_set_debug_utils_object_name(
1134                device,
1135                semaphore,
1136                "graphchain per-image rendered semaphore",
1137            );
1138
1139            self.rendered[image_idx as usize] = Some(semaphore);
1140        }
1141
1142        Ok(self.rendered[image_idx as usize].expect("missing rendered semaphore"))
1143    }
1144
1145    fn present_image(&mut self, _frame_idx: usize, _image_idx: u32) {}
1146
1147    fn retire_pending(&mut self, _swapchain: &mut Swapchain) -> Result<(), GraphchainError> {
1148        Ok(())
1149    }
1150
1151    fn reset(&mut self, device: &Device) {
1152        self.destroy_retired(device);
1153        self.retired.extend(self.rendered.drain(..).flatten());
1154    }
1155
1156    fn destroy(&mut self, device: &Device) {
1157        self.destroy_retired(device);
1158
1159        for semaphore in self
1160            .rendered
1161            .drain(..)
1162            .flatten()
1163            .chain(self.retired.drain(..))
1164        {
1165            unsafe {
1166                device.destroy_semaphore(semaphore, None);
1167            }
1168        }
1169    }
1170
1171    fn destroy_retired(&mut self, device: &Device) {
1172        for semaphore in self.retired.drain(..) {
1173            unsafe {
1174                device.destroy_semaphore(semaphore, None);
1175            }
1176        }
1177    }
1178}
1179
1180struct FrameSlot {
1181    cmd_buf: CommandBuffer,
1182    fence: Fence,
1183    swapchain_acquired: vk::Semaphore,
1184}
1185
1186/// Query result proxies for graph work submitted by [`Graphchain::present_image`].
1187///
1188/// These handles are detached from the frame slot when it is reused. Timestamp completion is
1189/// reported after `Graphchain` observes the submitted frame fence signal.
1190///
1191/// This does not describe presentation status; presentation failures are reported through
1192/// [`GraphchainError`].
1193#[derive(Clone, Debug)]
1194#[non_exhaustive]
1195pub struct SubmissionQueryPool {
1196    /// Timestamp query results for the submitted work.
1197    pub timestamps: TimestampQueryPool,
1198}
1199
1200#[cfg(test)]
1201mod test {
1202    use super::*;
1203
1204    type Info = GraphchainInfo;
1205    type Builder = GraphchainInfoBuilder;
1206    type Effective = EffectiveGraphchainInfo;
1207
1208    #[test]
1209    fn graphchain_info_round_trips_through_builder() {
1210        let info = Info {
1211            acquire_timeout: 42,
1212            clipped: false,
1213            composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1214            frame_capacity: 6,
1215            height: 123,
1216            min_image_count: 3,
1217            present_mode: vk::PresentModeKHR::FIFO,
1218            surface: vk::SurfaceFormatKHR::default()
1219                .format(vk::Format::B8G8R8A8_UNORM)
1220                .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1221            width: 456,
1222        };
1223
1224        assert_eq!(info, info.into_builder().build());
1225    }
1226
1227    #[test]
1228    fn graphchain_info_builder_defaults() {
1229        assert_eq!(
1230            Builder::default().build(),
1231            Info {
1232                acquire_timeout: u64::MAX,
1233                clipped: true,
1234                composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
1235                frame_capacity: 4,
1236                height: 0,
1237                min_image_count: 2,
1238                present_mode: vk::PresentModeKHR::MAILBOX,
1239                surface: vk::SurfaceFormatKHR::default(),
1240                width: 0,
1241            }
1242        );
1243    }
1244
1245    #[test]
1246    fn effective_graphchain_info_into_requested_info() {
1247        let info = Effective {
1248            acquire_timeout: 42,
1249            clipped: false,
1250            composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1251            frame_capacity: 6,
1252            frame_count: 8,
1253            height: 123,
1254            image_count: 7,
1255            min_image_count: 3,
1256            present_mode: vk::PresentModeKHR::FIFO,
1257            surface: vk::SurfaceFormatKHR::default()
1258                .format(vk::Format::B8G8R8A8_UNORM)
1259                .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1260            width: 456,
1261        };
1262
1263        assert_eq!(
1264            info.into_requested_info(),
1265            Info {
1266                acquire_timeout: 42,
1267                clipped: false,
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    }
1280
1281    // #[test]
1282    // fn effective_graphchain_info_from_requested_info() {
1283    //     let info = Info {
1284    //         acquire_timeout: 42,
1285    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1286    //         frame_capacity: 6,
1287    //         height: 123,
1288    //         min_image_count: 3,
1289    //         present_mode: vk::PresentModeKHR::FIFO,
1290    //         surface: vk::SurfaceFormatKHR::default()
1291    //             .format(vk::Format::B8G8R8A8_UNORM)
1292    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1293    //         width: 456,
1294    //     };
1295
1296    //     assert_eq!(
1297    //         Effective::from(info),
1298    //         Effective {
1299    //             acquire_timeout: 42,
1300    //             composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1301    //             frame_capacity: 6,
1302    //             frame_count: 6,
1303    //             height: 123,
1304    //             image_count: 0,
1305    //             present_mode: vk::PresentModeKHR::FIFO,
1306    //             surface: vk::SurfaceFormatKHR::default()
1307    //                 .format(vk::Format::B8G8R8A8_UNORM)
1308    //                 .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1309    //             width: 456,
1310    //         }
1311    //     );
1312    // }
1313
1314    // #[test]
1315    // fn effective_graphchain_info_has_only_runtime_fields() {
1316    //     let info = Effective {
1317    //         acquire_timeout: 42,
1318    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1319    //         frame_capacity: 6,
1320    //         frame_count: 8,
1321    //         height: 123,
1322    //         image_count: 7,
1323    //         present_mode: vk::PresentModeKHR::FIFO,
1324    //         surface: vk::SurfaceFormatKHR::default()
1325    //             .format(vk::Format::B8G8R8A8_UNORM)
1326    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1327    //         width: 456,
1328    //     };
1329
1330    //     assert_eq!(info.frame_count, 8);
1331    //     assert_eq!(info.image_count, 7);
1332    // }
1333
1334    // #[test]
1335    // fn effective_graphchain_info_round_trips_through_builder() {
1336    //     let info = Effective {
1337    //         acquire_timeout: 42,
1338    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1339    //         frame_capacity: 6,
1340    //         frame_count: 8,
1341    //         height: 123,
1342    //         image_count: 7,
1343    //         present_mode: vk::PresentModeKHR::FIFO,
1344    //         surface: vk::SurfaceFormatKHR::default()
1345    //             .format(vk::Format::B8G8R8A8_UNORM)
1346    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1347    //         width: 456,
1348    //     };
1349
1350    //     assert_eq!(info, info.into_builder().build());
1351    // }
1352
1353    // #[test]
1354    // fn effective_graphchain_info_builder_defaults() {
1355    //     assert_eq!(
1356    //         EffectiveBuilder::default().build(),
1357    //         Effective {
1358    //             acquire_timeout: u64::MAX,
1359    //             composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
1360    //             frame_capacity: 4,
1361    //             frame_count: 4,
1362    //             height: 0,
1363    //             image_count: 0,
1364    //             present_mode: vk::PresentModeKHR::MAILBOX,
1365    //             surface: vk::SurfaceFormatKHR::default(),
1366    //             width: 0,
1367    //         }
1368    //     );
1369    // }
1370
1371    // #[test]
1372    // fn effective_graphchain_info_is_not_requested_info() {
1373    //     let info = Info {
1374    //         acquire_timeout: 42,
1375    //         composite_alpha: vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
1376    //         frame_capacity: 6,
1377    //         height: 123,
1378    //         min_image_count: 3,
1379    //         present_mode: vk::PresentModeKHR::FIFO,
1380    //         surface: vk::SurfaceFormatKHR::default()
1381    //             .format(vk::Format::B8G8R8A8_UNORM)
1382    //             .color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR),
1383    //         width: 456,
1384    //     };
1385
1386    //     assert_eq!(Effective::from(info).image_count, 0);
1387    // }
1388}