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