Skip to main content

vk_graph/
submission.rs

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