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