Skip to main content

vk_graph/
submission.rs

1//! Submission and recording types.
2//!
3//! This module contains the execution-facing types produced by [`Graph::finalize`].
4//!
5//! Typical usage starts with a [`Submission`], which represents a finalized graph that has not yet
6//! been bound to a command buffer:
7//!
8//! - Use [`Submission::queue_submit`] for the one-shot path that allocates, records, and submits a
9//!   command buffer internally.
10//! - Use [`Submission::record`] with a [`RecordSelection`] to bind the submission to an existing
11//!   command buffer and obtain a [`Recording`].
12//!
13//! A [`Recording`] keeps the remaining graph work paired with the command buffer it was
14//! recorded into. This typestate prevents recording with one command buffer and accidentally
15//! submitting with another.
16//!
17//! [`Graph::finalize`]: crate::Graph::finalize
18
19use {
20    super::{
21        AnyResource, Attachment, CommandData, ExecutionAccess, ExecutionPipeline, Graph, LoadOp,
22        Node, NodeIndex, TimestampQueryData, TimestampQueryPlacement,
23        cmd::{SubresourceAccess, SubresourceRange},
24    },
25    crate::{
26        StoreOp, TimestampQuery,
27        cmd::CommandRef,
28        driver::{
29            AttachmentInfo, AttachmentRef, Descriptor, DescriptorInfo, DriverError,
30            FramebufferAttachmentImageInfo, FramebufferInfo, RawDescriptorSet, SharingMode,
31            SubpassDependency, SubpassInfo,
32            accel_struct::AccelerationStructure,
33            buffer::{Buffer, BufferSubresourceRange},
34            cmd_buf::{CommandBuffer, CommandBufferInfo},
35            descriptor_set::{DescriptorPool, DescriptorPoolInfo, DescriptorSet},
36            device::Device,
37            fence::{Fence, FenceDroppable},
38            format_aspect_mask,
39            graphics::{DepthStencilInfo, GraphicsPipeline},
40            image::{
41                DenseMap, Image, ImageInfo, image_subresource_range_contains,
42                image_subresource_range_intersection,
43            },
44            initial_image_layout_access, is_read_access,
45            physical_device::Vulkan10Limits,
46            pipeline_stage_access_flags,
47            query_pool::{QueryPool, QueryPoolInfo},
48            render_pass::{RenderPass, RenderPassInfo},
49        },
50        lazy_str,
51        node::AnyNode,
52        pool::{Lease, Pool, SubmissionPool},
53    },
54    ash::vk::{self, QueueFamilyProperties},
55    fixedbitset::FixedBitSet,
56    log::{
57        Level::{Debug, Trace},
58        debug, log_enabled, trace, warn,
59    },
60    smallvec::SmallVec,
61    std::{
62        cell::RefCell,
63        cmp::Reverse,
64        collections::{BTreeMap, BTreeSet, HashMap},
65        iter::repeat_n,
66        mem::take,
67        ops::Range,
68        slice,
69        sync::{Arc, Mutex},
70        time::Duration,
71    },
72    vk_sync::{
73        AccessType, BufferBarrier, GlobalBarrier, ImageBarrier, ImageLayout,
74        get_buffer_memory_barrier, get_image_memory_barrier, get_memory_barrier,
75    },
76};
77
78#[cfg(feature = "checked")]
79use super::GraphId;
80
81#[cfg(not(feature = "checked"))]
82use std::hint::unreachable_unchecked;
83
84thread_local! {
85    static SUBMIT: RefCell<SubmitScratch> = Default::default();
86}
87
88fn aspect_mask_for_span(base_aspect: u32, start: u32, end: u32) -> vk::ImageAspectFlags {
89    let mut mask = vk::ImageAspectFlags::empty();
90
91    for ordinal in start..end {
92        mask |= vk::ImageAspectFlags::from_raw(1 << (base_aspect + ordinal));
93    }
94
95    mask
96}
97
98fn buffer_barriers_from_transfers<'a>(
99    buffer: vk::Buffer,
100    prev_access: &'a AccessType,
101    next_access: &'a AccessType,
102    range: BufferSubresourceRange,
103    transfers: &'a [BufferQueueOwnershipTransfer],
104) -> impl Iterator<Item = BufferBarrier<'a>> + 'a {
105    struct BufferBarrierIter<'a> {
106        buffer: vk::Buffer,
107        cuts: SmallVec<[vk::DeviceSize; 4]>,
108        cut_idx: usize,
109        next_access: &'a AccessType,
110        prev_access: &'a AccessType,
111        transfers: &'a [BufferQueueOwnershipTransfer],
112    }
113
114    impl<'a> Iterator for BufferBarrierIter<'a> {
115        type Item = BufferBarrier<'a>;
116
117        fn next(&mut self) -> Option<Self::Item> {
118            while self.cut_idx + 1 < self.cuts.len() {
119                let range = BufferSubresourceRange {
120                    start: self.cuts[self.cut_idx],
121                    end: self.cuts[self.cut_idx + 1],
122                };
123                self.cut_idx += 1;
124
125                if range.start == range.end {
126                    continue;
127                }
128
129                let transfer = self
130                    .transfers
131                    .iter()
132                    .find(|transfer| transfer.range.contains(range));
133
134                trace!(
135                    "    buffer {:?} {:?} {:?}->{:?}",
136                    self.buffer,
137                    range.start..range.end,
138                    self.prev_access,
139                    self.next_access,
140                );
141
142                return Some(BufferBarrier {
143                    next_accesses: slice::from_ref(self.next_access),
144                    previous_accesses: slice::from_ref(self.prev_access),
145                    src_queue_family_index: transfer.map_or(vk::QUEUE_FAMILY_IGNORED, |transfer| {
146                        transfer.src_queue_family_index
147                    }),
148                    dst_queue_family_index: transfer.map_or(vk::QUEUE_FAMILY_IGNORED, |transfer| {
149                        transfer.dst_queue_family_index
150                    }),
151                    buffer: self.buffer,
152                    offset: range.start as _,
153                    size: (range.end - range.start) as _,
154                });
155            }
156
157            None
158        }
159    }
160
161    let mut cuts = SmallVec::<[vk::DeviceSize; 4]>::with_capacity(
162        transfers.len().saturating_mul(2).saturating_add(2),
163    );
164    cuts.extend([range.start, range.end]);
165
166    for transfer in transfers {
167        if let Some(overlap) = range.intersection(transfer.range) {
168            cuts.push(overlap.start);
169            cuts.push(overlap.end);
170        }
171    }
172
173    cuts.sort_unstable();
174    cuts.dedup();
175
176    BufferBarrierIter {
177        buffer,
178        cuts,
179        cut_idx: 0,
180        next_access,
181        prev_access,
182        transfers,
183    }
184}
185
186fn buffer_subresource_range_intersects(
187    lhs: BufferSubresourceRange,
188    rhs: BufferSubresourceRange,
189) -> bool {
190    lhs.start < rhs.end && lhs.end > rhs.start
191}
192
193fn check_queue_submit_args(
194    waits: &[SemaphoreSubmitInfo],
195    signals: &[SemaphoreSubmitInfo],
196) -> Result<(), DriverError> {
197    waits
198        .iter()
199        .chain(signals.iter())
200        .all(SemaphoreSubmitInfo::is_supported_legacy_submit)
201        .then_some(())
202        .ok_or(DriverError::Unsupported)
203}
204
205fn check_queue_submit2_args(
206    device: &Device,
207    waits: &[SemaphoreSubmit2Info],
208    signals: &[SemaphoreSubmit2Info],
209) -> Result<(), DriverError> {
210    if !device.physical.vk_khr_synchronization2 {
211        return Err(DriverError::Unsupported);
212    }
213
214    if (waits.iter().any(|wait| wait.value != 0) || signals.iter().any(|signal| signal.value != 0))
215        && !supports_timeline_semaphores(device)
216    {
217        return Err(DriverError::Unsupported);
218    }
219
220    Ok(())
221}
222
223fn consume_pending_buffer_transfers(
224    transfers: &mut Vec<BufferQueueOwnershipTransfer>,
225    range: BufferSubresourceRange,
226) -> bool {
227    transfers.retain(|transfer| !buffer_subresource_range_intersects(transfer.range, range));
228    transfers.is_empty()
229}
230
231fn consume_pending_image_transfers(
232    transfers: &mut Vec<ImageQueueOwnershipTransfer>,
233    range: vk::ImageSubresourceRange,
234) -> bool {
235    transfers
236        .retain(|transfer| image_subresource_range_intersection(transfer.range, range).is_none());
237    transfers.is_empty()
238}
239
240fn exclusive_transfer_source(sharing: SharingMode, queue_family_index: u32) -> Option<(u32, u32)> {
241    let SharingMode::Exclusive(Some((src_queue_family_index, src_queue_index))) = sharing else {
242        return None;
243    };
244
245    (src_queue_family_index != queue_family_index)
246        .then_some((src_queue_family_index, src_queue_index))
247}
248
249const fn image_access_layout(access: AccessType) -> ImageLayout {
250    if matches!(access, AccessType::Present | AccessType::ComputeShaderWrite) {
251        ImageLayout::General
252    } else {
253        ImageLayout::Optimal
254    }
255}
256
257fn image_barriers_from_transfers<'a>(
258    image: vk::Image,
259    prev_access: &'a AccessType,
260    next_access: &'a AccessType,
261    range: vk::ImageSubresourceRange,
262    transfers: &'a [ImageQueueOwnershipTransfer],
263    discard_contents: bool,
264) -> impl Iterator<Item = ImageBarrier<'a>> + 'a {
265    image_barrier_transfer_ranges(transfers, range).map(move |(range, transfer)| {
266        trace!(
267            "    image {:?} {:?} {:?}->{:?}",
268            image,
269            ImageSubresourceRangeDebug(range),
270            prev_access,
271            next_access,
272        );
273
274        ImageBarrier {
275            next_accesses: slice::from_ref(next_access),
276            next_layout: image_access_layout(*next_access),
277            previous_accesses: slice::from_ref(prev_access),
278            previous_layout: image_access_layout(*prev_access),
279            discard_contents,
280            src_queue_family_index: transfer.map_or(vk::QUEUE_FAMILY_IGNORED, |transfer| {
281                transfer.src_queue_family_index
282            }),
283            dst_queue_family_index: transfer.map_or(vk::QUEUE_FAMILY_IGNORED, |transfer| {
284                transfer.dst_queue_family_index
285            }),
286            image,
287            range,
288        }
289    })
290}
291
292fn image_barrier_transfer_ranges<'a>(
293    transfers: &'a [ImageQueueOwnershipTransfer],
294    range: vk::ImageSubresourceRange,
295) -> impl Iterator<
296    Item = (
297        vk::ImageSubresourceRange,
298        Option<&'a ImageQueueOwnershipTransfer>,
299    ),
300> + 'a {
301    thread_local! {
302        static IMAGE_TRANSFER: RefCell<ImageTransferScratch> = Default::default();
303    }
304
305    #[derive(Default)]
306    struct ImageTransferScratch {
307        overlaps: Vec<(usize, vk::ImageSubresourceRange)>,
308        aspect_cuts: Vec<u32>,
309        layer_cuts: Vec<u32>,
310        mip_cuts: Vec<u32>,
311    }
312
313    struct ImageBarrierTransferIter<'a> {
314        transfers: &'a [ImageQueueOwnershipTransfer],
315        overlaps: Vec<(usize, vk::ImageSubresourceRange)>,
316        aspect_cuts: Vec<u32>,
317        layer_cuts: Vec<u32>,
318        mip_cuts: Vec<u32>,
319        base_aspect: u32,
320        range: vk::ImageSubresourceRange,
321        aspect_idx: usize,
322        layer_idx: usize,
323        mip_idx: usize,
324        yielded_empty: bool,
325    }
326
327    impl<'a> Iterator for ImageBarrierTransferIter<'a> {
328        type Item = (
329            vk::ImageSubresourceRange,
330            Option<&'a ImageQueueOwnershipTransfer>,
331        );
332
333        fn next(&mut self) -> Option<Self::Item> {
334            if self.overlaps.is_empty() {
335                return if self.yielded_empty {
336                    None
337                } else {
338                    self.yielded_empty = true;
339                    Some((self.range, None))
340                };
341            }
342
343            let aspect_windows = self.aspect_cuts.len().saturating_sub(1);
344            let layer_windows = self.layer_cuts.len().saturating_sub(1);
345            let mip_windows = self.mip_cuts.len().saturating_sub(1);
346
347            while self.aspect_idx < aspect_windows {
348                let aspect_start = self.aspect_cuts[self.aspect_idx];
349                let aspect_end = self.aspect_cuts[self.aspect_idx + 1];
350                if aspect_start == aspect_end {
351                    self.aspect_idx += 1;
352                    self.layer_idx = 0;
353                    self.mip_idx = 0;
354                    continue;
355                }
356
357                let aspect_mask = aspect_mask_for_span(self.base_aspect, aspect_start, aspect_end);
358
359                while self.layer_idx < layer_windows {
360                    let layer_start = self.layer_cuts[self.layer_idx];
361                    let layer_end = self.layer_cuts[self.layer_idx + 1];
362                    if layer_start == layer_end {
363                        self.layer_idx += 1;
364                        self.mip_idx = 0;
365                        continue;
366                    }
367
368                    while self.mip_idx < mip_windows {
369                        let mip_start = self.mip_cuts[self.mip_idx];
370                        let mip_end = self.mip_cuts[self.mip_idx + 1];
371                        self.mip_idx += 1;
372                        if mip_start == mip_end {
373                            continue;
374                        }
375
376                        let subrange = vk::ImageSubresourceRange {
377                            aspect_mask,
378                            base_array_layer: self.range.base_array_layer + layer_start,
379                            layer_count: layer_end - layer_start,
380                            base_mip_level: self.range.base_mip_level + mip_start,
381                            level_count: mip_end - mip_start,
382                        };
383
384                        let transfer = self
385                            .overlaps
386                            .iter()
387                            .find(|(_, overlap)| {
388                                image_subresource_range_contains(*overlap, subrange)
389                            })
390                            .map(|(transfer_idx, _)| &self.transfers[*transfer_idx]);
391
392                        return Some((subrange, transfer));
393                    }
394
395                    self.layer_idx += 1;
396                    self.mip_idx = 0;
397                }
398
399                self.aspect_idx += 1;
400                self.layer_idx = 0;
401                self.mip_idx = 0;
402            }
403
404            None
405        }
406    }
407
408    impl Drop for ImageBarrierTransferIter<'_> {
409        fn drop(&mut self) {
410            IMAGE_TRANSFER.with_borrow_mut(|tls| {
411                tls.overlaps = take(&mut self.overlaps);
412                tls.aspect_cuts = take(&mut self.aspect_cuts);
413                tls.layer_cuts = take(&mut self.layer_cuts);
414                tls.mip_cuts = take(&mut self.mip_cuts);
415            });
416        }
417    }
418
419    IMAGE_TRANSFER.with_borrow_mut(|tls| {
420        let mut overlaps = take(&mut tls.overlaps);
421        let mut aspect_cuts = take(&mut tls.aspect_cuts);
422        let mut layer_cuts = take(&mut tls.layer_cuts);
423        let mut mip_cuts = take(&mut tls.mip_cuts);
424
425        overlaps.clear();
426        aspect_cuts.clear();
427        layer_cuts.clear();
428        mip_cuts.clear();
429
430        overlaps.extend(
431            transfers
432                .iter()
433                .enumerate()
434                .filter_map(|(transfer_idx, transfer)| {
435                    image_subresource_range_intersection(transfer.range, range)
436                        .map(|intersection| (transfer_idx, intersection))
437                }),
438        );
439
440        let base_aspect = range.aspect_mask.as_raw().trailing_zeros();
441
442        if overlaps.is_empty() {
443            // Yield the whole range once when there is no overlapping transfer
444        } else {
445            let aspect_count = range.aspect_mask.as_raw().count_ones();
446
447            aspect_cuts.extend([0, aspect_count]);
448            layer_cuts.extend([0, range.layer_count]);
449            mip_cuts.extend([0, range.level_count]);
450
451            for (_, overlap) in &overlaps {
452                let aspect_start = overlap.aspect_mask.as_raw().trailing_zeros() - base_aspect;
453                let aspect_end = aspect_start + overlap.aspect_mask.as_raw().count_ones();
454                aspect_cuts.push(aspect_start);
455                aspect_cuts.push(aspect_end);
456
457                let layer_start = overlap.base_array_layer - range.base_array_layer;
458                let layer_end = layer_start + overlap.layer_count;
459                layer_cuts.push(layer_start);
460                layer_cuts.push(layer_end);
461
462                let mip_start = overlap.base_mip_level - range.base_mip_level;
463                let mip_end = mip_start + overlap.level_count;
464                mip_cuts.push(mip_start);
465                mip_cuts.push(mip_end);
466            }
467
468            aspect_cuts.sort_unstable();
469            aspect_cuts.dedup();
470            layer_cuts.sort_unstable();
471            layer_cuts.dedup();
472            mip_cuts.sort_unstable();
473            mip_cuts.dedup();
474        }
475
476        ImageBarrierTransferIter {
477            transfers,
478            overlaps,
479            aspect_cuts,
480            layer_cuts,
481            mip_cuts,
482            base_aspect,
483            range,
484            aspect_idx: 0,
485            layer_idx: 0,
486            mip_idx: 0,
487            yielded_empty: false,
488        }
489    })
490}
491
492fn image_execution_discard_contents(prev_access: AccessType) -> bool {
493    prev_access == AccessType::Nothing
494}
495
496fn image_layout_transition_discard_contents(
497    prev_access: AccessType,
498    next_access: AccessType,
499) -> bool {
500    // Read/modify/write accesses must preserve the existing image contents
501    // Check for "not-read" here because some accesses both read and write
502    // Color Attachment Read/Write (blending) will prevent discarding contents
503    prev_access == AccessType::Nothing || !is_read_access(next_access)
504}
505
506fn image_subresource_range_eq(
507    lhs: vk::ImageSubresourceRange,
508    rhs: vk::ImageSubresourceRange,
509) -> bool {
510    lhs.aspect_mask == rhs.aspect_mask
511        && lhs.base_array_layer == rhs.base_array_layer
512        && lhs.layer_count == rhs.layer_count
513        && lhs.base_mip_level == rhs.base_mip_level
514        && lhs.level_count == rhs.level_count
515}
516
517// Added because vk-sync requires allocation to record barriers, see that impl for reference
518fn pipeline_barrier_from_iters<'a>(
519    device: &Device,
520    command_buffer: vk::CommandBuffer,
521    global_barrier: Option<GlobalBarrier<'a>>,
522    buffer_barriers: impl IntoIterator<Item = BufferBarrier<'a>>,
523    image_barriers: impl IntoIterator<Item = ImageBarrier<'a>>,
524) {
525    #[derive(Default)]
526    struct BarrierScratch {
527        memory_barriers: Vec<vk::MemoryBarrier<'static>>,
528        buffer_barriers: Vec<vk::BufferMemoryBarrier<'static>>,
529        image_barriers: Vec<vk::ImageMemoryBarrier<'static>>,
530    }
531
532    thread_local! {
533        static BARRIER: RefCell<BarrierScratch> = Default::default();
534    }
535
536    BARRIER.with_borrow_mut(|tls| {
537        tls.memory_barriers.clear();
538        tls.buffer_barriers.clear();
539        tls.image_barriers.clear();
540
541        let mut src_stage_mask = vk::PipelineStageFlags::TOP_OF_PIPE;
542        let mut dst_stage_mask = vk::PipelineStageFlags::BOTTOM_OF_PIPE;
543
544        if let Some(ref barrier) = global_barrier {
545            let (src_mask, dst_mask, barrier) = get_memory_barrier(barrier);
546            src_stage_mask |= src_mask;
547            dst_stage_mask |= dst_mask;
548            tls.memory_barriers.push(vk::MemoryBarrier {
549                src_access_mask: barrier.src_access_mask,
550                dst_access_mask: barrier.dst_access_mask,
551                ..Default::default()
552            });
553        }
554
555        for buffer_barrier in buffer_barriers {
556            let (src_mask, dst_mask, barrier) = get_buffer_memory_barrier(&buffer_barrier);
557            src_stage_mask |= src_mask;
558            dst_stage_mask |= dst_mask;
559            tls.buffer_barriers.push(vk::BufferMemoryBarrier {
560                src_access_mask: barrier.src_access_mask,
561                dst_access_mask: barrier.dst_access_mask,
562                src_queue_family_index: barrier.src_queue_family_index,
563                dst_queue_family_index: barrier.dst_queue_family_index,
564                buffer: barrier.buffer,
565                offset: barrier.offset,
566                size: barrier.size,
567                ..Default::default()
568            });
569        }
570
571        for image_barrier in image_barriers {
572            let (src_mask, dst_mask, barrier) = get_image_memory_barrier(&image_barrier);
573            src_stage_mask |= src_mask;
574            dst_stage_mask |= dst_mask;
575            tls.image_barriers.push(vk::ImageMemoryBarrier {
576                src_access_mask: barrier.src_access_mask,
577                dst_access_mask: barrier.dst_access_mask,
578                old_layout: barrier.old_layout,
579                new_layout: barrier.new_layout,
580                src_queue_family_index: barrier.src_queue_family_index,
581                dst_queue_family_index: barrier.dst_queue_family_index,
582                image: barrier.image,
583                subresource_range: barrier.subresource_range,
584                ..Default::default()
585            });
586        }
587
588        unsafe {
589            device.cmd_pipeline_barrier(
590                command_buffer,
591                src_stage_mask,
592                dst_stage_mask,
593                vk::DependencyFlags::empty(),
594                tls.memory_barriers.as_slice(),
595                tls.buffer_barriers.as_slice(),
596                tls.image_barriers.as_slice(),
597            );
598        }
599    });
600}
601
602fn schedule_dependency_cmds_before_target_access(
603    target_node_idx: usize,
604    first_target_cmd_idx: usize,
605    schedule: &mut Schedule,
606) {
607    let required_prefixes = schedule
608        .access_index
609        .read_nodes_for_cmd(first_target_cmd_idx)
610        .filter(|&node_idx| node_idx != target_node_idx)
611        .map(|node_idx| (node_idx, first_target_cmd_idx))
612        .collect::<SmallVec<[_; 8]>>();
613
614    schedule.schedule_required_node_prefixes(required_prefixes);
615}
616
617fn submit_stage_mask_legacy(stage_mask: vk::PipelineStageFlags2) -> vk::PipelineStageFlags {
618    match stage_mask {
619        vk::PipelineStageFlags2::NONE => vk::PipelineStageFlags::ALL_COMMANDS,
620        vk::PipelineStageFlags2::ALL_COMMANDS => vk::PipelineStageFlags::ALL_COMMANDS,
621        _ => {
622            #[cfg(feature = "checked")]
623            panic!("invalid legacy submit wait stage mask: {stage_mask:?}");
624
625            #[cfg(not(feature = "checked"))]
626            {
627                vk::PipelineStageFlags::ALL_COMMANDS
628            }
629        }
630    }
631}
632
633fn supports_timeline_semaphores(device: &Device) -> bool {
634    device.physical.features_v1_2.timeline_semaphore
635}
636
637/// Builds and submits a release barrier command buffer for each release group, calling
638/// `submit_release` to perform the final queue submission.
639fn submit_queue_ownership_releases<P>(
640    pool: &mut P,
641    release_groups: &[QueueOwnershipReleaseGroup],
642    target_queue_family_index: u32,
643    submit_release: impl Fn(
644        &Device,
645        vk::Queue,
646        vk::CommandBuffer,
647        vk::Fence,
648        vk::Semaphore,
649    ) -> Result<(), DriverError>,
650) -> Result<Vec<QueueOwnershipRelease>, DriverError>
651where
652    P: Pool<CommandBufferInfo, CommandBuffer>,
653{
654    let mut releases = Vec::new();
655
656    if !release_groups.is_empty() {
657        for group in release_groups {
658            let mut release_cmd =
659                pool.resource(CommandBufferInfo::new(group.src_queue_family_index as _))?;
660            let mut release_fence = Fence::create(&release_cmd.device, false)?;
661
662            #[cfg(feature = "checked")]
663            {
664                release_fence.wait()?;
665                release_fence.reset()?;
666            }
667
668            let semaphore = release_cmd.release_semaphore()?;
669
670            release_cmd.set_debug_name(lazy_str!(
671                "queue ownership release qf{}:{} -> qf{}",
672                group.src_queue_family_index,
673                group.src_queue_index,
674                target_queue_family_index
675            ));
676
677            Device::begin_command_buffer(
678                &release_cmd.device,
679                release_cmd.handle,
680                &vk::CommandBufferBeginInfo::default()
681                    .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
682            )?;
683
684            {
685                let _ = CommandBufferDebugLabel::begin(
686                    &release_cmd,
687                    lazy_str!(
688                        "queue ownership release qf{}:{} -> qf{}",
689                        group.src_queue_family_index,
690                        group.src_queue_index,
691                        target_queue_family_index
692                    ),
693                );
694
695                SUBMIT.with_borrow_mut(|tls| {
696                    let _ = CommandBufferDebugLabel::begin(&release_cmd, "queue ownership barrier");
697
698                    tls.release_image_barriers.clear();
699                    tls.release_buffer_barriers.clear();
700                    tls.release_buffer_barriers.reserve(group.buffers.len());
701                    tls.release_image_barriers.reserve(group.images.len());
702
703                    tls.release_buffer_barriers.extend(group.buffers.iter().map(
704                        |&(handle, range)| {
705                            vk::BufferMemoryBarrier::default()
706                                .src_access_mask(vk::AccessFlags::MEMORY_WRITE)
707                                .dst_access_mask(vk::AccessFlags::empty())
708                                .src_queue_family_index(group.src_queue_family_index)
709                                .dst_queue_family_index(target_queue_family_index)
710                                .buffer(handle)
711                                .offset(range.start)
712                                .size(range.end - range.start)
713                        },
714                    ));
715
716                    tls.release_image_barriers.extend(group.images.iter().map(
717                        |&(handle, current_layout, subresource_range)| {
718                            vk::ImageMemoryBarrier::default()
719                                .src_access_mask(vk::AccessFlags::MEMORY_WRITE)
720                                .dst_access_mask(vk::AccessFlags::empty())
721                                .old_layout(current_layout)
722                                .new_layout(current_layout)
723                                .src_queue_family_index(group.src_queue_family_index)
724                                .dst_queue_family_index(target_queue_family_index)
725                                .image(handle)
726                                .subresource_range(subresource_range)
727                        },
728                    ));
729
730                    unsafe {
731                        release_cmd.device.cmd_pipeline_barrier(
732                            release_cmd.handle,
733                            vk::PipelineStageFlags::ALL_COMMANDS,
734                            vk::PipelineStageFlags::ALL_COMMANDS,
735                            vk::DependencyFlags::empty(),
736                            &[],
737                            tls.release_buffer_barriers.as_slice(),
738                            tls.release_image_barriers.as_slice(),
739                        );
740                    }
741                });
742
743                Device::with_queue(
744                    &release_cmd.device,
745                    group.src_queue_family_index,
746                    group.src_queue_index,
747                    |queue| {
748                        Device::end_command_buffer(&release_cmd.device, release_cmd.handle)?;
749                        submit_release(
750                            &release_cmd.device,
751                            queue,
752                            release_cmd.handle,
753                            release_fence.handle,
754                            semaphore,
755                        )?;
756
757                        release_fence.mark_queued();
758
759                        Ok::<_, DriverError>(())
760                    },
761                )?;
762            }
763
764            releases.push(QueueOwnershipRelease {
765                _cmd_buf: release_cmd,
766                _fence: release_fence,
767                semaphore,
768            });
769        }
770    }
771
772    Ok(releases)
773}
774
775#[derive(Clone, Copy, Debug)]
776struct BufferQueueOwnershipTransfer {
777    range: BufferSubresourceRange,
778    dst_queue_family_index: u32,
779    src_queue_family_index: u32,
780}
781
782#[derive(Clone, Default)]
783struct CommandAccessIndex {
784    cmds_by_node: Vec<Vec<usize>>,
785    accessed_nodes_by_cmd: Vec<Vec<usize>>,
786}
787
788impl CommandAccessIndex {
789    #[profiling::function]
790    fn read_nodes_for_cmd(&self, cmd_idx: usize) -> impl ExactSizeIterator<Item = usize> + '_ {
791        self.accessed_nodes_by_cmd[cmd_idx].iter().copied()
792    }
793
794    fn update(&mut self, graph: &Graph, end_cmd_idx: usize) {
795        let binding_count = graph.resources.len();
796        let cmds = &graph.cmds[0..end_cmd_idx];
797        self.update_from_cmds(cmds, binding_count);
798    }
799
800    fn update_from_cmds(&mut self, cmds: &[CommandData], binding_count: usize) {
801        self.cmds_by_node.clear();
802        self.cmds_by_node.resize_with(binding_count, Vec::new);
803
804        self.accessed_nodes_by_cmd.clear();
805        self.accessed_nodes_by_cmd.resize_with(cmds.len(), Vec::new);
806
807        thread_local! {
808            static SEEN_NODES: RefCell<(FixedBitSet, FixedBitSet)> = Default::default();
809        }
810
811        SEEN_NODES.with_borrow_mut(|(seen_nodes, seen_accesses)| {
812            seen_nodes.clear();
813            seen_nodes.grow(binding_count);
814
815            seen_accesses.clear();
816            seen_accesses.grow(binding_count);
817
818            for (cmd_idx, cmd) in cmds.iter().enumerate() {
819                let accessed_nodes = &mut self.accessed_nodes_by_cmd[cmd_idx];
820
821                for (node_idx, _) in cmd.execs.iter().flat_map(|exec| exec.accesses.iter()) {
822                    if !seen_nodes.put(node_idx) {
823                        self.cmds_by_node[node_idx].push(cmd_idx);
824                    }
825
826                    if !seen_accesses.put(node_idx) {
827                        accessed_nodes.push(node_idx);
828                    }
829                }
830
831                seen_nodes.clear();
832                seen_nodes.grow(binding_count);
833                seen_accesses.clear();
834                seen_accesses.grow(binding_count);
835            }
836        });
837    }
838}
839
840struct CommandBufferDebugLabel<'a> {
841    cmd_buf: &'a CommandBuffer,
842}
843
844impl<'a> CommandBufferDebugLabel<'a> {
845    fn begin(cmd_buf: &'a CommandBuffer, name: impl AsRef<str>) -> Option<Self> {
846        Device::begin_debug_utils_label(&cmd_buf.device, cmd_buf.handle, name)
847            .ok()
848            .map(|_| Self { cmd_buf })
849    }
850}
851
852impl Drop for CommandBufferDebugLabel<'_> {
853    fn drop(&mut self) {
854        let _ = Device::end_debug_utils_label(&self.cmd_buf.device, self.cmd_buf.handle);
855    }
856}
857
858#[derive(Default)]
859struct ExternalRenderPassAccessHistory {
860    accesses_by_node: Vec<Vec<PipelineStageAccessFlags>>,
861}
862
863impl ExternalRenderPassAccessHistory {
864    fn new(node_count: usize) -> Self {
865        let mut accesses_by_node = Vec::with_capacity(node_count);
866        accesses_by_node.resize_with(node_count, Vec::new);
867
868        Self { accesses_by_node }
869    }
870
871    fn accesses(&self, node_idx: usize) -> &[PipelineStageAccessFlags] {
872        &self.accesses_by_node[node_idx]
873    }
874
875    fn record_cmd(&mut self, cmd: &CommandData) {
876        for exec in &cmd.execs {
877            for (node_idx, accesses) in exec.accesses.iter() {
878                self.accesses_by_node[node_idx].extend(
879                    accesses
880                        .iter()
881                        .map(|access| PipelineStageAccessFlags::new(access.access)),
882                );
883            }
884        }
885    }
886}
887
888#[derive(Clone, Copy, Debug)]
889struct QueueOwnershipReleaseWait {
890    semaphore: vk::Semaphore,
891    stage_mask: vk::PipelineStageFlags2,
892    value: u64,
893    device_index: u32,
894}
895
896#[derive(Debug, Default)]
897struct CommandRecordingResources {
898    descriptor_pool: Option<Lease<DescriptorPool>>,
899    descriptor_sets: Vec<Vec<RecordingDescriptorSet>>,
900    render_pass: Option<Lease<RenderPass>>,
901}
902
903impl CommandRecordingResources {
904    /// # Panics
905    ///
906    /// Panics if the physical pass has no render pass.
907    fn expect_render_pass_mut(&mut self) -> &mut Lease<RenderPass> {
908        self.render_pass.as_mut().expect("missing render pass")
909    }
910}
911
912impl Drop for CommandRecordingResources {
913    fn drop(&mut self) {
914        self.descriptor_sets.clear();
915        self.descriptor_pool = None;
916    }
917}
918
919#[derive(Debug)]
920struct SubmittedCommand {
921    cmd: CommandData,
922    _resources: CommandRecordingResources,
923}
924
925impl SubmittedCommand {
926    fn signal_executed(&self) {
927        self.cmd.tracking.signal_executed();
928    }
929}
930
931#[derive(Clone, Copy, Debug)]
932struct ImageQueueOwnershipTransfer {
933    dst_queue_family_index: u32,
934    layout: vk::ImageLayout,
935    range: vk::ImageSubresourceRange,
936    src_queue_family_index: u32,
937    src_queue_index: u32,
938}
939
940impl PartialEq for ImageQueueOwnershipTransfer {
941    fn eq(&self, other: &Self) -> bool {
942        self.dst_queue_family_index == other.dst_queue_family_index
943            && self.layout == other.layout
944            && self.src_queue_family_index == other.src_queue_family_index
945            && self.src_queue_index == other.src_queue_index
946            && image_subresource_range_eq(self.range, other.range)
947    }
948}
949
950struct ImageSubresourceRangeDebug(vk::ImageSubresourceRange);
951
952impl std::fmt::Debug for ImageSubresourceRangeDebug {
953    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
954        self.0.aspect_mask.fmt(f)?;
955
956        f.write_str(" array: ")?;
957
958        let array_layers = self.0.base_array_layer..self.0.base_array_layer + self.0.layer_count;
959        array_layers.fmt(f)?;
960
961        f.write_str(" mip: ")?;
962
963        let mip_levels = self.0.base_mip_level..self.0.base_mip_level + self.0.level_count;
964        mip_levels.fmt(f)
965    }
966}
967
968#[derive(Debug)]
969struct NodeIndexedScratch<T> {
970    entries: Vec<NodeIndexedScratchEntry<T>>,
971    indices: Vec<NodeIndex>,
972}
973
974impl<T> NodeIndexedScratch<T> {
975    fn clear(&mut self) {
976        for &node_idx in self.indices.iter() {
977            let Some(entry) = self.entries.get_mut(node_idx) else {
978                continue;
979            };
980
981            entry.occupied = false;
982            entry.values.clear();
983        }
984
985        self.indices.clear();
986    }
987
988    fn get(&self, node_idx: NodeIndex) -> &[T] {
989        self.entries
990            .get(node_idx)
991            .filter(|entry| entry.occupied)
992            .map_or_else(Default::default, |entry| entry.values.as_slice())
993    }
994
995    fn push(&mut self, node_idx: NodeIndex, value: T) {
996        if self.entries.len() <= node_idx {
997            self.entries
998                .resize_with(node_idx.saturating_add(1), Default::default);
999        }
1000
1001        let entry = &mut self.entries[node_idx];
1002
1003        if !entry.occupied {
1004            entry.occupied = true;
1005            self.indices.push(node_idx);
1006        }
1007
1008        entry.values.push(value);
1009    }
1010}
1011
1012impl<T> Default for NodeIndexedScratch<T> {
1013    fn default() -> Self {
1014        Self {
1015            entries: Default::default(),
1016            indices: Default::default(),
1017        }
1018    }
1019}
1020
1021#[derive(Debug)]
1022struct NodeIndexedScratchEntry<T> {
1023    occupied: bool,
1024    values: Vec<T>,
1025}
1026
1027impl<T> Default for NodeIndexedScratchEntry<T> {
1028    fn default() -> Self {
1029        Self {
1030            occupied: false,
1031            values: Default::default(),
1032        }
1033    }
1034}
1035
1036#[derive(Debug)]
1037struct PendingTransferNode<H, T> {
1038    handle: H,
1039    transfers: Vec<T>,
1040}
1041
1042#[derive(Debug)]
1043struct PendingTransferNodes<H, T> {
1044    entries: Vec<Option<PendingTransferNode<H, T>>>,
1045    indices: Vec<NodeIndex>,
1046}
1047
1048impl<H, T> PendingTransferNodes<H, T>
1049where
1050    H: Copy,
1051{
1052    fn new(node_count: usize) -> Self {
1053        let mut entries = Vec::with_capacity(node_count);
1054        entries.resize_with(node_count, || None);
1055
1056        Self {
1057            entries,
1058            indices: Vec::new(),
1059        }
1060    }
1061
1062    fn contains(&self, node_idx: NodeIndex) -> bool {
1063        self.entries[node_idx].is_some()
1064    }
1065
1066    fn is_empty(&self) -> bool {
1067        self.indices.is_empty()
1068    }
1069
1070    fn iter(&self) -> impl Iterator<Item = (NodeIndex, H, &[T])> + '_ {
1071        self.indices.iter().filter_map(|&node_idx| {
1072            self.entries[node_idx]
1073                .as_ref()
1074                .map(|entry| (node_idx, entry.handle, entry.transfers.as_slice()))
1075        })
1076    }
1077
1078    fn push_transfer(&mut self, node_idx: NodeIndex, handle: H, transfer: T) -> bool {
1079        let inserted = self.entries[node_idx].is_none();
1080
1081        if inserted {
1082            self.indices.push(node_idx);
1083            self.entries[node_idx] = Some(PendingTransferNode {
1084                handle,
1085                transfers: vec![transfer],
1086            });
1087        } else {
1088            let entry = self.entries[node_idx]
1089                .as_mut()
1090                .expect("missing pending transfer node");
1091
1092            entry.handle = handle;
1093            entry.transfers.push(transfer);
1094        }
1095
1096        inserted
1097    }
1098
1099    fn remove_where<F>(&mut self, mut remove: F)
1100    where
1101        F: FnMut(NodeIndex, H, &mut Vec<T>) -> bool,
1102    {
1103        let mut pending_idx = 0;
1104
1105        while pending_idx < self.indices.len() {
1106            let node_idx = self.indices[pending_idx];
1107
1108            let Some(entry) = self.entries[node_idx].as_mut() else {
1109                self.indices.swap_remove(pending_idx);
1110                continue;
1111            };
1112
1113            if remove(node_idx, entry.handle, &mut entry.transfers) {
1114                self.entries[node_idx] = None;
1115                self.indices.swap_remove(pending_idx);
1116            } else {
1117                pending_idx += 1;
1118            }
1119        }
1120    }
1121}
1122
1123#[derive(Clone, Copy)]
1124struct PipelineStageAccessFlags {
1125    access_flags: vk::AccessFlags,
1126    stage_flags: vk::PipelineStageFlags,
1127}
1128
1129impl PipelineStageAccessFlags {
1130    fn new(access: AccessType) -> Self {
1131        let (mut stage_flags, access_flags) = pipeline_stage_access_flags(access);
1132        if stage_flags.contains(vk::PipelineStageFlags::ALL_COMMANDS) {
1133            stage_flags |= vk::PipelineStageFlags::ALL_GRAPHICS;
1134            stage_flags &= !vk::PipelineStageFlags::ALL_COMMANDS;
1135        }
1136
1137        Self {
1138            access_flags,
1139            stage_flags,
1140        }
1141    }
1142}
1143
1144#[derive(Debug)]
1145struct QueueOwnershipRelease {
1146    _cmd_buf: Lease<CommandBuffer>,
1147    _fence: Fence,
1148    semaphore: vk::Semaphore,
1149}
1150
1151#[derive(Debug)]
1152struct QueueOwnershipReleaseGroup {
1153    buffers: Vec<(vk::Buffer, BufferSubresourceRange)>,
1154    images: Vec<(vk::Image, vk::ImageLayout, vk::ImageSubresourceRange)>,
1155    src_queue_family_index: u32,
1156    src_queue_index: u32,
1157}
1158
1159fn queue_ownership_release_group(
1160    groups: &mut Vec<QueueOwnershipReleaseGroup>,
1161    src_queue_family_index: u32,
1162    src_queue_index: u32,
1163) -> &mut QueueOwnershipReleaseGroup {
1164    if let Some(group_idx) = groups.iter().position(|group| {
1165        group.src_queue_family_index == src_queue_family_index
1166            && group.src_queue_index == src_queue_index
1167    }) {
1168        return &mut groups[group_idx];
1169    }
1170
1171    groups.push(QueueOwnershipReleaseGroup {
1172        buffers: Vec::new(),
1173        images: Vec::new(),
1174        src_queue_family_index,
1175        src_queue_index,
1176    });
1177    groups.last_mut().expect("missing ownership release group")
1178}
1179
1180/// Submission payload for [`RecordedSubmission::queue_submit`].
1181#[derive(Clone, Copy, Debug)]
1182pub enum QueueSubmitInfo<'a> {
1183    /// Submit using `vkQueueSubmit`.
1184    ///
1185    /// See [`vkQueueSubmit`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit.html).
1186    QueueSubmit {
1187        /// Semaphores to wait on before execution begins.
1188        waits: &'a [SemaphoreSubmitInfo],
1189
1190        /// Semaphores to signal after execution completes.
1191        signals: &'a [SemaphoreSubmitInfo],
1192    },
1193
1194    /// Submit using `vkQueueSubmit2`.
1195    ///
1196    /// See [`vkQueueSubmit2`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit2.html).
1197    QueueSubmit2 {
1198        /// Semaphores to wait on before execution begins.
1199        waits: &'a [SemaphoreSubmit2Info],
1200
1201        /// Semaphores to signal after execution completes.
1202        signals: &'a [SemaphoreSubmit2Info],
1203    },
1204}
1205
1206impl QueueSubmitInfo<'static> {
1207    /// A `vkQueueSubmit` payload with no waits or signals.
1208    pub const QUEUE_SUBMIT: Self = Self::QueueSubmit {
1209        waits: &[],
1210        signals: &[],
1211    };
1212
1213    /// A `vkQueueSubmit2` payload with no waits or signals.
1214    pub const QUEUE_SUBMIT2: Self = Self::QueueSubmit2 {
1215        waits: &[],
1216        signals: &[],
1217    };
1218}
1219
1220impl<'a> QueueSubmitInfo<'a> {
1221    /// Creates a `vkQueueSubmit` payload.
1222    pub fn queue_submit(
1223        waits: &'a [SemaphoreSubmitInfo],
1224        signals: &'a [SemaphoreSubmitInfo],
1225    ) -> Self {
1226        Self::QueueSubmit { waits, signals }
1227    }
1228
1229    /// Creates a `vkQueueSubmit2` payload.
1230    pub fn queue_submit2(
1231        waits: &'a [SemaphoreSubmit2Info],
1232        signals: &'a [SemaphoreSubmit2Info],
1233    ) -> Self {
1234        Self::QueueSubmit2 { waits, signals }
1235    }
1236}
1237
1238impl<'a> From<(&'a [SemaphoreSubmitInfo], &'a [SemaphoreSubmitInfo])> for QueueSubmitInfo<'a> {
1239    fn from((waits, signals): (&'a [SemaphoreSubmitInfo], &'a [SemaphoreSubmitInfo])) -> Self {
1240        Self::QueueSubmit { waits, signals }
1241    }
1242}
1243
1244impl<'a> From<(&'a [SemaphoreSubmit2Info], &'a [SemaphoreSubmit2Info])> for QueueSubmitInfo<'a> {
1245    fn from((waits, signals): (&'a [SemaphoreSubmit2Info], &'a [SemaphoreSubmit2Info])) -> Self {
1246        Self::QueueSubmit2 { waits, signals }
1247    }
1248}
1249
1250#[derive(Debug)]
1251enum RecordingDescriptorSet {
1252    Automatic(RawDescriptorSet),
1253    Supplied(DescriptorSet),
1254}
1255
1256impl RecordingDescriptorSet {
1257    fn handle(&self) -> vk::DescriptorSet {
1258        match self {
1259            Self::Automatic(descriptor_set) => **descriptor_set,
1260            Self::Supplied(descriptor_set) => descriptor_set.handle(),
1261        }
1262    }
1263}
1264
1265/// Selects which pending work from a [`Submission`] should be recorded.
1266#[derive(Clone, Copy, Debug)]
1267pub enum RecordSelection<'a> {
1268    /// Record all remaining work.
1269    All,
1270
1271    /// Record prerequisite work, excluding commands that directly access the target node.
1272    Dependencies(AnyNode),
1273
1274    /// Record work required by the target node.
1275    Node(AnyNode),
1276
1277    /// Record work required by all of the target nodes.
1278    ///
1279    /// Nodes are processed sequentially in slice order against the same evolving submission state.
1280    Nodes(&'a [AnyNode]),
1281}
1282
1283impl<'a> RecordSelection<'a> {
1284    /// Creates a selection that records prerequisite work for `node` without recording commands that
1285    /// directly access it.
1286    pub fn dependencies(node: impl Into<AnyNode>) -> Self {
1287        Self::Dependencies(node.into())
1288    }
1289
1290    /// Creates a selection that records work required by `node`.
1291    pub fn node(node: impl Into<AnyNode>) -> Self {
1292        Self::Node(node.into())
1293    }
1294
1295    /// Creates a selection that records work required by all `nodes`.
1296    ///
1297    /// Nodes are processed in slice order.
1298    pub fn nodes(nodes: &'a [AnyNode]) -> Self {
1299        Self::Nodes(nodes)
1300    }
1301}
1302
1303impl<'a> From<AnyNode> for RecordSelection<'a> {
1304    fn from(node: AnyNode) -> Self {
1305        Self::Node(node)
1306    }
1307}
1308
1309macro_rules! record_selection_from_node {
1310    ($node:ty) => {
1311        impl<'a> From<$node> for RecordSelection<'a> {
1312            fn from(node: $node) -> Self {
1313                Self::Node(node.into())
1314            }
1315        }
1316    };
1317}
1318
1319record_selection_from_node!(crate::node::AnyAccelerationStructureNode);
1320record_selection_from_node!(crate::node::AnyBufferNode);
1321record_selection_from_node!(crate::node::AnyImageNode);
1322record_selection_from_node!(crate::node::AccelerationStructureNode);
1323record_selection_from_node!(crate::node::AccelerationStructureLeaseNode);
1324record_selection_from_node!(crate::node::BufferNode);
1325record_selection_from_node!(crate::node::BufferLeaseNode);
1326record_selection_from_node!(crate::node::ImageNode);
1327record_selection_from_node!(crate::node::ImageLeaseNode);
1328record_selection_from_node!(crate::node::SwapchainImageNode);
1329
1330/// Graph-side recorded payload for a command buffer that has already been recorded.
1331#[derive(Debug)]
1332#[read_only::cast]
1333pub struct RecordedSubmission<Cb> {
1334    cmd_buf: Cb,
1335    queue_ownership_release_waits: Vec<QueueOwnershipReleaseWait>,
1336    state: Arc<Mutex<RecordedSubmissionState>>,
1337}
1338
1339impl<Cb> RecordedSubmission<Cb>
1340where
1341    Cb: AsRef<CommandBuffer>,
1342{
1343    fn attach_locked(
1344        state: &mut RecordedSubmissionState,
1345        cmd_buf: &CommandBuffer,
1346        queue_index: u32,
1347    ) -> Option<SubmittedTimestampQueries> {
1348        let queue_family_index = cmd_buf.info.queue_family_index;
1349
1350        for (node_idx, ranges) in &state.submission.exclusive_buffer_ranges {
1351            if let Some(resource) = state.submission.graph.resources[*node_idx].as_buffer() {
1352                resource.set_sharing_ranges(
1353                    SharingMode::Exclusive(Some((queue_family_index, queue_index))),
1354                    ranges.as_slice(),
1355                );
1356            }
1357        }
1358
1359        for (node_idx, ranges) in &state.submission.exclusive_image_ranges {
1360            if let Some(resource) = state.submission.graph.resources[*node_idx].as_image() {
1361                resource.set_sharing_ranges(
1362                    SharingMode::Exclusive(Some((queue_family_index, queue_index))),
1363                    ranges.as_slice(),
1364                );
1365            }
1366        }
1367
1368        state.submission.query_pool_results.take()
1369    }
1370
1371    /// Submits this recorded submission using either `vkQueueSubmit` or `vkQueueSubmit2`.
1372    pub fn queue_submit<'a>(
1373        &mut self,
1374        fence: &mut Fence,
1375        queue_index: u32,
1376        submit_info: impl Into<QueueSubmitInfo<'a>>,
1377    ) -> Result<(), DriverError> {
1378        #[cfg(feature = "checked")]
1379        if fence.queued.get() {
1380            fence.wait()?;
1381            fence.reset()?;
1382        }
1383
1384        let command_buffer = self.cmd_buf.as_ref();
1385        let device = &command_buffer.device;
1386        let queue_family_index = command_buffer.info.queue_family_index;
1387
1388        match submit_info.into() {
1389            QueueSubmitInfo::QueueSubmit { waits, signals } => {
1390                check_queue_submit_args(waits, signals)?;
1391
1392                let extra_waits = self.queue_ownership_release_waits.as_slice();
1393                let wait_count = waits.len() + extra_waits.len();
1394
1395                Device::with_queue(device, queue_family_index, queue_index, |queue| {
1396                    SUBMIT.with_borrow_mut(|tls| {
1397                        tls.wait_semaphores.clear();
1398                        tls.wait_stage_masks.clear();
1399                        tls.signal_semaphores.clear();
1400                        tls.wait_semaphores.reserve(wait_count);
1401                        tls.wait_stage_masks.reserve(wait_count);
1402                        tls.signal_semaphores.reserve(signals.len());
1403
1404                        tls.wait_semaphores
1405                            .extend(waits.iter().map(|wait| wait.semaphore));
1406                        tls.wait_stage_masks.extend(
1407                            waits
1408                                .iter()
1409                                .map(|wait| submit_stage_mask_legacy(wait.stage_mask)),
1410                        );
1411                        tls.wait_semaphores
1412                            .extend(extra_waits.iter().map(|wait| wait.semaphore));
1413                        tls.wait_stage_masks.extend(
1414                            extra_waits
1415                                .iter()
1416                                .map(|wait| submit_stage_mask_legacy(wait.stage_mask)),
1417                        );
1418                        tls.signal_semaphores
1419                            .extend(signals.iter().map(|signal| signal.semaphore));
1420
1421                        let mut submit_info = vk::SubmitInfo::default()
1422                            .command_buffers(slice::from_ref(&command_buffer.handle))
1423                            .signal_semaphores(tls.signal_semaphores.as_slice());
1424
1425                        if !tls.wait_semaphores.is_empty() {
1426                            submit_info = submit_info
1427                                .wait_semaphores(tls.wait_semaphores.as_slice())
1428                                .wait_dst_stage_mask(tls.wait_stage_masks.as_slice());
1429                        }
1430
1431                        Device::queue_submit(
1432                            device,
1433                            queue,
1434                            slice::from_ref(&submit_info),
1435                            fence.handle,
1436                        )?;
1437
1438                        Ok::<(), DriverError>(())
1439                    })
1440                })?;
1441                fence.mark_queued();
1442            }
1443            QueueSubmitInfo::QueueSubmit2 { waits, signals } => {
1444                check_queue_submit2_args(device, waits, signals)?;
1445
1446                let extra_waits = self.queue_ownership_release_waits.as_slice();
1447                let wait_count = waits.len() + extra_waits.len();
1448
1449                Device::with_queue(device, queue_family_index, queue_index, |queue| {
1450                    SUBMIT.with_borrow_mut(|tls| {
1451                        tls.wait_infos.clear();
1452                        tls.signal_infos.clear();
1453                        tls.wait_infos.reserve(wait_count);
1454                        tls.signal_infos.reserve(signals.len());
1455
1456                        tls.wait_infos.extend(waits.iter().map(|wait| {
1457                            vk::SemaphoreSubmitInfo::default()
1458                                .semaphore(wait.semaphore)
1459                                .stage_mask(wait.stage_mask)
1460                                .value(wait.value)
1461                                .device_index(wait.device_index)
1462                        }));
1463                        tls.wait_infos.extend(extra_waits.iter().map(|wait| {
1464                            vk::SemaphoreSubmitInfo::default()
1465                                .semaphore(wait.semaphore)
1466                                .stage_mask(wait.stage_mask)
1467                                .value(wait.value)
1468                                .device_index(wait.device_index)
1469                        }));
1470                        tls.signal_infos.extend(signals.iter().map(|signal| {
1471                            vk::SemaphoreSubmitInfo::default()
1472                                .semaphore(signal.semaphore)
1473                                .stage_mask(signal.stage_mask)
1474                                .value(signal.value)
1475                                .device_index(signal.device_index)
1476                        }));
1477
1478                        let command_buffer_info = vk::CommandBufferSubmitInfo::default()
1479                            .command_buffer(command_buffer.handle);
1480                        let mut submit_info = vk::SubmitInfo2::default()
1481                            .command_buffer_infos(slice::from_ref(&command_buffer_info));
1482
1483                        if !tls.wait_infos.is_empty() {
1484                            submit_info =
1485                                submit_info.wait_semaphore_infos(tls.wait_infos.as_slice());
1486                        }
1487
1488                        if !tls.signal_infos.is_empty() {
1489                            submit_info =
1490                                submit_info.signal_semaphore_infos(tls.signal_infos.as_slice());
1491                        }
1492
1493                        Device::queue_submit2(
1494                            device,
1495                            queue,
1496                            slice::from_ref(&submit_info),
1497                            fence.handle,
1498                        )?;
1499
1500                        Ok::<(), DriverError>(())
1501                    })
1502                })?;
1503                fence.mark_queued();
1504            }
1505        }
1506
1507        let mut state = self
1508            .state
1509            .lock()
1510            .expect("poisoned recorded submission state");
1511
1512        #[cfg(feature = "checked")]
1513        let timestamp_query_graph_id = state.submission.graph.graph_id();
1514
1515        let submitted_timestamps = Self::attach_locked(&mut state, command_buffer, queue_index);
1516        drop(state);
1517
1518        #[cfg(feature = "checked")]
1519        fence.set_timestamps(TimestampQueryPool::pending(timestamp_query_graph_id));
1520
1521        #[cfg(not(feature = "checked"))]
1522        fence.set_timestamps(TimestampQueryPool::pending());
1523
1524        if let Some(submitted_timestamps) = submitted_timestamps {
1525            fence.drop_fence_droppable(submitted_timestamps);
1526        } else {
1527            fence.drop_fence_droppable(TimestampQueryCompletion);
1528        }
1529
1530        fence.drop_fence_droppable(RecordedSubmissionDrop(self.state.clone()));
1531        self.queue_ownership_release_waits.clear();
1532
1533        Ok(())
1534    }
1535}
1536
1537#[derive(Debug)]
1538struct RecordedSubmissionState {
1539    _releases: Vec<QueueOwnershipRelease>,
1540    executed: bool,
1541    submission: Submission,
1542}
1543
1544impl RecordedSubmissionState {
1545    fn signal_executed(&mut self) {
1546        if self.executed {
1547            return;
1548        }
1549
1550        self.executed = true;
1551        self.submission.signal_executed();
1552    }
1553}
1554
1555#[derive(Debug)]
1556struct RecordedSubmissionDrop(Arc<Mutex<RecordedSubmissionState>>);
1557
1558impl FenceDroppable for RecordedSubmissionDrop {
1559    fn fence_signaled(&mut self, _fence: &Fence) {
1560        self.0
1561            .lock()
1562            .expect("poisoned recorded submission state")
1563            .signal_executed();
1564    }
1565}
1566
1567/// A [`Submission`] bound to a specific command buffer for explicit recording and submission.
1568#[derive(Debug)]
1569#[read_only::cast]
1570pub struct Recording<'p, P, Cb> {
1571    /// The command buffer bound to this recording.
1572    ///
1573    /// _Note:_ This field is read-only.
1574    #[readonly]
1575    pub cmd_buf: Cb,
1576
1577    /// The pool used to allocate resources used during recording.
1578    ///
1579    /// _Note:_ This field may be mutated in between calls to `record`. The updated pool will be
1580    /// used for future calls to record.
1581    #[readonly]
1582    pub resource_pool: &'p mut P,
1583
1584    ownership: RecordingOwnership,
1585    submission: Submission,
1586}
1587
1588impl<'p, P, Cb> Recording<'p, P, Cb>
1589where
1590    Cb: AsRef<CommandBuffer>,
1591{
1592    /// Returns `true` when this submission contains no more commands to record.
1593    pub fn is_empty(&self) -> bool {
1594        self.submission.is_empty()
1595    }
1596
1597    /// Returns a borrow of the original Vulkan resource (buffer, image or acceleration structure)
1598    /// which the given node represents.
1599    pub fn resource<N>(&self, resource_node: N) -> &N::Resource
1600    where
1601        N: Node,
1602    {
1603        self.submission.resource(resource_node)
1604    }
1605
1606    /// Finalizes recording into a recorded submission for a caller-owned command buffer.
1607    pub fn finish(self) -> Result<RecordedSubmission<Cb>, DriverError>
1608    where
1609        P: Pool<CommandBufferInfo, CommandBuffer>,
1610    {
1611        let Self {
1612            ownership: _,
1613            cmd_buf,
1614            resource_pool,
1615            submission,
1616        } = self;
1617
1618        let queue_family_index = cmd_buf.as_ref().info.queue_family_index;
1619        let releases = submit_queue_ownership_releases(
1620            resource_pool,
1621            &submission.queue_ownership_release_groups,
1622            queue_family_index,
1623            |device, queue, cmd_handle, fence, semaphore| {
1624                let submit_info = vk::SubmitInfo::default()
1625                    .command_buffers(slice::from_ref(&cmd_handle))
1626                    .signal_semaphores(slice::from_ref(&semaphore));
1627                Device::queue_submit(device, queue, slice::from_ref(&submit_info), fence)
1628            },
1629        )?;
1630        let waits = releases
1631            .iter()
1632            .map(|release| QueueOwnershipReleaseWait {
1633                semaphore: release.semaphore,
1634                stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
1635                value: 0,
1636                device_index: 0,
1637            })
1638            .collect();
1639
1640        Ok(submission.into_recorded_submission(cmd_buf, releases, waits))
1641    }
1642}
1643
1644impl<'p, P, Cb> Recording<'p, P, Cb>
1645where
1646    P: SubmissionPool,
1647    Cb: AsRef<CommandBuffer>,
1648{
1649    /// Records any remaining graph commands into this submission's command buffer.
1650    ///
1651    /// When `selection` is [`RecordSelection::Nodes`], nodes are processed sequentially in the
1652    /// provided slice order and each step mutates the remaining submission state.
1653    #[profiling::function]
1654    pub fn record<'s>(
1655        &mut self,
1656        selection: impl Into<RecordSelection<'s>>,
1657    ) -> Result<(), DriverError> {
1658        self.submission.record_selection_impl(
1659            self.resource_pool,
1660            self.cmd_buf.as_ref(),
1661            selection.into(),
1662            &mut self.ownership,
1663        )
1664    }
1665}
1666
1667#[derive(Debug, Default)]
1668struct RecordingOwnership {
1669    // These ranges are effectively owned by this recording, but global ownership is not updated
1670    // until its command buffer is submitted successfully.
1671    buffers: HashMap<usize, Vec<BufferSubresourceRange>>,
1672    images: HashMap<usize, DenseMap<bool>>,
1673}
1674
1675impl RecordingOwnership {
1676    fn claim_buffer(
1677        &mut self,
1678        node_idx: usize,
1679        range: BufferSubresourceRange,
1680    ) -> SmallVec<[BufferSubresourceRange; 4]> {
1681        let claimed = self.buffers.entry(node_idx).or_default();
1682        let mut unclaimed = SmallVec::<[BufferSubresourceRange; 4]>::from_slice(&[range]);
1683
1684        for &claimed_range in claimed.iter() {
1685            let mut remaining = SmallVec::<[BufferSubresourceRange; 4]>::new();
1686
1687            for range in unclaimed.drain(..) {
1688                let Some(overlap) = range.intersection(claimed_range) else {
1689                    remaining.push(range);
1690                    continue;
1691                };
1692
1693                if range.start < overlap.start {
1694                    remaining.push(BufferSubresourceRange {
1695                        start: range.start,
1696                        end: overlap.start,
1697                    });
1698                }
1699                if overlap.end < range.end {
1700                    remaining.push(BufferSubresourceRange {
1701                        start: overlap.end,
1702                        end: range.end,
1703                    });
1704                }
1705            }
1706
1707            unclaimed = remaining;
1708            if unclaimed.is_empty() {
1709                break;
1710            }
1711        }
1712
1713        claimed.extend(unclaimed.iter().copied());
1714        unclaimed
1715    }
1716
1717    fn claim_image(
1718        &mut self,
1719        node_idx: usize,
1720        info: ImageInfo,
1721        range: vk::ImageSubresourceRange,
1722    ) -> SmallVec<[vk::ImageSubresourceRange; 4]> {
1723        self.images
1724            .entry(node_idx)
1725            .or_insert_with(|| DenseMap::new(info, false))
1726            .swap(true, range)
1727            .filter_map(|(claimed, range)| (!claimed).then_some(range))
1728            .collect()
1729    }
1730}
1731
1732#[derive(Default)]
1733struct NodeScheduleScratch {
1734    covered_node_prefixes: Vec<usize>,
1735    pending_cmds: Vec<usize>,
1736    selected_cmds: FixedBitSet,
1737}
1738
1739#[derive(Default)]
1740struct Schedule {
1741    access_index: CommandAccessIndex,
1742    cmds: Vec<usize>,
1743    local_of_global: Vec<usize>,
1744    successors: Vec<Vec<usize>>,
1745    predecessor_counts: Vec<usize>,
1746    remaining_predecessors: Vec<usize>,
1747    ready: BTreeSet<(usize, Reverse<usize>)>,
1748    reordered: Vec<usize>,
1749    node_schedule: NodeScheduleScratch,
1750}
1751
1752impl Schedule {
1753    fn schedule_required_node_prefixes(
1754        &mut self,
1755        required_prefixes: impl IntoIterator<Item = (usize, usize)>,
1756    ) {
1757        fn schedule_node_prefix(
1758            access_index: &CommandAccessIndex,
1759            schedule: &mut Vec<usize>,
1760            scratch: &mut NodeScheduleScratch,
1761            node_idx: usize,
1762            end_cmd_idx: usize,
1763        ) {
1764            let node_cmds = &access_index.cmds_by_node[node_idx];
1765            let end_prefix = node_cmds.partition_point(|&cmd_idx| cmd_idx < end_cmd_idx);
1766            let start_prefix = scratch.covered_node_prefixes[node_idx];
1767
1768            if end_prefix <= start_prefix {
1769                return;
1770            }
1771
1772            scratch.covered_node_prefixes[node_idx] = end_prefix;
1773
1774            // Selecting any user of a resource requires the complete preceding resource prefix.
1775            for &cmd_idx in &node_cmds[start_prefix..end_prefix] {
1776                if !scratch.selected_cmds.put(cmd_idx) {
1777                    schedule.push(cmd_idx);
1778                    scratch.pending_cmds.push(cmd_idx);
1779                }
1780            }
1781        }
1782
1783        self.cmds.clear();
1784        self.node_schedule.covered_node_prefixes.clear();
1785        self.node_schedule
1786            .covered_node_prefixes
1787            .resize(self.access_index.cmds_by_node.len(), 0);
1788        self.node_schedule.pending_cmds.clear();
1789        self.node_schedule.selected_cmds.clear();
1790        self.node_schedule
1791            .selected_cmds
1792            .grow(self.access_index.accessed_nodes_by_cmd.len());
1793
1794        for (node_idx, end_cmd_idx) in required_prefixes {
1795            schedule_node_prefix(
1796                &self.access_index,
1797                &mut self.cmds,
1798                &mut self.node_schedule,
1799                node_idx,
1800                end_cmd_idx,
1801            );
1802        }
1803
1804        while let Some(cmd_idx) = self.node_schedule.pending_cmds.pop() {
1805            for node_idx in self.access_index.read_nodes_for_cmd(cmd_idx) {
1806                schedule_node_prefix(
1807                    &self.access_index,
1808                    &mut self.cmds,
1809                    &mut self.node_schedule,
1810                    node_idx,
1811                    cmd_idx + 1,
1812                );
1813            }
1814        }
1815
1816        self.cmds.sort_unstable();
1817    }
1818
1819    #[profiling::function]
1820    fn reorder_cmds(&mut self, end_cmd_idx: usize) {
1821        if self.cmds.len() < 3 {
1822            return;
1823        }
1824
1825        let cmd_count = self.cmds.len();
1826
1827        self.local_of_global.resize(end_cmd_idx, usize::MAX);
1828        self.local_of_global.fill(usize::MAX);
1829
1830        for (local_idx, &cmd_idx) in self.cmds.iter().enumerate() {
1831            self.local_of_global[cmd_idx] = local_idx;
1832        }
1833
1834        for successors in &mut self.successors {
1835            successors.clear();
1836        }
1837        self.successors.resize_with(cmd_count, Vec::new);
1838        self.predecessor_counts.resize(cmd_count, 0);
1839        self.predecessor_counts.fill(0);
1840
1841        // Consecutive selected users of each resource form a dependency chain. This preserves the
1842        // original relative order for every shared-resource pair while still allowing unrelated
1843        // command chains to be grouped for locality.
1844        for resource_cmds in &self.access_index.cmds_by_node {
1845            let mut previous = Option::<usize>::None;
1846            for &cmd_idx in resource_cmds {
1847                let Some(&local_idx) = self.local_of_global.get(cmd_idx) else {
1848                    continue;
1849                };
1850
1851                if local_idx == usize::MAX {
1852                    continue;
1853                }
1854
1855                if let Some(previous_idx) = previous {
1856                    self.successors[previous_idx].push(local_idx);
1857                    self.predecessor_counts[local_idx] += 1;
1858                }
1859
1860                previous = Some(local_idx);
1861            }
1862        }
1863
1864        self.remaining_predecessors
1865            .clone_from(&self.predecessor_counts);
1866        self.ready.clear();
1867
1868        for local_idx in self
1869            .remaining_predecessors
1870            .iter()
1871            .enumerate()
1872            .filter_map(|(idx, remaining)| (*remaining == 0).then_some(idx))
1873        {
1874            self.ready.insert((0, Reverse(local_idx)));
1875        }
1876
1877        self.reordered.clear();
1878        self.reordered.reserve(cmd_count);
1879
1880        while let Some((_, Reverse(local_idx))) = self.ready.pop_last() {
1881            self.reordered.push(self.cmds[local_idx]);
1882
1883            for &successor_idx in &self.successors[local_idx] {
1884                let remaining = &mut self.remaining_predecessors[successor_idx];
1885
1886                debug_assert!(*remaining > 0);
1887
1888                *remaining -= 1;
1889
1890                if *remaining == 0 {
1891                    self.ready.insert((
1892                        self.predecessor_counts[successor_idx],
1893                        Reverse(successor_idx),
1894                    ));
1895                }
1896            }
1897        }
1898
1899        assert_eq!(
1900            self.reordered.len(),
1901            cmd_count,
1902            "command dependency cycle detected"
1903        );
1904
1905        self.cmds.clear();
1906        self.cmds.append(&mut self.reordered);
1907    }
1908}
1909
1910/// Semaphore information used during submission.
1911///
1912/// Used for both waits and signals. The legacy `vkQueueSubmit` path only supports binary
1913/// semaphores and coarse stage masks: [`value`](Self::value) must be `0`, and
1914/// [`stage_mask`](Self::stage_mask) must be [`vk::PipelineStageFlags2::ALL_COMMANDS`] or
1915/// [`vk::PipelineStageFlags2::NONE`]. Use [`SemaphoreSubmit2Info`] with
1916/// [`QueueSubmitInfo::QueueSubmit2`] when a more precise stage mask is required.
1917///
1918/// See [`VkSubmitInfo`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSubmitInfo.html).
1919#[derive(Clone, Copy, Debug, Default)]
1920pub struct SemaphoreSubmitInfo {
1921    /// Semaphore to wait on or signal.
1922    ///
1923    /// Defaults to [`vk::Semaphore::null`].
1924    pub semaphore: vk::Semaphore,
1925
1926    /// Stages blocked by this wait, or stages after which the semaphore is signaled.
1927    ///
1928    /// Defaults to [`vk::PipelineStageFlags2::empty`].
1929    pub stage_mask: vk::PipelineStageFlags2,
1930
1931    /// Timeline value to wait for or signal, or `0` for binary semaphores.
1932    pub value: u64,
1933}
1934
1935impl SemaphoreSubmitInfo {
1936    fn is_supported_legacy_submit(&self) -> bool {
1937        self.value == 0
1938            && matches!(
1939                self.stage_mask,
1940                vk::PipelineStageFlags2::ALL_COMMANDS | vk::PipelineStageFlags2::NONE
1941            )
1942    }
1943}
1944
1945/// Semaphore information used during `queue_submit2` submission.
1946///
1947/// Requires Vulkan 1.3 core or the `VK_KHR_synchronization2` extension. Using a non-zero
1948/// [`value`](Self::value) additionally requires the [`timeline_semaphore`] feature.
1949///
1950/// See [`VkSemaphoreSubmitInfo`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSemaphoreSubmitInfo.html).
1951///
1952/// [`timeline_semaphore`]: https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceTimelineSemaphoreFeatures.html
1953#[derive(Clone, Copy, Debug, Default)]
1954pub struct SemaphoreSubmit2Info {
1955    /// Semaphore to wait on or signal.
1956    ///
1957    /// Defaults to [`vk::Semaphore::null`].
1958    pub semaphore: vk::Semaphore,
1959
1960    /// Stages blocked by this wait, or stages after which the semaphore is signaled.
1961    ///
1962    /// Defaults to [`vk::PipelineStageFlags2::empty`].
1963    pub stage_mask: vk::PipelineStageFlags2,
1964
1965    /// Timeline value to wait for or signal, or `0` for binary semaphores.
1966    pub value: u64,
1967
1968    /// Device index for device-group submissions.
1969    pub device_index: u32,
1970}
1971
1972/// A finalized graph execution plan.
1973///
1974/// `Submission` owns the remaining commands of a [`Graph`] after [`Graph::finalize`] has ended the
1975/// graph-building phase. It supports two execution styles:
1976///
1977/// - [`Submission::queue_submit`] for a one-shot submission path.
1978/// - [`Submission::record`] with a [`RecordSelection`] for explicit command-buffer recording,
1979///   returning a [`Recording`].
1980#[derive(Debug)]
1981pub struct Submission {
1982    exclusive_buffer_ranges: HashMap<usize, Vec<BufferSubresourceRange>>,
1983    exclusive_image_ranges: HashMap<usize, Vec<vk::ImageSubresourceRange>>,
1984    graph: Graph,
1985    pending_buffer_transfer_nodes:
1986        Option<PendingTransferNodes<vk::Buffer, BufferQueueOwnershipTransfer>>,
1987    pending_image_transfer_nodes:
1988        Option<PendingTransferNodes<vk::Image, ImageQueueOwnershipTransfer>>,
1989    queue_ownership_release_groups: Vec<QueueOwnershipReleaseGroup>,
1990    query_pool_results: Option<SubmittedTimestampQueries>,
1991    query_pool_reset: bool,
1992    recorded_commands: Vec<CommandRecordingResources>,
1993    submit_retained: Vec<SubmittedCommand>,
1994}
1995
1996impl Submission {
1997    const GRAPHICS_STAGES: vk::PipelineStageFlags = vk::PipelineStageFlags::from_raw(
1998        vk::PipelineStageFlags::DRAW_INDIRECT.as_raw()
1999            | vk::PipelineStageFlags::VERTEX_INPUT.as_raw()
2000            | vk::PipelineStageFlags::VERTEX_SHADER.as_raw()
2001            | vk::PipelineStageFlags::TESSELLATION_CONTROL_SHADER.as_raw()
2002            | vk::PipelineStageFlags::TESSELLATION_EVALUATION_SHADER.as_raw()
2003            | vk::PipelineStageFlags::GEOMETRY_SHADER.as_raw()
2004            | vk::PipelineStageFlags::FRAGMENT_SHADER.as_raw()
2005            | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS.as_raw()
2006            | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS.as_raw()
2007            | vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT.as_raw()
2008            | vk::PipelineStageFlags::TASK_SHADER_EXT.as_raw()
2009            | vk::PipelineStageFlags::MESH_SHADER_EXT.as_raw(),
2010    );
2011
2012    pub(super) fn new(graph: Graph) -> Self {
2013        let recorded_commands = Vec::with_capacity(graph.cmds.len());
2014        Self {
2015            exclusive_buffer_ranges: HashMap::new(),
2016            exclusive_image_ranges: HashMap::new(),
2017            pending_buffer_transfer_nodes: None,
2018            graph,
2019            queue_ownership_release_groups: Vec::new(),
2020            query_pool_results: None,
2021            query_pool_reset: false,
2022            recorded_commands,
2023            pending_image_transfer_nodes: None,
2024            submit_retained: Vec::new(),
2025        }
2026    }
2027
2028    pub(crate) fn graph(&self) -> &Graph {
2029        &self.graph
2030    }
2031
2032    fn signal_executed(&self) {
2033        for command in &self.submit_retained {
2034            command.signal_executed();
2035        }
2036    }
2037
2038    pub(crate) fn assert_reusable_commands(&self) {
2039        for cmd in &self.graph.cmds {
2040            for exec in &cmd.execs {
2041                assert!(
2042                    exec.func
2043                        .as_ref()
2044                        .is_some_and(crate::CommandFunction::is_reusable),
2045                    "command stream contains a one-shot callback"
2046                );
2047            }
2048        }
2049    }
2050
2051    pub(crate) fn prepare_command_stream<P>(&mut self, pool: &mut P) -> Result<(), DriverError>
2052    where
2053        P: SubmissionPool,
2054    {
2055        if self.graph.cmds.is_empty() {
2056            return Ok(());
2057        }
2058
2059        thread_local! {
2060            static SCHEDULE: RefCell<Schedule> = Default::default();
2061        }
2062
2063        SCHEDULE.with_borrow_mut(|schedule| {
2064            schedule
2065                .access_index
2066                .update(&self.graph, self.graph.cmds.len());
2067            schedule.cmds.clear();
2068            schedule.cmds.extend(0..self.graph.cmds.len());
2069
2070            debug_assert!(
2071                schedule.cmds.windows(2).all(|w| w[0] <= w[1]),
2072                "Unsorted schedule"
2073            );
2074
2075            schedule.reorder_cmds(self.graph.cmds.len());
2076            self.merge_scheduled_cmds(&mut schedule.cmds);
2077            self.lease_scheduled_resources(pool, &schedule.cmds)
2078        })
2079    }
2080
2081    pub(crate) fn record_prepared_command_stream(
2082        &mut self,
2083        cmd_buf: &CommandBuffer,
2084        resources: crate::ResourceMap,
2085    ) -> Result<(), DriverError> {
2086        let original_resources = std::mem::replace(&mut self.graph.resources, resources);
2087
2088        let result = self.record_prepared_command_stream_inner(cmd_buf);
2089
2090        self.graph.resources = original_resources;
2091
2092        result
2093    }
2094
2095    fn record_prepared_command_stream_inner(
2096        &mut self,
2097        cmd_buf: &CommandBuffer,
2098    ) -> Result<(), DriverError> {
2099        let mut ownership = RecordingOwnership::default();
2100
2101        thread_local! {
2102            static SCHEDULE: RefCell<Schedule> = Default::default();
2103        }
2104
2105        SCHEDULE.with_borrow_mut(|schedule| {
2106            schedule
2107                .access_index
2108                .update(&self.graph, self.graph.cmds.len());
2109            schedule.cmds.clear();
2110            schedule.cmds.extend(0..self.graph.cmds.len());
2111            self.track_pending_transfers(schedule, cmd_buf.info.queue_family_index, &mut ownership);
2112        });
2113
2114        self.record_cmd_indices(cmd_buf, 0..self.graph.cmds.len())?;
2115
2116        Ok(())
2117    }
2118
2119    fn into_recorded_submission<Cb>(
2120        self,
2121        cmd_buf: Cb,
2122        releases: Vec<QueueOwnershipRelease>,
2123        waits: Vec<QueueOwnershipReleaseWait>,
2124    ) -> RecordedSubmission<Cb>
2125    where
2126        Cb: AsRef<CommandBuffer>,
2127    {
2128        RecordedSubmission {
2129            cmd_buf,
2130            queue_ownership_release_waits: waits,
2131            state: Arc::new(Mutex::new(RecordedSubmissionState {
2132                _releases: releases,
2133                executed: false,
2134                submission: self,
2135            })),
2136        }
2137    }
2138
2139    fn is_framebuffer_space(stages: vk::PipelineStageFlags) -> bool {
2140        stages.intersects(
2141            vk::PipelineStageFlags::FRAGMENT_SHADER
2142                | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
2143                | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS
2144                | vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
2145        )
2146    }
2147
2148    fn subpass_dependency_stage_masks(
2149        previous: vk::PipelineStageFlags,
2150        current: vk::PipelineStageFlags,
2151    ) -> Option<(vk::PipelineStageFlags, vk::PipelineStageFlags)> {
2152        let all_graphics = vk::PipelineStageFlags::ALL_GRAPHICS;
2153        let previous_all_graphics = previous.contains(all_graphics);
2154        let current_all_graphics = current.contains(all_graphics);
2155
2156        let overlaps = if previous_all_graphics && current_all_graphics {
2157            true
2158        } else if previous_all_graphics {
2159            current.intersects(Self::GRAPHICS_STAGES)
2160        } else if current_all_graphics {
2161            previous.intersects(Self::GRAPHICS_STAGES)
2162        } else {
2163            previous.intersects(current)
2164        };
2165
2166        if !overlaps {
2167            return None;
2168        }
2169
2170        if previous_all_graphics || current_all_graphics {
2171            Some((previous, current))
2172        } else {
2173            let stages = previous & current;
2174
2175            Some((stages, stages))
2176        }
2177    }
2178
2179    fn record_subpass_dependency(
2180        dependencies: &mut BTreeMap<(usize, usize), SubpassDependency>,
2181        src_subpass: usize,
2182        dst_subpass: usize,
2183        previous: PipelineStageAccessFlags,
2184        dst_stage_mask: vk::PipelineStageFlags,
2185        current: &mut PipelineStageAccessFlags,
2186    ) -> bool {
2187        let Some((src_stage_mask, matched_dst_stages)) =
2188            Self::subpass_dependency_stage_masks(previous.stage_flags, current.stage_flags)
2189        else {
2190            return false;
2191        };
2192
2193        let dep = dependencies
2194            .entry((src_subpass, dst_subpass))
2195            .or_insert_with(|| SubpassDependency::new(src_subpass as _, dst_subpass as _));
2196
2197        dep.src_stage_mask |= src_stage_mask;
2198        dep.src_access_mask |= previous.access_flags;
2199        dep.dst_stage_mask |= dst_stage_mask;
2200        dep.dst_access_mask |= current.access_flags;
2201
2202        if Self::is_framebuffer_space(previous.stage_flags | current.stage_flags) {
2203            dep.dependency_flags |= vk::DependencyFlags::BY_REGION;
2204        }
2205
2206        current.stage_flags &= !matched_dst_stages;
2207
2208        current.stage_flags.is_empty()
2209    }
2210
2211    #[profiling::function]
2212    fn allow_merge_passes(lhs: &CommandData, rhs: &CommandData) -> bool {
2213        fn first_graphic_pipeline(pass: &CommandData) -> Option<&GraphicsPipeline> {
2214            pass.execs
2215                .first()
2216                .and_then(|exec| exec.pipeline.as_ref().map(ExecutionPipeline::as_graphics))
2217                .flatten()
2218        }
2219
2220        fn is_multiview(view_mask: u32) -> bool {
2221            view_mask != 0
2222        }
2223
2224        let lhs_pipeline = first_graphic_pipeline(lhs);
2225        if lhs_pipeline.is_none() {
2226            trace!("  {} is not graphics", lhs.name());
2227
2228            return false;
2229        }
2230
2231        let rhs_pipeline = first_graphic_pipeline(rhs);
2232        if rhs_pipeline.is_none() {
2233            trace!("  {} is not graphics", rhs.name());
2234
2235            return false;
2236        }
2237
2238        let lhs_pipeline = unsafe { lhs_pipeline.unwrap_unchecked() };
2239        let rhs_pipeline = unsafe { rhs_pipeline.unwrap_unchecked() };
2240
2241        // Must be same general rasterization modes
2242        let lhs_info = lhs_pipeline.inner.info;
2243        let rhs_info = rhs_pipeline.inner.info;
2244        if lhs_info.blend != rhs_info.blend
2245            || lhs_info.cull_mode != rhs_info.cull_mode
2246            || lhs_info.front_face != rhs_info.front_face
2247            || lhs_info.polygon_mode != rhs_info.polygon_mode
2248            || lhs_info.samples != rhs_info.samples
2249        {
2250            trace!("  different rasterization modes",);
2251
2252            return false;
2253        }
2254
2255        let rhs = rhs.execs.first();
2256
2257        // Commands without executions are filtered before scheduling.
2258        debug_assert!(rhs.is_some());
2259
2260        let rhs = unsafe { rhs.unwrap_unchecked() };
2261
2262        let mut common_color_attachment = false;
2263        let mut common_depth_attachment = false;
2264
2265        // Now we need to know what the subpasses (we may have prior merges) wrote
2266        for lhs in lhs.execs.iter().rev() {
2267            // Multiview subpasses cannot be combined with non-multiview subpasses
2268            if is_multiview(lhs.view_mask) != is_multiview(rhs.view_mask) {
2269                trace!("  incompatible multiview");
2270
2271                return false;
2272            }
2273
2274            // Compare individual color attachments for compatibility
2275            for (attachment_idx, lhs_attachment) in lhs.attachments.color_attachments() {
2276                let rhs_attachment = rhs
2277                    .attachments
2278                    .color_attachment(attachment_idx)
2279                    .map(|state| state.attachment);
2280
2281                if !Attachment::are_compatible(Some(lhs_attachment.attachment), rhs_attachment) {
2282                    trace!("  incompatible color attachments");
2283
2284                    return false;
2285                }
2286
2287                common_color_attachment = true;
2288            }
2289
2290            // Compare depth/stencil attachments for compatibility
2291            let lhs_depth_stencil = lhs
2292                .attachments
2293                .depth_stencil_attachment()
2294                .map(|state| state.attachment);
2295
2296            let rhs_depth_stencil = rhs
2297                .attachments
2298                .depth_stencil_attachment()
2299                .map(|state| state.attachment);
2300
2301            if !Attachment::are_compatible(lhs_depth_stencil, rhs_depth_stencil) {
2302                trace!("  incompatible depth/stencil attachments");
2303
2304                return false;
2305            }
2306
2307            common_depth_attachment |= lhs_depth_stencil.is_some() && rhs_depth_stencil.is_some();
2308        }
2309
2310        // Keep color and depth on tile
2311        if common_color_attachment || common_depth_attachment {
2312            trace!("  merging due to common image");
2313
2314            return true;
2315        }
2316
2317        // Keep input on tile
2318        if !rhs_pipeline.inner.input_attachments.is_empty() {
2319            trace!("  merging due to subpass input");
2320
2321            return true;
2322        }
2323
2324        trace!("  not merging");
2325
2326        // No reason to merge, so don't
2327        false
2328    }
2329
2330    fn attachment_layout(
2331        aspect_mask: vk::ImageAspectFlags,
2332        is_random_access: bool,
2333        is_input: bool,
2334    ) -> vk::ImageLayout {
2335        if aspect_mask.contains(vk::ImageAspectFlags::COLOR) {
2336            if is_input {
2337                vk::ImageLayout::GENERAL
2338            } else {
2339                vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL
2340            }
2341        } else if aspect_mask.contains(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL)
2342        {
2343            if is_random_access {
2344                if is_input {
2345                    vk::ImageLayout::GENERAL
2346                } else {
2347                    vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL
2348                }
2349            } else {
2350                vk::ImageLayout::DEPTH_STENCIL_READ_ONLY_OPTIMAL
2351            }
2352        } else if aspect_mask.contains(vk::ImageAspectFlags::DEPTH) {
2353            if is_random_access {
2354                if is_input {
2355                    vk::ImageLayout::GENERAL
2356                } else {
2357                    vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL
2358                }
2359            } else {
2360                vk::ImageLayout::DEPTH_READ_ONLY_OPTIMAL
2361            }
2362        } else if aspect_mask.contains(vk::ImageAspectFlags::STENCIL) {
2363            if is_random_access {
2364                if is_input {
2365                    vk::ImageLayout::GENERAL
2366                } else {
2367                    vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL
2368                }
2369            } else {
2370                vk::ImageLayout::STENCIL_READ_ONLY_OPTIMAL
2371            }
2372        } else {
2373            vk::ImageLayout::UNDEFINED
2374        }
2375    }
2376
2377    fn attachment_stage(aspect_mask: vk::ImageAspectFlags) -> vk::PipelineStageFlags {
2378        match aspect_mask {
2379            mask if mask.contains(vk::ImageAspectFlags::COLOR) => {
2380                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
2381            }
2382            mask if mask
2383                .intersects(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL) =>
2384            {
2385                vk::PipelineStageFlags::LATE_FRAGMENT_TESTS
2386            }
2387            _ => vk::PipelineStageFlags::ALL_GRAPHICS,
2388        }
2389    }
2390
2391    fn attachment_read_stage(aspect_mask: vk::ImageAspectFlags) -> vk::PipelineStageFlags {
2392        match aspect_mask {
2393            mask if mask.contains(vk::ImageAspectFlags::COLOR) => {
2394                vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
2395            }
2396            mask if mask
2397                .intersects(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL) =>
2398            {
2399                vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
2400                    | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS
2401            }
2402            _ => vk::PipelineStageFlags::ALL_GRAPHICS,
2403        }
2404    }
2405
2406    fn subpass_stage_mask(stages: vk::PipelineStageFlags) -> vk::PipelineStageFlags {
2407        if stages.is_empty() {
2408            return stages;
2409        }
2410
2411        if stages.contains(vk::PipelineStageFlags::ALL_GRAPHICS) {
2412            return vk::PipelineStageFlags::ALL_GRAPHICS;
2413        }
2414
2415        let graphics_stages = stages & Self::GRAPHICS_STAGES;
2416        if graphics_stages.is_empty() {
2417            vk::PipelineStageFlags::ALL_GRAPHICS
2418        } else {
2419            graphics_stages
2420        }
2421    }
2422
2423    fn attachment_write_access(aspect_mask: vk::ImageAspectFlags) -> vk::AccessFlags {
2424        match aspect_mask {
2425            mask if mask.contains(vk::ImageAspectFlags::COLOR) => {
2426                vk::AccessFlags::COLOR_ATTACHMENT_WRITE
2427            }
2428            mask if mask
2429                .intersects(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL) =>
2430            {
2431                vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE
2432            }
2433            _ => vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
2434        }
2435    }
2436
2437    fn accel_struct_canonical_accesses<'a>(
2438        accesses: &'a [SubresourceAccess],
2439        scratch: &'a mut Vec<AccessType>,
2440    ) -> &'a [AccessType] {
2441        scratch.clear();
2442
2443        let [access] = accesses else {
2444            for access in accesses {
2445                if !scratch.contains(&access.access) {
2446                    scratch.push(access.access);
2447                }
2448            }
2449
2450            return scratch.as_slice();
2451        };
2452
2453        slice::from_ref(&access.access)
2454    }
2455
2456    fn attachment_read_write_access(
2457        aspect_mask: vk::ImageAspectFlags,
2458    ) -> (vk::AccessFlags, vk::AccessFlags) {
2459        match aspect_mask {
2460            mask if mask.contains(vk::ImageAspectFlags::COLOR) => (
2461                vk::AccessFlags::COLOR_ATTACHMENT_READ,
2462                vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
2463            ),
2464            mask if mask
2465                .intersects(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL) =>
2466            {
2467                (
2468                    vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ,
2469                    vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
2470                )
2471            }
2472            _ => (
2473                vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
2474                vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE,
2475            ),
2476        }
2477    }
2478
2479    fn color_attachment_is_read(load: LoadOp<[f32; 4]>) -> bool {
2480        matches!(load, LoadOp::Load)
2481    }
2482
2483    fn color_attachment_is_write(
2484        load: LoadOp<[f32; 4]>,
2485        store: StoreOp,
2486        has_resolve: bool,
2487    ) -> bool {
2488        matches!(load, LoadOp::Clear(_)) || store == StoreOp::Store || has_resolve
2489    }
2490
2491    fn depth_stencil_attachment_is_read(load: LoadOp<vk::ClearDepthStencilValue>) -> bool {
2492        matches!(load, LoadOp::Load)
2493    }
2494
2495    fn depth_stencil_attachment_is_write(
2496        load: LoadOp<vk::ClearDepthStencilValue>,
2497        store: StoreOp,
2498        has_resolve: bool,
2499    ) -> bool {
2500        matches!(load, LoadOp::Clear(_)) || store == StoreOp::Store || has_resolve
2501    }
2502
2503    fn expect_attachment_image<'a>(
2504        bindings: &'a [AnyResource],
2505        attachment: &Attachment,
2506    ) -> &'a Image {
2507        bindings[attachment.target]
2508            .as_image()
2509            .expect("invalid attachment target image")
2510    }
2511
2512    #[profiling::function]
2513    fn begin_render_pass(
2514        cmd_buf: &CommandBuffer,
2515        bindings: &[AnyResource],
2516        pass: &CommandData,
2517        recorded_command: &mut CommandRecordingResources,
2518        render_area: vk::Rect2D,
2519    ) -> Result<(), DriverError> {
2520        trace!("  begin render pass");
2521
2522        let render_pass = recorded_command.expect_render_pass_mut();
2523        let attachment_count = render_pass.info.attachments.len();
2524
2525        let mut attachments = Vec::with_capacity(attachment_count);
2526        attachments.resize(
2527            attachment_count,
2528            FramebufferAttachmentImageInfo {
2529                flags: vk::ImageCreateFlags::empty(),
2530                usage: vk::ImageUsageFlags::empty(),
2531                width: 0,
2532                height: 0,
2533                layer_count: 0,
2534                view_formats: vec![],
2535            },
2536        );
2537
2538        thread_local! {
2539            static CLEARS_VIEWS: RefCell<(
2540                Vec<vk::ClearValue>,
2541                Vec<vk::ImageView>,
2542            )> = Default::default();
2543        }
2544
2545        CLEARS_VIEWS.with_borrow_mut(|(clear_values, image_views)| {
2546            clear_values.resize_with(attachment_count, vk::ClearValue::default);
2547            image_views.resize(attachment_count, vk::ImageView::null());
2548
2549            for exec in &pass.execs {
2550                for (attachment_idx, state) in exec.attachments.color_attachments() {
2551                    let attachment = state.attachment;
2552                    let attachment_image = &mut attachments[attachment_idx as usize];
2553                    if let Err(idx) = attachment_image
2554                        .view_formats
2555                        .binary_search(&attachment.format)
2556                    {
2557                        if let LoadOp::Clear(clear_value) = state.load {
2558                            clear_values[attachment_idx as usize] = vk::ClearValue {
2559                                color: vk::ClearColorValue {
2560                                    float32: clear_value,
2561                                },
2562                            };
2563                        }
2564
2565                        let image = Self::expect_attachment_image(bindings, &attachment);
2566
2567                        attachment_image.flags = image.info.flags;
2568                        attachment_image.usage = image.info.usage;
2569                        attachment_image.width = image.info.width >> attachment.base_mip_level;
2570                        attachment_image.height = image.info.height >> attachment.base_mip_level;
2571                        attachment_image.layer_count = attachment.array_layer_count;
2572                        attachment_image.view_formats.insert(idx, attachment.format);
2573
2574                        image_views[attachment_idx as usize] =
2575                            Image::view(image, attachment.image_view_info(image.info))?;
2576                    }
2577                }
2578
2579                if let Some(state) = exec.attachments.depth_stencil_attachment()
2580                    && state.is_attachment
2581                {
2582                    let attachment = state.attachment;
2583                    let attachment_idx = attachments.len() - 1 - state.resolve.is_some() as usize;
2584                    let attachment_image = &mut attachments[attachment_idx];
2585                    if let Err(idx) = attachment_image
2586                        .view_formats
2587                        .binary_search(&attachment.format)
2588                    {
2589                        if let LoadOp::Clear(depth_stencil) = state.load {
2590                            clear_values[attachment_idx] = vk::ClearValue { depth_stencil };
2591                        }
2592
2593                        let image = Self::expect_attachment_image(bindings, &attachment);
2594
2595                        attachment_image.flags = image.info.flags;
2596                        attachment_image.usage = image.info.usage;
2597                        attachment_image.width = image.info.width >> attachment.base_mip_level;
2598                        attachment_image.height = image.info.height >> attachment.base_mip_level;
2599                        attachment_image.layer_count = attachment.array_layer_count;
2600                        attachment_image.view_formats.insert(idx, attachment.format);
2601
2602                        image_views[attachment_idx] =
2603                            Image::view(image, attachment.image_view_info(image.info))?;
2604                    }
2605                }
2606
2607                if let Some(state) = exec
2608                    .attachments
2609                    .depth_stencil_attachment()
2610                    .and_then(|state| state.resolve)
2611                {
2612                    let attachment_idx = attachments.len() - 1;
2613                    let attachment_image = &mut attachments[attachment_idx];
2614                    if let Err(idx) = attachment_image
2615                        .view_formats
2616                        .binary_search(&state.attachment.format)
2617                    {
2618                        let image = Self::expect_attachment_image(bindings, &state.attachment);
2619
2620                        attachment_image.flags = image.info.flags;
2621                        attachment_image.usage = image.info.usage;
2622                        attachment_image.width =
2623                            image.info.width >> state.attachment.base_mip_level;
2624                        attachment_image.height =
2625                            image.info.height >> state.attachment.base_mip_level;
2626                        attachment_image.layer_count = state.attachment.array_layer_count;
2627                        attachment_image
2628                            .view_formats
2629                            .insert(idx, state.attachment.format);
2630
2631                        image_views[attachment_idx] =
2632                            Image::view(image, state.attachment.image_view_info(image.info))?;
2633                    }
2634                }
2635            }
2636
2637            let framebuffer =
2638                RenderPass::framebuffer(render_pass, FramebufferInfo { attachments })?;
2639
2640            unsafe {
2641                cmd_buf.device.cmd_begin_render_pass(
2642                    cmd_buf.handle,
2643                    &vk::RenderPassBeginInfo::default()
2644                        .render_pass(render_pass.handle)
2645                        .framebuffer(framebuffer)
2646                        .render_area(render_area)
2647                        .clear_values(clear_values)
2648                        .push_next(
2649                            &mut vk::RenderPassAttachmentBeginInfoKHR::default()
2650                                .attachments(image_views),
2651                        ),
2652                    vk::SubpassContents::INLINE,
2653                );
2654            }
2655
2656            Ok(())
2657        })
2658    }
2659
2660    #[profiling::function]
2661    fn bind_descriptor_sets(
2662        cmd_buf: &CommandBuffer,
2663        pipeline: &ExecutionPipeline,
2664        recorded_command: &CommandRecordingResources,
2665        exec_idx: usize,
2666    ) {
2667        if let Some(exec_descriptor_sets) = recorded_command.descriptor_sets.get(exec_idx) {
2668            thread_local! {
2669                static DESCRIPTOR_SETS: RefCell<Vec<vk::DescriptorSet>> = Default::default();
2670            }
2671
2672            if exec_descriptor_sets.is_empty() {
2673                return;
2674            }
2675
2676            DESCRIPTOR_SETS.with_borrow_mut(|descriptor_sets| {
2677                descriptor_sets.clear();
2678                descriptor_sets.extend(
2679                    exec_descriptor_sets
2680                        .iter()
2681                        .map(RecordingDescriptorSet::handle),
2682                );
2683
2684                trace!("    bind descriptor sets {:?}", descriptor_sets);
2685
2686                unsafe {
2687                    cmd_buf.device.cmd_bind_descriptor_sets(
2688                        cmd_buf.handle,
2689                        pipeline.bind_point(),
2690                        pipeline.layout(),
2691                        0,
2692                        descriptor_sets,
2693                        &[],
2694                    );
2695                }
2696            });
2697        }
2698    }
2699
2700    #[profiling::function]
2701    fn bind_pipeline(
2702        cmd_buf: &CommandBuffer,
2703        recorded_command: &mut CommandRecordingResources,
2704        exec_idx: usize,
2705        pipeline: &mut ExecutionPipeline,
2706        depth_stencil: Option<DepthStencilInfo>,
2707    ) -> Result<(), DriverError> {
2708        if log_enabled!(Trace) {
2709            let (pipeline_kind, name, vk_pipeline) = match pipeline {
2710                ExecutionPipeline::Compute(pipeline) => (
2711                    "compute",
2712                    Device::private_data_object_name(
2713                        pipeline.device(),
2714                        vk::ObjectType::PIPELINE,
2715                        pipeline.handle(),
2716                    ),
2717                    pipeline.handle(),
2718                ),
2719                ExecutionPipeline::Graphics(pipeline) => (
2720                    "graphics",
2721                    Device::private_data_object_name(
2722                        pipeline.device(),
2723                        vk::ObjectType::PIPELINE_LAYOUT,
2724                        pipeline.inner.layout,
2725                    ),
2726                    vk::Pipeline::null(),
2727                ),
2728                ExecutionPipeline::RayTracing(pipeline) => (
2729                    "ray tracing",
2730                    Device::private_data_object_name(
2731                        pipeline.device(),
2732                        vk::ObjectType::PIPELINE,
2733                        pipeline.handle(),
2734                    ),
2735                    pipeline.handle(),
2736                ),
2737            };
2738            if let Some(name) = name {
2739                trace!("    bind {pipeline_kind} pipeline {name} ({vk_pipeline:?})");
2740            } else {
2741                trace!("    bind {pipeline_kind} pipeline {vk_pipeline:?}");
2742            }
2743        }
2744
2745        // We store a shared reference to this pipeline inside the command buffer!
2746        let bind_point = pipeline.bind_point();
2747        let pipeline = match pipeline {
2748            ExecutionPipeline::Compute(pipeline) => pipeline.handle(),
2749            ExecutionPipeline::Graphics(pipeline) => RenderPass::pipeline_handle(
2750                recorded_command.expect_render_pass_mut(),
2751                pipeline,
2752                depth_stencil,
2753                exec_idx as _,
2754            )?,
2755            ExecutionPipeline::RayTracing(pipeline) => pipeline.handle(),
2756        };
2757
2758        unsafe {
2759            cmd_buf
2760                .device
2761                .cmd_bind_pipeline(cmd_buf.handle, bind_point, pipeline);
2762        }
2763
2764        Ok(())
2765    }
2766
2767    /// Returns `true` when this submission contains no more commands to record.
2768    pub fn is_empty(&self) -> bool {
2769        self.graph.cmds.is_empty()
2770    }
2771
2772    #[allow(clippy::type_complexity)]
2773    #[profiling::function]
2774    fn lease_descriptor_pool<P>(
2775        pool: &mut P,
2776        pass: &CommandData,
2777    ) -> Result<Option<Lease<DescriptorPool>>, DriverError>
2778    where
2779        P: SubmissionPool,
2780    {
2781        let max_sets = pass
2782            .execs
2783            .iter()
2784            .filter_map(|exec| {
2785                exec.pipeline.as_ref().map(|pipeline| {
2786                    pipeline
2787                        .descriptor_info()
2788                        .layouts
2789                        .keys()
2790                        .filter(|set| !exec.descriptor_sets.contains_key(set))
2791                        .count() as u32
2792                })
2793            })
2794            .sum();
2795        let mut info = DescriptorPoolInfo {
2796            max_sets,
2797            ..Default::default()
2798        };
2799
2800        // Find the total count of descriptors per type (there may be multiple pipelines!)
2801        for pool_size in pass.descriptor_pools_sizes() {
2802            for (&descriptor_ty, &descriptor_count) in pool_size {
2803                debug_assert_ne!(descriptor_count, 0);
2804
2805                match descriptor_ty {
2806                    vk::DescriptorType::ACCELERATION_STRUCTURE_KHR => {
2807                        info.acceleration_structure_count += descriptor_count;
2808                    }
2809                    vk::DescriptorType::COMBINED_IMAGE_SAMPLER => {
2810                        info.combined_image_sampler_count += descriptor_count;
2811                    }
2812                    vk::DescriptorType::INPUT_ATTACHMENT => {
2813                        info.input_attachment_count += descriptor_count;
2814                    }
2815                    vk::DescriptorType::SAMPLED_IMAGE => {
2816                        info.sampled_image_count += descriptor_count;
2817                    }
2818                    vk::DescriptorType::SAMPLER => {
2819                        info.sampler_count += descriptor_count;
2820                    }
2821                    vk::DescriptorType::STORAGE_BUFFER => {
2822                        info.storage_buffer_count += descriptor_count;
2823                    }
2824                    vk::DescriptorType::STORAGE_BUFFER_DYNAMIC => {
2825                        info.storage_buffer_dynamic_count += descriptor_count;
2826                    }
2827                    vk::DescriptorType::STORAGE_IMAGE => {
2828                        info.storage_image_count += descriptor_count;
2829                    }
2830                    vk::DescriptorType::STORAGE_TEXEL_BUFFER => {
2831                        info.storage_texel_buffer_count += descriptor_count;
2832                    }
2833                    vk::DescriptorType::UNIFORM_BUFFER => {
2834                        info.uniform_buffer_count += descriptor_count;
2835                    }
2836                    vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC => {
2837                        info.uniform_buffer_dynamic_count += descriptor_count;
2838                    }
2839                    vk::DescriptorType::UNIFORM_TEXEL_BUFFER => {
2840                        info.uniform_texel_buffer_count += descriptor_count;
2841                    }
2842                    _ => {
2843                        warn!(
2844                            "unsupported descriptor type {:?} for command {}",
2845                            descriptor_ty,
2846                            pass.name(),
2847                        );
2848
2849                        return Err(DriverError::Unsupported);
2850                    }
2851                };
2852            }
2853        }
2854
2855        // It's possible to execute a command-only pipeline or use only supplied descriptor sets.
2856        if info.max_sets == 0 {
2857            return Ok(None);
2858        }
2859
2860        // Trivially round up the descriptor counts to increase cache coherence
2861        const ATOM: u32 = 1 << 5;
2862        info.acceleration_structure_count =
2863            info.acceleration_structure_count.next_multiple_of(ATOM);
2864        info.combined_image_sampler_count =
2865            info.combined_image_sampler_count.next_multiple_of(ATOM);
2866        info.input_attachment_count = info.input_attachment_count.next_multiple_of(ATOM);
2867        info.sampled_image_count = info.sampled_image_count.next_multiple_of(ATOM);
2868        info.sampler_count = info.sampler_count.next_multiple_of(ATOM);
2869        info.storage_buffer_count = info.storage_buffer_count.next_multiple_of(ATOM);
2870        info.storage_buffer_dynamic_count =
2871            info.storage_buffer_dynamic_count.next_multiple_of(ATOM);
2872        info.storage_image_count = info.storage_image_count.next_multiple_of(ATOM);
2873        info.storage_texel_buffer_count = info.storage_texel_buffer_count.next_multiple_of(ATOM);
2874        info.uniform_buffer_count = info.uniform_buffer_count.next_multiple_of(ATOM);
2875        info.uniform_buffer_dynamic_count =
2876            info.uniform_buffer_dynamic_count.next_multiple_of(ATOM);
2877        info.uniform_texel_buffer_count = info.uniform_texel_buffer_count.next_multiple_of(ATOM);
2878
2879        // Rounded descriptor counts make descriptor pools more reusable across similar pipelines
2880
2881        // debug!("{:#?}", info);
2882
2883        Ok(Some(pool.descriptor_pool(info)?))
2884    }
2885
2886    #[profiling::function]
2887    fn lease_render_pass<P>(
2888        &self,
2889        pool: &mut P,
2890        pass_idx: usize,
2891        external_access_history: &ExternalRenderPassAccessHistory,
2892    ) -> Result<Lease<RenderPass>, DriverError>
2893    where
2894        P: SubmissionPool,
2895    {
2896        let pass = &self.graph.cmds[pass_idx];
2897        let (mut color_attachment_count, mut depth_stencil_attachment_count) = (0, 0);
2898        for exec in &pass.execs {
2899            color_attachment_count = color_attachment_count.max(exec.attachments.color.len());
2900
2901            let depth_stencil = exec.attachments.depth_stencil_attachment();
2902            let has_depth_stencil_attachment =
2903                depth_stencil.is_some_and(|state| state.is_attachment);
2904            let has_depth_stencil_resolve = depth_stencil.and_then(|state| state.resolve).is_some();
2905
2906            depth_stencil_attachment_count = depth_stencil_attachment_count
2907                .max(has_depth_stencil_attachment as usize + has_depth_stencil_resolve as usize);
2908        }
2909
2910        let attachment_count = color_attachment_count + depth_stencil_attachment_count;
2911        let mut attachments = Vec::with_capacity(attachment_count);
2912        attachments.resize_with(attachment_count, AttachmentInfo::default);
2913
2914        let mut subpasses = Vec::<SubpassInfo>::with_capacity(pass.execs.len());
2915
2916        {
2917            let mut color_set = FixedBitSet::with_capacity(attachment_count);
2918            color_set.grow(attachment_count);
2919            let mut depth_stencil_set = false;
2920
2921            // Add load op attachments using the first executions
2922            for exec in &pass.execs {
2923                for (attachment_idx, state) in exec.attachments.color_attachments() {
2924                    let attachment_idx = attachment_idx as usize;
2925                    if color_set.put(attachment_idx) {
2926                        continue;
2927                    }
2928
2929                    let attachment = &mut attachments[attachment_idx];
2930                    attachment.format = state.attachment.format;
2931                    attachment.sample_count = state.attachment.sample_count;
2932                    attachment.initial_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL;
2933                    attachment.load_op = match state.load {
2934                        LoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
2935                        LoadOp::Load => vk::AttachmentLoadOp::LOAD,
2936                        LoadOp::Clear(_) => vk::AttachmentLoadOp::CLEAR,
2937                    };
2938                }
2939
2940                if !depth_stencil_set {
2941                    if let Some(state) = exec
2942                        .attachments
2943                        .depth_stencil_attachment()
2944                        .filter(|state| state.is_attachment)
2945                    {
2946                        let attachment = &mut attachments[color_attachment_count];
2947                        attachment.format = state.attachment.format;
2948                        attachment.sample_count = state.attachment.sample_count;
2949                        let is_load = matches!(state.load, LoadOp::Load);
2950                        attachment.initial_layout =
2951                            if state.attachment.aspect_mask.contains(
2952                                vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL,
2953                            ) {
2954                                attachment.load_op = match state.load {
2955                                    LoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
2956                                    LoadOp::Load => vk::AttachmentLoadOp::LOAD,
2957                                    LoadOp::Clear(_) => vk::AttachmentLoadOp::CLEAR,
2958                                };
2959                                attachment.stencil_load_op = match state.load {
2960                                    LoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
2961                                    LoadOp::Load => vk::AttachmentLoadOp::LOAD,
2962                                    LoadOp::Clear(_) => vk::AttachmentLoadOp::CLEAR,
2963                                };
2964
2965                                if is_load {
2966                                    vk::ImageLayout::DEPTH_STENCIL_READ_ONLY_OPTIMAL
2967                                } else {
2968                                    vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL
2969                                }
2970                            } else if state
2971                                .attachment
2972                                .aspect_mask
2973                                .contains(vk::ImageAspectFlags::DEPTH)
2974                            {
2975                                attachment.load_op = match state.load {
2976                                    LoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
2977                                    LoadOp::Load => vk::AttachmentLoadOp::LOAD,
2978                                    LoadOp::Clear(_) => vk::AttachmentLoadOp::CLEAR,
2979                                };
2980
2981                                if is_load {
2982                                    vk::ImageLayout::DEPTH_READ_ONLY_OPTIMAL
2983                                } else {
2984                                    vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL
2985                                }
2986                            } else {
2987                                attachment.stencil_load_op = match state.load {
2988                                    LoadOp::DontCare => vk::AttachmentLoadOp::DONT_CARE,
2989                                    LoadOp::Load => vk::AttachmentLoadOp::LOAD,
2990                                    LoadOp::Clear(_) => vk::AttachmentLoadOp::CLEAR,
2991                                };
2992
2993                                if is_load {
2994                                    vk::ImageLayout::STENCIL_READ_ONLY_OPTIMAL
2995                                } else {
2996                                    vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL
2997                                }
2998                            };
2999                        depth_stencil_set = true;
3000                    } else if exec.attachments.depth_stencil_attachment().is_some() {
3001                        depth_stencil_set = true;
3002                    }
3003                }
3004            }
3005        }
3006
3007        {
3008            let mut color_set = FixedBitSet::with_capacity(attachment_count);
3009            color_set.grow(attachment_count);
3010            let mut depth_stencil_set = false;
3011            let mut depth_stencil_resolve_set = false;
3012
3013            // Add store op attachments using the last executions
3014            for exec in pass.execs.iter().rev() {
3015                for (attachment_idx, state) in exec.attachments.color_attachments() {
3016                    let attachment_idx = attachment_idx as usize;
3017                    if color_set.put(attachment_idx) {
3018                        continue;
3019                    }
3020
3021                    let attachment = &mut attachments[attachment_idx];
3022                    attachment.format = state.attachment.format;
3023                    attachment.sample_count = state.attachment.sample_count;
3024                    attachment.store_op = if state.store == StoreOp::Store {
3025                        vk::AttachmentStoreOp::STORE
3026                    } else {
3027                        vk::AttachmentStoreOp::DONT_CARE
3028                    };
3029                    attachment.final_layout = vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL;
3030                }
3031
3032                if !depth_stencil_set
3033                    && let Some(state) = exec
3034                        .attachments
3035                        .depth_stencil_attachment()
3036                        .filter(|state| state.is_attachment)
3037                {
3038                    let attachment = &mut attachments[color_attachment_count];
3039                    attachment.format = state.attachment.format;
3040                    attachment.sample_count = state.attachment.sample_count;
3041                    attachment.final_layout = if state
3042                        .attachment
3043                        .aspect_mask
3044                        .contains(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL)
3045                    {
3046                        attachment.store_op = if state.store == StoreOp::Store {
3047                            vk::AttachmentStoreOp::STORE
3048                        } else {
3049                            vk::AttachmentStoreOp::DONT_CARE
3050                        };
3051                        attachment.stencil_store_op = if state.store == StoreOp::Store {
3052                            vk::AttachmentStoreOp::STORE
3053                        } else {
3054                            vk::AttachmentStoreOp::DONT_CARE
3055                        };
3056
3057                        vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL
3058                    } else if state
3059                        .attachment
3060                        .aspect_mask
3061                        .contains(vk::ImageAspectFlags::DEPTH)
3062                    {
3063                        attachment.store_op = if state.store == StoreOp::Store {
3064                            vk::AttachmentStoreOp::STORE
3065                        } else {
3066                            vk::AttachmentStoreOp::DONT_CARE
3067                        };
3068
3069                        vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL
3070                    } else {
3071                        attachment.stencil_store_op = if state.store == StoreOp::Store {
3072                            vk::AttachmentStoreOp::STORE
3073                        } else {
3074                            vk::AttachmentStoreOp::DONT_CARE
3075                        };
3076
3077                        vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL
3078                    };
3079                    depth_stencil_set = true;
3080                }
3081
3082                if !depth_stencil_resolve_set
3083                    && let Some(state) = exec
3084                        .attachments
3085                        .depth_stencil_attachment()
3086                        .and_then(|state| state.resolve)
3087                {
3088                    let attachment = attachments
3089                        .last_mut()
3090                        .expect("missing depth stencil resolve attachment");
3091                    attachment.format = state.attachment.format;
3092                    attachment.sample_count = state.attachment.sample_count;
3093                    attachment.final_layout = if state
3094                        .attachment
3095                        .aspect_mask
3096                        .contains(vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL)
3097                    {
3098                        vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL
3099                    } else if state
3100                        .attachment
3101                        .aspect_mask
3102                        .contains(vk::ImageAspectFlags::DEPTH)
3103                    {
3104                        vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL
3105                    } else {
3106                        vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL
3107                    };
3108                    depth_stencil_resolve_set = true;
3109                }
3110            }
3111        }
3112
3113        for attachment in &mut attachments {
3114            if attachment.load_op == vk::AttachmentLoadOp::DONT_CARE {
3115                attachment.initial_layout = vk::ImageLayout::UNDEFINED;
3116            } else if attachment.store_op == vk::AttachmentStoreOp::DONT_CARE
3117                && attachment.stencil_store_op == vk::AttachmentStoreOp::DONT_CARE
3118            {
3119                attachment.final_layout = attachment.initial_layout;
3120            }
3121        }
3122
3123        // Add subpasses
3124        for (exec_idx, exec) in pass.execs.iter().enumerate() {
3125            let pipeline = exec
3126                .pipeline
3127                .as_ref()
3128                .expect("missing graphics pipeline")
3129                .expect_graphics();
3130            let mut subpass_info = SubpassInfo::with_capacity(attachment_count);
3131
3132            // Add input attachments
3133            for attachment_idx in pipeline.inner.input_attachments.iter() {
3134                let exec_attachment = exec
3135                    .attachments
3136                    .color_attachment(*attachment_idx)
3137                    .expect("missing input attachment");
3138                debug_assert!(
3139                    !matches!(exec_attachment.load, LoadOp::Clear(_)),
3140                    "cannot clear color attachment {attachment_idx} because it uses subpass input",
3141                );
3142
3143                let is_random_access = exec_attachment.store == StoreOp::Store;
3144                subpass_info.input_attachments.push(AttachmentRef {
3145                    attachment: *attachment_idx,
3146                    aspect_mask: exec_attachment.attachment.aspect_mask,
3147                    layout: Self::attachment_layout(
3148                        exec_attachment.attachment.aspect_mask,
3149                        is_random_access,
3150                        true,
3151                    ),
3152                });
3153
3154                // Preserve the attachment in previous subpasses as needed. Input render passes are
3155                // expected to resolve to real prior subpasses here.
3156                for prev_exec_idx in (0..exec_idx).rev() {
3157                    let prev_exec = &pass.execs[prev_exec_idx];
3158                    if prev_exec
3159                        .attachments
3160                        .color_attachment(*attachment_idx)
3161                        .is_some_and(|state| state.store == StoreOp::Store)
3162                    {
3163                        break;
3164                    }
3165
3166                    let prev_subpass = &mut subpasses[prev_exec_idx];
3167                    prev_subpass.preserve_attachments.push(*attachment_idx);
3168                }
3169            }
3170
3171            // Set color attachments to defaults
3172            for attachment_idx in 0..color_attachment_count as u32 {
3173                let is_input = subpass_info
3174                    .input_attachments
3175                    .iter()
3176                    .any(|input| input.attachment == attachment_idx);
3177                subpass_info.color_attachments.push(AttachmentRef {
3178                    attachment: vk::ATTACHMENT_UNUSED,
3179                    aspect_mask: vk::ImageAspectFlags::COLOR,
3180                    layout: Self::attachment_layout(vk::ImageAspectFlags::COLOR, true, is_input),
3181                });
3182            }
3183
3184            for (attachment_idx, state) in exec.attachments.color_attachments() {
3185                if state.is_attachment {
3186                    subpass_info.color_attachments[attachment_idx as usize].attachment =
3187                        attachment_idx;
3188                }
3189            }
3190
3191            // Set depth/stencil attachment
3192            if let Some(state) = exec
3193                .attachments
3194                .depth_stencil_attachment()
3195                .filter(|state| state.is_attachment)
3196            {
3197                let is_random_access = matches!(state.load, LoadOp::Clear(_))
3198                    || matches!(state.load, LoadOp::Load)
3199                    || state.store == StoreOp::Store;
3200                subpass_info.depth_stencil_attachment = Some(AttachmentRef {
3201                    attachment: color_attachment_count as u32,
3202                    aspect_mask: state.attachment.aspect_mask,
3203                    layout: Self::attachment_layout(
3204                        state.attachment.aspect_mask,
3205                        is_random_access,
3206                        false,
3207                    ),
3208                });
3209            }
3210
3211            // Set color resolves to defaults
3212            subpass_info.color_resolve_attachments.extend(repeat_n(
3213                AttachmentRef {
3214                    attachment: vk::ATTACHMENT_UNUSED,
3215                    aspect_mask: vk::ImageAspectFlags::empty(),
3216                    layout: vk::ImageLayout::UNDEFINED,
3217                },
3218                color_attachment_count,
3219            ));
3220
3221            // Set any used color resolve attachments now
3222            for (dst_attachment_idx, state) in exec.attachments.color_attachments() {
3223                let Some(state) = state.resolve else {
3224                    continue;
3225                };
3226
3227                let is_input = subpass_info
3228                    .input_attachments
3229                    .iter()
3230                    .any(|input| input.attachment == dst_attachment_idx);
3231                subpass_info.color_resolve_attachments[state.src_attachment_idx as usize] =
3232                    AttachmentRef {
3233                        attachment: dst_attachment_idx,
3234                        aspect_mask: state.attachment.aspect_mask,
3235                        layout: Self::attachment_layout(
3236                            state.attachment.aspect_mask,
3237                            true,
3238                            is_input,
3239                        ),
3240                    };
3241            }
3242
3243            if let Some(state) = exec
3244                .attachments
3245                .depth_stencil_attachment()
3246                .and_then(|state| state.resolve)
3247            {
3248                subpass_info.depth_stencil_resolve_attachment = Some((
3249                    AttachmentRef {
3250                        attachment: state.dst_attachment_idx + 1,
3251                        aspect_mask: state.attachment.aspect_mask,
3252                        layout: Self::attachment_layout(state.attachment.aspect_mask, true, false),
3253                    },
3254                    state.depth_mode,
3255                    state.stencil_mode,
3256                ))
3257            }
3258
3259            subpass_info.view_mask = exec.view_mask;
3260            subpass_info.correlated_view_mask = exec.correlated_view_mask;
3261
3262            subpasses.push(subpass_info);
3263        }
3264
3265        let dependencies = Self::build_subpass_dependencies(pass, external_access_history);
3266
3267        // let info = RenderPassInfo {
3268        //     attachments,
3269        //     dependencies,
3270        //     subpasses,
3271        // };
3272
3273        // trace!("{:#?}", info);
3274
3275        pool.render_pass(RenderPassInfo {
3276            attachments,
3277            dependencies,
3278            subpasses,
3279        })
3280    }
3281
3282    fn build_subpass_dependencies(
3283        pass: &CommandData,
3284        external_access_history: &ExternalRenderPassAccessHistory,
3285    ) -> Vec<SubpassDependency> {
3286        let mut dependencies = BTreeMap::new();
3287        let mut pass_access_history =
3288            HashMap::<NodeIndex, Vec<(usize, PipelineStageAccessFlags)>>::new();
3289
3290        for (exec_idx, exec) in pass.execs.iter().enumerate() {
3291            'exec_accesses: for (node_idx, accesses) in exec.accesses.iter() {
3292                for access in accesses {
3293                    let mut current = PipelineStageAccessFlags::new(access.access);
3294                    current.stage_flags = Self::subpass_stage_mask(current.stage_flags);
3295
3296                    if let Some(prev_accesses) = pass_access_history.get(&node_idx) {
3297                        for &(prev_exec_idx, previous) in prev_accesses.iter().rev() {
3298                            if Self::record_subpass_dependency(
3299                                &mut dependencies,
3300                                prev_exec_idx,
3301                                exec_idx,
3302                                previous,
3303                                current.stage_flags,
3304                                &mut current,
3305                            ) {
3306                                continue 'exec_accesses;
3307                            }
3308                        }
3309                    }
3310
3311                    for &previous in external_access_history.accesses(node_idx).iter().rev() {
3312                        if Self::record_subpass_dependency(
3313                            &mut dependencies,
3314                            vk::SUBPASS_EXTERNAL as usize,
3315                            exec_idx,
3316                            previous,
3317                            current.stage_flags,
3318                            &mut current,
3319                        ) {
3320                            continue 'exec_accesses;
3321                        }
3322                    }
3323
3324                    if !current.stage_flags.is_empty() {
3325                        let dep = dependencies
3326                            .entry((vk::SUBPASS_EXTERNAL as usize, exec_idx))
3327                            .or_insert_with(|| {
3328                                SubpassDependency::new(vk::SUBPASS_EXTERNAL, exec_idx as _)
3329                            });
3330
3331                        dep.src_stage_mask |= vk::PipelineStageFlags::ALL_COMMANDS;
3332                        dep.src_access_mask |=
3333                            vk::AccessFlags::MEMORY_READ | vk::AccessFlags::MEMORY_WRITE;
3334                        dep.dst_stage_mask |= current.stage_flags;
3335                        dep.dst_access_mask |= current.access_flags;
3336                    }
3337                }
3338            }
3339
3340            for (node_idx, accesses) in exec.accesses.iter() {
3341                let prev_accesses = pass_access_history.entry(node_idx).or_default();
3342                prev_accesses.extend(accesses.iter().map(|access| {
3343                    let mut access_info = PipelineStageAccessFlags::new(access.access);
3344                    access_info.stage_flags = Self::subpass_stage_mask(access_info.stage_flags);
3345
3346                    (exec_idx, access_info)
3347                }));
3348            }
3349
3350            // Look for attachments of this exec being read or written in other execs of the
3351            // same pass
3352            for (other_idx, other) in pass.execs[0..exec_idx].iter().enumerate() {
3353                // Look for color attachments we're reading
3354                for (attachment_idx, state) in
3355                    exec.attachments.color_attachments().filter(|(_, state)| {
3356                        state.is_input || Self::color_attachment_is_read(state.load)
3357                    })
3358                {
3359                    // Look for writes in the other exec
3360                    if let Some(other_state) = other.attachments.color_attachment(attachment_idx)
3361                        && Self::color_attachment_is_write(
3362                            other_state.load,
3363                            other_state.store,
3364                            other_state.resolve.is_some(),
3365                        )
3366                    {
3367                        let dep = dependencies
3368                            .entry((other_idx, exec_idx))
3369                            .or_insert_with(|| {
3370                                SubpassDependency::new(other_idx as _, exec_idx as _)
3371                            });
3372
3373                        dep.src_stage_mask |= vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT;
3374                        dep.src_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_WRITE;
3375
3376                        if state.is_input {
3377                            dep.dst_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER;
3378                            dep.dst_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ;
3379                        } else {
3380                            dep.dst_stage_mask |=
3381                                Self::attachment_read_stage(state.attachment.aspect_mask);
3382                            dep.dst_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_READ;
3383                        }
3384                    }
3385
3386                    if let Some(other_state) = other.attachments.color_attachment(attachment_idx)
3387                        && (other_state.is_input
3388                            || Self::color_attachment_is_read(other_state.load))
3389                    {
3390                        let dep = dependencies
3391                            .entry((other_idx, exec_idx))
3392                            .or_insert_with(|| {
3393                                SubpassDependency::new(other_idx as _, exec_idx as _)
3394                            });
3395
3396                        if other_state.is_input {
3397                            dep.src_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER;
3398                            dep.src_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ;
3399                        } else {
3400                            dep.src_stage_mask |=
3401                                Self::attachment_read_stage(state.attachment.aspect_mask);
3402                            dep.src_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_READ;
3403                        }
3404
3405                        if state.is_input {
3406                            dep.dst_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER;
3407                            dep.dst_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ;
3408                        } else {
3409                            dep.dst_stage_mask |=
3410                                Self::attachment_read_stage(state.attachment.aspect_mask);
3411                            dep.dst_access_mask |= vk::AccessFlags::COLOR_ATTACHMENT_READ;
3412                        }
3413                    }
3414                }
3415
3416                if let Some(state) = exec.attachments.depth_stencil_attachment().filter(|state| {
3417                    state.is_attachment && Self::depth_stencil_attachment_is_read(state.load)
3418                }) {
3419                    let aspect_mask = state.attachment.aspect_mask;
3420
3421                    if other
3422                        .attachments
3423                        .depth_stencil_attachment()
3424                        .is_some_and(|state| {
3425                            Self::depth_stencil_attachment_is_write(
3426                                state.load,
3427                                state.store,
3428                                state.resolve.is_some(),
3429                            )
3430                        })
3431                    {
3432                        let dep = dependencies
3433                            .entry((other_idx, exec_idx))
3434                            .or_insert_with(|| {
3435                                SubpassDependency::new(other_idx as _, exec_idx as _)
3436                            });
3437
3438                        dep.src_stage_mask |= vk::PipelineStageFlags::LATE_FRAGMENT_TESTS;
3439                        dep.src_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE;
3440                        dep.dst_stage_mask |= vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS;
3441                        dep.dst_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ;
3442                    }
3443
3444                    if other
3445                        .attachments
3446                        .depth_stencil_attachment()
3447                        .is_some_and(|state| Self::depth_stencil_attachment_is_read(state.load))
3448                    {
3449                        let dep = dependencies
3450                            .entry((other_idx, exec_idx))
3451                            .or_insert_with(|| {
3452                                SubpassDependency::new(other_idx as _, exec_idx as _)
3453                            });
3454
3455                        dep.src_stage_mask |= vk::PipelineStageFlags::LATE_FRAGMENT_TESTS;
3456                        dep.src_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ;
3457                        dep.dst_stage_mask |= Self::attachment_read_stage(aspect_mask);
3458                        dep.dst_access_mask |= vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ;
3459                    }
3460                }
3461
3462                for (attachment_idx, state) in
3463                    exec.attachments.color_attachments().filter(|(_, state)| {
3464                        Self::color_attachment_is_write(
3465                            state.load,
3466                            state.store,
3467                            state.resolve.is_some(),
3468                        )
3469                    })
3470                {
3471                    let aspect_mask = state.attachment.aspect_mask;
3472                    let stage = Self::attachment_stage(aspect_mask);
3473
3474                    if other
3475                        .attachments
3476                        .color_attachment(attachment_idx)
3477                        .is_some_and(|state| {
3478                            Self::color_attachment_is_write(
3479                                state.load,
3480                                state.store,
3481                                state.resolve.is_some(),
3482                            )
3483                        })
3484                    {
3485                        let access = Self::attachment_write_access(aspect_mask);
3486                        let dep = dependencies
3487                            .entry((other_idx, exec_idx))
3488                            .or_insert_with(|| {
3489                                SubpassDependency::new(other_idx as _, exec_idx as _)
3490                            });
3491
3492                        dep.src_stage_mask |= stage;
3493                        dep.src_access_mask |= access;
3494                        dep.dst_stage_mask |= stage;
3495                        dep.dst_access_mask |= access;
3496                    }
3497
3498                    if let Some(other_state) = other.attachments.color_attachment(attachment_idx)
3499                        && (other_state.is_input
3500                            || Self::color_attachment_is_read(other_state.load))
3501                    {
3502                        let (src_access, dst_access) =
3503                            Self::attachment_read_write_access(aspect_mask);
3504                        let dep = dependencies
3505                            .entry((other_idx, exec_idx))
3506                            .or_insert_with(|| {
3507                                SubpassDependency::new(other_idx as _, exec_idx as _)
3508                            });
3509
3510                        if other_state.is_input {
3511                            dep.src_stage_mask |= vk::PipelineStageFlags::FRAGMENT_SHADER;
3512                            dep.src_access_mask |= vk::AccessFlags::INPUT_ATTACHMENT_READ;
3513                        } else {
3514                            dep.src_stage_mask |= Self::attachment_read_stage(aspect_mask);
3515                            dep.src_access_mask |= src_access;
3516                        }
3517                        dep.dst_stage_mask |= stage;
3518                        dep.dst_access_mask |= dst_access;
3519                    }
3520                }
3521
3522                if let Some(state) = exec.attachments.depth_stencil_attachment().filter(|state| {
3523                    Self::depth_stencil_attachment_is_write(
3524                        state.load,
3525                        state.store,
3526                        state.resolve.is_some(),
3527                    )
3528                }) {
3529                    let aspect_mask = state.attachment.aspect_mask;
3530                    let stage = Self::attachment_stage(aspect_mask);
3531
3532                    if other
3533                        .attachments
3534                        .depth_stencil_attachment()
3535                        .is_some_and(|state| {
3536                            Self::depth_stencil_attachment_is_write(
3537                                state.load,
3538                                state.store,
3539                                state.resolve.is_some(),
3540                            )
3541                        })
3542                    {
3543                        let access = Self::attachment_write_access(aspect_mask);
3544                        let dep = dependencies
3545                            .entry((other_idx, exec_idx))
3546                            .or_insert_with(|| {
3547                                SubpassDependency::new(other_idx as _, exec_idx as _)
3548                            });
3549
3550                        dep.src_stage_mask |= stage;
3551                        dep.src_access_mask |= access;
3552                        dep.dst_stage_mask |= stage;
3553                        dep.dst_access_mask |= access;
3554                    }
3555
3556                    if other
3557                        .attachments
3558                        .depth_stencil_attachment()
3559                        .is_some_and(|state| Self::depth_stencil_attachment_is_read(state.load))
3560                    {
3561                        let (src_access, dst_access) =
3562                            Self::attachment_read_write_access(aspect_mask);
3563                        let dep = dependencies
3564                            .entry((other_idx, exec_idx))
3565                            .or_insert_with(|| {
3566                                SubpassDependency::new(other_idx as _, exec_idx as _)
3567                            });
3568
3569                        dep.src_stage_mask |= Self::attachment_read_stage(aspect_mask);
3570                        dep.src_access_mask |= src_access;
3571                        dep.dst_stage_mask |= stage;
3572                        dep.dst_access_mask |= dst_access;
3573                    }
3574                }
3575            }
3576        }
3577
3578        dependencies.into_values().collect()
3579    }
3580
3581    #[profiling::function]
3582    fn lease_scheduled_resources<P>(
3583        &mut self,
3584        pool: &mut P,
3585        schedule: &[usize],
3586    ) -> Result<(), DriverError>
3587    where
3588        P: SubmissionPool,
3589    {
3590        let mut render_pass_access_history =
3591            ExternalRenderPassAccessHistory::new(self.graph.resources.len());
3592
3593        for pass_idx in schedule.iter().copied() {
3594            // At the time this function runs the pass will already have been optimized into a
3595            // larger pass made out of anything that might have been merged into it - so we
3596            // only care about one pass at a time here
3597            let pass = &self.graph.cmds[pass_idx];
3598
3599            trace!("requesting [{pass_idx}: {}]", pass.name());
3600
3601            let descriptor_pool = Self::lease_descriptor_pool(pool, pass)?;
3602            let mut descriptor_sets = Vec::with_capacity(pass.execs.len());
3603            descriptor_sets.resize_with(pass.execs.len(), Vec::new);
3604            for (exec_idx, exec) in pass.execs.iter().enumerate() {
3605                let Some(pipeline) = exec.pipeline.as_ref() else {
3606                    continue;
3607                };
3608
3609                descriptor_sets[exec_idx] = pipeline
3610                    .descriptor_info()
3611                    .layouts
3612                    .iter()
3613                    .map(|(&set, descriptor_set_layout)| {
3614                        if let Some(descriptor_set) = exec.descriptor_sets.get(&set) {
3615                            Ok(RecordingDescriptorSet::Supplied(descriptor_set.clone()))
3616                        } else {
3617                            let descriptor_pool = descriptor_pool
3618                                .as_ref()
3619                                .expect("missing automatic descriptor pool");
3620                            DescriptorPool::allocate_descriptor_set(
3621                                descriptor_pool,
3622                                descriptor_set_layout,
3623                            )
3624                            .map(RecordingDescriptorSet::Automatic)
3625                        }
3626                    })
3627                    .collect::<Result<_, _>>()?;
3628            }
3629
3630            /*
3631            As a side effect of merging compatible passes, all input passes should be attached to
3632            their preceding passes by now. This allows subpasses to use input attachments. If a pass
3633            still starts with input-only work here, it cannot be represented correctly.
3634            */
3635            debug_assert!(!pass.execs.is_empty());
3636            debug_assert!(
3637                pass.expect_first_exec().pipeline.is_none()
3638                    || !pass
3639                        .expect_first_exec()
3640                        .pipeline
3641                        .as_ref()
3642                        .is_some_and(|pipeline| pipeline.is_graphics())
3643                    || pass
3644                        .expect_first_exec()
3645                        .pipeline
3646                        .as_ref()
3647                        .expect("missing graphics pipeline")
3648                        .expect_graphics()
3649                        .inner
3650                        .descriptor_info
3651                        .pool_sizes
3652                        .iter()
3653                        .filter(|(set, _)| {
3654                            !pass.expect_first_exec().descriptor_sets.contains_key(set)
3655                        })
3656                        .filter_map(|(_, pool)| { pool.get(&vk::DescriptorType::INPUT_ATTACHMENT) })
3657                        .next()
3658                        .is_none()
3659            );
3660
3661            // Also, the render pass may be None if the pass contained no graphics operations.
3662            let render_pass = if pass
3663                .expect_first_exec()
3664                .pipeline
3665                .as_ref()
3666                .map(|pipeline| pipeline.is_graphics())
3667                .unwrap_or_default()
3668            {
3669                Some(self.lease_render_pass(pool, pass_idx, &render_pass_access_history)?)
3670            } else {
3671                None
3672            };
3673
3674            render_pass_access_history.record_cmd(pass);
3675
3676            self.recorded_commands.push(CommandRecordingResources {
3677                descriptor_pool,
3678                descriptor_sets,
3679                render_pass,
3680            });
3681        }
3682
3683        Ok(())
3684    }
3685
3686    // Merge contiguous scheduled graphics commands with compatible attachments. Scheduled command
3687    // order is final during this function.
3688    #[profiling::function]
3689    fn merge_scheduled_cmds(&mut self, schedule: &mut Vec<usize>) {
3690        thread_local! {
3691            static CMD_SLOTS: RefCell<Vec<Option<CommandData>>> = Default::default();
3692        }
3693
3694        CMD_SLOTS.with_borrow_mut(|cmds| {
3695            debug_assert!(cmds.is_empty());
3696
3697            let old_cmd_len = self.graph.cmds.len();
3698            let mut old_to_new_cmd = vec![(0, 0); old_cmd_len + 1];
3699            cmds.extend(self.graph.cmds.drain(..).map(Some));
3700
3701            let mut schedule_idx = 0;
3702
3703            // debug!("attempting to merge {} passes", schedule.len(),);
3704
3705            while schedule_idx < schedule.len() {
3706                let first_cmd_idx = schedule[schedule_idx];
3707                let mut cmd = cmds[schedule[schedule_idx]]
3708                    .take()
3709                    .expect("missing scheduled cmd");
3710                let new_cmd_idx = self.graph.cmds.len();
3711                old_to_new_cmd[first_cmd_idx] = (new_cmd_idx, 0);
3712
3713                // Find candidates
3714                let merge_start = schedule_idx + 1;
3715                let mut merge_end = merge_start;
3716                while merge_end < schedule.len() {
3717                    let other = cmds[schedule[merge_end]]
3718                        .as_ref()
3719                        .expect("missing scheduled cmd");
3720
3721                    debug!(
3722                        "attempting to merge [{schedule_idx}: {}] with [{merge_end}: {}]",
3723                        cmd.name(),
3724                        other.name()
3725                    );
3726
3727                    if Self::allow_merge_passes(&cmd, other) {
3728                        merge_end += 1;
3729                    } else {
3730                        break;
3731                    }
3732                }
3733
3734                if log_enabled!(Trace) && merge_start != merge_end {
3735                    trace!(
3736                        "merging {} passes into [{schedule_idx}: {}]",
3737                        merge_end - merge_start,
3738                        cmd.name()
3739                    );
3740                }
3741
3742                let mut name = cmd.name().to_owned();
3743
3744                // Grow the merged cmd once, not per merge
3745                {
3746                    let mut additional_name_len = 0;
3747                    let mut additional_exec_count = 0;
3748                    for merge_idx in merge_start..merge_end {
3749                        let other = cmds[schedule[merge_idx]]
3750                            .as_ref()
3751                            .expect("missing scheduled cmd");
3752                        additional_name_len += other.name().len() + 3;
3753                        additional_exec_count += other.execs.len();
3754                    }
3755
3756                    name.reserve(additional_name_len);
3757                    cmd.execs.reserve(additional_exec_count);
3758                }
3759
3760                let mut exec_offset = cmd.execs.len();
3761                for merge_idx in merge_start..merge_end {
3762                    let old_cmd_idx = schedule[merge_idx];
3763                    let mut other = cmds[schedule[merge_idx]]
3764                        .take()
3765                        .expect("missing scheduled cmd");
3766                    old_to_new_cmd[old_cmd_idx] = (new_cmd_idx, exec_offset);
3767                    exec_offset += other.execs.len();
3768                    name.push_str(" + ");
3769                    name.push_str(other.name());
3770                    cmd.execs.append(&mut other.execs);
3771                }
3772
3773                #[cfg(debug_assertions)]
3774                {
3775                    cmd.name = Some(name);
3776                }
3777
3778                self.graph.cmds.push(cmd);
3779                schedule_idx += 1 + merge_end - merge_start;
3780            }
3781
3782            // Reschedule cmds
3783            schedule.truncate(self.graph.cmds.len());
3784
3785            for (idx, cmd_idx) in schedule.iter_mut().enumerate() {
3786                *cmd_idx = idx;
3787            }
3788
3789            // Add the remaining cmds back into the graph for later
3790            for (old_cmd_idx, cmd) in cmds.drain(..).enumerate() {
3791                let Some(cmd) = cmd else {
3792                    continue;
3793                };
3794
3795                old_to_new_cmd[old_cmd_idx] = (self.graph.cmds.len(), 0);
3796                self.graph.cmds.push(cmd);
3797            }
3798            old_to_new_cmd[old_cmd_len] = (self.graph.cmds.len(), 0);
3799
3800            if let Some(timestamp_queries) = &mut self.graph.timestamp_queries {
3801                for query in timestamp_queries.iter_mut().flatten() {
3802                    let (command_idx, exec_idx) = old_to_new_cmd[query.command_idx];
3803                    query.command_idx = command_idx;
3804                    query.exec_idx += exec_idx;
3805                }
3806            }
3807        });
3808    }
3809
3810    fn next_subpass(cmd: &CommandBuffer) {
3811        trace!("next_subpass");
3812
3813        unsafe {
3814            cmd.device
3815                .cmd_next_subpass(cmd.handle, vk::SubpassContents::INLINE);
3816        }
3817    }
3818
3819    fn prepare_timestamp_query_results(
3820        &mut self,
3821        cmd_buf: &CommandBuffer,
3822    ) -> Result<(), DriverError> {
3823        let Some(timestamp_queries) = &self.graph.timestamp_queries else {
3824            return Ok(());
3825        };
3826
3827        let query_capacity = cmd_buf
3828            .device
3829            .physical
3830            .properties_v1_1
3831            .max_multiview_view_count
3832            .max(1);
3833        let pending_pool_query_count =
3834            timestamp_queries.iter().flatten().count() as u32 * query_capacity;
3835        if pending_pool_query_count == 0 {
3836            return Ok(());
3837        }
3838
3839        let result_info_count = timestamp_queries
3840            .iter()
3841            .flatten()
3842            .map(|timestamp_query| timestamp_query.query.index() + 1)
3843            .max()
3844            .unwrap_or_default();
3845
3846        self.query_pool_results = SubmittedTimestampQueries::create(
3847            &cmd_buf.device,
3848            cmd_buf.info.queue_family_index,
3849            result_info_count,
3850            1 + pending_pool_query_count,
3851        )
3852        .map(Some)?;
3853
3854        Ok(())
3855    }
3856
3857    fn queue_family_supports_timestamp_queries(queue_family: &QueueFamilyProperties) -> bool {
3858        queue_family.timestamp_valid_bits != 0
3859            && queue_family
3860                .queue_flags
3861                .intersects(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE)
3862    }
3863
3864    fn timestamp_query_pool_query_count(
3865        cmds: &[CommandData],
3866        timestamp_query: &TimestampQueryData,
3867    ) -> u32 {
3868        if matches!(
3869            timestamp_query.placement,
3870            TimestampQueryPlacement::BeforeExec
3871        ) && timestamp_query.exec_idx == 0
3872        {
3873            return 1;
3874        }
3875
3876        cmds.get(timestamp_query.command_idx)
3877            .and_then(|cmd| cmd.execs.get(timestamp_query.exec_idx))
3878            .map(|exec| exec.view_mask.count_ones().max(1))
3879            .unwrap_or(1)
3880    }
3881
3882    fn prepare_timestamp_queries_for_commands(
3883        &mut self,
3884        command_indices: &[usize],
3885        include_final_timestamp_queries: bool,
3886    ) {
3887        let Some(timestamp_queries) = &mut self.graph.timestamp_queries else {
3888            return;
3889        };
3890        let Some(query_pool_results) = &mut self.query_pool_results else {
3891            return;
3892        };
3893
3894        let cmds = &self.graph.cmds;
3895        let command_count = cmds.len();
3896        let mut scheduled_commands = FixedBitSet::with_capacity(command_count + 1);
3897        for command_idx in command_indices.iter().copied() {
3898            scheduled_commands.insert(command_idx);
3899        }
3900        if include_final_timestamp_queries {
3901            scheduled_commands.insert(command_count);
3902        }
3903
3904        for timestamp_query in timestamp_queries.iter_mut().flatten() {
3905            if !scheduled_commands.contains(timestamp_query.command_idx) {
3906                continue;
3907            }
3908
3909            let pool_query = timestamp_query.pool_query.unwrap_or_else(|| {
3910                let pool_query = query_pool_results.allocate_query(
3911                    Self::timestamp_query_pool_query_count(cmds, timestamp_query),
3912                );
3913                timestamp_query.pool_query = Some(pool_query);
3914
3915                pool_query
3916            });
3917
3918            query_pool_results.set_result_info(
3919                timestamp_query.query,
3920                TimestampQueryResultInfo {
3921                    timestamp_query: pool_query,
3922                },
3923            );
3924        }
3925    }
3926
3927    fn record_node<P>(
3928        &mut self,
3929        resource_pool: &mut P,
3930        cmd_buf: &CommandBuffer,
3931        node: AnyNode,
3932        ownership: &mut RecordingOwnership,
3933    ) -> Result<(), DriverError>
3934    where
3935        P: SubmissionPool,
3936    {
3937        match node {
3938            AnyNode::AccelerationStructure(node) => {
3939                self.record_resource_impl(resource_pool, cmd_buf, node, ownership)
3940            }
3941            AnyNode::Buffer(node) => {
3942                self.record_resource_impl(resource_pool, cmd_buf, node, ownership)
3943            }
3944            AnyNode::Image(node) => {
3945                self.record_resource_impl(resource_pool, cmd_buf, node, ownership)
3946            }
3947        }
3948    }
3949
3950    #[profiling::function]
3951    fn record_selection_impl<'a, P>(
3952        &mut self,
3953        resource_pool: &mut P,
3954        cmd_buf: &CommandBuffer,
3955        selection: RecordSelection<'a>,
3956        ownership: &mut RecordingOwnership,
3957    ) -> Result<(), DriverError>
3958    where
3959        P: SubmissionPool,
3960    {
3961        let _ = CommandBufferDebugLabel::begin(cmd_buf, "graph submission");
3962
3963        match selection {
3964            RecordSelection::All => self.record_impl(resource_pool, cmd_buf, ownership),
3965            RecordSelection::Dependencies(node) => match node {
3966                AnyNode::AccelerationStructure(node) => {
3967                    self.record_resource_dependencies_impl(resource_pool, cmd_buf, node, ownership)
3968                }
3969                AnyNode::Buffer(node) => {
3970                    self.record_resource_dependencies_impl(resource_pool, cmd_buf, node, ownership)
3971                }
3972                AnyNode::Image(node) => {
3973                    self.record_resource_dependencies_impl(resource_pool, cmd_buf, node, ownership)
3974                }
3975            },
3976            RecordSelection::Node(node) => {
3977                self.record_node(resource_pool, cmd_buf, node, ownership)
3978            }
3979            RecordSelection::Nodes(nodes) => {
3980                for &node in nodes {
3981                    self.record_node(resource_pool, cmd_buf, node, ownership)?;
3982                }
3983
3984                Ok(())
3985            }
3986        }
3987    }
3988
3989    #[profiling::function]
3990    fn record_execution_barriers<'a>(
3991        cmd_buf: &CommandBuffer,
3992        resources: &mut [AnyResource],
3993        accesses: &'a ExecutionAccess,
3994        pending_buffer_transfer_nodes: &mut Option<
3995            PendingTransferNodes<vk::Buffer, BufferQueueOwnershipTransfer>,
3996        >,
3997        pending_image_transfer_nodes: &mut Option<
3998            PendingTransferNodes<vk::Image, ImageQueueOwnershipTransfer>,
3999        >,
4000    ) {
4001        // We store a Barriers in TLS to save an alloc; contents are POD
4002        thread_local! {
4003            static BARRIER: RefCell<BarrierScratch> = Default::default();
4004        }
4005
4006        struct AccessBarrier<T> {
4007            next_access: AccessType,
4008            prev_access: AccessType,
4009            resource: T,
4010        }
4011
4012        struct BufferBarrierTarget {
4013            buffer: vk::Buffer,
4014            range: BufferSubresourceRange,
4015        }
4016
4017        struct ImageBarrierTarget {
4018            image: vk::Image,
4019            range: vk::ImageSubresourceRange,
4020        }
4021
4022        #[derive(Default)]
4023        struct BarrierScratch {
4024            accel_struct_accesses: Vec<AccessType>,
4025            buffers: Vec<AccessBarrier<BufferBarrierTarget>>,
4026            images: Vec<AccessBarrier<ImageBarrierTarget>>,
4027            next_accesses: Vec<AccessType>,
4028            pending_buffers: NodeIndexedScratch<AccessBarrier<BufferBarrierTarget>>,
4029            pending_images: NodeIndexedScratch<AccessBarrier<ImageBarrierTarget>>,
4030            prev_accesses: Vec<AccessType>,
4031        }
4032
4033        BARRIER.with_borrow_mut(|tls| {
4034            // Initialize TLS from a previous call
4035            tls.accel_struct_accesses.clear();
4036            tls.buffers.clear();
4037            tls.images.clear();
4038            tls.next_accesses.clear();
4039            tls.pending_buffers.clear();
4040            tls.pending_images.clear();
4041            tls.prev_accesses.clear();
4042
4043            // Map remaining accesses into vk_sync barriers (some accesses may have been removed by
4044            // the render pass request function)
4045
4046            for (node_idx, node_accesses) in accesses.iter() {
4047                enum ResourceRef<'a> {
4048                    AccelerationStructure(&'a AccelerationStructure),
4049                    Buffer(&'a Buffer),
4050                    Image(&'a Image),
4051                }
4052
4053                let resource = match &resources[node_idx] {
4054                    AnyResource::AccelerationStructure(resource) => {
4055                        ResourceRef::AccelerationStructure(resource)
4056                    }
4057                    AnyResource::AccelerationStructureArg(_) => {
4058                        panic!("unbound command stream acceleration structure argument")
4059                    }
4060                    AnyResource::AccelerationStructureLease(resource) => {
4061                        ResourceRef::AccelerationStructure(resource)
4062                    }
4063                    AnyResource::Buffer(resource) => ResourceRef::Buffer(resource),
4064                    AnyResource::BufferArg(_) => panic!("unbound command stream buffer argument"),
4065                    AnyResource::BufferLease(resource) => ResourceRef::Buffer(resource),
4066                    AnyResource::Image(resource) => ResourceRef::Image(resource),
4067                    AnyResource::ImageArg(_) => panic!("unbound command stream image argument"),
4068                    AnyResource::ImageLease(resource) => ResourceRef::Image(resource),
4069                    AnyResource::SwapchainImage(resource) => ResourceRef::Image(resource),
4070                };
4071
4072                match resource {
4073                    ResourceRef::AccelerationStructure(accel_struct) => {
4074                        let canonical_accesses = Self::accel_struct_canonical_accesses(
4075                            node_accesses,
4076                            &mut tls.accel_struct_accesses,
4077                        );
4078                        tls.next_accesses.extend(canonical_accesses.iter().copied());
4079                        tls.prev_accesses
4080                            .extend(AccelerationStructure::swap_accesses(
4081                                accel_struct,
4082                                canonical_accesses,
4083                            ));
4084                    }
4085                    ResourceRef::Buffer(buffer) => {
4086                        for (next_access, prev_access, range) in Buffer::swap_accesses(
4087                            buffer,
4088                            node_accesses.iter().map(
4089                                |&SubresourceAccess {
4090                                     access,
4091                                     subresource,
4092                                 }| {
4093                                    let SubresourceRange::Buffer(range) = subresource else {
4094                                        unreachable!()
4095                                    };
4096
4097                                    (access, range)
4098                                },
4099                            ),
4100                        ) {
4101                            let barrier = AccessBarrier {
4102                                next_access,
4103                                prev_access,
4104                                resource: BufferBarrierTarget {
4105                                    buffer: buffer.handle,
4106                                    range,
4107                                },
4108                            };
4109
4110                            if pending_buffer_transfer_nodes
4111                                .as_ref()
4112                                .is_some_and(|pending| pending.contains(node_idx))
4113                            {
4114                                tls.pending_buffers.push(node_idx, barrier);
4115                            } else {
4116                                tls.buffers.push(barrier);
4117                            }
4118                        }
4119                    }
4120                    ResourceRef::Image(image) => {
4121                        for (next_access, prev_access, range) in Image::swap_accesses(
4122                            image,
4123                            node_accesses.iter().map(
4124                                |&SubresourceAccess {
4125                                     access,
4126                                     subresource,
4127                                 }| {
4128                                    let SubresourceRange::Image(range) = subresource else {
4129                                        unreachable!()
4130                                    };
4131
4132                                    (access, range)
4133                                },
4134                            ),
4135                        ) {
4136                            let barrier = AccessBarrier {
4137                                next_access,
4138                                prev_access,
4139                                resource: ImageBarrierTarget {
4140                                    image: image.handle,
4141                                    range,
4142                                },
4143                            };
4144
4145                            if pending_image_transfer_nodes
4146                                .as_ref()
4147                                .is_some_and(|pending| pending.contains(node_idx))
4148                            {
4149                                tls.pending_images.push(node_idx, barrier);
4150                            } else {
4151                                tls.images.push(barrier);
4152                            }
4153                        }
4154                    }
4155                }
4156            }
4157
4158            let global_barrier = if !tls.next_accesses.is_empty() {
4159                // No resource attached - we use a global barrier for these
4160                trace!(
4161                    "    global {:?}->{:?}",
4162                    tls.next_accesses, tls.prev_accesses
4163                );
4164
4165                Some(GlobalBarrier {
4166                    next_accesses: tls.next_accesses.as_slice(),
4167                    previous_accesses: tls.prev_accesses.as_slice(),
4168                })
4169            } else {
4170                None
4171            };
4172            let mut buffer_barriers = Vec::new();
4173            for AccessBarrier {
4174                next_access,
4175                prev_access,
4176                resource,
4177            } in tls.buffers.iter()
4178            {
4179                let BufferBarrierTarget { buffer, range, .. } = *resource;
4180
4181                buffer_barriers.push(BufferBarrier {
4182                    next_accesses: slice::from_ref(next_access),
4183                    previous_accesses: slice::from_ref(prev_access),
4184                    src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
4185                    dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
4186                    buffer,
4187                    offset: range.start as _,
4188                    size: (range.end - range.start) as _,
4189                });
4190            }
4191
4192            if let Some(pending_buffer_transfer_nodes) = pending_buffer_transfer_nodes.as_ref() {
4193                for (node_idx, _buffer, transfers) in pending_buffer_transfer_nodes.iter() {
4194                    for AccessBarrier {
4195                        next_access,
4196                        prev_access,
4197                        resource,
4198                    } in tls.pending_buffers.get(node_idx)
4199                    {
4200                        buffer_barriers.extend(buffer_barriers_from_transfers(
4201                            resource.buffer,
4202                            prev_access,
4203                            next_access,
4204                            resource.range,
4205                            transfers,
4206                        ));
4207                    }
4208                }
4209            }
4210
4211            let mut image_barriers = Vec::new();
4212            for AccessBarrier {
4213                next_access,
4214                prev_access,
4215                resource,
4216            } in tls.images.iter()
4217            {
4218                let ImageBarrierTarget { image, range, .. } = *resource;
4219
4220                image_barriers.push(ImageBarrier {
4221                    next_accesses: slice::from_ref(next_access),
4222                    previous_accesses: slice::from_ref(prev_access),
4223                    next_layout: image_access_layout(*next_access),
4224                    previous_layout: image_access_layout(*prev_access),
4225                    discard_contents: image_execution_discard_contents(*prev_access),
4226                    src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
4227                    dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
4228                    image,
4229                    range,
4230                });
4231            }
4232
4233            if let Some(pending_image_transfer_nodes) = pending_image_transfer_nodes.as_ref() {
4234                for (node_idx, _image, transfers) in pending_image_transfer_nodes.iter() {
4235                    for AccessBarrier {
4236                        next_access,
4237                        prev_access,
4238                        resource,
4239                    } in tls.pending_images.get(node_idx)
4240                    {
4241                        image_barriers.extend(image_barriers_from_transfers(
4242                            resource.image,
4243                            prev_access,
4244                            next_access,
4245                            resource.range,
4246                            transfers,
4247                            image_execution_discard_contents(*prev_access),
4248                        ));
4249                    }
4250                }
4251            }
4252
4253            pipeline_barrier_from_iters(
4254                &cmd_buf.device,
4255                cmd_buf.handle,
4256                global_barrier,
4257                buffer_barriers.into_iter(),
4258                image_barriers.into_iter(),
4259            );
4260
4261            if let Some(pending) = pending_buffer_transfer_nodes.as_mut() {
4262                pending.remove_where(|node_idx, _buffer, transfers| {
4263                    for AccessBarrier { resource, .. } in tls.pending_buffers.get(node_idx) {
4264                        let range = resource.range;
4265
4266                        if consume_pending_buffer_transfers(transfers, range) {
4267                            return true;
4268                        }
4269                    }
4270
4271                    false
4272                });
4273
4274                if pending.is_empty() {
4275                    *pending_buffer_transfer_nodes = None;
4276                }
4277            }
4278
4279            if let Some(pending) = pending_image_transfer_nodes.as_mut() {
4280                pending.remove_where(|node_idx, _image, transfers| {
4281                    for AccessBarrier { resource, .. } in tls.pending_images.get(node_idx) {
4282                        let range = resource.range;
4283
4284                        if consume_pending_image_transfers(transfers, range) {
4285                            return true;
4286                        }
4287                    }
4288
4289                    false
4290                });
4291
4292                if pending.is_empty() {
4293                    *pending_image_transfer_nodes = None;
4294                }
4295            }
4296        });
4297    }
4298
4299    #[profiling::function]
4300    fn record_image_layout_transitions(
4301        cmd_buf: &CommandBuffer,
4302        resources: &mut [AnyResource],
4303        pass: &mut CommandData,
4304        pending_buffer_transfer_nodes: &mut Option<
4305            PendingTransferNodes<vk::Buffer, BufferQueueOwnershipTransfer>,
4306        >,
4307        pending_image_transfer_nodes: &mut Option<
4308            PendingTransferNodes<vk::Image, ImageQueueOwnershipTransfer>,
4309        >,
4310    ) {
4311        struct ImageResourceBarrier {
4312            image: vk::Image,
4313            node_idx: NodeIndex,
4314            next_access: AccessType,
4315            prev_access: AccessType,
4316            range: vk::ImageSubresourceRange,
4317        }
4318
4319        struct BufferResourceBarrier {
4320            buffer: vk::Buffer,
4321            next_access: AccessType,
4322            prev_access: AccessType,
4323            range: BufferSubresourceRange,
4324        }
4325
4326        #[derive(Default)]
4327        struct LayoutTransitionScratch {
4328            buffers: Vec<BufferResourceBarrier>,
4329            images: Vec<ImageResourceBarrier>,
4330            first_layout_uses: HashMap<usize, DenseMap<bool>>,
4331            pending_buffers: NodeIndexedScratch<BufferResourceBarrier>,
4332            pending_images: NodeIndexedScratch<ImageResourceBarrier>,
4333        }
4334
4335        // We store a LayoutTransitionScratch in TLS to save an alloc; contents are POD
4336        thread_local! {
4337            static LAYOUT_TRANSITION: RefCell<LayoutTransitionScratch> = Default::default();
4338        }
4339
4340        LAYOUT_TRANSITION.with_borrow_mut(|tls| {
4341            tls.buffers.clear();
4342            tls.images.clear();
4343            tls.first_layout_uses.clear();
4344            tls.pending_buffers.clear();
4345            tls.pending_images.clear();
4346
4347            for (node_idx, accesses) in pass.execs.iter_mut().flat_map(|exec| exec.accesses.iter())
4348            {
4349                debug_assert!(resources.get(node_idx).is_some());
4350
4351                let resource = unsafe {
4352                    // CommandRef enforces this during push_resource_access
4353                    resources.get_unchecked(node_idx)
4354                };
4355
4356                enum ResourceRef<'a> {
4357                    AccelerationStructure(&'a AccelerationStructure),
4358                    Buffer(&'a Buffer),
4359                    Image(&'a Image),
4360                }
4361
4362                let resource = match resource {
4363                    AnyResource::AccelerationStructure(resource) => {
4364                        ResourceRef::AccelerationStructure(resource)
4365                    }
4366                    AnyResource::AccelerationStructureArg(_) => {
4367                        panic!("unbound command stream acceleration structure argument")
4368                    }
4369                    AnyResource::AccelerationStructureLease(resource) => {
4370                        ResourceRef::AccelerationStructure(resource)
4371                    }
4372                    AnyResource::Buffer(resource) => ResourceRef::Buffer(resource),
4373                    AnyResource::BufferArg(_) => panic!("unbound command stream buffer argument"),
4374                    AnyResource::BufferLease(resource) => ResourceRef::Buffer(resource),
4375                    AnyResource::Image(resource) => ResourceRef::Image(resource),
4376                    AnyResource::ImageArg(_) => panic!("unbound command stream image argument"),
4377                    AnyResource::ImageLease(resource) => ResourceRef::Image(resource),
4378                    AnyResource::SwapchainImage(resource) => ResourceRef::Image(resource),
4379                };
4380
4381                match resource {
4382                    ResourceRef::AccelerationStructure(accel_struct) => {
4383                        AccelerationStructure::swap_access(accel_struct, AccessType::Nothing)
4384                            .for_each(drop);
4385                    }
4386                    ResourceRef::Buffer(buffer) => {
4387                        for subresource_access in accesses {
4388                            let &SubresourceAccess {
4389                                access,
4390                                subresource: SubresourceRange::Buffer(access_range),
4391                            } = subresource_access
4392                            else {
4393                                #[cfg(feature = "checked")]
4394                                unreachable!();
4395
4396                                #[cfg(not(feature = "checked"))]
4397                                unsafe {
4398                                    // This cannot be reached because command access recording
4399                                    // preserves the buffer subresource type for this node.
4400                                    unreachable_unchecked()
4401                                }
4402                            };
4403
4404                            for (prev_access, range) in
4405                                Buffer::swap_access(buffer, AccessType::Nothing, access_range)
4406                            {
4407                                if !pending_buffer_transfer_nodes
4408                                    .as_ref()
4409                                    .is_some_and(|pending| pending.contains(node_idx))
4410                                {
4411                                    continue;
4412                                }
4413
4414                                tls.pending_buffers.push(
4415                                    node_idx,
4416                                    BufferResourceBarrier {
4417                                        buffer: buffer.handle,
4418                                        next_access: access,
4419                                        prev_access,
4420                                        range,
4421                                    },
4422                                );
4423                            }
4424                        }
4425                    }
4426                    ResourceRef::Image(image) => {
4427                        let first_layout_uses = tls
4428                            .first_layout_uses
4429                            .entry(node_idx)
4430                            .or_insert_with(|| DenseMap::new(image.info, true));
4431
4432                        for subresource_access in accesses {
4433                            let &SubresourceAccess {
4434                                access,
4435                                subresource: SubresourceRange::Image(access_range),
4436                            } = subresource_access
4437                            else {
4438                                #[cfg(feature = "checked")]
4439                                unreachable!();
4440
4441                                #[cfg(not(feature = "checked"))]
4442                                unsafe {
4443                                    // This cannot be reached because command access recording
4444                                    // preserves the image subresource type for this node.
4445                                    unreachable_unchecked()
4446                                }
4447                            };
4448
4449                            let access_range = image.info.resolve_subresource_counts(access_range);
4450
4451                            for (is_initial_layout, layout_range) in
4452                                first_layout_uses.swap(false, access_range)
4453                            {
4454                                for (prev_access, range) in
4455                                    Image::swap_access(image, access, layout_range)
4456                                {
4457                                    if is_initial_layout {
4458                                        let barrier = ImageResourceBarrier {
4459                                            image: image.handle,
4460                                            node_idx,
4461                                            next_access: initial_image_layout_access(access),
4462                                            prev_access,
4463                                            range,
4464                                        };
4465
4466                                        if pending_image_transfer_nodes
4467                                            .as_ref()
4468                                            .is_some_and(|pending| pending.contains(node_idx))
4469                                        {
4470                                            tls.pending_images.push(node_idx, barrier);
4471                                        } else {
4472                                            tls.images.push(barrier);
4473                                        }
4474                                    }
4475                                }
4476                            }
4477                        }
4478                    }
4479                }
4480            }
4481
4482            let mut buffer_barriers = Vec::new();
4483            if let Some(pending_buffer_transfer_nodes) = pending_buffer_transfer_nodes.as_ref() {
4484                for (node_idx, _buffer, transfers) in pending_buffer_transfer_nodes.iter() {
4485                    for BufferResourceBarrier {
4486                        buffer,
4487                        next_access,
4488                        prev_access,
4489                        range,
4490                        ..
4491                    } in tls.pending_buffers.get(node_idx)
4492                    {
4493                        for transfer in transfers.iter().copied() {
4494                            let Some(range) = range.intersection(transfer.range) else {
4495                                continue;
4496                            };
4497
4498                            trace!(
4499                                "    buffer {:?} {:?} {:?}->{:?}",
4500                                buffer,
4501                                range.start..range.end,
4502                                prev_access,
4503                                next_access,
4504                            );
4505
4506                            buffer_barriers.push(BufferBarrier {
4507                                next_accesses: slice::from_ref(next_access),
4508                                previous_accesses: slice::from_ref(prev_access),
4509                                src_queue_family_index: transfer.src_queue_family_index,
4510                                dst_queue_family_index: transfer.dst_queue_family_index,
4511                                buffer: *buffer,
4512                                offset: range.start as _,
4513                                size: (range.end - range.start) as _,
4514                            });
4515                        }
4516                    }
4517                }
4518            }
4519
4520            let mut image_barriers = Vec::new();
4521            for ImageResourceBarrier {
4522                image,
4523                node_idx,
4524                next_access,
4525                prev_access,
4526                range,
4527            } in tls.images.iter()
4528            {
4529                if pending_image_transfer_nodes
4530                    .as_ref()
4531                    .is_some_and(|pending| pending.contains(*node_idx))
4532                {
4533                    continue;
4534                }
4535
4536                image_barriers.extend(image_barriers_from_transfers(
4537                    *image,
4538                    prev_access,
4539                    next_access,
4540                    *range,
4541                    &[],
4542                    image_layout_transition_discard_contents(*prev_access, *next_access),
4543                ));
4544            }
4545
4546            if let Some(pending_image_transfer_nodes) = pending_image_transfer_nodes.as_ref() {
4547                for (node_idx, _image, transfers) in pending_image_transfer_nodes.iter() {
4548                    for ImageResourceBarrier {
4549                        image,
4550                        next_access,
4551                        prev_access,
4552                        range,
4553                        ..
4554                    } in tls.pending_images.get(node_idx)
4555                    {
4556                        image_barriers.extend(image_barriers_from_transfers(
4557                            *image,
4558                            prev_access,
4559                            next_access,
4560                            *range,
4561                            transfers,
4562                            image_layout_transition_discard_contents(*prev_access, *next_access),
4563                        ));
4564                    }
4565                }
4566            }
4567
4568            pipeline_barrier_from_iters(
4569                &cmd_buf.device,
4570                cmd_buf.handle,
4571                None,
4572                buffer_barriers.into_iter(),
4573                image_barriers.into_iter(),
4574            );
4575
4576            if let Some(pending) = pending_buffer_transfer_nodes.as_mut() {
4577                pending.remove_where(|node_idx, _buffer, transfers| {
4578                    for BufferResourceBarrier { range, .. } in tls.pending_buffers.get(node_idx) {
4579                        if consume_pending_buffer_transfers(transfers, *range) {
4580                            return true;
4581                        }
4582                    }
4583
4584                    false
4585                });
4586
4587                if pending.is_empty() {
4588                    *pending_buffer_transfer_nodes = None;
4589                }
4590            }
4591
4592            if let Some(pending) = pending_image_transfer_nodes.as_mut() {
4593                pending.remove_where(|node_idx, _image, transfers| {
4594                    for ImageResourceBarrier { range, .. } in tls.pending_images.get(node_idx) {
4595                        if consume_pending_image_transfers(transfers, *range) {
4596                            return true;
4597                        }
4598                    }
4599
4600                    false
4601                });
4602
4603                if pending.is_empty() {
4604                    *pending_image_transfer_nodes = None;
4605                }
4606            }
4607        });
4608    }
4609
4610    #[profiling::function]
4611    fn record_node_cmds<P>(
4612        &mut self,
4613        pool: &mut P,
4614        cmd_buf: &CommandBuffer,
4615        node_idx: usize,
4616        end_cmd_idx: usize,
4617        ownership: &mut RecordingOwnership,
4618    ) -> Result<(), DriverError>
4619    where
4620        P: SubmissionPool,
4621    {
4622        thread_local! {
4623            static SCHEDULE: RefCell<Schedule> = Default::default();
4624        }
4625
4626        SCHEDULE.with_borrow_mut(|schedule| {
4627            schedule.access_index.update(&self.graph, end_cmd_idx);
4628            schedule.cmds.clear();
4629
4630            self.schedule_node_cmds(node_idx, end_cmd_idx, schedule);
4631            self.record_scheduled_cmds(pool, cmd_buf, schedule, end_cmd_idx, ownership)
4632        })
4633    }
4634
4635    fn track_pending_transfers(
4636        &mut self,
4637        schedule: &Schedule,
4638        queue_family_index: u32,
4639        ownership: &mut RecordingOwnership,
4640    ) {
4641        let resource_count = self.graph.resources.len();
4642
4643        for cmd_idx in schedule.cmds.iter().copied() {
4644            let cmd = &self.graph.cmds[cmd_idx];
4645
4646            for (node_idx, accesses) in cmd.execs.iter().flat_map(|exec| exec.accesses.iter()) {
4647                if let Some(buffer) = self.graph.resources[node_idx].as_buffer() {
4648                    if buffer.info.sharing_mode == vk::SharingMode::CONCURRENT {
4649                        continue;
4650                    }
4651
4652                    for access in accesses.iter() {
4653                        let SubresourceRange::Buffer(access_range) = access.subresource else {
4654                            continue;
4655                        };
4656                        let unclaimed = ownership
4657                            .claim_buffer(node_idx, access_range.resolve_whole(buffer.info.size));
4658
4659                        self.exclusive_buffer_ranges
4660                            .entry(node_idx)
4661                            .or_default()
4662                            .extend(unclaimed.iter().copied());
4663
4664                        for access_range in unclaimed {
4665                            for (subresource, sharing) in
4666                                buffer.sync_info_with_sharing_range(access_range)
4667                            {
4668                                let Some(range) = subresource.range.intersection(access_range)
4669                                else {
4670                                    continue;
4671                                };
4672                                let Some((src_queue_family_index, src_queue_index)) =
4673                                    exclusive_transfer_source(sharing, queue_family_index)
4674                                else {
4675                                    continue;
4676                                };
4677                                let transfer = BufferQueueOwnershipTransfer {
4678                                    src_queue_family_index,
4679                                    dst_queue_family_index: queue_family_index,
4680                                    range,
4681                                };
4682
4683                                queue_ownership_release_group(
4684                                    &mut self.queue_ownership_release_groups,
4685                                    src_queue_family_index,
4686                                    src_queue_index,
4687                                )
4688                                .buffers
4689                                .push((buffer.handle, range));
4690                                self.pending_buffer_transfer_nodes
4691                                    .get_or_insert_with(|| {
4692                                        PendingTransferNodes::new(resource_count)
4693                                    })
4694                                    .push_transfer(node_idx, buffer.handle, transfer);
4695                            }
4696                        }
4697                    }
4698
4699                    continue;
4700                }
4701
4702                let Some(image) = self.graph.resources[node_idx].as_image() else {
4703                    continue;
4704                };
4705                if image.info.sharing_mode == vk::SharingMode::CONCURRENT {
4706                    continue;
4707                }
4708
4709                for access in accesses.iter() {
4710                    let SubresourceRange::Image(access_range) = access.subresource else {
4711                        continue;
4712                    };
4713                    let unclaimed = ownership.claim_image(
4714                        node_idx,
4715                        image.info,
4716                        image.info.resolve_subresource_counts(access_range),
4717                    );
4718
4719                    self.exclusive_image_ranges
4720                        .entry(node_idx)
4721                        .or_default()
4722                        .extend(unclaimed.iter().copied());
4723
4724                    for access_range in unclaimed {
4725                        for (subresource, sharing) in
4726                            image.sync_info_with_sharing_range(access_range)
4727                        {
4728                            let Some(range) = image_subresource_range_intersection(
4729                                subresource.range,
4730                                access_range,
4731                            ) else {
4732                                continue;
4733                            };
4734                            let Some((src_queue_family_index, src_queue_index)) =
4735                                exclusive_transfer_source(sharing, queue_family_index)
4736                            else {
4737                                continue;
4738                            };
4739                            let layout = subresource.layout.unwrap_or(vk::ImageLayout::UNDEFINED);
4740                            let transfer = ImageQueueOwnershipTransfer {
4741                                src_queue_family_index,
4742                                src_queue_index,
4743                                dst_queue_family_index: queue_family_index,
4744                                layout,
4745                                range,
4746                            };
4747
4748                            queue_ownership_release_group(
4749                                &mut self.queue_ownership_release_groups,
4750                                src_queue_family_index,
4751                                src_queue_index,
4752                            )
4753                            .images
4754                            .push((image.handle, layout, range));
4755                            self.pending_image_transfer_nodes
4756                                .get_or_insert_with(|| PendingTransferNodes::new(resource_count))
4757                                .push_transfer(node_idx, image.handle, transfer);
4758                        }
4759                    }
4760                }
4761            }
4762        }
4763    }
4764
4765    fn record_cmd_indices(
4766        &mut self,
4767        cmd_buf: &CommandBuffer,
4768        cmd_indices: impl IntoIterator<Item = usize>,
4769    ) -> Result<(), DriverError> {
4770        #[cfg(feature = "checked")]
4771        let graph_id = self.graph.graph_id();
4772        let query_pool = self
4773            .query_pool_results
4774            .as_ref()
4775            .map(SubmittedTimestampQueries::query_pool);
4776        for cmd_idx in cmd_indices {
4777            let timestamp_queries = self.take_timestamp_queries_for_command(cmd_idx);
4778            let cmd = &mut self.graph.cmds[cmd_idx];
4779
4780            profiling::scope!("Cmd", cmd.name());
4781            let stream_label = cmd
4782                .stream_scope_id
4783                .and_then(|_| CommandBufferDebugLabel::begin(cmd_buf, "command stream boundary"));
4784            let _cmd_label = CommandBufferDebugLabel::begin(cmd_buf, cmd.name());
4785            let mut next_timestamp_query_idx = 0;
4786
4787            if let Some(timestamp_queries) = &timestamp_queries {
4788                next_timestamp_query_idx = Self::write_timestamp_queries(
4789                    cmd_buf,
4790                    query_pool,
4791                    timestamp_queries,
4792                    TimestampQueryPlacement::BeforeExec,
4793                    0,
4794                    next_timestamp_query_idx,
4795                );
4796            }
4797
4798            let recorded_command = &mut self.recorded_commands[cmd_idx];
4799            let is_graphics = recorded_command.render_pass.is_some();
4800
4801            trace!("recording cmd [{}: {}]", cmd_idx, cmd.name());
4802
4803            if !recorded_command.descriptor_sets.is_empty() {
4804                Self::write_descriptor_sets(cmd_buf, &self.graph.resources, cmd, recorded_command)?;
4805            }
4806
4807            let (render_area, render_pass_label) = if is_graphics {
4808                Self::record_image_layout_transitions(
4809                    cmd_buf,
4810                    &mut self.graph.resources,
4811                    cmd,
4812                    &mut self.pending_buffer_transfer_nodes,
4813                    &mut self.pending_image_transfer_nodes,
4814                );
4815
4816                let render_area = vk::Rect2D {
4817                    offset: vk::Offset2D { x: 0, y: 0 },
4818                    extent: Self::render_extent(&self.graph.resources, cmd),
4819                };
4820                let render_pass_label = CommandBufferDebugLabel::begin(
4821                    cmd_buf,
4822                    format!("{} / render pass", cmd.name()),
4823                );
4824
4825                Self::begin_render_pass(
4826                    cmd_buf,
4827                    &self.graph.resources,
4828                    cmd,
4829                    recorded_command,
4830                    render_area,
4831                )?;
4832
4833                (Some(render_area), render_pass_label)
4834            } else {
4835                (None, None)
4836            };
4837
4838            for exec_idx in 0..cmd.execs.len() {
4839                let render_area = if is_graphics {
4840                    Some(
4841                        cmd.execs[exec_idx]
4842                            .render_area
4843                            .unwrap_or(render_area.expect("missing render area")),
4844                    )
4845                } else {
4846                    None
4847                };
4848                let exec_label_name = cmd_buf
4849                    .device
4850                    .physical
4851                    .instance
4852                    .info
4853                    .debug
4854                    .then(|| format!("{} / exec {exec_idx}", cmd.name()));
4855
4856                let exec = &mut cmd.execs[exec_idx];
4857
4858                if exec_idx > 0 {
4859                    if is_graphics {
4860                        Self::next_subpass(cmd_buf);
4861                    }
4862
4863                    if let Some(timestamp_queries) = &timestamp_queries {
4864                        next_timestamp_query_idx = Self::write_timestamp_queries(
4865                            cmd_buf,
4866                            query_pool,
4867                            timestamp_queries,
4868                            TimestampQueryPlacement::BeforeExec,
4869                            exec_idx,
4870                            next_timestamp_query_idx,
4871                        );
4872                    }
4873                }
4874
4875                if let Some(pipeline) = exec.pipeline.as_mut() {
4876                    Self::bind_pipeline(
4877                        cmd_buf,
4878                        recorded_command,
4879                        exec_idx,
4880                        pipeline,
4881                        exec.depth_stencil,
4882                    )?;
4883
4884                    if is_graphics {
4885                        let render_area = render_area.expect("missing render area");
4886
4887                        // In this case we set the viewport and scissor for the user
4888                        Self::set_viewport(
4889                            cmd_buf,
4890                            render_area.offset.x as _,
4891                            render_area.offset.y as _,
4892                            render_area.extent.width as _,
4893                            render_area.extent.height as _,
4894                            exec.depth_stencil
4895                                .map(|depth_stencil| {
4896                                    let min = depth_stencil.min.0;
4897                                    let max = depth_stencil.max.0;
4898                                    min..max
4899                                })
4900                                .unwrap_or(0.0..1.0),
4901                        );
4902                        Self::set_scissor(
4903                            cmd_buf,
4904                            render_area.offset.x,
4905                            render_area.offset.y,
4906                            render_area.extent.width,
4907                            render_area.extent.height,
4908                        );
4909                    }
4910
4911                    Self::bind_descriptor_sets(cmd_buf, pipeline, recorded_command, exec_idx);
4912                }
4913
4914                if !is_graphics {
4915                    Self::record_execution_barriers(
4916                        cmd_buf,
4917                        &mut self.graph.resources,
4918                        &exec.accesses,
4919                        &mut self.pending_buffer_transfer_nodes,
4920                        &mut self.pending_image_transfer_nodes,
4921                    );
4922                }
4923
4924                trace!("    > exec[{exec_idx}]");
4925
4926                {
4927                    profiling::scope!("Execute callback");
4928                    let _exec_label = exec_label_name.as_deref().and_then(|exec_label_name| {
4929                        CommandBufferDebugLabel::begin(cmd_buf, exec_label_name)
4930                    });
4931
4932                    let exec_func = exec.func.take().expect("missing command function");
4933                    exec.func = exec_func.record(CommandRef::new(
4934                        cmd_buf,
4935                        &self.graph.resources,
4936                        exec,
4937                        #[cfg(feature = "checked")]
4938                        graph_id,
4939                    ));
4940                }
4941
4942                if let Some(timestamp_queries) = &timestamp_queries {
4943                    next_timestamp_query_idx = Self::write_timestamp_queries(
4944                        cmd_buf,
4945                        query_pool,
4946                        timestamp_queries,
4947                        TimestampQueryPlacement::AfterExec,
4948                        exec_idx,
4949                        next_timestamp_query_idx,
4950                    );
4951                }
4952            }
4953
4954            if is_graphics {
4955                trace!("  end render pass");
4956
4957                cmd_buf.end_render_pass();
4958            }
4959
4960            drop(render_pass_label);
4961            drop(stream_label);
4962        }
4963        Ok(())
4964    }
4965
4966    #[profiling::function]
4967    fn record_scheduled_cmds<P>(
4968        &mut self,
4969        pool: &mut P,
4970        cmd_buf: &CommandBuffer,
4971        schedule: &mut Schedule,
4972        end_cmd_idx: usize,
4973        ownership: &mut RecordingOwnership,
4974    ) -> Result<(), DriverError>
4975    where
4976        P: SubmissionPool,
4977    {
4978        if schedule.cmds.is_empty() {
4979            return Ok(());
4980        }
4981
4982        // // Print some handy details or hit a breakpoint if you set the flag
4983        // if log_enabled!(Debug) && self.graph.debug {
4984        //     debug!("resolving the following graph:\n\n{:#?}\n\n", self.graph);
4985        // }
4986
4987        debug_assert!(
4988            schedule.cmds.windows(2).all(|w| w[0] <= w[1]),
4989            "Unsorted schedule"
4990        );
4991
4992        // Optimize the schedule; requesting the required resources it needs
4993        schedule.reorder_cmds(end_cmd_idx);
4994        self.merge_scheduled_cmds(&mut schedule.cmds);
4995        self.lease_scheduled_resources(pool, &schedule.cmds)?;
4996        self.track_pending_transfers(schedule, cmd_buf.info.queue_family_index, ownership);
4997
4998        let has_pending_timestamp_queries = self
4999            .graph
5000            .timestamp_queries
5001            .as_ref()
5002            .is_some_and(|timestamp_queries| timestamp_queries.iter().any(Option::is_some));
5003        let include_final_timestamp_queries = schedule.cmds.len() == self.graph.cmds.len();
5004
5005        if has_pending_timestamp_queries {
5006            if cmd_buf
5007                .device
5008                .physical
5009                .queue_families
5010                .get(cmd_buf.info.queue_family_index as usize)
5011                .is_none_or(|queue_family| {
5012                    !Self::queue_family_supports_timestamp_queries(queue_family)
5013                })
5014            {
5015                self.graph.timestamp_queries = None;
5016            } else {
5017                if self.query_pool_results.is_none() {
5018                    self.prepare_timestamp_query_results(cmd_buf)?;
5019                }
5020
5021                self.prepare_timestamp_queries_for_commands(
5022                    &schedule.cmds,
5023                    include_final_timestamp_queries,
5024                );
5025
5026                if !self.query_pool_reset {
5027                    let query_pool_results = self
5028                        .query_pool_results
5029                        .as_ref()
5030                        .expect("missing query pool results");
5031                    query_pool_results.reset(cmd_buf);
5032                    query_pool_results.write_epoch(cmd_buf);
5033                    self.query_pool_reset = true;
5034                }
5035            }
5036        }
5037
5038        self.record_cmd_indices(cmd_buf, schedule.cmds.iter().copied())?;
5039
5040        if include_final_timestamp_queries
5041            && let Some(timestamp_queries) =
5042                self.take_timestamp_queries_for_command(self.graph.cmds.len())
5043        {
5044            let query_pool = self
5045                .query_pool_results
5046                .as_ref()
5047                .map(SubmittedTimestampQueries::query_pool);
5048
5049            Self::write_timestamp_queries(
5050                cmd_buf,
5051                query_pool,
5052                &timestamp_queries,
5053                TimestampQueryPlacement::BeforeExec,
5054                0,
5055                0,
5056            );
5057        }
5058
5059        self.remap_timestamp_queries_after_removing_scheduled(&schedule.cmds);
5060
5061        thread_local! {
5062            static PASSES: RefCell<Vec<CommandData>> = Default::default();
5063        }
5064
5065        PASSES.with_borrow_mut(|passes| {
5066            debug_assert!(passes.is_empty());
5067
5068            // We have to keep the bindings and pipelines alive until the gpu is done
5069            schedule.cmds.sort_unstable();
5070            while let Some(schedule_idx) = schedule.cmds.pop() {
5071                debug_assert!(!self.graph.cmds.is_empty());
5072
5073                while let Some(cmd) = self.graph.cmds.pop() {
5074                    let cmd_idx = self.graph.cmds.len();
5075
5076                    if cmd_idx == schedule_idx {
5077                        // This was a scheduled cmd - store it!
5078
5079                        self.submit_retained.push(SubmittedCommand {
5080                            cmd,
5081                            _resources: self
5082                                .recorded_commands
5083                                .pop()
5084                                .expect("missing recorded command"),
5085                        });
5086                        break;
5087                    } else {
5088                        debug_assert!(cmd_idx > schedule_idx);
5089
5090                        passes.push(cmd);
5091                    }
5092                }
5093            }
5094
5095            debug_assert!(self.recorded_commands.is_empty());
5096
5097            // Put the other passes back for future resolves
5098            self.graph.cmds.extend(passes.drain(..).rev());
5099        });
5100
5101        log::trace!("Recorded passes");
5102
5103        Ok(())
5104    }
5105
5106    fn remap_timestamp_queries_after_removing_scheduled(&mut self, schedule: &[usize]) {
5107        let old_cmd_len = self.graph.cmds.len();
5108        let mut scheduled = FixedBitSet::with_capacity(old_cmd_len);
5109        for cmd_idx in schedule.iter().copied() {
5110            scheduled.insert(cmd_idx);
5111        }
5112
5113        let mut old_to_new_cmd_idx = vec![0; old_cmd_len + 1];
5114        let mut new_cmd_idx = 0;
5115        for (old_cmd_idx, new_idx) in old_to_new_cmd_idx.iter_mut().enumerate().take(old_cmd_len) {
5116            *new_idx = new_cmd_idx;
5117            if !scheduled.contains(old_cmd_idx) {
5118                new_cmd_idx += 1;
5119            }
5120        }
5121
5122        old_to_new_cmd_idx[old_cmd_len] = new_cmd_idx;
5123
5124        if let Some(timestamp_queries) = &mut self.graph.timestamp_queries {
5125            for query in timestamp_queries.iter_mut().flatten() {
5126                query.command_idx = old_to_new_cmd_idx[query.command_idx];
5127            }
5128        }
5129    }
5130
5131    #[profiling::function]
5132    fn render_extent(bindings: &[AnyResource], pass: &CommandData) -> vk::Extent2D {
5133        // set_render_area was not specified so we're going to guess using the minimum common
5134        // attachment extents
5135        let first_exec = pass.expect_first_exec();
5136
5137        // We must be able to find the render area because render passes require at least one
5138        // image to be attached
5139        let (mut width, mut height) = (u32::MAX, u32::MAX);
5140        for (attachment_width, attachment_height) in first_exec
5141            .attachments
5142            .color_attachments()
5143            .map(|(_, state)| state.attachment)
5144            .chain(
5145                first_exec
5146                    .attachments
5147                    .depth_stencil_attachment()
5148                    .into_iter()
5149                    .filter(|state| state.is_attachment)
5150                    .map(|state| state.attachment),
5151            )
5152            .map(|attachment| {
5153                let info = Self::expect_attachment_image(bindings, &attachment).info;
5154
5155                (
5156                    info.width >> attachment.base_mip_level,
5157                    info.height >> attachment.base_mip_level,
5158                )
5159            })
5160        {
5161            width = width.min(attachment_width);
5162            height = height.min(attachment_height);
5163        }
5164
5165        vk::Extent2D { height, width }
5166    }
5167
5168    /// Returns a borrow of the original Vulkan resource (buffer, image or acceleration structure)
5169    /// which the given node represents.
5170    pub fn resource<N>(&self, resource_node: N) -> &N::Resource
5171    where
5172        N: Node,
5173    {
5174        self.graph.resource(resource_node)
5175    }
5176
5177    /// Mutates a schedule of command indices that are required to be executed, in order, for the
5178    /// given node.
5179    #[profiling::function]
5180    fn schedule_node_cmds(&self, node_idx: usize, end_cmd_idx: usize, schedule: &mut Schedule) {
5181        trace!("scheduling node {node_idx}");
5182        schedule.schedule_required_node_prefixes([(node_idx, end_cmd_idx)]);
5183
5184        if log_enabled!(Debug) {
5185            if !schedule.cmds.is_empty() {
5186                debug!(
5187                    "schedule: {}",
5188                    schedule
5189                        .cmds
5190                        .iter()
5191                        .copied()
5192                        .map(|idx| format!("[{}: {}]", idx, self.graph.cmds[idx].name()))
5193                        .collect::<Vec<_>>()
5194                        .join(", ")
5195                );
5196            }
5197
5198            if log_enabled!(Trace) {
5199                let unscheduled = (0..end_cmd_idx)
5200                    .filter(|&cmd_idx| !schedule.node_schedule.selected_cmds.contains(cmd_idx))
5201                    .collect::<Box<_>>();
5202
5203                if !unscheduled.is_empty() {
5204                    trace!(
5205                        "delaying: {}",
5206                        unscheduled
5207                            .iter()
5208                            .copied()
5209                            .map(|idx| format!("[{}: {}]", idx, self.graph.cmds[idx].name()))
5210                            .collect::<Vec<_>>()
5211                            .join(", ")
5212                    );
5213                }
5214
5215                if end_cmd_idx < self.graph.cmds.len() {
5216                    trace!(
5217                        "ignoring: {}",
5218                        self.graph.cmds[end_cmd_idx..]
5219                            .iter()
5220                            .enumerate()
5221                            .map(|(idx, cmd)| {
5222                                format!("[{}: {}]", idx + end_cmd_idx, cmd.name())
5223                            })
5224                            .collect::<Vec<_>>()
5225                            .join(", ")
5226                    );
5227                }
5228            }
5229        }
5230    }
5231
5232    fn set_scissor(cmd_buf: &CommandBuffer, x: i32, y: i32, width: u32, height: u32) {
5233        unsafe {
5234            cmd_buf.device.cmd_set_scissor(
5235                cmd_buf.handle,
5236                0,
5237                slice::from_ref(&vk::Rect2D {
5238                    extent: vk::Extent2D { width, height },
5239                    offset: vk::Offset2D { x, y },
5240                }),
5241            );
5242        }
5243    }
5244
5245    fn set_viewport(
5246        cmd_buf: &CommandBuffer,
5247        x: f32,
5248        y: f32,
5249        width: f32,
5250        height: f32,
5251        depth: Range<f32>,
5252    ) {
5253        unsafe {
5254            cmd_buf.device.cmd_set_viewport(
5255                cmd_buf.handle,
5256                0,
5257                slice::from_ref(&vk::Viewport {
5258                    x,
5259                    y,
5260                    width,
5261                    height,
5262                    min_depth: depth.start,
5263                    max_depth: depth.end,
5264                }),
5265            );
5266        }
5267    }
5268
5269    fn take_timestamp_queries_for_command(
5270        &mut self,
5271        command_idx: usize,
5272    ) -> Option<Box<[TimestampQueryData]>> {
5273        let Some(graph_timestamp_queries) = &mut self.graph.timestamp_queries else {
5274            return None;
5275        };
5276
5277        let mut timestamp_queries = Vec::new();
5278
5279        for timestamp_query in graph_timestamp_queries {
5280            if timestamp_query
5281                .as_ref()
5282                .is_some_and(|timestamp_query| timestamp_query.command_idx == command_idx)
5283            {
5284                timestamp_queries.push(
5285                    timestamp_query
5286                        .take()
5287                        .expect("missing timestamp query after command match"),
5288                );
5289            }
5290        }
5291
5292        timestamp_queries.sort_unstable_by_key(|timestamp_query| {
5293            (
5294                timestamp_query.exec_idx,
5295                timestamp_query.placement,
5296                timestamp_query.query.index(),
5297            )
5298        });
5299
5300        (!timestamp_queries.is_empty()).then(|| timestamp_queries.into_boxed_slice())
5301    }
5302
5303    /// Records and submits all remaining commands using an internally allocated command buffer.
5304    ///
5305    /// This legacy submit path only supports binary semaphore behavior. All wait and signal
5306    /// values must be `0`, and wait and signal stage masks must be `ALL_COMMANDS` or `NONE`.
5307    pub fn queue_submit<P>(
5308        self,
5309        resource_pool: &mut P,
5310        queue_family_index: u32,
5311        queue_index: u32,
5312    ) -> Result<Fence, DriverError>
5313    where
5314        P: Pool<CommandBufferInfo, CommandBuffer> + SubmissionPool,
5315    {
5316        trace!("queue_submit");
5317
5318        /*
5319        Phase 1: Get the main command buffer and record commands. This also discovers any ownership
5320        transfers required by the scheduled work.
5321        */
5322        let cmd_buf = resource_pool.resource(CommandBufferInfo::new(queue_family_index as _))?;
5323        let mut fence = Fence::create(&cmd_buf.device, false)?;
5324        cmd_buf.begin(
5325            &vk::CommandBufferBeginInfo::default()
5326                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
5327        )?;
5328        let recording = self.record(resource_pool, cmd_buf, RecordSelection::All)?;
5329        recording.cmd_buf.end()?;
5330
5331        let mut recorded = recording.finish()?;
5332        recorded.queue_submit(&mut fence, queue_index, QueueSubmitInfo::QUEUE_SUBMIT)?;
5333
5334        fence.drop_when_signaled(recorded);
5335
5336        Ok(fence)
5337    }
5338
5339    /// Records any remaining graph commands into `cmd_buf` and returns a [`Recording`].
5340    ///
5341    /// When `selection` is [`RecordSelection::Nodes`], nodes are processed sequentially in the
5342    /// provided slice order and each step mutates the remaining submission state.
5343    #[profiling::function]
5344    pub fn record<'p, 's, P, Cb>(
5345        mut self,
5346        resource_pool: &'p mut P,
5347        cmd_buf: Cb,
5348        selection: impl Into<RecordSelection<'s>>,
5349    ) -> Result<Recording<'p, P, Cb>, DriverError>
5350    where
5351        P: SubmissionPool,
5352        Cb: AsRef<CommandBuffer>,
5353    {
5354        let mut ownership = RecordingOwnership::default();
5355        self.record_selection_impl(
5356            resource_pool,
5357            cmd_buf.as_ref(),
5358            selection.into(),
5359            &mut ownership,
5360        )?;
5361
5362        Ok(Recording {
5363            ownership,
5364            cmd_buf,
5365            resource_pool,
5366            submission: self,
5367        })
5368    }
5369
5370    #[profiling::function]
5371    fn record_impl<P>(
5372        &mut self,
5373        pool: &mut P,
5374        cmd_buf: &CommandBuffer,
5375        ownership: &mut RecordingOwnership,
5376    ) -> Result<(), DriverError>
5377    where
5378        P: SubmissionPool,
5379    {
5380        if self.graph.cmds.is_empty() {
5381            return Ok(());
5382        }
5383
5384        thread_local! {
5385            static SCHEDULE: RefCell<Schedule> = Default::default();
5386        }
5387
5388        SCHEDULE.with_borrow_mut(|schedule| {
5389            schedule
5390                .access_index
5391                .update(&self.graph, self.graph.cmds.len());
5392            schedule.cmds.clear();
5393            schedule.cmds.extend(0..self.graph.cmds.len());
5394
5395            self.record_scheduled_cmds(pool, cmd_buf, schedule, self.graph.cmds.len(), ownership)
5396        })
5397    }
5398
5399    #[profiling::function]
5400    fn record_resource_dependencies_impl<P>(
5401        &mut self,
5402        pool: &mut P,
5403        cmd_buf: &CommandBuffer,
5404        resource_node: impl Node,
5405        ownership: &mut RecordingOwnership,
5406    ) -> Result<(), DriverError>
5407    where
5408        P: SubmissionPool,
5409    {
5410        self.graph.assert_node_owner(&resource_node);
5411
5412        let node_idx = resource_node.index();
5413
5414        debug_assert!(self.graph.resources.get(node_idx).is_some());
5415
5416        // We record up to but not including the first command which accesses the target node.
5417        if let Some(end_pass_idx) = self.graph.first_node_access_pass_index(resource_node) {
5418            thread_local! {
5419                static SCHEDULE: RefCell<Schedule> = Default::default();
5420            }
5421
5422            SCHEDULE.with_borrow_mut(|tls| {
5423                tls.access_index.update(&self.graph, end_pass_idx + 1);
5424                schedule_dependency_cmds_before_target_access(node_idx, end_pass_idx, tls);
5425                self.record_scheduled_cmds(pool, cmd_buf, tls, end_pass_idx, ownership)
5426            })?;
5427        }
5428
5429        Ok(())
5430    }
5431
5432    #[profiling::function]
5433    fn record_resource_impl<P>(
5434        &mut self,
5435        pool: &mut P,
5436        cmd_buf: &CommandBuffer,
5437        resource_node: impl Node,
5438        ownership: &mut RecordingOwnership,
5439    ) -> Result<(), DriverError>
5440    where
5441        P: SubmissionPool,
5442    {
5443        self.graph.assert_node_owner(&resource_node);
5444
5445        let node_idx = resource_node.index();
5446
5447        debug_assert!(self.graph.resources.get(node_idx).is_some());
5448
5449        if self.graph.cmds.is_empty() {
5450            return Ok(());
5451        }
5452
5453        let end_pass_idx = self.graph.cmds.len();
5454        self.record_node_cmds(pool, cmd_buf, node_idx, end_pass_idx, ownership)
5455    }
5456
5457    #[profiling::function]
5458    fn write_descriptor_sets(
5459        cmd_buf: &CommandBuffer,
5460        bindings: &[AnyResource],
5461        pass: &CommandData,
5462        recorded_command: &CommandRecordingResources,
5463    ) -> Result<(), DriverError> {
5464        #[derive(Clone, Copy)]
5465        struct IndexedWrite<'a> {
5466            info_idx: usize,
5467            write: vk::WriteDescriptorSet<'a>,
5468        }
5469
5470        #[derive(Default)]
5471        struct DescriptorScratch<'a> {
5472            accel_struct_handles: Vec<vk::AccelerationStructureKHR>,
5473            accel_struct_infos: Vec<vk::WriteDescriptorSetAccelerationStructureKHR<'a>>,
5474            accel_struct_writes: Vec<IndexedWrite<'static>>,
5475            buffer_infos: Vec<vk::DescriptorBufferInfo>,
5476            buffer_writes: Vec<IndexedWrite<'a>>,
5477            descriptors: Vec<vk::WriteDescriptorSet<'a>>,
5478            image_infos: Vec<vk::DescriptorImageInfo>,
5479            image_writes: Vec<IndexedWrite<'a>>,
5480        }
5481
5482        thread_local! {
5483            static DESCRIPTOR: RefCell<DescriptorScratch<'static>> = Default::default();
5484        }
5485
5486        DESCRIPTOR.with_borrow_mut(|tls| {
5487            tls.accel_struct_handles.clear();
5488            tls.accel_struct_infos.clear();
5489            tls.accel_struct_writes.clear();
5490            tls.buffer_infos.clear();
5491            tls.buffer_writes.clear();
5492            tls.descriptors.clear();
5493            tls.image_infos.clear();
5494            tls.image_writes.clear();
5495
5496            for (exec_idx, exec, pipeline) in pass
5497            .execs
5498            .iter()
5499            .enumerate()
5500            .filter_map(|(exec_idx, exec)| {
5501                exec.pipeline
5502                    .as_ref()
5503                    .map(|pipeline| (exec_idx, exec, pipeline))
5504            })
5505            .filter(|(.., pipeline)| !pipeline.descriptor_info().layouts.is_empty())
5506        {
5507            let descriptor_sets = &recorded_command.descriptor_sets[exec_idx];
5508
5509            // Write the manually bound things (access, read, and write functions)
5510            for (descriptor, (node_idx, view_info)) in exec.bindings.iter() {
5511                let (descriptor_set_idx, dst_binding, binding_offset) = descriptor.into_tuple();
5512                let Some((descriptor_info, _)) = pipeline.descriptor_bindings().get(&Descriptor {
5513                    set: descriptor_set_idx,
5514                    binding: dst_binding,
5515                }) else {
5516                    warn!(
5517                        "binding {}.{}[{}] not found in shader reflection for command \"{}\"",
5518                        descriptor_set_idx,
5519                        dst_binding,
5520                        binding_offset,
5521                        pass.name(),
5522                    );
5523                    return Err(DriverError::InvalidData);
5524                };
5525                if exec.descriptor_sets.contains_key(&descriptor_set_idx) {
5526                    continue;
5527                }
5528
5529                let descriptor_type = descriptor_info.descriptor_type();
5530                let bound_node = &bindings[*node_idx];
5531                if let Some(image) = bound_node.as_image() {
5532                    let mut image_view_info = *view_info.expect_image();
5533
5534                    // Handle default views which did not specify a particular aspect
5535                    if image_view_info.aspect_mask.is_empty() {
5536                        image_view_info.aspect_mask = format_aspect_mask(image.info.format);
5537                    }
5538
5539                    let image_view = Image::view(image, image_view_info)?;
5540                    let image_layout = match descriptor_type {
5541                        vk::DescriptorType::COMBINED_IMAGE_SAMPLER
5542                        | vk::DescriptorType::SAMPLED_IMAGE => {
5543                            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
5544                        }
5545                        vk::DescriptorType::STORAGE_IMAGE => vk::ImageLayout::GENERAL,
5546                        _ => {
5547                            warn!(
5548                                "invalid image descriptor type at binding {}.{}[{}] in command \"{}\"",
5549                                descriptor_set_idx,
5550                                dst_binding,
5551                                binding_offset,
5552                                pass.name()
5553                            );
5554
5555                            return Err(DriverError::InvalidData);
5556                        }
5557                    };
5558
5559                    if binding_offset == 0 {
5560                        tls.image_writes.push(IndexedWrite {
5561                            info_idx: tls.image_infos.len(),
5562                            write: vk::WriteDescriptorSet {
5563                                dst_set: descriptor_sets[descriptor_set_idx as usize].handle(),
5564                                dst_binding,
5565                                descriptor_type,
5566                                descriptor_count: 1,
5567                                ..Default::default()
5568                            },
5569                        });
5570                    } else {
5571                        tls.image_writes
5572                            .last_mut()
5573                            .expect("missing image descriptor write")
5574                            .write
5575                            .descriptor_count += 1;
5576                    }
5577
5578                    tls.image_infos.push(
5579                        vk::DescriptorImageInfo::default()
5580                            .image_layout(image_layout)
5581                            .image_view(image_view),
5582                    );
5583                } else if let Some(buffer) = bound_node.as_buffer() {
5584                    let buffer_view_info = view_info.expect_buffer();
5585
5586                    if binding_offset == 0 {
5587                        tls.buffer_writes.push(IndexedWrite {
5588                            info_idx: tls.buffer_infos.len(),
5589                            write: vk::WriteDescriptorSet {
5590                                dst_set: descriptor_sets[descriptor_set_idx as usize].handle(),
5591                                dst_binding,
5592                                descriptor_type,
5593                                descriptor_count: 1,
5594                                ..Default::default()
5595                            },
5596                        });
5597                    } else {
5598                        tls.buffer_writes
5599                            .last_mut()
5600                            .expect("missing buffer descriptor write")
5601                            .write
5602                            .descriptor_count += 1;
5603                    }
5604
5605                    tls.buffer_infos.push(
5606                        vk::DescriptorBufferInfo::default()
5607                            .buffer(buffer.handle)
5608                            .offset(buffer_view_info.start)
5609                            .range(buffer_view_info.end - buffer_view_info.start),
5610                    );
5611                } else if let Some(accel_struct) = bound_node.as_accel_struct() {
5612                    if binding_offset == 0 {
5613                        tls.accel_struct_writes.push(IndexedWrite {
5614                            info_idx: tls.accel_struct_handles.len(),
5615                            write: vk::WriteDescriptorSet::default()
5616                                .dst_set(descriptor_sets[descriptor_set_idx as usize].handle())
5617                                .dst_binding(dst_binding)
5618                                .descriptor_type(descriptor_type)
5619                                .descriptor_count(1),
5620                        });
5621                    } else {
5622                        tls
5623                            .accel_struct_writes
5624                            .last_mut()
5625                            .expect("missing acceleration structure descriptor write")
5626                            .write
5627                            .descriptor_count += 1;
5628                    }
5629
5630                    tls.accel_struct_handles.push(accel_struct.handle);
5631                } else {
5632                    warn!(
5633                        "invalid bound resource kind at descriptor {}.{}[{}] in command \"{}\"",
5634                        descriptor_set_idx,
5635                        dst_binding,
5636                        binding_offset,
5637                        pass.name()
5638                    );
5639
5640                    return Err(DriverError::InvalidData);
5641                }
5642            }
5643
5644            if let ExecutionPipeline::Graphics(pipeline) = pipeline {
5645                // Write graphics render pass input attachments (they're automatic)
5646                if exec_idx > 0 {
5647                    for (
5648                        &Descriptor {
5649                            set: descriptor_set_idx,
5650                            binding: dst_binding,
5651                        },
5652                        (descriptor_info, _),
5653                    ) in &pipeline.inner.descriptor_bindings
5654                    {
5655                        if exec.descriptor_sets.contains_key(&descriptor_set_idx) {
5656                            continue;
5657                        }
5658
5659                        if let DescriptorInfo::InputAttachment(_, attachment_idx) = *descriptor_info
5660                        {
5661                            let current_attachment = exec
5662                                .attachments
5663                                .color_attachment(attachment_idx)
5664                                .map(|state| state.attachment)
5665                                .expect("missing input attachment target");
5666                            let attachment = pass.execs[0..exec_idx]
5667                                .iter()
5668                                .rev()
5669                                .find_map(|exec| {
5670                                    exec.attachments
5671                                        .color_attachment(attachment_idx)
5672                                        .map(|state| state.attachment)
5673                                        .filter(|attachment| {
5674                                            Attachment::are_compatible(
5675                                                Some(current_attachment),
5676                                                Some(*attachment),
5677                                            )
5678                                        })
5679                                })
5680                                .expect("input attachment not written");
5681                            let image_binding = &bindings[attachment.target];
5682                            let image = image_binding.expect_image();
5683                            let image_view =
5684                                Image::view(image, attachment.image_view_info(image.info))?;
5685
5686                            tls.image_writes.push(IndexedWrite {
5687                                info_idx: tls.image_infos.len(),
5688                                write: vk::WriteDescriptorSet {
5689                                    dst_set: descriptor_sets[descriptor_set_idx as usize].handle(),
5690                                    dst_binding,
5691                                    descriptor_type: vk::DescriptorType::INPUT_ATTACHMENT,
5692                                    descriptor_count: 1,
5693                                    ..Default::default()
5694                                },
5695                            });
5696
5697                            tls.image_infos.push(vk::DescriptorImageInfo {
5698                                image_layout: Self::attachment_layout(
5699                                    attachment.aspect_mask,
5700                                    exec.attachments
5701                                        .color_attachment(attachment_idx)
5702                                        .map(|state| {
5703                                            state.store == StoreOp::Store || state.resolve.is_some()
5704                                        })
5705                                        .unwrap_or_default(),
5706                                    true,
5707                                ),
5708                                image_view,
5709                                sampler: vk::Sampler::null(),
5710                            });
5711                        }
5712                    }
5713                }
5714            }
5715        }
5716
5717        // NOTE: We assign the below pointers after the above insertions so they remain stable!
5718
5719        let accel_struct_handles = tls.accel_struct_handles.as_ptr();
5720        for write_idx in 0..tls.accel_struct_writes.len() {
5721            let IndexedWrite {
5722                info_idx: handle_idx,
5723                write,
5724            } = tls.accel_struct_writes[write_idx];
5725
5726            unsafe {
5727                tls.accel_struct_infos.push(
5728                    vk::WriteDescriptorSetAccelerationStructureKHR {
5729                        acceleration_structure_count: write.descriptor_count,
5730                        p_acceleration_structures: accel_struct_handles.add(handle_idx),
5731                        ..Default::default()
5732                    },
5733                );
5734            }
5735        }
5736
5737        let infos = tls.accel_struct_infos.as_ptr();
5738        for (write_idx, IndexedWrite { mut write, .. }) in
5739            tls.accel_struct_writes.drain(..).enumerate()
5740        {
5741            unsafe {
5742                write.p_next = infos.add(write_idx) as *const _;
5743            }
5744
5745            tls.descriptors.push(write);
5746        }
5747
5748        let buffer_infos_ptr = tls.buffer_infos.as_ptr();
5749        for write_idx in 0..tls.buffer_writes.len() {
5750            let IndexedWrite {
5751            info_idx,
5752            mut write,
5753            } = tls.buffer_writes[write_idx];
5754            unsafe {
5755                write.p_buffer_info = buffer_infos_ptr.add(info_idx);
5756            }
5757            tls.descriptors.push(write);
5758        }
5759
5760        let image_infos_ptr = tls.image_infos.as_ptr();
5761        for write_idx in 0..tls.image_writes.len() {
5762            let IndexedWrite {
5763            info_idx,
5764            mut write,
5765            } = tls.image_writes[write_idx];
5766            unsafe {
5767                write.p_image_info = image_infos_ptr.add(info_idx);
5768            }
5769            tls.descriptors.push(write);
5770        }
5771
5772        if !tls.descriptors.is_empty() {
5773            trace!(
5774                "  writing {} descriptors ({} buffers, {} images)",
5775                tls.descriptors.len(),
5776                tls.buffer_infos.len(),
5777                tls.image_infos.len()
5778            );
5779
5780            unsafe {
5781                cmd_buf
5782                    .device
5783                    .update_descriptor_sets(tls.descriptors.as_slice(), &[]);
5784            }
5785        }
5786
5787        Ok(())
5788        })
5789    }
5790
5791    fn write_timestamp_queries(
5792        cmd_buf: &CommandBuffer,
5793        query_pool: Option<vk::QueryPool>,
5794        timestamp_queries: &[TimestampQueryData],
5795        placement: TimestampQueryPlacement,
5796        exec_idx: usize,
5797        start_idx: usize,
5798    ) -> usize {
5799        let mut query_idx = start_idx;
5800
5801        while let Some(timestamp_query) = timestamp_queries.get(query_idx) {
5802            if timestamp_query.exec_idx < exec_idx
5803                || timestamp_query.exec_idx == exec_idx && timestamp_query.placement < placement
5804            {
5805                query_idx += 1;
5806                continue;
5807            }
5808
5809            if timestamp_query.exec_idx > exec_idx
5810                || timestamp_query.exec_idx == exec_idx && timestamp_query.placement > placement
5811            {
5812                break;
5813            }
5814
5815            let query_pool = query_pool.expect("missing query pool results");
5816            let pool_query = timestamp_query
5817                .pool_query
5818                .expect("missing timestamp query pool index");
5819
5820            unsafe {
5821                cmd_buf.device.cmd_write_timestamp(
5822                    cmd_buf.handle,
5823                    vk::PipelineStageFlags::BOTTOM_OF_PIPE,
5824                    query_pool,
5825                    pool_query,
5826                );
5827            }
5828
5829            query_idx += 1;
5830        }
5831
5832        query_idx
5833    }
5834}
5835
5836#[derive(Clone, Copy, Debug)]
5837struct TimestampQueryResultInfo {
5838    timestamp_query: u32,
5839}
5840
5841#[derive(Default)]
5842struct SubmitScratch {
5843    release_buffer_barriers: Vec<vk::BufferMemoryBarrier<'static>>,
5844    release_image_barriers: Vec<vk::ImageMemoryBarrier<'static>>,
5845    signal_infos: Vec<vk::SemaphoreSubmitInfo<'static>>,
5846    signal_semaphores: Vec<vk::Semaphore>,
5847    wait_infos: Vec<vk::SemaphoreSubmitInfo<'static>>,
5848    wait_semaphores: Vec<vk::Semaphore>,
5849    wait_stage_masks: Vec<vk::PipelineStageFlags>,
5850}
5851
5852#[derive(Debug)]
5853pub(crate) struct SubmittedTimestampQueries {
5854    epoch_query: u32,
5855    next_query: u32,
5856    query_pool: QueryPool,
5857    query_count: u32,
5858    result_infos: Vec<Option<TimestampQueryResultInfo>>,
5859    timestamp_period: f32,
5860    timestamp_valid_bits: u32,
5861}
5862
5863impl SubmittedTimestampQueries {
5864    fn create(
5865        device: &Device,
5866        queue_family_index: u32,
5867        result_info_count: u32,
5868        query_count: u32,
5869    ) -> Result<Self, DriverError> {
5870        let device = device.clone();
5871        let Vulkan10Limits {
5872            timestamp_period, ..
5873        } = device.physical.properties_v1_0.limits;
5874        let QueueFamilyProperties {
5875            timestamp_valid_bits,
5876            ..
5877        } = device.physical.queue_families[queue_family_index as usize];
5878        let query_pool = QueryPool::create(&device, QueryPoolInfo::timestamp(query_count))?;
5879
5880        Ok(Self {
5881            epoch_query: 0,
5882            next_query: 1,
5883            query_pool,
5884            query_count,
5885            result_infos: vec![None; result_info_count as usize],
5886            timestamp_period,
5887            timestamp_valid_bits,
5888        })
5889    }
5890
5891    fn allocate_query(&mut self, query_count: u32) -> u32 {
5892        let query_count = query_count.max(1);
5893        let query = self.next_query;
5894        self.next_query += query_count;
5895
5896        assert!(
5897            self.next_query <= self.query_count,
5898            "timestamp query pool exhausted while assigning query"
5899        );
5900
5901        query
5902    }
5903
5904    fn set_result_info(&mut self, query: TimestampQuery, result_info: TimestampQueryResultInfo) {
5905        let index = query.index() as usize;
5906        if index >= self.result_infos.len() {
5907            self.result_infos.resize(index + 1, None);
5908        }
5909
5910        self.result_infos[index] = Some(result_info);
5911    }
5912
5913    fn query_pool(&self) -> vk::QueryPool {
5914        self.query_pool.handle
5915    }
5916
5917    fn reset(&self, cmd_buf: &CommandBuffer) {
5918        self.query_pool.reset(cmd_buf, 0, self.query_count);
5919    }
5920
5921    fn write_epoch(&self, cmd_buf: &CommandBuffer) {
5922        unsafe {
5923            cmd_buf.device.cmd_write_timestamp(
5924                cmd_buf.handle,
5925                vk::PipelineStageFlags::TOP_OF_PIPE,
5926                self.query_pool.handle,
5927                self.epoch_query,
5928            );
5929        }
5930    }
5931
5932    fn timestamp_results(&self) -> Result<Box<[Option<Duration>]>, DriverError> {
5933        let epoch =
5934            self.query_pool
5935                .results_u64(self.epoch_query, 1, vk::QueryResultFlags::empty())?[0];
5936
5937        let mut results = Vec::with_capacity(self.result_infos.len());
5938        for result_info in &self.result_infos {
5939            let Some(result_info) = result_info else {
5940                results.push(None);
5941                continue;
5942            };
5943
5944            let timestamp = self.query_pool.results_u64(
5945                result_info.timestamp_query,
5946                1,
5947                vk::QueryResultFlags::empty(),
5948            )?[0];
5949
5950            results.push(Some(Self::timestamp_duration_since(
5951                timestamp,
5952                epoch,
5953                self.timestamp_valid_bits,
5954                self.timestamp_period,
5955            )));
5956        }
5957
5958        Ok(results.into_boxed_slice())
5959    }
5960
5961    fn timestamp_duration_since(
5962        timestamp: u64,
5963        earlier: u64,
5964        timestamp_valid_bits: u32,
5965        timestamp_period: f32,
5966    ) -> Duration {
5967        let mask = if timestamp_valid_bits >= u64::BITS {
5968            u64::MAX
5969        } else {
5970            (1_u64 << timestamp_valid_bits) - 1
5971        };
5972        let elapsed_ticks = timestamp.wrapping_sub(earlier) & mask;
5973
5974        Duration::from_secs_f64(elapsed_ticks as f64 * timestamp_period as f64 / 1_000_000_000.0)
5975    }
5976}
5977
5978impl FenceDroppable for SubmittedTimestampQueries {
5979    fn fence_signaled(&mut self, fence: &Fence) {
5980        match self.timestamp_results() {
5981            Ok(results) => fence.timestamps.set(results),
5982            Err(err) => {
5983                warn!("unable to read timestamp query pool results: {err}");
5984                fence.timestamps.complete_without_results();
5985            }
5986        }
5987    }
5988}
5989
5990#[derive(Debug)]
5991struct TimestampQueryCompletion;
5992
5993impl FenceDroppable for TimestampQueryCompletion {
5994    fn fence_signaled(&mut self, fence: &Fence) {
5995        fence.timestamps.complete_without_results();
5996    }
5997}
5998
5999/// Timestamp query results associated with a completed fence.
6000#[derive(Clone, Debug)]
6001pub struct TimestampQueryPool {
6002    inner: Arc<Mutex<TimestampQueryPoolInner>>,
6003}
6004
6005impl TimestampQueryPool {
6006    pub(crate) fn empty() -> Self {
6007        Self {
6008            inner: Arc::new(Mutex::new(TimestampQueryPoolInner {
6009                got_results: true,
6010                #[cfg(feature = "checked")]
6011                graph_id: None,
6012                timestamps: None,
6013            })),
6014        }
6015    }
6016
6017    pub(crate) fn pending(#[cfg(feature = "checked")] graph_id: GraphId) -> Self {
6018        Self {
6019            inner: Arc::new(Mutex::new(TimestampQueryPoolInner {
6020                got_results: false,
6021                #[cfg(feature = "checked")]
6022                graph_id: Some(graph_id),
6023                timestamps: None,
6024            })),
6025        }
6026    }
6027
6028    pub(crate) fn set(&self, timestamps: Box<[Option<Duration>]>) {
6029        let mut inner = self.inner.lock().expect("timestamp query pool poisoned");
6030        inner.timestamps = Some(timestamps);
6031        inner.got_results = true;
6032    }
6033
6034    pub(crate) fn complete_without_results(&self) {
6035        self.inner
6036            .lock()
6037            .expect("timestamp query pool poisoned")
6038            .got_results = true;
6039    }
6040
6041    /// Returns `true` once the associated submission has completed.
6042    ///
6043    /// A complete pool can still return `None` for a query when timestamps were unsupported,
6044    /// omitted, or never submitted.
6045    pub fn has_results(&self) -> bool {
6046        self.inner
6047            .lock()
6048            .expect("timestamp query pool poisoned")
6049            .got_results
6050    }
6051
6052    /// Returns the duration from submission start to `query`, or `None` if results are not available.
6053    ///
6054    /// `None` can mean the submission is still pending, timestamps were unsupported for the queue,
6055    /// or the query point was not part of submitted graph work. Use [`Self::has_results`] to
6056    /// distinguish pending work from a completed submission with no timestamp for this query.
6057    ///
6058    /// When the `checked` feature is enabled, this panics if `query` belongs to a different graph.
6059    pub fn duration(&self, query: TimestampQuery) -> Option<Duration> {
6060        let inner = self.inner.lock().expect("timestamp query pool poisoned");
6061
6062        #[cfg(feature = "checked")]
6063        assert_eq!(
6064            inner.graph_id,
6065            Some(query.graph_id()),
6066            "timestamp query belongs to a different graph"
6067        );
6068
6069        inner
6070            .timestamps
6071            .as_ref()
6072            .and_then(|timestamps| timestamps.get(query.index() as usize).copied().flatten())
6073    }
6074}
6075
6076#[derive(Debug)]
6077struct TimestampQueryPoolInner {
6078    got_results: bool,
6079    #[cfg(feature = "checked")]
6080    graph_id: Option<GraphId>,
6081    timestamps: Option<Box<[Option<Duration>]>>,
6082}
6083
6084#[doc(hidden)]
6085pub mod bench {
6086    use {
6087        super::{CommandAccessIndex, Schedule},
6088        crate::Graph,
6089    };
6090
6091    /// Synthetic workload description for scheduler benchmarks.
6092    #[derive(Clone, Copy, Debug)]
6093    pub struct ReorderBenchSpec {
6094        /// Number of scheduled cmds.
6095        pub cmd_count: usize,
6096
6097        /// Number of resources participating in the schedule.
6098        pub resource_count: usize,
6099
6100        /// Typical cmd count for short-lived resources.
6101        pub short_lived_uses: usize,
6102
6103        /// Number of long-lived resources shared across many cmds.
6104        pub long_lived_resource_count: usize,
6105
6106        /// Typical cmd count for each long-lived resource.
6107        pub long_lived_uses: usize,
6108    }
6109
6110    /// Reusable benchmark harness for `Schedule::reorder_cmds`.
6111    pub struct ReorderBenchHarness {
6112        schedule: Schedule,
6113        original_cmds: Vec<usize>,
6114        end_cmd_idx: usize,
6115    }
6116
6117    impl ReorderBenchHarness {
6118        /// Builds a deterministic synthetic schedule for benchmarking.
6119        pub fn new(spec: ReorderBenchSpec) -> Self {
6120            assert!(spec.cmd_count > 0, "cmd_count must be greater than zero");
6121            assert!(
6122                spec.resource_count > 0,
6123                "resource_count must be greater than zero"
6124            );
6125            assert!(
6126                spec.short_lived_uses > 0,
6127                "short_lived_uses must be greater than zero"
6128            );
6129
6130            let mut cmds_by_node = vec![Vec::new(); spec.resource_count];
6131            let mut accessed_nodes_by_cmd = vec![Vec::new(); spec.cmd_count];
6132
6133            for (node_idx, cmds) in cmds_by_node.iter_mut().enumerate() {
6134                let is_long_lived = node_idx < spec.long_lived_resource_count;
6135                let uses = if is_long_lived {
6136                    spec.long_lived_uses.max(spec.short_lived_uses)
6137                } else {
6138                    spec.short_lived_uses
6139                }
6140                .min(spec.cmd_count);
6141
6142                let seed = splitmix64(node_idx as u64 ^ ((spec.cmd_count as u64) << 32));
6143                let stride = odd_stride(seed, spec.cmd_count);
6144                let start = (seed as usize) % spec.cmd_count;
6145                let cluster_len = uses.max(1).min(spec.cmd_count);
6146
6147                cmds.reserve(uses);
6148
6149                for use_idx in 0..uses {
6150                    let cmd_idx = if is_long_lived {
6151                        (start + use_idx * stride) % spec.cmd_count
6152                    } else {
6153                        (start + use_idx % cluster_len + (use_idx / cluster_len) * stride)
6154                            % spec.cmd_count
6155                    };
6156
6157                    cmds.push(cmd_idx);
6158                }
6159
6160                cmds.sort_unstable();
6161                cmds.dedup();
6162
6163                while cmds.len() < uses {
6164                    let next_cmd = (start + cmds.len() * stride + cmds.len()) % spec.cmd_count;
6165                    if cmds.binary_search(&next_cmd).is_err() {
6166                        cmds.push(next_cmd);
6167                    }
6168                }
6169
6170                cmds.sort_unstable();
6171
6172                for &cmd_idx in cmds.iter() {
6173                    accessed_nodes_by_cmd[cmd_idx].push(node_idx);
6174                }
6175            }
6176
6177            for nodes in &mut accessed_nodes_by_cmd {
6178                nodes.sort_unstable();
6179                nodes.dedup();
6180            }
6181
6182            let cmds = (0..spec.cmd_count).collect::<Vec<_>>();
6183
6184            Self {
6185                schedule: Schedule {
6186                    access_index: CommandAccessIndex {
6187                        cmds_by_node,
6188                        accessed_nodes_by_cmd,
6189                    },
6190                    cmds: cmds.clone(),
6191                    ..Default::default()
6192                },
6193                original_cmds: cmds,
6194                end_cmd_idx: spec.cmd_count,
6195            }
6196        }
6197
6198        /// Builds a scheduler benchmark from a graph, optionally repeating its disconnected
6199        /// topology with independently remapped command and resource indices.
6200        pub fn from_graph(graph: &Graph, repeat_count: usize) -> Self {
6201            assert!(repeat_count > 0, "repeat_count must be greater than zero");
6202
6203            let base_cmd_count = graph.cmds.len();
6204            let base_resource_count = graph.resources.len();
6205            assert!(base_cmd_count > 0, "graph must contain commands");
6206            assert!(base_resource_count > 0, "graph must contain resources");
6207
6208            let mut base = CommandAccessIndex::default();
6209            base.update_from_cmds(&graph.cmds, base_resource_count);
6210
6211            let cmd_count = base_cmd_count * repeat_count;
6212            let resource_count = base_resource_count * repeat_count;
6213            let mut cmds_by_node = Vec::with_capacity(resource_count);
6214            let mut accessed_nodes_by_cmd = Vec::with_capacity(cmd_count);
6215
6216            for copy_idx in 0..repeat_count {
6217                let cmd_offset = copy_idx * base_cmd_count;
6218                let resource_offset = copy_idx * base_resource_count;
6219
6220                cmds_by_node.extend(base.cmds_by_node.iter().map(|cmds| {
6221                    cmds.iter()
6222                        .map(|cmd_idx| cmd_offset + cmd_idx)
6223                        .collect::<Vec<_>>()
6224                }));
6225                accessed_nodes_by_cmd.extend(base.accessed_nodes_by_cmd.iter().map(|nodes| {
6226                    nodes
6227                        .iter()
6228                        .map(|node_idx| resource_offset + node_idx)
6229                        .collect::<Vec<_>>()
6230                }));
6231            }
6232
6233            let cmds = (0..cmd_count).collect::<Vec<_>>();
6234            Self {
6235                schedule: Schedule {
6236                    access_index: CommandAccessIndex {
6237                        cmds_by_node,
6238                        accessed_nodes_by_cmd,
6239                    },
6240                    cmds: cmds.clone(),
6241                    ..Default::default()
6242                },
6243                original_cmds: cmds,
6244                end_cmd_idx: cmd_count,
6245            }
6246        }
6247
6248        /// Returns the number of commands reordered by each benchmark iteration.
6249        pub fn cmd_count(&self) -> usize {
6250            self.end_cmd_idx
6251        }
6252
6253        /// Restores the original schedule, reorders it once, and returns a checksum.
6254        pub fn reorder_once(&mut self) -> u64 {
6255            self.schedule.cmds.clear();
6256            self.schedule
6257                .cmds
6258                .extend(self.original_cmds.iter().copied());
6259
6260            self.schedule.reorder_cmds(self.end_cmd_idx);
6261
6262            self.schedule
6263                .cmds
6264                .iter()
6265                .enumerate()
6266                .fold(0u64, |checksum, (idx, &pass_idx)| {
6267                    checksum.wrapping_mul(1_099_511_628_211).wrapping_add(
6268                        ((idx as u64) << 32) ^ pass_idx as u64 ^ 0x9e37_79b9_7f4a_7c15,
6269                    )
6270                })
6271        }
6272    }
6273
6274    fn odd_stride(seed: u64, cmd_count: usize) -> usize {
6275        let stride = ((seed >> 32) as usize % cmd_count.max(2)) | 1;
6276
6277        stride.min(cmd_count.max(1) - 1).max(1)
6278    }
6279
6280    fn splitmix64(mut value: u64) -> u64 {
6281        value = value.wrapping_add(0x9e37_79b9_7f4a_7c15);
6282        value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
6283        value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
6284        value ^ (value >> 31)
6285    }
6286}
6287
6288#[doc(hidden)]
6289pub mod fuzz {
6290    use super::{CommandAccessIndex, Schedule};
6291
6292    #[derive(Clone, Copy, Debug)]
6293    pub struct ResourceAccess {
6294        pub cmd_idx: usize,
6295        pub write: bool,
6296    }
6297
6298    pub fn check_schedule_reordering(cmd_count: usize, resource_accesses: &[Vec<ResourceAccess>]) {
6299        let cmd_count = cmd_count.min(256);
6300        if cmd_count == 0 {
6301            return;
6302        }
6303
6304        let (access_index, normalized_accesses) = build_access_index(cmd_count, resource_accesses);
6305
6306        let mut schedule = Schedule {
6307            access_index: access_index.clone(),
6308            cmds: (0..cmd_count).collect(),
6309            ..Default::default()
6310        };
6311
6312        schedule.reorder_cmds(cmd_count);
6313
6314        let reordered = schedule.cmds.clone();
6315
6316        assert_eq!(reordered.len(), cmd_count, "reordered cmd count changed");
6317
6318        let mut sorted = reordered.clone();
6319        sorted.sort_unstable();
6320        assert_eq!(
6321            sorted,
6322            (0..cmd_count).collect::<Vec<_>>(),
6323            "reordered cmds are not a permutation"
6324        );
6325
6326        let mut repeat = Schedule {
6327            access_index: access_index.clone(),
6328            cmds: (0..cmd_count).collect(),
6329            ..Default::default()
6330        };
6331        repeat.reorder_cmds(cmd_count);
6332        assert_eq!(reordered, repeat.cmds, "reordering is not deterministic");
6333
6334        let expected = reference_reorder(access_index, cmd_count);
6335        assert_eq!(
6336            reordered, expected,
6337            "reordering diverged from reference implementation"
6338        );
6339
6340        assert_hazard_order_preserved(&reordered, &normalized_accesses);
6341    }
6342
6343    fn build_access_index(
6344        cmd_count: usize,
6345        resource_accesses: &[Vec<ResourceAccess>],
6346    ) -> (CommandAccessIndex, Vec<Vec<ResourceAccess>>) {
6347        let mut cmds_by_node = Vec::with_capacity(resource_accesses.len());
6348        let mut accessed_nodes_by_cmd = vec![Vec::new(); cmd_count];
6349        let mut normalized_accesses = Vec::with_capacity(resource_accesses.len());
6350
6351        for (node_idx, accesses) in resource_accesses.iter().enumerate() {
6352            let mut normalized = accesses
6353                .iter()
6354                .copied()
6355                .filter(|access| access.cmd_idx < cmd_count)
6356                .collect::<Vec<_>>();
6357            normalized.sort_unstable_by_key(|access| access.cmd_idx);
6358
6359            let mut deduped = Vec::<ResourceAccess>::with_capacity(normalized.len());
6360            for access in normalized {
6361                if let Some(prev) = deduped.last_mut()
6362                    && prev.cmd_idx == access.cmd_idx
6363                {
6364                    prev.write |= access.write;
6365                    continue;
6366                }
6367
6368                deduped.push(access);
6369            }
6370
6371            for access in &deduped {
6372                accessed_nodes_by_cmd[access.cmd_idx].push(node_idx);
6373            }
6374
6375            cmds_by_node.push(deduped.iter().map(|access| access.cmd_idx).collect());
6376            normalized_accesses.push(deduped);
6377        }
6378
6379        (
6380            CommandAccessIndex {
6381                cmds_by_node,
6382                accessed_nodes_by_cmd,
6383            },
6384            normalized_accesses,
6385        )
6386    }
6387
6388    fn assert_hazard_order_preserved(
6389        reordered: &[usize],
6390        resource_accesses: &[Vec<ResourceAccess>],
6391    ) {
6392        let mut positions = vec![usize::MAX; reordered.len()];
6393        for (position, &cmd_idx) in reordered.iter().enumerate() {
6394            positions[cmd_idx] = position;
6395        }
6396
6397        for accesses in resource_accesses {
6398            for (left_idx, left) in accesses.iter().enumerate() {
6399                for right in &accesses[(left_idx + 1)..] {
6400                    if left.write || right.write {
6401                        assert!(
6402                            positions[left.cmd_idx] < positions[right.cmd_idx],
6403                            "hazard order changed for resource accesses {:?} -> {:?}: {:?}",
6404                            left,
6405                            right,
6406                            reordered
6407                        );
6408                    }
6409                }
6410            }
6411        }
6412    }
6413
6414    fn reference_reorder(access_index: CommandAccessIndex, cmd_count: usize) -> Vec<usize> {
6415        if cmd_count < 3 {
6416            return (0..cmd_count).collect();
6417        }
6418
6419        let mut predecessors = vec![Vec::new(); cmd_count];
6420        for resource_cmds in &access_index.cmds_by_node {
6421            for pair in resource_cmds.windows(2) {
6422                predecessors[pair[1]].push(pair[0]);
6423            }
6424        }
6425
6426        let mut scheduled = vec![false; cmd_count];
6427        let mut reordered = Vec::with_capacity(cmd_count);
6428        while reordered.len() < cmd_count {
6429            let mut best = None;
6430            for cmd_idx in 0..cmd_count {
6431                if scheduled[cmd_idx]
6432                    || !predecessors[cmd_idx]
6433                        .iter()
6434                        .all(|&predecessor| scheduled[predecessor])
6435                {
6436                    continue;
6437                }
6438
6439                let score = predecessors[cmd_idx].len();
6440                if best.is_none_or(|(best_score, best_idx)| {
6441                    score > best_score || (score == best_score && cmd_idx < best_idx)
6442                }) {
6443                    best = Some((score, cmd_idx));
6444                }
6445            }
6446
6447            let (_, best_idx) = best.expect("command dependency cycle detected");
6448            scheduled[best_idx] = true;
6449            reordered.push(best_idx);
6450        }
6451
6452        reordered
6453    }
6454}
6455
6456#[cfg(test)]
6457mod test {
6458    use super::{
6459        BufferQueueOwnershipTransfer, CommandAccessIndex, CommandData,
6460        ExternalRenderPassAccessHistory, ImageQueueOwnershipTransfer, NodeIndex,
6461        PipelineStageAccessFlags, QueueSubmitInfo, RecordSelection, RecordedSubmission,
6462        RecordedSubmissionState, RecordingOwnership, Schedule, SemaphoreSubmitInfo, Submission,
6463        SubresourceAccess, SubresourceRange, check_queue_submit_args, fuzz,
6464    };
6465    use crate::{
6466        AnyResource, Attachment, DepthStencilAttachment, Execution, Graph, LoadOp, Node, StoreOp,
6467        TimestampQuery,
6468        driver::{
6469            DriverError, SharingMode,
6470            accel_struct::{AccelerationStructure, AccelerationStructureInfo},
6471            ash::vk,
6472            buffer::{Buffer, BufferInfo, BufferSubresourceRange},
6473            cmd_buf::{CommandBuffer, CommandBufferInfo},
6474            compute::{ComputePipeline, ComputePipelineInfo},
6475            descriptor_set::{DescriptorSet, DescriptorSetInfo, DescriptorSetUpdateInfo},
6476            device::{Device, DeviceInfo},
6477            fence::Fence,
6478            graphics::{GraphicsPipeline, GraphicsPipelineInfo},
6479            image::{Image, ImageInfo, SampleCount},
6480            render_pass::SubpassDependency,
6481        },
6482        node::{AnyNode, BufferNode},
6483        pool::{Pool, hash::HashPool},
6484    };
6485    use {
6486        ash::vk::Handle,
6487        std::{
6488            env::set_var,
6489            mem::ManuallyDrop,
6490            ops::Deref,
6491            sync::{Arc, Mutex, MutexGuard, OnceLock},
6492            time::Duration,
6493        },
6494        vk_shader_macros::glsl,
6495        vk_sync::AccessType,
6496    };
6497
6498    fn color_subresource_range(
6499        array_layers: std::ops::Range<u32>,
6500        mip_levels: std::ops::Range<u32>,
6501    ) -> vk::ImageSubresourceRange {
6502        vk::ImageSubresourceRange {
6503            aspect_mask: vk::ImageAspectFlags::COLOR,
6504            base_array_layer: array_layers.start,
6505            layer_count: array_layers.end - array_layers.start,
6506            base_mip_level: mip_levels.start,
6507            level_count: mip_levels.end - mip_levels.start,
6508        }
6509    }
6510
6511    #[cfg(test)]
6512    fn sort_image_subresource_ranges(ranges: &mut [vk::ImageSubresourceRange]) {
6513        ranges.sort_unstable_by_key(|range| {
6514            (
6515                range.aspect_mask.as_raw(),
6516                range.base_array_layer,
6517                range.layer_count,
6518                range.base_mip_level,
6519                range.level_count,
6520            )
6521        });
6522    }
6523
6524    #[cfg(test)]
6525    fn sort_pending_image_transfers(transfers: &mut [ImageQueueOwnershipTransfer]) {
6526        transfers.sort_unstable_by_key(|transfer| {
6527            (
6528                transfer.src_queue_family_index,
6529                transfer.src_queue_index,
6530                transfer.dst_queue_family_index,
6531                transfer.layout.as_raw(),
6532                transfer.range.aspect_mask.as_raw(),
6533                transfer.range.base_array_layer,
6534                transfer.range.layer_count,
6535                transfer.range.base_mip_level,
6536                transfer.range.level_count,
6537            )
6538        });
6539    }
6540
6541    #[cfg(test)]
6542    fn sort_pending_buffer_transfers(transfers: &mut [BufferQueueOwnershipTransfer]) {
6543        transfers.sort_unstable_by_key(|transfer| {
6544            (
6545                transfer.src_queue_family_index,
6546                transfer.dst_queue_family_index,
6547                transfer.range.start,
6548                transfer.range.end,
6549            )
6550        });
6551    }
6552
6553    fn pending_buffer_transfer_for_range(
6554        transfers: &[BufferQueueOwnershipTransfer],
6555        range: BufferSubresourceRange,
6556    ) -> Option<&BufferQueueOwnershipTransfer> {
6557        transfers.iter().find(|transfer| transfer.range == range)
6558    }
6559
6560    fn pending_transfer_for_node<H: Copy, T>(
6561        pending: &super::PendingTransferNodes<H, T>,
6562        node_idx: NodeIndex,
6563    ) -> Option<(H, &[T])> {
6564        pending
6565            .iter()
6566            .find_map(|(idx, handle, transfers)| (idx == node_idx).then_some((handle, transfers)))
6567    }
6568
6569    fn simulate_partial_transfer_discovery(
6570        submission: &mut Submission,
6571        schedule: &Schedule,
6572        queue_family_index: u32,
6573        ownership: &mut RecordingOwnership,
6574    ) {
6575        submission.track_pending_transfers(schedule, queue_family_index, ownership);
6576        submission.pending_buffer_transfer_nodes = None;
6577        submission.pending_image_transfer_nodes = None;
6578    }
6579
6580    fn pending_timestamp_query_pool(query: TimestampQuery) -> super::TimestampQueryPool {
6581        #[cfg(feature = "checked")]
6582        {
6583            super::TimestampQueryPool::pending(query.graph_id())
6584        }
6585
6586        #[cfg(not(feature = "checked"))]
6587        {
6588            let _ = query;
6589            super::TimestampQueryPool::pending()
6590        }
6591    }
6592
6593    #[test]
6594    fn timestamp_query_pool_exposes_only_relative_results() {
6595        let mut graph = Graph::new();
6596        let start = graph.write_timestamp();
6597        let end = graph.write_timestamp();
6598        let pool = pending_timestamp_query_pool(start);
6599
6600        assert!(!pool.has_results());
6601
6602        pool.set(
6603            vec![
6604                Some(Duration::from_millis(5)),
6605                Some(Duration::from_millis(11)),
6606            ]
6607            .into_boxed_slice(),
6608        );
6609
6610        let result = pool.duration(start).expect("missing timestamp result");
6611        assert_eq!(result, Duration::from_millis(5));
6612        assert_eq!(pool.duration(end), Some(Duration::from_millis(11)));
6613        assert!(pool.has_results());
6614    }
6615
6616    #[test]
6617    fn timestamp_query_pool_returns_none_before_results_are_set() {
6618        let mut graph = Graph::new();
6619        let query = graph.write_timestamp();
6620        let pool = pending_timestamp_query_pool(query);
6621
6622        assert_eq!(pool.duration(query), None);
6623        assert!(!pool.has_results());
6624
6625        pool.complete_without_results();
6626
6627        assert_eq!(pool.duration(query), None);
6628        assert!(pool.has_results());
6629    }
6630
6631    #[test]
6632    fn timestamp_duration_uses_valid_bits_and_wraparound() {
6633        assert_eq!(
6634            super::SubmittedTimestampQueries::timestamp_duration_since(1, 14, 4, 1.0),
6635            Duration::from_nanos(3),
6636        );
6637        assert_eq!(
6638            super::SubmittedTimestampQueries::timestamp_duration_since(20, 4, 64, 2.0),
6639            Duration::from_nanos(32),
6640        );
6641    }
6642
6643    #[test]
6644    fn timestamp_queries_require_queue_family_that_can_reset_queries() {
6645        let mut queue_family = vk::QueueFamilyProperties {
6646            queue_flags: vk::QueueFlags::TRANSFER,
6647            timestamp_valid_bits: 64,
6648            ..Default::default()
6649        };
6650
6651        assert!(!Submission::queue_family_supports_timestamp_queries(
6652            &queue_family
6653        ));
6654
6655        queue_family.queue_flags = vk::QueueFlags::COMPUTE;
6656        assert!(Submission::queue_family_supports_timestamp_queries(
6657            &queue_family
6658        ));
6659
6660        queue_family.queue_flags = vk::QueueFlags::GRAPHICS;
6661        assert!(Submission::queue_family_supports_timestamp_queries(
6662            &queue_family
6663        ));
6664
6665        queue_family.timestamp_valid_bits = 0;
6666        assert!(!Submission::queue_family_supports_timestamp_queries(
6667            &queue_family
6668        ));
6669    }
6670
6671    #[test]
6672    fn pending_transfer_nodes_set_tracks_each_node_once() {
6673        let mut pending = super::PendingTransferNodes::new(4);
6674
6675        assert!(pending.push_transfer(2, 10, 20));
6676        assert!(!pending.push_transfer(2, 11, 21));
6677
6678        assert!(pending.contains(2));
6679        let (handle, transfers) = pending_transfer_for_node(&pending, 2).unwrap();
6680        assert_eq!(handle, 11);
6681        assert_eq!(pending.indices, vec![2]);
6682        assert_eq!(transfers, &[20, 21]);
6683        assert_eq!(pending.iter().count(), 1);
6684    }
6685
6686    #[test]
6687    fn pending_transfer_nodes_remove_where_uses_swap_remove() {
6688        let mut pending = super::PendingTransferNodes::new(4);
6689
6690        pending.push_transfer(0, 10, 20);
6691        pending.push_transfer(1, 11, 21);
6692        pending.push_transfer(2, 12, 22);
6693
6694        pending.remove_where(|node_idx, _, _| node_idx == 1);
6695
6696        assert!(pending_transfer_for_node(&pending, 1).is_none());
6697        assert_eq!(pending.indices.len(), 2);
6698        assert!(pending.indices.contains(&0));
6699        assert!(pending.indices.contains(&2));
6700        assert_eq!(pending.iter().collect::<Vec<_>>().len(), 2);
6701    }
6702
6703    #[test]
6704    fn pending_transfer_nodes_remove_where_drops_stale_indices() {
6705        let mut pending = super::PendingTransferNodes::new(3);
6706
6707        pending.push_transfer(1, 11, 21);
6708        pending.entries[1] = None;
6709        pending.remove_where(|_, _, _| false);
6710
6711        assert!(pending.indices.is_empty());
6712        assert_eq!(pending.iter().count(), 0);
6713    }
6714
6715    #[test]
6716    fn node_indexed_scratch_tracks_each_node_once() {
6717        let mut scratch = super::NodeIndexedScratch::default();
6718
6719        scratch.push(2, 20);
6720        scratch.push(2, 21);
6721        scratch.push(0, 10);
6722
6723        assert_eq!(scratch.indices, vec![2, 0]);
6724        assert_eq!(scratch.get(2), &[20, 21]);
6725        assert_eq!(scratch.get(0), &[10]);
6726        assert_eq!(scratch.get(1), &[] as &[i32]);
6727    }
6728
6729    #[test]
6730    fn node_indexed_scratch_clear_resets_occupancy_and_reuses_entries() {
6731        let mut scratch = super::NodeIndexedScratch::default();
6732
6733        scratch.push(1, 10);
6734        scratch.clear();
6735
6736        assert!(scratch.indices.is_empty());
6737        assert_eq!(scratch.get(1), &[] as &[i32]);
6738
6739        scratch.push(1, 11);
6740        scratch.push(1, 12);
6741
6742        assert_eq!(scratch.indices, vec![1]);
6743        assert_eq!(scratch.get(1), &[11, 12]);
6744    }
6745
6746    #[test]
6747    fn node_indexed_scratch_resizes_for_high_indices() {
6748        let mut scratch = super::NodeIndexedScratch::default();
6749
6750        scratch.push(5, 50);
6751
6752        assert_eq!(scratch.indices, vec![5]);
6753        assert_eq!(scratch.get(5), &[50]);
6754        assert_eq!(scratch.get(4), &[] as &[i32]);
6755    }
6756
6757    #[test]
6758    fn pending_transfer_nodes_remove_where_keeps_partially_consumed_node() {
6759        let mut pending = super::PendingTransferNodes::new(2);
6760
6761        pending.push_transfer(1, 11, 20);
6762        pending.push_transfer(1, 11, 21);
6763
6764        pending.remove_where(|_, _, transfers| {
6765            transfers.retain(|&transfer| transfer != 20);
6766            transfers.is_empty()
6767        });
6768
6769        assert!(pending.contains(1));
6770        assert_eq!(pending_transfer_for_node(&pending, 1).unwrap().1, &[21]);
6771        assert!(!pending.is_empty());
6772
6773        pending.remove_where(|_, _, transfers| {
6774            transfers.retain(|&transfer| transfer != 21);
6775            transfers.is_empty()
6776        });
6777
6778        assert!(!pending.contains(1));
6779        assert!(pending_transfer_for_node(&pending, 1).is_none());
6780        assert!(pending.is_empty());
6781    }
6782
6783    #[test]
6784    fn consume_pending_buffer_transfers_removes_intersecting_ranges() {
6785        let consumed = BufferSubresourceRange { start: 4, end: 8 };
6786        let kept = BufferSubresourceRange { start: 8, end: 12 };
6787        let mut pending = vec![
6788            BufferQueueOwnershipTransfer {
6789                dst_queue_family_index: 0,
6790                range: consumed,
6791                src_queue_family_index: 1,
6792            },
6793            BufferQueueOwnershipTransfer {
6794                dst_queue_family_index: 0,
6795                range: kept,
6796                src_queue_family_index: 1,
6797            },
6798        ];
6799
6800        assert!(!super::consume_pending_buffer_transfers(
6801            &mut pending,
6802            consumed
6803        ));
6804
6805        assert_eq!(pending.len(), 1);
6806        assert_eq!(pending[0].range, kept);
6807    }
6808
6809    #[test]
6810    fn consume_pending_image_transfers_removes_intersecting_ranges() {
6811        let consumed = color_subresource_range(0..1, 0..1);
6812        let kept = color_subresource_range(1..2, 0..1);
6813        let mut pending = vec![
6814            ImageQueueOwnershipTransfer {
6815                dst_queue_family_index: 0,
6816                layout: vk::ImageLayout::GENERAL,
6817                range: consumed,
6818                src_queue_family_index: 1,
6819                src_queue_index: 0,
6820            },
6821            ImageQueueOwnershipTransfer {
6822                dst_queue_family_index: 0,
6823                layout: vk::ImageLayout::GENERAL,
6824                range: kept,
6825                src_queue_family_index: 1,
6826                src_queue_index: 0,
6827            },
6828        ];
6829
6830        assert!(!super::consume_pending_image_transfers(
6831            &mut pending,
6832            consumed
6833        ));
6834
6835        assert_eq!(pending.len(), 1);
6836        assert!(super::image_subresource_range_eq(pending[0].range, kept));
6837    }
6838
6839    #[test]
6840    fn recording_ownership_only_returns_unclaimed_buffer_ranges() {
6841        let mut ownership = RecordingOwnership::default();
6842        let first = BufferSubresourceRange { start: 0, end: 8 };
6843        let overlap = BufferSubresourceRange { start: 4, end: 12 };
6844
6845        assert_eq!(ownership.claim_buffer(0, first).as_slice(), &[first]);
6846        assert_eq!(
6847            ownership.claim_buffer(0, overlap).as_slice(),
6848            &[BufferSubresourceRange { start: 8, end: 12 }]
6849        );
6850        assert!(ownership.claim_buffer(0, overlap).is_empty());
6851    }
6852
6853    #[test]
6854    fn recording_ownership_only_returns_unclaimed_image_subresources() {
6855        let mut ownership = RecordingOwnership::default();
6856        let info =
6857            ImageInfo::image_2d_array(1, 1, 3, vk::Format::R8_UINT, vk::ImageUsageFlags::SAMPLED);
6858        let first = color_subresource_range(0..2, 0..1);
6859        let overlap = color_subresource_range(1..3, 0..1);
6860        let remaining = color_subresource_range(2..3, 0..1);
6861
6862        let claimed = ownership.claim_image(0, info, first);
6863        assert_eq!(claimed.len(), 1);
6864        assert!(super::image_subresource_range_eq(claimed[0], first));
6865
6866        let claimed = ownership.claim_image(0, info, overlap);
6867        assert_eq!(claimed.len(), 1);
6868        assert!(super::image_subresource_range_eq(claimed[0], remaining));
6869        assert!(ownership.claim_image(0, info, overlap).is_empty());
6870    }
6871
6872    #[test]
6873    fn dependency_selection_schedules_inputs_to_first_target_access() {
6874        let access_index = CommandAccessIndex {
6875            /*
6876            Node 0 is produced by cmd 0 and then read by cmd 1. Node 1 is the target written by
6877            cmd 1, so dependencies(node 1) should include cmd 0 but not cmd 1.
6878            */
6879            cmds_by_node: vec![vec![0, 1], vec![1]],
6880            accessed_nodes_by_cmd: vec![vec![0], vec![0, 1]],
6881        };
6882        let mut schedule = Schedule {
6883            access_index,
6884            ..Default::default()
6885        };
6886
6887        super::schedule_dependency_cmds_before_target_access(1, 1, &mut schedule);
6888
6889        assert_eq!(schedule.cmds, vec![0]);
6890    }
6891
6892    #[test]
6893    fn dependency_selection_revisits_node_at_later_boundary() {
6894        let access_index = CommandAccessIndex {
6895            /*
6896            A is first discovered through cmd 0, then rediscovered through cmd 2. The later
6897            boundary must extend A's selected prefix to include cmd 1.
6898
6899            cmd 0: A, B
6900            cmd 1: A
6901            cmd 2: A, C
6902            cmd 3: B, C, T
6903            */
6904            cmds_by_node: vec![vec![0, 1, 2], vec![0, 3], vec![2, 3], vec![3]],
6905            accessed_nodes_by_cmd: vec![vec![0, 1], vec![0], vec![0, 2], vec![1, 2, 3]],
6906        };
6907        let mut schedule = Schedule {
6908            access_index,
6909            ..Default::default()
6910        };
6911
6912        super::schedule_dependency_cmds_before_target_access(3, 3, &mut schedule);
6913
6914        assert_eq!(schedule.cmds, vec![0, 1, 2]);
6915    }
6916
6917    #[test]
6918    fn node_selection_revisits_node_at_later_boundary() {
6919        let mut graph = Graph::new();
6920        for _ in 0..4 {
6921            graph.bind_stream_arg_resource(AnyResource::BufferArg(BufferInfo::device_mem(
6922                1,
6923                vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST,
6924            )));
6925        }
6926        graph.cmds = vec![
6927            command_with_accesses(&[(0, AccessType::TransferRead), (1, AccessType::TransferRead)]),
6928            command_with_accesses(&[(0, AccessType::TransferRead)]),
6929            command_with_accesses(&[(0, AccessType::TransferRead), (2, AccessType::TransferRead)]),
6930            command_with_accesses(&[
6931                (1, AccessType::TransferRead),
6932                (2, AccessType::TransferRead),
6933                (3, AccessType::TransferWrite),
6934            ]),
6935        ];
6936
6937        let submission = Submission::new(graph);
6938        let mut schedule = Schedule::default();
6939        schedule.access_index.update(&submission.graph, 4);
6940
6941        submission.schedule_node_cmds(1, 4, &mut schedule);
6942
6943        assert_eq!(schedule.cmds, vec![0, 1, 2, 3]);
6944    }
6945
6946    #[cfg(test)]
6947    fn sort_queue_ownership_release_groups(groups: &mut [super::QueueOwnershipReleaseGroup]) {
6948        for group in groups.iter_mut() {
6949            group
6950                .buffers
6951                .sort_unstable_by_key(|(buffer, range)| (buffer.as_raw(), range.start, range.end));
6952
6953            group.images.sort_unstable_by_key(|(image, layout, range)| {
6954                (
6955                    image.as_raw(),
6956                    layout.as_raw(),
6957                    range.aspect_mask.as_raw(),
6958                    range.base_array_layer,
6959                    range.layer_count,
6960                    range.base_mip_level,
6961                    range.level_count,
6962                )
6963            });
6964        }
6965
6966        groups.sort_unstable_by_key(|group| (group.src_queue_family_index, group.src_queue_index));
6967    }
6968
6969    #[cfg(test)]
6970    fn sort_image_subresource_sync_infos(
6971        subresources: &mut [crate::driver::image::ImageSubresourceSyncInfo],
6972    ) {
6973        subresources.sort_unstable_by_key(|subresource| {
6974            (
6975                subresource.range.aspect_mask.as_raw(),
6976                subresource.range.base_array_layer,
6977                subresource.range.layer_count,
6978                subresource.range.base_mip_level,
6979                subresource.range.level_count,
6980            )
6981        });
6982    }
6983
6984    #[derive(Debug)]
6985    struct TestDevice {
6986        _guard: MutexGuard<'static, ()>,
6987        device: ManuallyDrop<Device>,
6988    }
6989
6990    impl Drop for TestDevice {
6991        fn drop(&mut self) {
6992            // Drop the Vulkan device while the global test-device lock is still held
6993            // `_guard` is a normal field, so it is released after this `Drop` returns
6994            unsafe {
6995                ManuallyDrop::drop(&mut self.device);
6996            }
6997        }
6998    }
6999
7000    impl Deref for TestDevice {
7001        type Target = Device;
7002
7003        fn deref(&self) -> &Self::Target {
7004            &self.device
7005        }
7006    }
7007
7008    fn test_device_lock() -> &'static Mutex<()> {
7009        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
7010
7011        LOCK.get_or_init(|| Mutex::new(()))
7012    }
7013
7014    fn assert_no_invalid_attachment_stage_access_pairs(dep: &SubpassDependency) {
7015        let dst_invalid_color_stages = dep.dst_stage_mask
7016            & (vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
7017                | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS
7018                | vk::PipelineStageFlags::FRAGMENT_SHADER);
7019        assert!(
7020            !dep.dst_access_mask
7021                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ)
7022                || dst_invalid_color_stages.is_empty(),
7023            "COLOR_ATTACHMENT_READ must not be paired with unsupported destination stages: {dep:?}"
7024        );
7025
7026        let src_invalid_color_stages = dep.src_stage_mask
7027            & (vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
7028                | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS
7029                | vk::PipelineStageFlags::FRAGMENT_SHADER);
7030        assert!(
7031            !dep.src_access_mask
7032                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ)
7033                || src_invalid_color_stages.is_empty(),
7034            "COLOR_ATTACHMENT_READ must not be paired with unsupported source stages: {dep:?}"
7035        );
7036
7037        assert!(
7038            !(dep
7039                .src_access_mask
7040                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
7041                && dep
7042                    .src_stage_mask
7043                    .contains(vk::PipelineStageFlags::FRAGMENT_SHADER)),
7044            "DEPTH_STENCIL_ATTACHMENT_READ must not be paired with FRAGMENT_SHADER in source stages: {dep:?}"
7045        );
7046        assert!(
7047            !(dep
7048                .dst_access_mask
7049                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
7050                && dep
7051                    .dst_stage_mask
7052                    .contains(vk::PipelineStageFlags::FRAGMENT_SHADER)),
7053            "DEPTH_STENCIL_ATTACHMENT_READ must not be paired with FRAGMENT_SHADER in destination stages: {dep:?}"
7054        );
7055    }
7056
7057    fn assert_attachment_read_stage_mappings(dep: &SubpassDependency) {
7058        if dep
7059            .src_access_mask
7060            .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ)
7061        {
7062            assert!(
7063                dep.src_stage_mask
7064                    .contains(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT),
7065                "COLOR_ATTACHMENT_READ source access should use COLOR_ATTACHMENT_OUTPUT: {dep:?}"
7066            );
7067        }
7068
7069        if dep
7070            .dst_access_mask
7071            .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ)
7072        {
7073            assert!(
7074                dep.dst_stage_mask
7075                    .contains(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT),
7076                "COLOR_ATTACHMENT_READ destination access should use COLOR_ATTACHMENT_OUTPUT: {dep:?}"
7077            );
7078        }
7079
7080        let fragment_tests = vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
7081            | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS;
7082
7083        if dep
7084            .src_access_mask
7085            .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
7086        {
7087            assert!(
7088                dep.src_stage_mask.intersects(fragment_tests),
7089                "DEPTH_STENCIL_ATTACHMENT_READ source access should use fragment-test stages: {dep:?}"
7090            );
7091        }
7092
7093        if dep
7094            .dst_access_mask
7095            .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
7096        {
7097            assert!(
7098                dep.dst_stage_mask.intersects(fragment_tests),
7099                "DEPTH_STENCIL_ATTACHMENT_READ destination access should use fragment-test stages: {dep:?}"
7100            );
7101        }
7102    }
7103
7104    fn exec_with_buffer_access(access: AccessType) -> Execution {
7105        let mut exec = Execution::default();
7106        exec.accesses.push(
7107            0,
7108            SubresourceAccess {
7109                access,
7110                subresource: SubresourceRange::Buffer((0..16).into()),
7111            },
7112        );
7113
7114        exec
7115    }
7116
7117    fn subpass_dependencies_for_accesses(
7118        previous: AccessType,
7119        current: AccessType,
7120    ) -> Vec<SubpassDependency> {
7121        let pass = CommandData {
7122            execs: vec![
7123                exec_with_buffer_access(previous),
7124                exec_with_buffer_access(current),
7125            ],
7126
7127            #[cfg(debug_assertions)]
7128            name: None,
7129
7130            stream_scope_id: None,
7131            tracking: Default::default(),
7132        };
7133
7134        Submission::build_subpass_dependencies(&pass, &ExternalRenderPassAccessHistory::new(1))
7135    }
7136
7137    fn depth_attachment_exec(
7138        load: LoadOp<vk::ClearDepthStencilValue>,
7139        store: StoreOp,
7140    ) -> Execution {
7141        let mut exec = Execution::default();
7142        exec.attachments.depth_stencil = Some(DepthStencilAttachment {
7143            attachment: Attachment {
7144                array_layer_count: 1,
7145                aspect_mask: vk::ImageAspectFlags::DEPTH,
7146                base_array_layer: 0,
7147                base_mip_level: 0,
7148                format: vk::Format::D32_SFLOAT,
7149                mip_level_count: 1,
7150                sample_count: SampleCount::Type1,
7151                target: 0,
7152            },
7153            load,
7154            store,
7155            resolve: None,
7156            is_attachment: true,
7157        });
7158
7159        exec
7160    }
7161
7162    fn depth_attachment_dependencies(
7163        previous_load: LoadOp<vk::ClearDepthStencilValue>,
7164        previous_store: StoreOp,
7165        current_load: LoadOp<vk::ClearDepthStencilValue>,
7166        current_store: StoreOp,
7167    ) -> Vec<SubpassDependency> {
7168        let pass = CommandData {
7169            execs: vec![
7170                depth_attachment_exec(previous_load, previous_store),
7171                depth_attachment_exec(current_load, current_store),
7172            ],
7173
7174            #[cfg(debug_assertions)]
7175            name: None,
7176
7177            stream_scope_id: None,
7178            tracking: Default::default(),
7179        };
7180
7181        Submission::build_subpass_dependencies(&pass, &ExternalRenderPassAccessHistory::new(1))
7182    }
7183
7184    fn schedule_with_access_index(
7185        cmds: &[usize],
7186        cmds_by_node: &[&[usize]],
7187        accessed_nodes_by_cmd: &[&[usize]],
7188    ) -> Schedule {
7189        Schedule {
7190            access_index: CommandAccessIndex {
7191                cmds_by_node: cmds_by_node.iter().map(|cmds| cmds.to_vec()).collect(),
7192                accessed_nodes_by_cmd: accessed_nodes_by_cmd
7193                    .iter()
7194                    .map(|nodes| nodes.to_vec())
7195                    .collect(),
7196            },
7197            cmds: cmds.to_vec(),
7198            ..Default::default()
7199        }
7200    }
7201
7202    #[test]
7203    fn image_execution_discard_only_when_previous_access_is_nothing() {
7204        assert!(super::image_execution_discard_contents(AccessType::Nothing));
7205        assert!(!super::image_execution_discard_contents(
7206            AccessType::TransferRead
7207        ));
7208        assert!(!super::image_execution_discard_contents(
7209            AccessType::TransferWrite
7210        ));
7211        assert!(!super::image_execution_discard_contents(
7212            AccessType::ColorAttachmentReadWrite
7213        ));
7214    }
7215
7216    #[test]
7217    fn image_layout_transition_discard_keeps_attachment_write_policy() {
7218        assert!(super::image_layout_transition_discard_contents(
7219            AccessType::Nothing,
7220            AccessType::TransferWrite,
7221        ));
7222        assert!(super::image_layout_transition_discard_contents(
7223            AccessType::TransferRead,
7224            AccessType::TransferWrite,
7225        ));
7226        assert!(!super::image_layout_transition_discard_contents(
7227            AccessType::TransferWrite,
7228            AccessType::ColorAttachmentReadWrite,
7229        ));
7230    }
7231
7232    fn command_with_accesses(accesses: &[(usize, AccessType)]) -> CommandData {
7233        let mut exec = Execution::default();
7234
7235        for &(node_idx, access) in accesses {
7236            exec.accesses.push(
7237                node_idx,
7238                SubresourceAccess {
7239                    access,
7240                    subresource: SubresourceRange::Buffer(BufferSubresourceRange {
7241                        start: 0,
7242                        end: 1,
7243                    }),
7244                },
7245            );
7246        }
7247
7248        CommandData {
7249            execs: vec![exec],
7250
7251            #[cfg(debug_assertions)]
7252            name: None,
7253
7254            stream_scope_id: None,
7255            tracking: Default::default(),
7256        }
7257    }
7258
7259    #[test]
7260    fn command_access_index_includes_read_and_write_accesses() {
7261        let cmds = vec![
7262            command_with_accesses(&[(0, AccessType::TransferRead)]),
7263            command_with_accesses(&[(1, AccessType::TransferWrite)]),
7264            command_with_accesses(&[(1, AccessType::TransferRead)]),
7265            command_with_accesses(&[(1, AccessType::TransferWrite)]),
7266        ];
7267        let mut access_index = CommandAccessIndex::default();
7268
7269        access_index.update_from_cmds(&cmds, 2);
7270
7271        assert_eq!(access_index.cmds_by_node[0], vec![0]);
7272        assert_eq!(access_index.cmds_by_node[1], vec![1, 2, 3]);
7273        assert_eq!(access_index.accessed_nodes_by_cmd[0], vec![0]);
7274        assert_eq!(access_index.accessed_nodes_by_cmd[1], vec![1]);
7275        assert_eq!(access_index.accessed_nodes_by_cmd[2], vec![1]);
7276        assert_eq!(access_index.accessed_nodes_by_cmd[3], vec![1]);
7277    }
7278
7279    #[test]
7280    fn command_access_index_dedupes_accesses_per_command_and_resets_between_commands() {
7281        let cmds = vec![
7282            command_with_accesses(&[
7283                (0, AccessType::TransferRead),
7284                (0, AccessType::TransferWrite),
7285                (1, AccessType::TransferRead),
7286                (1, AccessType::TransferWrite),
7287            ]),
7288            command_with_accesses(&[(0, AccessType::TransferRead), (1, AccessType::TransferRead)]),
7289        ];
7290        let mut access_index = CommandAccessIndex::default();
7291
7292        access_index.update_from_cmds(&cmds, 2);
7293
7294        assert_eq!(access_index.cmds_by_node[0], vec![0, 1]);
7295        assert_eq!(access_index.cmds_by_node[1], vec![0, 1]);
7296        assert_eq!(access_index.accessed_nodes_by_cmd[0], vec![0, 1]);
7297        assert_eq!(access_index.accessed_nodes_by_cmd[1], vec![0, 1]);
7298    }
7299
7300    #[test]
7301    fn dependency_selection_dedupes_repeated_read_dependencies() {
7302        let access_index = CommandAccessIndex {
7303            /*
7304            Cmd 1 reads node 0 twice and writes node 1. Dependency selection for node 1 must
7305            schedule cmd 0 once, not once per repeated read of node 0.
7306            */
7307            cmds_by_node: vec![vec![0, 1], vec![1]],
7308            accessed_nodes_by_cmd: vec![vec![0], vec![0, 0, 1]],
7309        };
7310        let mut schedule = Schedule {
7311            access_index,
7312            ..Default::default()
7313        };
7314
7315        super::schedule_dependency_cmds_before_target_access(1, 1, &mut schedule);
7316
7317        assert_eq!(schedule.cmds, vec![0]);
7318    }
7319
7320    #[test]
7321    fn reorder_scheduled_cmds_preserves_hazards_from_command_access_index_update() {
7322        let cmds = vec![
7323            command_with_accesses(&[(0, AccessType::TransferRead)]),
7324            command_with_accesses(&[(1, AccessType::TransferWrite)]),
7325            command_with_accesses(&[(1, AccessType::TransferRead)]),
7326            command_with_accesses(&[(1, AccessType::TransferWrite)]),
7327            command_with_accesses(&[(0, AccessType::TransferRead)]),
7328        ];
7329        let mut access_index = CommandAccessIndex::default();
7330        access_index.update_from_cmds(&cmds, 2);
7331        let mut schedule = Schedule {
7332            access_index,
7333            cmds: (0..cmds.len()).collect(),
7334            ..Default::default()
7335        };
7336
7337        schedule.reorder_cmds(cmds.len());
7338
7339        let position = |cmd_idx| {
7340            schedule
7341                .cmds
7342                .iter()
7343                .position(|&scheduled_cmd_idx| scheduled_cmd_idx == cmd_idx)
7344                .expect("command was not scheduled")
7345        };
7346        assert!(position(1) < position(2), "write-read hazard crossed");
7347        assert!(position(2) < position(3), "read-write hazard crossed");
7348    }
7349
7350    #[test]
7351    fn reorder_scheduled_cmds_groups_ready_dependency_chain() {
7352        let mut schedule = schedule_with_access_index(
7353            &[0, 1, 2, 3],
7354            &[&[0, 1], &[1, 2], &[1, 3]],
7355            &[&[0], &[0, 1, 2], &[1], &[2]],
7356        );
7357
7358        schedule.reorder_cmds(4);
7359
7360        assert_eq!(schedule.cmds, vec![0, 1, 2, 3]);
7361    }
7362
7363    #[test]
7364    fn queue_ownership_release_groups_group_by_source_queue() {
7365        use super::{image_subresource_range_eq, queue_ownership_release_group};
7366
7367        let mut submission = Submission::new(Graph::new());
7368        let image = vk::Image::null();
7369
7370        let range_a = vk::ImageSubresourceRange {
7371            aspect_mask: vk::ImageAspectFlags::COLOR,
7372            base_array_layer: 0,
7373            layer_count: 1,
7374            base_mip_level: 0,
7375            level_count: 1,
7376        };
7377        let range_b = vk::ImageSubresourceRange {
7378            aspect_mask: vk::ImageAspectFlags::COLOR,
7379            base_array_layer: 1,
7380            layer_count: 1,
7381            base_mip_level: 0,
7382            level_count: 1,
7383        };
7384
7385        queue_ownership_release_group(&mut submission.queue_ownership_release_groups, 1, 2)
7386            .images
7387            .push((image, vk::ImageLayout::GENERAL, range_a));
7388        queue_ownership_release_group(&mut submission.queue_ownership_release_groups, 1, 2)
7389            .images
7390            .push((image, vk::ImageLayout::GENERAL, range_b));
7391        queue_ownership_release_group(&mut submission.queue_ownership_release_groups, 4, 5)
7392            .images
7393            .push((image, vk::ImageLayout::GENERAL, range_a));
7394
7395        let mut groups = submission.queue_ownership_release_groups;
7396        sort_queue_ownership_release_groups(&mut groups);
7397
7398        assert_eq!(groups.len(), 2);
7399        assert_eq!(groups[0].images.len(), 2);
7400        assert_eq!(groups[1].images.len(), 1);
7401        assert_eq!(groups[0].images[0].0, image);
7402        assert!(image_subresource_range_eq(groups[0].images[0].2, range_a));
7403    }
7404
7405    #[test]
7406    fn barrier_transfer_ranges_only_marks_overlapping_ranges() {
7407        use super::{image_barrier_transfer_ranges, image_subresource_range_eq};
7408
7409        let range_a = color_subresource_range(0..1, 0..1);
7410        let range_b = color_subresource_range(1..2, 0..1);
7411        let transfers = [ImageQueueOwnershipTransfer {
7412            src_queue_family_index: 1,
7413            src_queue_index: 2,
7414            dst_queue_family_index: 3,
7415            layout: vk::ImageLayout::GENERAL,
7416            range: range_a,
7417        }];
7418
7419        let ranges = image_barrier_transfer_ranges(&transfers, color_subresource_range(0..2, 0..1))
7420            .collect::<Vec<_>>();
7421
7422        assert_eq!(ranges.len(), 2);
7423        assert!(image_subresource_range_eq(ranges[0].0, range_a));
7424        assert_eq!(
7425            ranges[0].1.map(|transfer| (
7426                transfer.src_queue_family_index,
7427                transfer.src_queue_index,
7428                transfer.dst_queue_family_index,
7429            )),
7430            Some((1, 2, 3))
7431        );
7432        assert!(image_subresource_range_eq(ranges[1].0, range_b));
7433        assert!(ranges[1].1.is_none());
7434    }
7435
7436    #[test]
7437    #[ignore = "requires Vulkan device"]
7438    fn track_pending_transfers_only_collects_touched_subresources() -> Result<(), DriverError> {
7439        let device = test_device()?;
7440        let mut graph = Graph::new();
7441        let image = graph.bind_resource(Image::create(
7442            &device,
7443            ImageInfo::image_2d_array(1, 1, 2, vk::Format::R8_UINT, vk::ImageUsageFlags::SAMPLED),
7444        )?);
7445        let range_a = color_subresource_range(0..1, 0..1);
7446        let range_b = color_subresource_range(1..2, 0..1);
7447        let image_handle = graph.resource(image).handle;
7448
7449        {
7450            let image_resource = graph.resource(image);
7451            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range_a]);
7452            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((2, 0))), &[range_b]);
7453
7454            image_resource
7455                .swap_access(AccessType::TransferRead, range_a)
7456                .for_each(drop);
7457            image_resource
7458                .swap_access(AccessType::TransferRead, range_b)
7459                .for_each(drop);
7460        }
7461
7462        graph
7463            .begin_cmd()
7464            .debug_name("touch first layer only")
7465            .subresource_access(image, range_a, AccessType::TransferWrite)
7466            .record_cmd(|_| {})
7467            .end_cmd();
7468
7469        let mut submission = graph.finalize();
7470        let mut ownership = RecordingOwnership::default();
7471        submission.track_pending_transfers(
7472            &Schedule {
7473                cmds: vec![0],
7474                ..Default::default()
7475            },
7476            3,
7477            &mut ownership,
7478        );
7479
7480        let (handle, transfers) = pending_transfer_for_node(
7481            submission
7482                .pending_image_transfer_nodes
7483                .as_ref()
7484                .expect("missing pending transfer nodes"),
7485            image.index(),
7486        )
7487        .expect("missing pending transfer for touched subresource");
7488        assert_eq!(handle, image_handle);
7489        assert_eq!(
7490            submission
7491                .pending_image_transfer_nodes
7492                .as_ref()
7493                .expect("missing pending transfer nodes")
7494                .indices,
7495            vec![image.index()]
7496        );
7497        let mut transfers = transfers.to_vec();
7498        sort_pending_image_transfers(&mut transfers);
7499
7500        assert_eq!(transfers.len(), 1);
7501        assert!(super::image_subresource_range_eq(
7502            transfers[0].range,
7503            range_a
7504        ));
7505        let ranges = &submission.exclusive_image_ranges[&image.index()];
7506        let mut ranges = ranges.clone();
7507        sort_image_subresource_ranges(&mut ranges);
7508        assert_eq!(ranges.len(), 1);
7509        assert!(super::image_subresource_range_eq(ranges[0], range_a));
7510
7511        Ok(())
7512    }
7513
7514    #[test]
7515    #[ignore = "requires Vulkan device"]
7516    fn track_pending_transfers_only_collects_touched_buffer_ranges() -> Result<(), DriverError> {
7517        let device = test_device()?;
7518        let mut graph = Graph::new();
7519        let buffer = graph.bind_resource(Buffer::create(
7520            &device,
7521            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
7522        )?);
7523        let range_a = BufferSubresourceRange { start: 0, end: 8 };
7524        let range_b = BufferSubresourceRange { start: 8, end: 16 };
7525        let buffer_handle = graph.resource(buffer).handle;
7526
7527        {
7528            let buffer_resource = graph.resource(buffer);
7529            buffer_resource.set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range_a]);
7530            buffer_resource.set_sharing_ranges(SharingMode::Exclusive(Some((2, 0))), &[range_b]);
7531
7532            buffer_resource
7533                .swap_access(AccessType::TransferRead, range_a)
7534                .for_each(drop);
7535            buffer_resource
7536                .swap_access(AccessType::TransferRead, range_b)
7537                .for_each(drop);
7538        }
7539
7540        graph
7541            .begin_cmd()
7542            .debug_name("touch first buffer range only")
7543            .subresource_access(buffer, range_a, AccessType::TransferWrite)
7544            .record_cmd(|_| {})
7545            .end_cmd();
7546
7547        let mut submission = graph.finalize();
7548        let mut ownership = RecordingOwnership::default();
7549        submission.track_pending_transfers(
7550            &Schedule {
7551                cmds: vec![0],
7552                ..Default::default()
7553            },
7554            3,
7555            &mut ownership,
7556        );
7557
7558        let (handle, transfers) = pending_transfer_for_node(
7559            submission
7560                .pending_buffer_transfer_nodes
7561                .as_ref()
7562                .expect("missing pending transfer nodes"),
7563            buffer.index(),
7564        )
7565        .expect("missing pending transfer for touched buffer range");
7566        assert_eq!(handle, buffer_handle);
7567        assert_eq!(
7568            submission
7569                .pending_buffer_transfer_nodes
7570                .as_ref()
7571                .expect("missing pending transfer nodes")
7572                .indices,
7573            vec![buffer.index()]
7574        );
7575        let mut transfers = transfers.to_vec();
7576        sort_pending_buffer_transfers(&mut transfers);
7577
7578        assert_eq!(transfers.len(), 1);
7579        assert!(pending_buffer_transfer_for_range(&transfers, range_a).is_some());
7580        assert!(pending_buffer_transfer_for_range(&transfers, range_b).is_none());
7581
7582        let ranges = &submission.exclusive_buffer_ranges[&buffer.index()];
7583        let mut ranges = ranges.clone();
7584        ranges.sort_unstable_by_key(|range| (range.start, range.end));
7585        assert_eq!(ranges, vec![range_a]);
7586
7587        Ok(())
7588    }
7589
7590    #[test]
7591    #[ignore = "requires Vulkan device"]
7592    fn repeated_partial_recording_does_not_duplicate_buffer_ownership_transfer()
7593    -> Result<(), DriverError> {
7594        let device = test_device()?;
7595        let mut graph = Graph::new();
7596        let buffer = graph.bind_resource(Buffer::create(
7597            &device,
7598            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
7599        )?);
7600        let range = BufferSubresourceRange { start: 0, end: 16 };
7601
7602        graph
7603            .resource(buffer)
7604            .set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range]);
7605        graph
7606            .begin_cmd()
7607            .debug_name("touch shared range")
7608            .subresource_access(buffer, range, AccessType::TransferWrite)
7609            .record_cmd(|_| {})
7610            .end_cmd();
7611
7612        let mut submission = graph.finalize();
7613        let schedule = Schedule {
7614            cmds: vec![0],
7615            ..Default::default()
7616        };
7617        let mut ownership = RecordingOwnership::default();
7618
7619        simulate_partial_transfer_discovery(&mut submission, &schedule, 3, &mut ownership);
7620        simulate_partial_transfer_discovery(&mut submission, &schedule, 3, &mut ownership);
7621
7622        let released_ranges = submission
7623            .queue_ownership_release_groups
7624            .iter()
7625            .flat_map(|group| group.buffers.iter())
7626            .map(|(_, range)| *range)
7627            .collect::<Vec<_>>();
7628        assert_eq!(released_ranges, vec![range]);
7629        assert_eq!(
7630            submission.exclusive_buffer_ranges[&buffer.index()],
7631            vec![range]
7632        );
7633
7634        Ok(())
7635    }
7636
7637    #[test]
7638    #[ignore = "requires Vulkan device"]
7639    fn partial_recording_transfers_only_unclaimed_buffer_overlap() -> Result<(), DriverError> {
7640        let device = test_device()?;
7641        let mut graph = Graph::new();
7642        let buffer = graph.bind_resource(Buffer::create(
7643            &device,
7644            BufferInfo::device_mem(12, vk::BufferUsageFlags::TRANSFER_DST),
7645        )?);
7646        let first = BufferSubresourceRange { start: 0, end: 8 };
7647        let second = BufferSubresourceRange { start: 4, end: 12 };
7648
7649        graph.resource(buffer).set_sharing_ranges(
7650            SharingMode::Exclusive(Some((1, 0))),
7651            &[BufferSubresourceRange { start: 0, end: 12 }],
7652        );
7653        graph
7654            .begin_cmd()
7655            .debug_name("touch first overlapping range")
7656            .subresource_access(buffer, first, AccessType::TransferWrite)
7657            .record_cmd(|_| {})
7658            .end_cmd();
7659        graph
7660            .begin_cmd()
7661            .debug_name("touch second overlapping range")
7662            .subresource_access(buffer, second, AccessType::TransferWrite)
7663            .record_cmd(|_| {})
7664            .end_cmd();
7665
7666        let mut submission = graph.finalize();
7667        let mut ownership = RecordingOwnership::default();
7668        simulate_partial_transfer_discovery(
7669            &mut submission,
7670            &Schedule {
7671                cmds: vec![0],
7672                ..Default::default()
7673            },
7674            3,
7675            &mut ownership,
7676        );
7677        simulate_partial_transfer_discovery(
7678            &mut submission,
7679            &Schedule {
7680                cmds: vec![1],
7681                ..Default::default()
7682            },
7683            3,
7684            &mut ownership,
7685        );
7686
7687        let mut released_ranges = submission
7688            .queue_ownership_release_groups
7689            .iter()
7690            .flat_map(|group| group.buffers.iter())
7691            .map(|(_, range)| *range)
7692            .collect::<Vec<_>>();
7693        released_ranges.sort_unstable_by_key(|range| (range.start, range.end));
7694
7695        assert!(
7696            released_ranges
7697                .windows(2)
7698                .all(|ranges| ranges[0].end <= ranges[1].start),
7699            "released ranges overlap: {released_ranges:?}"
7700        );
7701        assert_eq!(
7702            released_ranges
7703                .iter()
7704                .map(|range| range.end - range.start)
7705                .sum::<vk::DeviceSize>(),
7706            12
7707        );
7708
7709        Ok(())
7710    }
7711
7712    #[test]
7713    #[ignore = "requires Vulkan device"]
7714    fn repeated_partial_recording_does_not_duplicate_image_ownership_transfer()
7715    -> Result<(), DriverError> {
7716        let device = test_device()?;
7717        let mut graph = Graph::new();
7718        let image = graph.bind_resource(Image::create(
7719            &device,
7720            ImageInfo::image_2d_array(1, 1, 2, vk::Format::R8_UINT, vk::ImageUsageFlags::SAMPLED),
7721        )?);
7722        let range = color_subresource_range(0..2, 0..1);
7723
7724        graph
7725            .resource(image)
7726            .set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range]);
7727        graph
7728            .begin_cmd()
7729            .debug_name("touch shared image range")
7730            .subresource_access(image, range, AccessType::TransferWrite)
7731            .record_cmd(|_| {})
7732            .end_cmd();
7733
7734        let mut submission = graph.finalize();
7735        let schedule = Schedule {
7736            cmds: vec![0],
7737            ..Default::default()
7738        };
7739        let mut ownership = RecordingOwnership::default();
7740
7741        simulate_partial_transfer_discovery(&mut submission, &schedule, 3, &mut ownership);
7742        let first_released_ranges = submission
7743            .queue_ownership_release_groups
7744            .iter()
7745            .flat_map(|group| group.images.iter())
7746            .map(|(_, _, range)| *range)
7747            .collect::<Vec<_>>();
7748        simulate_partial_transfer_discovery(&mut submission, &schedule, 3, &mut ownership);
7749
7750        let released_ranges = submission
7751            .queue_ownership_release_groups
7752            .iter()
7753            .flat_map(|group| group.images.iter())
7754            .map(|(_, _, range)| *range)
7755            .collect::<Vec<_>>();
7756        assert_eq!(released_ranges.len(), first_released_ranges.len());
7757        assert!(
7758            released_ranges
7759                .iter()
7760                .zip(&first_released_ranges)
7761                .all(|(&lhs, &rhs)| super::image_subresource_range_eq(lhs, rhs)),
7762            "released ranges changed: {first_released_ranges:?} -> {released_ranges:?}"
7763        );
7764        assert_eq!(submission.exclusive_image_ranges[&image.index()].len(), 1);
7765        assert!(super::image_subresource_range_eq(
7766            submission.exclusive_image_ranges[&image.index()][0],
7767            range
7768        ));
7769
7770        Ok(())
7771    }
7772
7773    #[test]
7774    #[ignore = "requires Vulkan device"]
7775    fn track_pending_transfers_keeps_exclusive_owner_without_known_layout()
7776    -> Result<(), DriverError> {
7777        let device = test_device()?;
7778        let mut graph = Graph::new();
7779        let image = graph.bind_resource(Image::create(
7780            &device,
7781            ImageInfo::image_2d_array(1, 1, 2, vk::Format::R8_UINT, vk::ImageUsageFlags::SAMPLED),
7782        )?);
7783        let range_a = color_subresource_range(0..1, 0..1);
7784        let range_b = color_subresource_range(1..2, 0..1);
7785        let image_handle = graph.resource(image).handle;
7786
7787        {
7788            let image_resource = graph.resource(image);
7789            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range_a]);
7790            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((2, 0))), &[range_b]);
7791        }
7792
7793        graph
7794            .begin_cmd()
7795            .debug_name("touch first layer only")
7796            .subresource_access(image, range_a, AccessType::TransferWrite)
7797            .record_cmd(|_| {})
7798            .end_cmd();
7799
7800        let mut submission = graph.finalize();
7801        let mut ownership = RecordingOwnership::default();
7802        submission.track_pending_transfers(
7803            &Schedule {
7804                cmds: vec![0],
7805                ..Default::default()
7806            },
7807            3,
7808            &mut ownership,
7809        );
7810
7811        let (handle, transfers) = pending_transfer_for_node(
7812            submission
7813                .pending_image_transfer_nodes
7814                .as_ref()
7815                .expect("missing pending transfer nodes"),
7816            image.index(),
7817        )
7818        .expect("missing pending transfer for touched subresource");
7819        assert_eq!(handle, image_handle);
7820        assert_eq!(
7821            submission
7822                .pending_image_transfer_nodes
7823                .as_ref()
7824                .expect("missing pending transfer nodes")
7825                .indices,
7826            vec![image.index()]
7827        );
7828        let mut transfers = transfers.to_vec();
7829        sort_pending_image_transfers(&mut transfers);
7830
7831        assert_eq!(transfers.len(), 1);
7832        assert!(super::image_subresource_range_eq(
7833            transfers[0].range,
7834            range_a
7835        ));
7836        assert_eq!(transfers[0].layout, vk::ImageLayout::UNDEFINED);
7837
7838        let ranges = &submission.exclusive_image_ranges[&image.index()];
7839        let mut ranges = ranges.clone();
7840        sort_image_subresource_ranges(&mut ranges);
7841        assert_eq!(ranges.len(), 1);
7842        assert!(super::image_subresource_range_eq(ranges[0], range_a));
7843
7844        Ok(())
7845    }
7846
7847    #[test]
7848    #[ignore = "requires Vulkan device"]
7849    fn recorded_submission_attach_updates_only_touched_subresources() -> Result<(), DriverError> {
7850        let device = test_device()?;
7851        let mut graph = Graph::new();
7852        let image = graph.bind_resource(Image::create(
7853            &device,
7854            ImageInfo::image_2d_array(1, 1, 2, vk::Format::R8_UINT, vk::ImageUsageFlags::SAMPLED),
7855        )?);
7856        let range_a = color_subresource_range(0..1, 0..1);
7857        let range_b = color_subresource_range(1..2, 0..1);
7858
7859        {
7860            let image_resource = graph.resource(image);
7861            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range_a]);
7862            image_resource.set_sharing_ranges(SharingMode::Exclusive(Some((2, 0))), &[range_b]);
7863
7864            image_resource
7865                .swap_access(AccessType::TransferRead, range_a)
7866                .for_each(drop);
7867            image_resource
7868                .swap_access(AccessType::TransferRead, range_b)
7869                .for_each(drop);
7870        }
7871
7872        let mut submission = graph.finalize();
7873        submission
7874            .exclusive_image_ranges
7875            .insert(image.index(), vec![range_a]);
7876
7877        let mut fence = Fence::create(&device, false)?;
7878        let cmd_buf = CommandBuffer::create(&device, CommandBufferInfo::new(3))?;
7879        cmd_buf.begin(
7880            &vk::CommandBufferBeginInfo::default()
7881                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
7882        )?;
7883        cmd_buf.end()?;
7884        let mut recorded = RecordedSubmission {
7885            cmd_buf,
7886            queue_ownership_release_waits: Vec::new(),
7887            state: Arc::new(Mutex::new(RecordedSubmissionState {
7888                submission,
7889                _releases: Vec::new(),
7890                executed: false,
7891            })),
7892        };
7893
7894        recorded.queue_submit(&mut fence, 0, QueueSubmitInfo::QUEUE_SUBMIT)?;
7895
7896        let state = recorded.state.lock().expect("poisoned recorded state");
7897        let sync_info = state.submission.graph.resource(image).sync_info();
7898        let mut subresources = sync_info.subresources.into_vec();
7899        sort_image_subresource_sync_infos(&mut subresources);
7900
7901        assert_eq!(subresources.len(), 2);
7902        assert_eq!(subresources[0].queue_family_index, Some(3));
7903        assert_eq!(subresources[1].queue_family_index, Some(2));
7904
7905        Ok(())
7906    }
7907
7908    #[test]
7909    #[ignore = "requires Vulkan device"]
7910    fn recorded_submission_attach_updates_only_touched_buffer_ranges() -> Result<(), DriverError> {
7911        let device = test_device()?;
7912        let mut graph = Graph::new();
7913        let buffer = graph.bind_resource(Buffer::create(
7914            &device,
7915            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
7916        )?);
7917        let range_a = BufferSubresourceRange { start: 0, end: 8 };
7918        let range_b = BufferSubresourceRange { start: 8, end: 16 };
7919
7920        {
7921            let buffer_resource = graph.resource(buffer);
7922            buffer_resource.set_sharing_ranges(SharingMode::Exclusive(Some((1, 0))), &[range_a]);
7923            buffer_resource.set_sharing_ranges(SharingMode::Exclusive(Some((2, 0))), &[range_b]);
7924
7925            buffer_resource
7926                .swap_access(AccessType::TransferRead, range_a)
7927                .for_each(drop);
7928            buffer_resource
7929                .swap_access(AccessType::TransferRead, range_b)
7930                .for_each(drop);
7931        }
7932
7933        let mut submission = graph.finalize();
7934        submission
7935            .exclusive_buffer_ranges
7936            .insert(buffer.index(), vec![range_a]);
7937
7938        let mut fence = Fence::create(&device, false)?;
7939        let cmd_buf = CommandBuffer::create(&device, CommandBufferInfo::new(3))?;
7940        cmd_buf.begin(
7941            &vk::CommandBufferBeginInfo::default()
7942                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
7943        )?;
7944        cmd_buf.end()?;
7945        let mut recorded = RecordedSubmission {
7946            cmd_buf,
7947            queue_ownership_release_waits: Vec::new(),
7948            state: Arc::new(Mutex::new(RecordedSubmissionState {
7949                submission,
7950                _releases: Vec::new(),
7951                executed: false,
7952            })),
7953        };
7954
7955        recorded.queue_submit(&mut fence, 0, QueueSubmitInfo::QUEUE_SUBMIT)?;
7956
7957        let state = recorded.state.lock().expect("poisoned recorded state");
7958        let sync_info = state.submission.graph.resource(buffer).sync_info();
7959        let mut ranges = sync_info.ranges.into_vec();
7960        ranges.sort_unstable_by_key(|range| (range.range.start, range.range.end));
7961
7962        assert_eq!(ranges.len(), 2);
7963        assert_eq!(ranges[0].queue_family_index, Some(3));
7964        assert_eq!(ranges[1].queue_family_index, Some(2));
7965
7966        Ok(())
7967    }
7968
7969    #[test]
7970    fn reorder_scheduled_cmds_keeps_disconnected_groups_deterministic() {
7971        let mut schedule = schedule_with_access_index(
7972            &[0, 1, 2, 3, 4],
7973            &[&[0, 1, 2], &[3, 4]],
7974            &[&[0], &[0], &[0], &[1], &[1]],
7975        );
7976
7977        schedule.reorder_cmds(5);
7978
7979        assert_eq!(schedule.cmds, vec![0, 1, 2, 3, 4]);
7980    }
7981
7982    #[test]
7983    fn reorder_scheduled_cmds_preserves_both_branches_before_join() {
7984        let mut schedule =
7985            schedule_with_access_index(&[0, 1, 2], &[&[0, 2], &[1, 2]], &[&[0], &[1], &[0, 1]]);
7986
7987        schedule.reorder_cmds(3);
7988
7989        assert_eq!(schedule.cmds, vec![0, 1, 2]);
7990    }
7991
7992    #[test]
7993    fn reorder_scheduled_cmds_prioritizes_ready_resource_successors() {
7994        let mut schedule = schedule_with_access_index(
7995            &[0, 1, 2, 3, 4],
7996            &[&[0, 1, 3], &[0, 4], &[3]],
7997            &[&[0, 1], &[0], &[], &[0, 2], &[1]],
7998        );
7999
8000        schedule.reorder_cmds(5);
8001
8002        assert_eq!(schedule.cmds, vec![0, 1, 3, 4, 2]);
8003    }
8004
8005    #[test]
8006    fn reorder_scheduled_cmds_ready_ties_use_original_order() {
8007        let mut schedule = schedule_with_access_index(
8008            &[0, 1, 2, 3, 4, 5],
8009            &[&[1, 2], &[1, 4], &[0, 1, 5]],
8010            &[&[2], &[0, 1, 2], &[0], &[], &[1], &[2]],
8011        );
8012
8013        schedule.reorder_cmds(6);
8014
8015        assert_eq!(schedule.cmds, vec![0, 1, 2, 4, 5, 3]);
8016    }
8017
8018    #[test]
8019    fn reorder_scheduled_cmds_handles_noncontiguous_global_indices() {
8020        let mut schedule = schedule_with_access_index(
8021            &[1, 3, 5, 7],
8022            &[&[1, 5], &[3, 5, 7]],
8023            &[&[], &[0], &[], &[1], &[], &[0, 1], &[], &[1]],
8024        );
8025
8026        schedule.reorder_cmds(8);
8027
8028        assert_eq!(schedule.cmds, vec![1, 3, 5, 7]);
8029    }
8030
8031    #[test]
8032    fn reorder_scheduled_cmds_preserves_write_only_order() {
8033        let mut schedule = schedule_with_access_index(
8034            &[0, 1, 2, 3],
8035            /*
8036            Resource 0 is written by cmd 0 and read by cmd 3. Resource 1 is written by cmds 1 and
8037            2, so their relative order must be preserved even though neither cmd reads it.
8038            */
8039            &[&[0, 3], &[1, 2]],
8040            &[&[0], &[1], &[1], &[0]],
8041        );
8042
8043        schedule.reorder_cmds(4);
8044
8045        let cmd_1_position = schedule
8046            .cmds
8047            .iter()
8048            .position(|&cmd_idx| cmd_idx == 1)
8049            .expect("cmd 1 was not scheduled");
8050        let cmd_2_position = schedule
8051            .cmds
8052            .iter()
8053            .position(|&cmd_idx| cmd_idx == 2)
8054            .expect("cmd 2 was not scheduled");
8055
8056        assert!(
8057            cmd_1_position < cmd_2_position,
8058            "write-only commands were reordered: {:?}",
8059            schedule.cmds
8060        );
8061    }
8062
8063    #[test]
8064    fn reorder_scheduled_cmds_preserves_write_after_write_hazard() {
8065        fuzz::check_schedule_reordering(
8066            4,
8067            &[
8068                vec![
8069                    fuzz::ResourceAccess {
8070                        cmd_idx: 1,
8071                        write: true,
8072                    },
8073                    fuzz::ResourceAccess {
8074                        cmd_idx: 2,
8075                        write: true,
8076                    },
8077                ],
8078                vec![fuzz::ResourceAccess {
8079                    cmd_idx: 0,
8080                    write: false,
8081                }],
8082                vec![fuzz::ResourceAccess {
8083                    cmd_idx: 3,
8084                    write: false,
8085                }],
8086            ],
8087        );
8088    }
8089
8090    #[test]
8091    fn reorder_scheduled_cmds_preserves_displaced_write_before_read() {
8092        fuzz::check_schedule_reordering(
8093            6,
8094            &[
8095                vec![
8096                    fuzz::ResourceAccess {
8097                        cmd_idx: 0,
8098                        write: false,
8099                    },
8100                    fuzz::ResourceAccess {
8101                        cmd_idx: 1,
8102                        write: true,
8103                    },
8104                    fuzz::ResourceAccess {
8105                        cmd_idx: 2,
8106                        write: false,
8107                    },
8108                    fuzz::ResourceAccess {
8109                        cmd_idx: 5,
8110                        write: true,
8111                    },
8112                ],
8113                vec![
8114                    fuzz::ResourceAccess {
8115                        cmd_idx: 0,
8116                        write: false,
8117                    },
8118                    fuzz::ResourceAccess {
8119                        cmd_idx: 3,
8120                        write: true,
8121                    },
8122                    fuzz::ResourceAccess {
8123                        cmd_idx: 4,
8124                        write: false,
8125                    },
8126                ],
8127            ],
8128        );
8129    }
8130
8131    #[test]
8132    fn reorder_scheduled_cmds_preserves_write_then_read_hazard() {
8133        fuzz::check_schedule_reordering(
8134            4,
8135            &[
8136                vec![
8137                    fuzz::ResourceAccess {
8138                        cmd_idx: 1,
8139                        write: true,
8140                    },
8141                    fuzz::ResourceAccess {
8142                        cmd_idx: 2,
8143                        write: false,
8144                    },
8145                ],
8146                vec![fuzz::ResourceAccess {
8147                    cmd_idx: 0,
8148                    write: false,
8149                }],
8150                vec![fuzz::ResourceAccess {
8151                    cmd_idx: 3,
8152                    write: false,
8153                }],
8154            ],
8155        );
8156    }
8157
8158    #[test]
8159    fn reorder_scheduled_cmds_preserves_read_then_write_hazard() {
8160        fuzz::check_schedule_reordering(
8161            4,
8162            &[
8163                vec![
8164                    fuzz::ResourceAccess {
8165                        cmd_idx: 1,
8166                        write: false,
8167                    },
8168                    fuzz::ResourceAccess {
8169                        cmd_idx: 2,
8170                        write: true,
8171                    },
8172                ],
8173                vec![fuzz::ResourceAccess {
8174                    cmd_idx: 0,
8175                    write: false,
8176                }],
8177                vec![fuzz::ResourceAccess {
8178                    cmd_idx: 3,
8179                    write: false,
8180                }],
8181            ],
8182        );
8183    }
8184
8185    #[test]
8186    fn reorder_scheduled_cmds_allows_unrelated_moves_without_crossing_hazard() {
8187        fuzz::check_schedule_reordering(
8188            6,
8189            &[
8190                vec![
8191                    fuzz::ResourceAccess {
8192                        cmd_idx: 1,
8193                        write: true,
8194                    },
8195                    fuzz::ResourceAccess {
8196                        cmd_idx: 4,
8197                        write: true,
8198                    },
8199                ],
8200                vec![
8201                    fuzz::ResourceAccess {
8202                        cmd_idx: 0,
8203                        write: false,
8204                    },
8205                    fuzz::ResourceAccess {
8206                        cmd_idx: 2,
8207                        write: false,
8208                    },
8209                    fuzz::ResourceAccess {
8210                        cmd_idx: 5,
8211                        write: false,
8212                    },
8213                ],
8214                vec![fuzz::ResourceAccess {
8215                    cmd_idx: 3,
8216                    write: false,
8217                }],
8218            ],
8219        );
8220    }
8221
8222    #[test]
8223    fn record_selection_from_node_creates_node_variant() {
8224        let node = BufferNode::new(
8225            7,
8226            #[cfg(feature = "checked")]
8227            crate::GraphId(1),
8228        );
8229
8230        let selection = RecordSelection::from(node);
8231
8232        match selection {
8233            RecordSelection::Node(AnyNode::Buffer(actual)) => assert_eq!(actual.index(), 7),
8234            _ => panic!("expected RecordSelection::Node(Buffer)"),
8235        }
8236    }
8237
8238    #[test]
8239    fn record_selection_nodes_preserves_slice() {
8240        let lhs = AnyNode::from(BufferNode::new(
8241            1,
8242            #[cfg(feature = "checked")]
8243            crate::GraphId(1),
8244        ));
8245        let rhs = AnyNode::from(BufferNode::new(
8246            2,
8247            #[cfg(feature = "checked")]
8248            crate::GraphId(1),
8249        ));
8250        let nodes = [lhs, rhs];
8251
8252        match RecordSelection::nodes(&nodes) {
8253            RecordSelection::Nodes(actual) => assert_eq!(actual.len(), 2),
8254            _ => panic!("expected RecordSelection::Nodes"),
8255        }
8256    }
8257
8258    #[test]
8259    fn legacy_submit_accepts_all_commands_and_none_wait_masks() {
8260        let waits = [
8261            SemaphoreSubmitInfo {
8262                semaphore: vk::Semaphore::null(),
8263                stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
8264                value: 0,
8265            },
8266            SemaphoreSubmitInfo {
8267                semaphore: vk::Semaphore::null(),
8268                stage_mask: vk::PipelineStageFlags2::NONE,
8269                value: 0,
8270            },
8271        ];
8272        let signals = [SemaphoreSubmitInfo {
8273            semaphore: vk::Semaphore::null(),
8274            stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
8275            value: 0,
8276        }];
8277
8278        assert!(check_queue_submit_args(&waits, &signals).is_ok());
8279    }
8280
8281    #[test]
8282    fn legacy_submit_rejects_precise_wait_stage_masks() {
8283        let waits = [SemaphoreSubmitInfo {
8284            semaphore: vk::Semaphore::null(),
8285            stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
8286            value: 0,
8287        }];
8288
8289        assert!(matches!(
8290            check_queue_submit_args(&waits, &[]),
8291            Err(DriverError::Unsupported)
8292        ));
8293    }
8294
8295    #[test]
8296    fn legacy_submit_rejects_timeline_values() {
8297        let waits = [SemaphoreSubmitInfo {
8298            semaphore: vk::Semaphore::null(),
8299            stage_mask: vk::PipelineStageFlags2::ALL_COMMANDS,
8300            value: 1,
8301        }];
8302
8303        assert!(matches!(
8304            check_queue_submit_args(&waits, &[]),
8305            Err(DriverError::Unsupported)
8306        ));
8307    }
8308
8309    #[test]
8310    #[ignore = "requires Vulkan device"]
8311    fn supplied_descriptor_set_reuses_compatible_pipeline_layout() -> Result<(), DriverError> {
8312        let device = test_device()?;
8313        let spirv = glsl!(
8314            r#"
8315            #version 460 core
8316            #pragma shader_stage(compute)
8317
8318            layout(local_size_x = 1) in;
8319            layout(set = 0, binding = 0) buffer DataA {
8320                uint value;
8321            } data_a;
8322            layout(set = 1, binding = 0) buffer DataB {
8323                uint value;
8324            } data_b;
8325
8326            void main() {
8327                data_a.value = 1;
8328                data_b.value = 2;
8329            }
8330            "#
8331        );
8332        let pipeline =
8333            ComputePipeline::create(&device, ComputePipelineInfo::default(), spirv.as_slice())?;
8334        let compatible_pipeline =
8335            ComputePipeline::create(&device, ComputePipelineInfo::default(), spirv.as_slice())?;
8336        let buffer_a = Arc::new(Buffer::create(
8337            &device,
8338            BufferInfo::device_mem(4, vk::BufferUsageFlags::STORAGE_BUFFER),
8339        )?);
8340        let buffer_b = Arc::new(Buffer::create(
8341            &device,
8342            BufferInfo::device_mem(4, vk::BufferUsageFlags::STORAGE_BUFFER),
8343        )?);
8344        let descriptor_set = DescriptorSet::alloc_and_update(
8345            &pipeline,
8346            DescriptorSetInfo::builder().set(0),
8347            DescriptorSetUpdateInfo::buffer(0, &buffer_a),
8348        )?;
8349        let descriptor_set_a = DescriptorSet::alloc_and_update(
8350            &pipeline,
8351            DescriptorSetInfo::builder().set(0),
8352            DescriptorSetUpdateInfo::copy(&descriptor_set, 0, 0),
8353        )?;
8354        let descriptor_set_b = DescriptorSet::alloc_and_update(
8355            &pipeline,
8356            DescriptorSetInfo::builder().set(1),
8357            DescriptorSetUpdateInfo::buffer(0, &buffer_b),
8358        )?;
8359        drop(descriptor_set);
8360        drop(pipeline);
8361
8362        let mut graph = Graph::new();
8363        let buffer_a_node = graph.bind_resource(&buffer_a);
8364        let buffer_b_node = graph.bind_resource(&buffer_b);
8365        graph
8366            .begin_cmd()
8367            .bind_pipeline(&compatible_pipeline)
8368            .bind_descriptor_set(&descriptor_set_a)
8369            .resource_access(buffer_a_node, AccessType::ComputeShaderWrite)
8370            .shader_resource_access((1, 0), buffer_b_node, AccessType::ComputeShaderWrite)
8371            .record_cmd(|cmd| {
8372                cmd.dispatch(1, 1, 1);
8373            })
8374            .end_cmd();
8375        graph
8376            .begin_cmd()
8377            .bind_pipeline(&compatible_pipeline)
8378            .bind_descriptor_set(&descriptor_set_a)
8379            .bind_descriptor_set(&descriptor_set_b)
8380            .resource_access(buffer_a_node, AccessType::ComputeShaderWrite)
8381            .resource_access(buffer_b_node, AccessType::ComputeShaderWrite)
8382            .record_cmd(|cmd| {
8383                cmd.dispatch(1, 1, 1);
8384            })
8385            .end_cmd();
8386
8387        let mut fence = graph
8388            .finalize()
8389            .queue_submit(&mut HashPool::new(&device), 0, 0)?;
8390        fence.wait()?;
8391
8392        Ok(())
8393    }
8394
8395    fn test_device() -> Result<TestDevice, DriverError> {
8396        let guard = test_device_lock()
8397            .lock()
8398            .expect("poisoned test device lock");
8399        let device = Device::create(DeviceInfo::default())?;
8400
8401        Ok(TestDevice {
8402            _guard: guard,
8403            device: ManuallyDrop::new(device),
8404        })
8405    }
8406
8407    fn test_debug_device() -> Result<TestDevice, DriverError> {
8408        let guard = test_device_lock()
8409            .lock()
8410            .expect("poisoned test device lock");
8411        let device = Device::create(DeviceInfo::builder().debug(true).build())?;
8412
8413        Ok(TestDevice {
8414            _guard: guard,
8415            device: ManuallyDrop::new(device),
8416        })
8417    }
8418
8419    fn init_validation_test_logging() {
8420        static INIT: OnceLock<()> = OnceLock::new();
8421
8422        INIT.get_or_init(|| {
8423            unsafe {
8424                set_var("RUST_LOG", "trace");
8425                set_var("VK_GRAPH_SKIP_VALIDATION_PARK", "1");
8426            }
8427
8428            let _ = pretty_env_logger::try_init();
8429        });
8430    }
8431
8432    fn test_triangle_pipeline(device: &Device) -> Result<GraphicsPipeline, DriverError> {
8433        GraphicsPipeline::create(
8434            device,
8435            GraphicsPipelineInfo::default(),
8436            [
8437                glsl!(
8438                    r#"
8439                    #version 460 core
8440                    #pragma shader_stage(vertex)
8441
8442                    vec2 POSITIONS[3] = vec2[](
8443                        vec2(-1.0, -1.0),
8444                        vec2(3.0, -1.0),
8445                        vec2(-1.0, 3.0)
8446                    );
8447
8448                    void main() {
8449                        gl_Position = vec4(POSITIONS[gl_VertexIndex], 0.0, 1.0);
8450                    }
8451                    "#
8452                )
8453                .as_slice(),
8454                glsl!(
8455                    r#"
8456                    #version 460 core
8457                    #pragma shader_stage(fragment)
8458
8459                    layout(location = 0) out vec4 vk_Color;
8460
8461                    void main() {
8462                        vk_Color = vec4(1.0, 0.0, 0.0, 1.0);
8463                    }
8464                    "#
8465                )
8466                .as_slice(),
8467            ],
8468        )
8469    }
8470
8471    fn test_input_attachment_pipelines(
8472        device: &Device,
8473    ) -> Result<(GraphicsPipeline, GraphicsPipeline), DriverError> {
8474        let vertex = glsl!(
8475            r#"
8476            #version 460 core
8477            #pragma shader_stage(vertex)
8478
8479            vec2 POSITIONS[3] = vec2[](
8480                vec2(-1.0, -1.0),
8481                vec2(3.0, -1.0),
8482                vec2(-1.0, 3.0)
8483            );
8484
8485            void main() {
8486                gl_Position = vec4(POSITIONS[gl_VertexIndex], 0.0, 1.0);
8487            }
8488            "#
8489        );
8490        let pipeline_a = GraphicsPipeline::create(
8491            device,
8492            GraphicsPipelineInfo::default(),
8493            [
8494                vertex.as_slice(),
8495                glsl!(
8496                    kind: frag,
8497                    r#"
8498                    #version 460 core
8499                    #pragma shader_stage(fragment)
8500
8501                    layout(location = 0) out vec4 color_out;
8502
8503                    void main() {
8504                        color_out = vec4(0.25, 0.5, 0.75, 1.0);
8505                    }
8506                    "#
8507                )
8508                .as_slice(),
8509            ],
8510        )?;
8511        let pipeline_b = GraphicsPipeline::create(
8512            device,
8513            GraphicsPipelineInfo::default(),
8514            [
8515                vertex.as_slice(),
8516                glsl!(
8517                    kind: frag,
8518                    r#"
8519                    #version 460 core
8520                    #pragma shader_stage(fragment)
8521
8522                    layout(input_attachment_index = 0, binding = 0) uniform subpassInput color_in;
8523                    layout(location = 0) out vec4 color_out;
8524
8525                    void main() {
8526                        color_out = subpassLoad(color_in);
8527                    }
8528                    "#
8529                )
8530                .as_slice(),
8531            ],
8532        )?;
8533
8534        Ok((pipeline_a, pipeline_b))
8535    }
8536
8537    #[test]
8538    #[ignore = "requires Vulkan device"]
8539    fn submission_record_all_consumes_single_pass_graph() -> Result<(), DriverError> {
8540        let device = test_device()?;
8541        let mut pool = HashPool::new(&device);
8542        let mut graph = Graph::new();
8543        let buffer = graph.bind_resource(Buffer::create(
8544            &device,
8545            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
8546        )?);
8547
8548        graph.fill_buffer(buffer, 0..16, 0xdead_beef);
8549
8550        let submission = graph.finalize();
8551        let mut cmd_buf = pool.resource(CommandBufferInfo::new(0))?;
8552
8553        cmd_buf.begin(
8554            &vk::CommandBufferBeginInfo::default()
8555                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
8556        )?;
8557
8558        let recorded = submission.record(&mut pool, &mut cmd_buf, RecordSelection::All)?;
8559
8560        assert!(recorded.is_empty());
8561
8562        Ok(())
8563    }
8564
8565    #[test]
8566    #[ignore = "requires Vulkan device"]
8567    fn submission_record_nodes_consumes_requested_outputs() -> Result<(), DriverError> {
8568        let device = test_device()?;
8569        let mut pool = HashPool::new(&device);
8570        let mut graph = Graph::new();
8571        let lhs = graph.bind_resource(Buffer::create(
8572            &device,
8573            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
8574        )?);
8575        let rhs = graph.bind_resource(Buffer::create(
8576            &device,
8577            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
8578        )?);
8579
8580        graph.fill_buffer(lhs, 0..16, 1);
8581        graph.fill_buffer(rhs, 0..16, 2);
8582
8583        let nodes = [AnyNode::from(lhs), AnyNode::from(rhs)];
8584        let submission = graph.finalize();
8585        let mut cmd_buf = pool.resource(CommandBufferInfo::new(0))?;
8586
8587        cmd_buf.begin(
8588            &vk::CommandBufferBeginInfo::default()
8589                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
8590        )?;
8591
8592        let recorded =
8593            submission.record(&mut pool, &mut cmd_buf, RecordSelection::nodes(&nodes))?;
8594
8595        assert!(recorded.is_empty());
8596
8597        Ok(())
8598    }
8599
8600    #[test]
8601    #[ignore = "requires Vulkan device"]
8602    fn submission_record_can_be_reused() -> Result<(), DriverError> {
8603        let device = test_device()?;
8604        let mut pool = HashPool::new(&device);
8605        let mut graph = Graph::new();
8606        let buffer = graph.bind_resource(Buffer::create(
8607            &device,
8608            BufferInfo::device_mem(16, vk::BufferUsageFlags::TRANSFER_DST),
8609        )?);
8610
8611        graph.fill_buffer(buffer, 0..16, 0xdead_beef);
8612
8613        let submission = graph.finalize();
8614        let mut cmd_buf = pool.resource(CommandBufferInfo::new(0))?;
8615        let mut fence = Fence::create(&device, false)?;
8616
8617        cmd_buf.begin(
8618            &vk::CommandBufferBeginInfo::default()
8619                .flags(vk::CommandBufferUsageFlags::SIMULTANEOUS_USE),
8620        )?;
8621
8622        let recorded = submission.record(&mut pool, &mut cmd_buf, RecordSelection::All)?;
8623        recorded.cmd_buf.end()?;
8624        let mut replay = recorded.finish()?;
8625        replay.queue_submit(&mut fence, 0, QueueSubmitInfo::QUEUE_SUBMIT)?;
8626
8627        Ok(())
8628    }
8629
8630    #[test]
8631    #[ignore = "requires Vulkan device"]
8632    fn accel_struct_mixed_accesses_preserve_all_stage_bits() -> Result<(), DriverError> {
8633        let device = test_device()?;
8634        let mut pool = HashPool::new(&device);
8635        let mut graph = Graph::new();
8636        let accel_struct = graph.bind_resource(AccelerationStructure::create(
8637            &device,
8638            AccelerationStructureInfo::blas(1024),
8639        )?);
8640
8641        graph
8642            .begin_cmd()
8643            .debug_name("mixed accel struct accesses")
8644            .resource_access(accel_struct, AccessType::AccelerationStructureBuildRead)
8645            .resource_access(
8646                accel_struct,
8647                AccessType::RayTracingShaderReadAccelerationStructure,
8648            )
8649            .record_cmd(|_| {});
8650
8651        let submission = graph.finalize();
8652        let mut cmd_buf = pool.resource(CommandBufferInfo::new(0))?;
8653
8654        cmd_buf.begin(
8655            &vk::CommandBufferBeginInfo::default()
8656                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
8657        )?;
8658
8659        let recording = submission.record(&mut pool, &mut cmd_buf, RecordSelection::All)?;
8660        let sync_info = recording.resource(accel_struct).sync_info();
8661
8662        assert!(
8663            sync_info
8664                .stage_mask
8665                .contains(vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR),
8666            "sync info should preserve build-read stage bits"
8667        );
8668        assert!(
8669            sync_info
8670                .stage_mask
8671                .contains(vk::PipelineStageFlags::RAY_TRACING_SHADER_KHR),
8672            "sync info should preserve ray-tracing-read stage bits"
8673        );
8674        assert_eq!(
8675            sync_info.access_mask,
8676            vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR,
8677            "mixed read-only accesses should stay read-only"
8678        );
8679
8680        Ok(())
8681    }
8682
8683    #[test]
8684    #[ignore = "requires Vulkan validation layers; inspect validation output"]
8685    fn submission_external_subpass_dependency_validation_repro() -> Result<(), DriverError> {
8686        init_validation_test_logging();
8687
8688        let device = test_debug_device()?;
8689        let mut pool = HashPool::new(&device);
8690        let pipeline = test_triangle_pipeline(&device)?;
8691        let mut graph = Graph::new();
8692        let image = graph.bind_resource(Image::create(
8693            &device,
8694            ImageInfo::image_2d(
8695                4,
8696                4,
8697                vk::Format::R8G8B8A8_UNORM,
8698                vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT,
8699            ),
8700        )?);
8701
8702        // Seed external_access_history with a transfer write so the later render pass relies on
8703        // the synthesized EXTERNAL -> first subpass dependency
8704        graph.clear_color_image(image, [0.0, 0.0, 0.0, 1.0]);
8705        graph
8706            .begin_cmd()
8707            .debug_name("validation repro render pass")
8708            .bind_pipeline(&pipeline)
8709            .color_attachment_image(0, image, LoadOp::Load, StoreOp::Store)
8710            .record_cmd(|cmd| {
8711                cmd.draw(3, 1, 0, 0);
8712            });
8713
8714        let submission = graph.finalize();
8715        let mut cmd_buf = pool.resource(CommandBufferInfo::new(0))?;
8716
8717        cmd_buf.begin(
8718            &vk::CommandBufferBeginInfo::default()
8719                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
8720        )?;
8721
8722        let recorded = submission.record(&mut pool, &mut cmd_buf, RecordSelection::All)?;
8723        recorded.cmd_buf.end()?;
8724
8725        let mut fence = Fence::create(&device, false)?;
8726        let mut recorded = recorded.finish()?;
8727
8728        recorded.queue_submit(&mut fence, 0, QueueSubmitInfo::QUEUE_SUBMIT)?;
8729        fence.wait()?;
8730
8731        Ok(())
8732    }
8733
8734    #[test]
8735    #[ignore = "requires Vulkan device"]
8736    fn external_subpass_dependency_targets_first_subpass_consumer() -> Result<(), DriverError> {
8737        let device = test_device()?;
8738        let pipeline = test_triangle_pipeline(&device)?;
8739        let mut graph = Graph::new();
8740        let image = graph.bind_resource(Image::create(
8741            &device,
8742            ImageInfo::image_2d(
8743                4,
8744                4,
8745                vk::Format::R8G8B8A8_UNORM,
8746                vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::COLOR_ATTACHMENT,
8747            ),
8748        )?);
8749
8750        graph.clear_color_image(image, [0.0, 0.0, 0.0, 1.0]);
8751        graph
8752            .begin_cmd()
8753            .debug_name("dependency inspection render pass")
8754            .bind_pipeline(&pipeline)
8755            .color_attachment_image(0, image, LoadOp::Load, StoreOp::Store)
8756            .record_cmd(|cmd| {
8757                cmd.draw(3, 1, 0, 0);
8758            });
8759
8760        let submission = graph.finalize();
8761        let mut external_access_history =
8762            ExternalRenderPassAccessHistory::new(submission.graph.resources.len());
8763        external_access_history.record_cmd(&submission.graph.cmds[0]);
8764
8765        let dependencies = Submission::build_subpass_dependencies(
8766            &submission.graph.cmds[1],
8767            &external_access_history,
8768        );
8769        let dep = dependencies
8770            .iter()
8771            .find(|dep| dep.src_subpass == vk::SUBPASS_EXTERNAL && dep.dst_subpass == 0)
8772            .expect("missing external -> first subpass dependency");
8773
8774        assert_eq!(
8775            dep.dst_stage_mask,
8776            vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
8777            "destination stage should describe the first subpass consumer"
8778        );
8779        assert_eq!(
8780            dep.dst_access_mask,
8781            vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
8782            "destination access should describe the first subpass attachment access"
8783        );
8784
8785        Ok(())
8786    }
8787
8788    #[test]
8789    #[ignore = "requires Vulkan device"]
8790    fn color_input_attachment_dependencies_use_fragment_shader_input_reads()
8791    -> Result<(), DriverError> {
8792        let device = test_device()?;
8793        let (pipeline_a, pipeline_b) = test_input_attachment_pipelines(&device)?;
8794        let mut graph = Graph::new();
8795        let image = graph.bind_resource(Image::create(
8796            &device,
8797            ImageInfo::image_2d(
8798                4,
8799                4,
8800                vk::Format::R8G8B8A8_UNORM,
8801                vk::ImageUsageFlags::COLOR_ATTACHMENT
8802                    | vk::ImageUsageFlags::INPUT_ATTACHMENT
8803                    | vk::ImageUsageFlags::TRANSFER_DST,
8804            ),
8805        )?);
8806
8807        graph
8808            .begin_cmd()
8809            .debug_name("input attachment writer")
8810            .bind_pipeline(&pipeline_a)
8811            .color_attachment_image(0, image, LoadOp::CLEAR_BLACK_ALPHA_ZERO, StoreOp::Store)
8812            .record_cmd(|cmd| {
8813                cmd.draw(3, 1, 0, 0);
8814            });
8815        graph
8816            .begin_cmd()
8817            .debug_name("input attachment reader")
8818            .bind_pipeline(&pipeline_b)
8819            .color_attachment_image(0, image, LoadOp::DontCare, StoreOp::Store)
8820            .record_cmd(|cmd| {
8821                cmd.draw(3, 1, 0, 0);
8822            });
8823
8824        let mut submission = graph.finalize();
8825        let mut schedule = vec![0, 1];
8826        submission.merge_scheduled_cmds(&mut schedule);
8827
8828        let dependencies = Submission::build_subpass_dependencies(
8829            &submission.graph.cmds[0],
8830            &ExternalRenderPassAccessHistory::new(submission.graph.resources.len()),
8831        );
8832        let dep = dependencies
8833            .iter()
8834            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
8835            .expect("missing subpass dependency for input attachment read");
8836
8837        assert!(
8838            dep.src_stage_mask
8839                .contains(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT),
8840            "source stage should include color attachment output"
8841        );
8842        assert!(
8843            dep.src_access_mask
8844                .contains(vk::AccessFlags::COLOR_ATTACHMENT_WRITE),
8845            "source access should include color attachment write"
8846        );
8847        assert!(
8848            dep.dst_stage_mask
8849                .contains(vk::PipelineStageFlags::FRAGMENT_SHADER),
8850            "destination stage should include fragment shader input attachment reads"
8851        );
8852        assert!(
8853            dep.dst_access_mask
8854                .contains(vk::AccessFlags::INPUT_ATTACHMENT_READ),
8855            "destination access should include input attachment reads"
8856        );
8857
8858        Ok(())
8859    }
8860
8861    #[test]
8862    #[ignore = "requires Vulkan device"]
8863    fn color_attachment_load_dependencies_avoid_invalid_stage_access_pairs()
8864    -> Result<(), DriverError> {
8865        let device = test_device()?;
8866        let pipeline = test_triangle_pipeline(&device)?;
8867        let mut graph = Graph::new();
8868        let image = graph.bind_resource(Image::create(
8869            &device,
8870            ImageInfo::image_2d(
8871                4,
8872                4,
8873                vk::Format::R8G8B8A8_UNORM,
8874                vk::ImageUsageFlags::COLOR_ATTACHMENT,
8875            ),
8876        )?);
8877
8878        graph
8879            .begin_cmd()
8880            .debug_name("color attachment writer")
8881            .bind_pipeline(&pipeline)
8882            .color_attachment_image(0, image, LoadOp::CLEAR_BLACK_ALPHA_ZERO, StoreOp::Store)
8883            .record_cmd(|cmd| {
8884                cmd.draw(3, 1, 0, 0);
8885            });
8886        graph
8887            .begin_cmd()
8888            .debug_name("color attachment reader")
8889            .bind_pipeline(&pipeline)
8890            .color_attachment_image(0, image, LoadOp::Load, StoreOp::Store)
8891            .record_cmd(|cmd| {
8892                cmd.draw(3, 1, 0, 0);
8893            });
8894
8895        let mut submission = graph.finalize();
8896        let mut schedule = vec![0, 1];
8897        submission.merge_scheduled_cmds(&mut schedule);
8898
8899        let dependencies = Submission::build_subpass_dependencies(
8900            &submission.graph.cmds[0],
8901            &ExternalRenderPassAccessHistory::new(submission.graph.resources.len()),
8902        );
8903        let dep = dependencies
8904            .iter()
8905            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
8906            .expect("missing subpass dependency for color attachment load");
8907
8908        assert!(
8909            dep.src_access_mask
8910                .contains(vk::AccessFlags::COLOR_ATTACHMENT_WRITE),
8911            "source access should include color attachment writes"
8912        );
8913        assert!(
8914            dep.dst_access_mask
8915                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ),
8916            "destination access should include color attachment reads"
8917        );
8918        assert_no_invalid_attachment_stage_access_pairs(dep);
8919        assert_attachment_read_stage_mappings(dep);
8920
8921        Ok(())
8922    }
8923
8924    #[test]
8925    #[ignore = "requires Vulkan device"]
8926    fn color_attachment_read_dependencies_avoid_invalid_stage_access_pairs()
8927    -> Result<(), DriverError> {
8928        let device = test_device()?;
8929        let pipeline = test_triangle_pipeline(&device)?;
8930        let mut graph = Graph::new();
8931        let image = graph.bind_resource(Image::create(
8932            &device,
8933            ImageInfo::image_2d(
8934                4,
8935                4,
8936                vk::Format::R8G8B8A8_UNORM,
8937                vk::ImageUsageFlags::COLOR_ATTACHMENT,
8938            ),
8939        )?);
8940
8941        graph
8942            .begin_cmd()
8943            .debug_name("color attachment first reader")
8944            .bind_pipeline(&pipeline)
8945            .color_attachment_image(0, image, LoadOp::Load, StoreOp::DontCare)
8946            .record_cmd(|cmd| {
8947                cmd.draw(3, 1, 0, 0);
8948            });
8949        graph
8950            .begin_cmd()
8951            .debug_name("color attachment second reader")
8952            .bind_pipeline(&pipeline)
8953            .color_attachment_image(0, image, LoadOp::Load, StoreOp::DontCare)
8954            .record_cmd(|cmd| {
8955                cmd.draw(3, 1, 0, 0);
8956            });
8957
8958        let mut submission = graph.finalize();
8959        let mut schedule = vec![0, 1];
8960        submission.merge_scheduled_cmds(&mut schedule);
8961
8962        let dependencies = Submission::build_subpass_dependencies(
8963            &submission.graph.cmds[0],
8964            &ExternalRenderPassAccessHistory::new(submission.graph.resources.len()),
8965        );
8966        let dep = dependencies
8967            .iter()
8968            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
8969            .expect("missing subpass dependency for color attachment read");
8970
8971        assert!(
8972            dep.src_access_mask
8973                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ),
8974            "source access should include color attachment reads"
8975        );
8976        assert!(
8977            dep.dst_access_mask
8978                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ),
8979            "destination access should include color attachment reads"
8980        );
8981        assert_no_invalid_attachment_stage_access_pairs(dep);
8982        assert_attachment_read_stage_mappings(dep);
8983
8984        Ok(())
8985    }
8986
8987    #[test]
8988    #[ignore = "requires Vulkan device"]
8989    fn color_attachment_read_to_write_dependencies_avoid_invalid_stage_access_pairs()
8990    -> Result<(), DriverError> {
8991        let device = test_device()?;
8992        let pipeline = test_triangle_pipeline(&device)?;
8993        let mut graph = Graph::new();
8994        let image = graph.bind_resource(Image::create(
8995            &device,
8996            ImageInfo::image_2d(
8997                4,
8998                4,
8999                vk::Format::R8G8B8A8_UNORM,
9000                vk::ImageUsageFlags::COLOR_ATTACHMENT,
9001            ),
9002        )?);
9003
9004        graph
9005            .begin_cmd()
9006            .debug_name("color attachment reader")
9007            .bind_pipeline(&pipeline)
9008            .color_attachment_image(0, image, LoadOp::Load, StoreOp::DontCare)
9009            .record_cmd(|cmd| {
9010                cmd.draw(3, 1, 0, 0);
9011            });
9012        graph
9013            .begin_cmd()
9014            .debug_name("color attachment writer")
9015            .bind_pipeline(&pipeline)
9016            .color_attachment_image(0, image, LoadOp::CLEAR_BLACK_ALPHA_ZERO, StoreOp::Store)
9017            .record_cmd(|cmd| {
9018                cmd.draw(3, 1, 0, 0);
9019            });
9020
9021        let mut submission = graph.finalize();
9022        let mut schedule = vec![0, 1];
9023        submission.merge_scheduled_cmds(&mut schedule);
9024
9025        let dependencies = Submission::build_subpass_dependencies(
9026            &submission.graph.cmds[0],
9027            &ExternalRenderPassAccessHistory::new(submission.graph.resources.len()),
9028        );
9029        let dep = dependencies
9030            .iter()
9031            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9032            .expect("missing subpass dependency for color attachment read to write");
9033
9034        assert!(
9035            dep.src_access_mask
9036                .contains(vk::AccessFlags::COLOR_ATTACHMENT_READ),
9037            "source access should include color attachment reads"
9038        );
9039        assert!(
9040            dep.dst_access_mask
9041                .contains(vk::AccessFlags::COLOR_ATTACHMENT_WRITE),
9042            "destination access should include color attachment writes"
9043        );
9044        assert_no_invalid_attachment_stage_access_pairs(dep);
9045        assert_attachment_read_stage_mappings(dep);
9046
9047        Ok(())
9048    }
9049
9050    #[test]
9051    #[ignore = "requires Vulkan device"]
9052    fn depth_attachment_load_dependencies_avoid_invalid_stage_access_pairs()
9053    -> Result<(), DriverError> {
9054        let device = test_device()?;
9055        let pipeline = test_triangle_pipeline(&device)?;
9056        let mut graph = Graph::new();
9057        let image = graph.bind_resource(Image::create(
9058            &device,
9059            ImageInfo::image_2d(
9060                4,
9061                4,
9062                vk::Format::D32_SFLOAT,
9063                vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
9064            ),
9065        )?);
9066
9067        graph
9068            .begin_cmd()
9069            .debug_name("depth attachment first reader")
9070            .bind_pipeline(&pipeline)
9071            .depth_stencil_attachment_image(image, LoadOp::Load, StoreOp::Store)
9072            .record_cmd(|cmd| {
9073                cmd.draw(3, 1, 0, 0);
9074            });
9075        graph
9076            .begin_cmd()
9077            .debug_name("depth attachment second reader")
9078            .bind_pipeline(&pipeline)
9079            .depth_stencil_attachment_image(image, LoadOp::Load, StoreOp::Store)
9080            .record_cmd(|cmd| {
9081                cmd.draw(3, 1, 0, 0);
9082            });
9083
9084        let mut submission = graph.finalize();
9085        let mut schedule = vec![0, 1];
9086        submission.merge_scheduled_cmds(&mut schedule);
9087
9088        let dependencies = Submission::build_subpass_dependencies(
9089            &submission.graph.cmds[0],
9090            &ExternalRenderPassAccessHistory::new(submission.graph.resources.len()),
9091        );
9092        let dep = dependencies
9093            .iter()
9094            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9095            .expect("missing subpass dependency for depth attachment load");
9096
9097        assert!(
9098            dep.src_access_mask
9099                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ),
9100            "source access should include depth/stencil attachment reads"
9101        );
9102        assert!(
9103            dep.dst_access_mask
9104                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ),
9105            "destination access should include depth/stencil attachment reads"
9106        );
9107        assert_no_invalid_attachment_stage_access_pairs(dep);
9108        assert_attachment_read_stage_mappings(dep);
9109
9110        Ok(())
9111    }
9112
9113    #[test]
9114    fn depth_attachment_read_to_write_dependency_includes_late_read_stage() {
9115        let dependencies = depth_attachment_dependencies(
9116            LoadOp::Load,
9117            StoreOp::DontCare,
9118            LoadOp::CLEAR_ONE_STENCIL_ZERO,
9119            StoreOp::Store,
9120        );
9121        let dep = dependencies
9122            .iter()
9123            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9124            .expect("missing subpass dependency for depth attachment read to write");
9125
9126        assert!(
9127            dep.src_access_mask
9128                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ),
9129            "source access should include depth/stencil attachment reads"
9130        );
9131        assert!(
9132            dep.dst_access_mask
9133                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE),
9134            "destination access should include depth/stencil attachment writes"
9135        );
9136        assert!(
9137            dep.src_stage_mask
9138                .contains(vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS),
9139            "source stage should include early fragment tests"
9140        );
9141        assert!(
9142            dep.src_stage_mask
9143                .contains(vk::PipelineStageFlags::LATE_FRAGMENT_TESTS),
9144            "source stage should include late fragment tests"
9145        );
9146    }
9147
9148    #[test]
9149    fn depth_attachment_write_to_write_dependency_uses_write_access() {
9150        let dependencies = depth_attachment_dependencies(
9151            LoadOp::CLEAR_ONE_STENCIL_ZERO,
9152            StoreOp::Store,
9153            LoadOp::CLEAR_ONE_STENCIL_ZERO,
9154            StoreOp::Store,
9155        );
9156        let dep = dependencies
9157            .iter()
9158            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9159            .expect("missing subpass dependency for depth attachment write to write");
9160
9161        assert!(
9162            dep.src_access_mask
9163                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE),
9164            "source access should include depth/stencil attachment writes"
9165        );
9166        assert!(
9167            !dep.src_access_mask
9168                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ),
9169            "source access should not include depth/stencil attachment reads"
9170        );
9171        assert!(
9172            dep.dst_access_mask
9173                .contains(vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE),
9174            "destination access should include depth/stencil attachment writes"
9175        );
9176    }
9177
9178    #[test]
9179    fn subpass_stage_mask_clamps_non_graphics_stages() {
9180        assert_eq!(
9181            Submission::subpass_stage_mask(vk::PipelineStageFlags::RAY_TRACING_SHADER_KHR),
9182            vk::PipelineStageFlags::ALL_GRAPHICS,
9183        );
9184        assert_eq!(
9185            Submission::subpass_stage_mask(
9186                vk::PipelineStageFlags::FRAGMENT_SHADER
9187                    | vk::PipelineStageFlags::RAY_TRACING_SHADER_KHR,
9188            ),
9189            vk::PipelineStageFlags::FRAGMENT_SHADER,
9190        );
9191    }
9192
9193    #[test]
9194    fn subpass_dependency_matches_all_graphics_source_stage() {
9195        let dependencies = subpass_dependencies_for_accesses(
9196            AccessType::AnyShaderWrite,
9197            AccessType::FragmentShaderReadOther,
9198        );
9199        let dep = dependencies
9200            .iter()
9201            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9202            .expect("missing subpass dependency for ALL_GRAPHICS source stage");
9203
9204        assert!(
9205            dep.src_stage_mask
9206                .contains(vk::PipelineStageFlags::ALL_GRAPHICS),
9207            "source stage should include ALL_GRAPHICS"
9208        );
9209        assert!(
9210            dep.src_access_mask.contains(vk::AccessFlags::SHADER_WRITE),
9211            "source access should include shader writes"
9212        );
9213        assert!(
9214            dep.dst_stage_mask
9215                .contains(vk::PipelineStageFlags::FRAGMENT_SHADER),
9216            "destination stage should include fragment shader"
9217        );
9218        assert!(
9219            dep.dst_access_mask.contains(vk::AccessFlags::SHADER_READ),
9220            "destination access should include shader reads"
9221        );
9222    }
9223
9224    #[test]
9225    fn subpass_dependency_matches_all_graphics_destination_stage() {
9226        let dependencies = subpass_dependencies_for_accesses(
9227            AccessType::FragmentShaderWrite,
9228            AccessType::AnyShaderReadOther,
9229        );
9230        let dep = dependencies
9231            .iter()
9232            .find(|dep| dep.src_subpass == 0 && dep.dst_subpass == 1)
9233            .expect("missing subpass dependency for ALL_GRAPHICS destination stage");
9234
9235        assert!(
9236            dep.src_stage_mask
9237                .contains(vk::PipelineStageFlags::FRAGMENT_SHADER),
9238            "source stage should include fragment shader"
9239        );
9240        assert!(
9241            dep.src_access_mask.contains(vk::AccessFlags::SHADER_WRITE),
9242            "source access should include shader writes"
9243        );
9244        assert!(
9245            dep.dst_stage_mask
9246                .contains(vk::PipelineStageFlags::ALL_GRAPHICS),
9247            "destination stage should include ALL_GRAPHICS"
9248        );
9249        assert!(
9250            dep.dst_access_mask.contains(vk::AccessFlags::SHADER_READ),
9251            "destination access should include shader reads"
9252        );
9253    }
9254
9255    #[test]
9256    fn record_subpass_dependency_preserves_dst_access_for_unmatched_stages() {
9257        let mut dependencies = std::collections::BTreeMap::new();
9258        let mut current = PipelineStageAccessFlags {
9259            stage_flags: vk::PipelineStageFlags::VERTEX_SHADER
9260                | vk::PipelineStageFlags::FRAGMENT_SHADER,
9261            access_flags: vk::AccessFlags::SHADER_READ,
9262        };
9263
9264        assert!(!Submission::record_subpass_dependency(
9265            &mut dependencies,
9266            0,
9267            2,
9268            PipelineStageAccessFlags {
9269                stage_flags: vk::PipelineStageFlags::VERTEX_SHADER,
9270                access_flags: vk::AccessFlags::SHADER_READ,
9271            },
9272            current.stage_flags,
9273            &mut current,
9274        ));
9275        assert!(Submission::record_subpass_dependency(
9276            &mut dependencies,
9277            1,
9278            2,
9279            PipelineStageAccessFlags {
9280                stage_flags: vk::PipelineStageFlags::FRAGMENT_SHADER,
9281                access_flags: vk::AccessFlags::SHADER_READ,
9282            },
9283            current.stage_flags,
9284            &mut current,
9285        ));
9286
9287        let dep = dependencies
9288            .get(&(1, 2))
9289            .expect("missing dependency for later matched stage");
9290        assert!(
9291            dep.dst_access_mask.contains(vk::AccessFlags::SHADER_READ),
9292            "later matched stage should retain destination access mask"
9293        );
9294    }
9295
9296    #[test]
9297    fn record_subpass_dependency_ignores_non_overlapping_stage() {
9298        let mut dependencies = std::collections::BTreeMap::new();
9299        let mut current = PipelineStageAccessFlags {
9300            stage_flags: vk::PipelineStageFlags::FRAGMENT_SHADER,
9301            access_flags: vk::AccessFlags::SHADER_READ,
9302        };
9303
9304        assert!(!Submission::record_subpass_dependency(
9305            &mut dependencies,
9306            0,
9307            1,
9308            PipelineStageAccessFlags {
9309                stage_flags: vk::PipelineStageFlags::VERTEX_SHADER,
9310                access_flags: vk::AccessFlags::SHADER_WRITE,
9311            },
9312            current.stage_flags,
9313            &mut current,
9314        ));
9315
9316        assert!(dependencies.is_empty());
9317        assert_eq!(current.stage_flags, vk::PipelineStageFlags::FRAGMENT_SHADER);
9318        assert_eq!(current.access_flags, vk::AccessFlags::SHADER_READ);
9319    }
9320
9321    #[test]
9322    fn build_subpass_dependencies_includes_later_access_stage_bits() {
9323        let mut exec = Execution::default();
9324
9325        exec.accesses.push(
9326            0,
9327            SubresourceAccess {
9328                access: AccessType::IndexBuffer,
9329                subresource: SubresourceRange::Buffer((0..16).into()),
9330            },
9331        );
9332        exec.accesses.push(
9333            0,
9334            SubresourceAccess {
9335                access: AccessType::FragmentShaderReadOther,
9336                subresource: SubresourceRange::Buffer((0..16).into()),
9337            },
9338        );
9339
9340        let pass = CommandData {
9341            execs: vec![exec],
9342
9343            #[cfg(debug_assertions)]
9344            name: None,
9345
9346            stream_scope_id: None,
9347            tracking: Default::default(),
9348        };
9349        let dependencies =
9350            Submission::build_subpass_dependencies(&pass, &ExternalRenderPassAccessHistory::new(1));
9351        let dep = dependencies
9352            .iter()
9353            .find(|dep| dep.src_subpass == vk::SUBPASS_EXTERNAL && dep.dst_subpass == 0)
9354            .expect("missing external dependency for mixed access slice");
9355
9356        assert!(
9357            dep.dst_stage_mask
9358                .contains(vk::PipelineStageFlags::VERTEX_INPUT),
9359            "first access stage should be preserved"
9360        );
9361        assert!(
9362            dep.dst_stage_mask
9363                .contains(vk::PipelineStageFlags::FRAGMENT_SHADER),
9364            "later access stages should also contribute"
9365        );
9366    }
9367
9368    #[test]
9369    fn accel_struct_canonical_accesses_preserves_mixed_slice_accesses() {
9370        let accesses = [
9371            SubresourceAccess {
9372                access: AccessType::AccelerationStructureBuildRead,
9373                subresource: SubresourceRange::AccelerationStructure,
9374            },
9375            SubresourceAccess {
9376                access: AccessType::RayTracingShaderReadAccelerationStructure,
9377                subresource: SubresourceRange::AccelerationStructure,
9378            },
9379        ];
9380
9381        let mut scratch = Vec::new();
9382        assert_eq!(
9383            Submission::accel_struct_canonical_accesses(&accesses, &mut scratch),
9384            &[
9385                AccessType::AccelerationStructureBuildRead,
9386                AccessType::RayTracingShaderReadAccelerationStructure,
9387            ],
9388            "mixed acceleration-structure slices should preserve all accesses for next-state tracking"
9389        );
9390    }
9391}