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