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