1use {
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 } 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 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
517fn 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
637fn 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 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#[derive(Clone, Copy, Debug)]
1182pub enum QueueSubmitInfo<'a> {
1183 QueueSubmit {
1187 waits: &'a [SemaphoreSubmitInfo],
1189
1190 signals: &'a [SemaphoreSubmitInfo],
1192 },
1193
1194 QueueSubmit2 {
1198 waits: &'a [SemaphoreSubmit2Info],
1200
1201 signals: &'a [SemaphoreSubmit2Info],
1203 },
1204}
1205
1206impl QueueSubmitInfo<'static> {
1207 pub const QUEUE_SUBMIT: Self = Self::QueueSubmit {
1209 waits: &[],
1210 signals: &[],
1211 };
1212
1213 pub const QUEUE_SUBMIT2: Self = Self::QueueSubmit2 {
1215 waits: &[],
1216 signals: &[],
1217 };
1218}
1219
1220impl<'a> QueueSubmitInfo<'a> {
1221 pub fn queue_submit(
1223 waits: &'a [SemaphoreSubmitInfo],
1224 signals: &'a [SemaphoreSubmitInfo],
1225 ) -> Self {
1226 Self::QueueSubmit { waits, signals }
1227 }
1228
1229 pub fn queue_submit2(
1231 waits: &'a [SemaphoreSubmit2Info],
1232 signals: &'a [SemaphoreSubmit2Info],
1233 ) -> Self {
1234 Self::QueueSubmit2 { waits, signals }
1235 }
1236}
1237
1238impl<'a> From<(&'a [SemaphoreSubmitInfo], &'a [SemaphoreSubmitInfo])> for QueueSubmitInfo<'a> {
1239 fn from((waits, signals): (&'a [SemaphoreSubmitInfo], &'a [SemaphoreSubmitInfo])) -> Self {
1240 Self::QueueSubmit { waits, signals }
1241 }
1242}
1243
1244impl<'a> From<(&'a [SemaphoreSubmit2Info], &'a [SemaphoreSubmit2Info])> for QueueSubmitInfo<'a> {
1245 fn from((waits, signals): (&'a [SemaphoreSubmit2Info], &'a [SemaphoreSubmit2Info])) -> Self {
1246 Self::QueueSubmit2 { waits, signals }
1247 }
1248}
1249
1250#[derive(Clone, Copy, Debug)]
1252pub enum RecordSelection<'a> {
1253 All,
1255
1256 Dependencies(AnyNode),
1258
1259 Node(AnyNode),
1261
1262 Nodes(&'a [AnyNode]),
1266}
1267
1268impl<'a> RecordSelection<'a> {
1269 pub fn dependencies(node: impl Into<AnyNode>) -> Self {
1272 Self::Dependencies(node.into())
1273 }
1274
1275 pub fn node(node: impl Into<AnyNode>) -> Self {
1277 Self::Node(node.into())
1278 }
1279
1280 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#[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 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#[derive(Debug)]
1554#[read_only::cast]
1555pub struct Recording<'p, P, Cb> {
1556 #[readonly]
1560 pub cmd_buf: Cb,
1561
1562 #[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 pub fn is_empty(&self) -> bool {
1579 self.submission.is_empty()
1580 }
1581
1582 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 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 #[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 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 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 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#[derive(Clone, Copy, Debug, Default)]
1905pub struct SemaphoreSubmitInfo {
1906 pub semaphore: vk::Semaphore,
1910
1911 pub stage_mask: vk::PipelineStageFlags2,
1915
1916 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#[derive(Clone, Copy, Debug, Default)]
1939pub struct SemaphoreSubmit2Info {
1940 pub semaphore: vk::Semaphore,
1944
1945 pub stage_mask: vk::PipelineStageFlags2,
1949
1950 pub value: u64,
1952
1953 pub device_index: u32,
1955}
1956
1957#[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 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 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 for lhs in lhs.execs.iter().rev() {
2252 if is_multiview(lhs.view_mask) != is_multiview(rhs.view_mask) {
2254 trace!(" incompatible multiview");
2255
2256 return false;
2257 }
2258
2259 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 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 if common_color_attachment || common_depth_attachment {
2297 trace!(" merging due to common image");
2298
2299 return true;
2300 }
2301
2302 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 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 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 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 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 if info.is_empty() {
2836 return Ok(None);
2837 }
2838
2839 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 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 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 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 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 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 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 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 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 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 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 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 for (other_idx, other) in pass.execs[0..exec_idx].iter().enumerate() {
3332 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 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 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 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 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 #[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 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 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 {
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 schedule.truncate(self.graph.cmds.len());
3753
3754 for (idx, cmd_idx) in schedule.iter_mut().enumerate() {
3755 *cmd_idx = idx;
3756 }
3757
3758 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 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 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 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 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 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 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 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 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) = ×tamp_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) = ×tamp_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 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) = ×tamp_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 debug_assert!(
4957 schedule.cmds.windows(2).all(|w| w[0] <= w[1]),
4958 "Unsorted schedule"
4959 );
4960
4961 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 ×tamp_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 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 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 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 let first_exec = pass.expect_first_exec();
5105
5106 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 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 #[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 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 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 #[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 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 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 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 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 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#[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 pub fn has_results(&self) -> bool {
6023 self.inner
6024 .lock()
6025 .expect("timestamp query pool poisoned")
6026 .got_results
6027 }
6028
6029 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 #[derive(Clone, Copy, Debug)]
6070 pub struct ReorderBenchSpec {
6071 pub cmd_count: usize,
6073
6074 pub resource_count: usize,
6076
6077 pub short_lived_uses: usize,
6079
6080 pub long_lived_resource_count: usize,
6082
6083 pub long_lived_uses: usize,
6085 }
6086
6087 pub struct ReorderBenchHarness {
6089 schedule: Schedule,
6090 original_cmds: Vec<usize>,
6091 end_cmd_idx: usize,
6092 }
6093
6094 impl ReorderBenchHarness {
6095 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 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 pub fn cmd_count(&self) -> usize {
6227 self.end_cmd_idx
6228 }
6229
6230 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 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 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 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 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 &[&[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 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}