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