1use super::{
4 Attachments, BarrierIndex, ExecutableTaskGraph, Instruction, NodeIndex, NodeInner,
5 RenderPassIndex, ResourceAccess, ResourceAccesses, SemaphoreIndex, Submission, TaskGraph,
6};
7use crate::{linear_map::LinearMap, resource::Flight, Id, QueueFamilyType};
8use ash::vk;
9use smallvec::{smallvec, SmallVec};
10use std::{cell::RefCell, cmp, error::Error, fmt, ops::Range, sync::Arc};
11use vulkano::{
12 device::{Device, DeviceOwned, Queue, QueueFlags},
13 format::Format,
14 image::{sampler::ComponentMapping, Image, ImageLayout, ImageUsage},
15 render_pass::{
16 AttachmentDescription, AttachmentLoadOp, AttachmentReference, AttachmentStoreOp,
17 Framebuffer, RenderPass, RenderPassCreateInfo, SubpassDependency, SubpassDescription,
18 },
19 swapchain::Swapchain,
20 sync::{semaphore::Semaphore, AccessFlags, DependencyFlags, PipelineStages},
21 Validated, VulkanError,
22};
23
24impl<W: ?Sized> TaskGraph<W> {
25 pub unsafe fn compile(
53 mut self,
54 compile_info: &CompileInfo<'_>,
55 ) -> Result<ExecutableTaskGraph<W>, CompileError<W>> {
56 let &CompileInfo {
57 queues,
58 present_queue,
59 flight_id,
60 _ne: _,
61 } = compile_info;
62
63 assert_ne!(queues.len(), 0, "expected to be given at least one queue");
64
65 let device = &self.device().clone();
66
67 for queue in queues {
68 assert_eq!(queue.device(), device);
69 assert_eq!(
70 queues
71 .iter()
72 .filter(|q| q.queue_family_index() == queue.queue_family_index())
73 .count(),
74 1,
75 "expected each queue in `compile_info.queues` to be from a unique queue family",
76 );
77 }
78
79 if let Some(present_queue) = &present_queue {
80 assert_eq!(present_queue.device(), device);
81 }
82
83 if !self.is_weakly_connected() {
84 return Err(CompileError::new(self, CompileErrorKind::Unconnected));
85 }
86
87 let topological_order = match self.topological_sort() {
88 Ok(topological_order) => topological_order,
89 Err(kind) => return Err(CompileError::new(self, kind)),
90 };
91 unsafe { self.dependency_levels(&topological_order) };
92 let queue_family_indices =
93 match unsafe { self.queue_family_indices(device, queues, &topological_order) } {
94 Ok(queue_family_indices) => queue_family_indices,
95 Err(kind) => return Err(CompileError::new(self, kind)),
96 };
97 let mut queues_by_queue_family_index: SmallVec<[_; 8]> =
98 smallvec![None; *queue_family_indices.iter().max().unwrap() as usize + 1];
99
100 for &queue in queues {
101 if let Some(x) =
102 queues_by_queue_family_index.get_mut(queue.queue_family_index() as usize)
103 {
104 *x = Some(queue);
105 }
106 }
107
108 let (
109 IntermediateRepresentationBuilder {
110 prev_accesses: last_accesses,
111 submissions,
112 nodes,
113 semaphore_count,
114 render_passes,
115 pre_present_queue_family_ownership_transfers,
116 ..
117 },
118 last_swapchain_accesses,
119 ) = match unsafe { self.lower(present_queue, &topological_order) } {
120 Ok(x) => x,
121 Err(kind) => return Err(CompileError::new(self, kind)),
122 };
123
124 let mut builder = FinalRepresentationBuilder::new(present_queue);
125 let mut prev_submission_end = 0;
126 let mut submission_index = 0;
127
128 while prev_submission_end < topological_order.len() {
129 let submission_state = &submissions[submission_index];
130 builder.initial_pipeline_barrier(&submission_state.initial_barriers);
131
132 for (i, &node_index) in
133 (prev_submission_end..).zip(&topological_order[prev_submission_end..])
134 {
135 let node = unsafe { self.nodes.node_unchecked_mut(node_index) };
136 let NodeInner::Task(task_node) = &mut node.inner else {
137 unreachable!();
138 };
139 let queue_family_index = task_node.queue_family_index;
140 let node_state = &nodes[node_index as usize];
141
142 for &(swapchain_id, stage_mask) in &node_state.wait_acquire {
143 builder.wait_acquire(swapchain_id, stage_mask);
144 }
145
146 for &semaphore_index in &node_state.wait_semaphores {
147 builder.wait_semaphore(semaphore_index);
148 }
149
150 builder.pipeline_barrier(&node_state.start_barriers);
151
152 if let Some(subpass) = node_state.subpass {
153 let render_pass_state = &render_passes[subpass.render_pass_index];
154
155 if node_index == render_pass_state.first_node_index {
156 builder.begin_render_pass(subpass.render_pass_index);
157 }
158
159 builder.next_subpass(subpass.subpass_index);
160
161 if !node_state.clear_attachments.is_empty() {
162 builder.clear_attachments(
163 node_index,
164 subpass.render_pass_index,
165 &node_state.clear_attachments,
166 );
167 }
168 }
169
170 builder.execute_task(node_index);
171
172 if let Some(subpass) = node_state.subpass {
173 let render_pass_state = &render_passes[subpass.render_pass_index];
174
175 if node_index == render_pass_state.last_node_index {
176 builder.end_render_pass();
177 }
178 }
179
180 builder.pipeline_barrier(&node_state.end_barriers);
181
182 for &semaphore_index in &node_state.signal_semaphores {
183 builder.signal_semaphore(semaphore_index);
184 }
185
186 for &(swapchain_id, stage_mask) in &node_state.signal_pre_present {
187 builder.signal_pre_present(swapchain_id, stage_mask);
188 }
189
190 for &(swapchain_id, stage_mask) in &node_state.signal_present {
191 builder.signal_present(swapchain_id, stage_mask);
192 }
193
194 let should_submit = if let Some(&next_node_index) = topological_order.get(i + 1) {
195 let next_node = unsafe { self.nodes.node_unchecked(next_node_index) };
196 let NodeInner::Task(next_task_node) = &next_node.inner else {
197 unreachable!();
198 };
199
200 next_task_node.queue_family_index != queue_family_index
201 } else {
202 true
203 };
204
205 if builder.should_flush_submit || should_submit {
206 builder.flush_submit();
207 }
208
209 if should_submit {
210 let queue = queues_by_queue_family_index[queue_family_index as usize]
211 .unwrap()
212 .clone();
213 builder.submit(queue);
214 prev_submission_end = i + 1;
215 submission_index += 1;
216 break;
217 }
218 }
219 }
220
221 if !pre_present_queue_family_ownership_transfers.is_empty() {
222 for swapchain_id in pre_present_queue_family_ownership_transfers {
223 builder.pre_present_acquire_queue_family_ownership(&last_accesses, swapchain_id);
224 }
225
226 builder.flush_submit();
227 builder.submit(present_queue.unwrap().clone());
228 }
229
230 let render_passes = match render_passes
231 .into_iter()
232 .map(|render_pass_state| create_render_pass(&self.resources, render_pass_state))
233 .collect::<Result<Vec<_>, _>>()
234 {
235 Ok(render_passes) => render_passes,
236 Err(kind) => return Err(CompileError::new(self, kind)),
237 };
238
239 if !render_passes.is_empty() {
240 for (id, node) in self.nodes.nodes_mut() {
241 let NodeInner::Task(task_node) = &mut node.inner else {
242 unreachable!();
243 };
244
245 if let Some(subpass) = nodes[id.index() as usize].subpass {
246 let render_pass = &render_passes[subpass.render_pass_index].render_pass;
247 task_node.subpass = Some(
248 vulkano::render_pass::Subpass::from(
249 render_pass.clone(),
250 subpass.subpass_index as u32,
251 )
252 .unwrap(),
253 );
254 }
255 }
256 }
257
258 let semaphores = match (0..semaphore_count)
259 .map(|_| {
260 unsafe { Semaphore::new_unchecked(device.clone(), Default::default()) }
262 .map(Arc::new)
263 })
264 .collect::<Result<_, _>>()
265 {
266 Ok(semaphores) => semaphores,
267 Err(err) => return Err(CompileError::new(self, CompileErrorKind::VulkanError(err))),
268 };
269
270 let swapchains = last_swapchain_accesses.keys().copied().collect();
271
272 Ok(ExecutableTaskGraph {
273 graph: self,
274 flight_id,
275 instructions: builder.instructions,
276 submissions: builder.submissions,
277 barriers: builder.barriers,
278 render_passes: RefCell::new(render_passes),
279 clear_attachments: builder.clear_attachments,
280 semaphores: RefCell::new(semaphores),
281 swapchains,
282 present_queue: present_queue.cloned(),
283 last_accesses,
284 })
285 }
286
287 fn is_weakly_connected(&self) -> bool {
296 unsafe fn dfs<W: ?Sized>(
297 graph: &TaskGraph<W>,
298 node_index: NodeIndex,
299 visited: &mut [bool],
300 visited_count: &mut u32,
301 ) {
302 let is_visited = &mut visited[node_index as usize];
303
304 if *is_visited {
305 return;
306 }
307
308 *is_visited = true;
309 *visited_count += 1;
310
311 let node = unsafe { graph.nodes.node_unchecked(node_index) };
312
313 for &node_index in node.in_edges.iter().chain(&node.out_edges) {
314 unsafe { dfs(graph, node_index, visited, visited_count) };
315 }
316 }
317
318 let mut visited = vec![false; self.nodes.capacity() as usize];
319 let mut visited_count = 0;
320
321 if let Some((id, _)) = self.nodes.nodes().next() {
322 unsafe { dfs(self, id.index(), &mut visited, &mut visited_count) };
323 }
324
325 visited_count == self.nodes.len()
326 }
327
328 fn topological_sort(&self) -> Result<Vec<NodeIndex>, CompileErrorKind> {
333 type NodeState = u8;
334
335 const VISITED_BIT: NodeState = 1 << 0;
336 const ON_STACK_BIT: NodeState = 1 << 1;
337
338 unsafe fn dfs<W: ?Sized>(
339 graph: &TaskGraph<W>,
340 node_index: NodeIndex,
341 state: &mut [NodeState],
342 output: &mut [NodeIndex],
343 mut output_index: u32,
344 ) -> Result<u32, CompileErrorKind> {
345 let node_state = &mut state[node_index as usize];
346
347 if *node_state == VISITED_BIT {
348 return Ok(output_index);
349 }
350
351 if *node_state == ON_STACK_BIT {
352 return Err(CompileErrorKind::Cycle);
353 }
354
355 *node_state = ON_STACK_BIT;
356
357 let node = unsafe { graph.nodes.node_unchecked(node_index) };
358
359 for &node_index in &node.out_edges {
360 output_index = unsafe { dfs(graph, node_index, state, output, output_index) }?;
361 }
362
363 state[node_index as usize] = VISITED_BIT;
364 output[output_index as usize] = node_index;
365
366 Ok(output_index.wrapping_sub(1))
367 }
368
369 let mut state = vec![0; self.nodes.capacity() as usize];
370 let mut output = vec![0; self.nodes.len() as usize];
371 let mut output_index = self.nodes.len().wrapping_sub(1);
372
373 for (id, _) in self.nodes.nodes() {
374 output_index = unsafe { dfs(self, id.index(), &mut state, &mut output, output_index) }?;
375 }
376
377 debug_assert_eq!(output_index, u32::MAX);
378
379 Ok(output)
380 }
381
382 unsafe fn dependency_levels(&mut self, topological_order: &[NodeIndex]) -> Vec<Vec<NodeIndex>> {
389 let mut distances = vec![0; self.nodes.capacity() as usize];
390 let mut max_level = 0;
391
392 for &node_index in topological_order {
393 let node = unsafe { self.nodes.node_unchecked(node_index) };
394
395 for &out_node_index in &node.out_edges {
396 let new_distance = distances[node_index as usize] + 1;
397
398 if distances[out_node_index as usize] < new_distance {
399 distances[out_node_index as usize] = new_distance;
400 max_level = cmp::max(max_level, new_distance);
401 }
402 }
403 }
404
405 let mut levels = vec![Vec::new(); max_level as usize + 1];
406
407 for (id, node) in self.nodes.nodes_mut() {
408 let NodeInner::Task(task_node) = &mut node.inner else {
409 unreachable!();
410 };
411
412 let level_index = distances[id.index() as usize];
413 levels[level_index as usize].push(id.index());
414 task_node.dependency_level_index = level_index;
415 }
416
417 levels
418 }
419
420 unsafe fn queue_family_indices(
423 &mut self,
424 device: &Device,
425 queues: &[&Arc<Queue>],
426 topological_order: &[NodeIndex],
427 ) -> Result<SmallVec<[u32; 3]>, CompileErrorKind> {
428 let queue_family_properties = device.physical_device().queue_family_properties();
429 let graphics_queue_family_index = queues
430 .iter()
431 .find(|q| {
432 queue_family_properties[q.queue_family_index() as usize]
433 .queue_flags
434 .contains(QueueFlags::GRAPHICS)
435 })
436 .map(|q| q.queue_family_index());
437 let compute_queue_family_index = queues
438 .iter()
439 .filter(|q| {
440 queue_family_properties[q.queue_family_index() as usize]
441 .queue_flags
442 .contains(QueueFlags::COMPUTE)
443 })
444 .min_by_key(|q| {
445 queue_family_properties[q.queue_family_index() as usize]
446 .queue_flags
447 .count()
448 })
449 .map(|q| q.queue_family_index());
450 let transfer_queue_family_index = queues
451 .iter()
452 .filter(|q| {
453 queue_family_properties[q.queue_family_index() as usize]
454 .queue_flags
455 .contains(QueueFlags::TRANSFER)
456 })
457 .min_by_key(|q| {
458 queue_family_properties[q.queue_family_index() as usize]
459 .queue_flags
460 .count()
461 })
462 .map(|q| q.queue_family_index())
463 .or(compute_queue_family_index)
464 .or(graphics_queue_family_index);
465
466 let mut queue_family_indices = SmallVec::new();
467
468 for &node_index in topological_order {
469 let node = unsafe { self.nodes.node_unchecked_mut(node_index) };
470 let NodeInner::Task(task_node) = &mut node.inner else {
471 unreachable!();
472 };
473
474 let queue_family_index = match task_node.queue_family_type() {
475 QueueFamilyType::Graphics => graphics_queue_family_index,
476 QueueFamilyType::Compute => compute_queue_family_index,
477 QueueFamilyType::Transfer => transfer_queue_family_index,
478 QueueFamilyType::Specific { index } => queues
479 .iter()
480 .any(|q| q.queue_family_index() == index)
481 .then_some(index),
482 }
483 .ok_or(CompileErrorKind::InsufficientQueues)?;
484
485 task_node.queue_family_index = queue_family_index;
486
487 if !queue_family_indices.contains(&queue_family_index) {
488 queue_family_indices.push(queue_family_index);
489 }
490 }
491
492 Ok(queue_family_indices)
493 }
494
495 unsafe fn lower(
498 &mut self,
499 present_queue: Option<&Arc<Queue>>,
500 topological_order: &[NodeIndex],
501 ) -> Result<
502 (
503 IntermediateRepresentationBuilder,
504 LinearMap<Id<Swapchain>, NodeIndex>,
505 ),
506 CompileErrorKind,
507 > {
508 let mut builder = IntermediateRepresentationBuilder::new(
509 self.nodes.capacity(),
510 self.resources.capacity(),
511 );
512 let mut prev_queue_family_index = vk::QUEUE_FAMILY_IGNORED;
513 let mut last_swapchain_accesses = LinearMap::new();
514
515 for &node_index in topological_order {
516 let node = unsafe { self.nodes.node_unchecked(node_index) };
517 let NodeInner::Task(task_node) = &node.inner else {
518 unreachable!();
519 };
520 let queue_family_index = task_node.queue_family_index;
521
522 for &out_node_index in &node.out_edges {
523 let out_node = unsafe { self.nodes.node_unchecked(out_node_index) };
524 let NodeInner::Task(out_task_node) = &out_node.inner else {
525 unreachable!();
526 };
527
528 if queue_family_index != out_task_node.queue_family_index {
529 let semaphore_index = builder.semaphore_count;
530 builder.nodes[node_index as usize]
531 .signal_semaphores
532 .push(semaphore_index);
533 builder.nodes[out_node_index as usize]
534 .wait_semaphores
535 .push(semaphore_index);
536 builder.semaphore_count += 1;
537 }
538 }
539
540 if prev_queue_family_index != queue_family_index {
541 builder.submissions.push(SubmissionState::new(node_index));
542 builder.is_render_pass_instance_active = false;
543 }
544
545 let submission_index = builder.submissions.len() - 1;
546 builder.nodes[node_index as usize].submission_index = submission_index;
547
548 if let Some(attachments) = &task_node.attachments {
549 builder.subpass(node_index, &task_node.accesses, attachments);
550 builder.is_render_pass_instance_active = true;
551 } else {
552 builder.is_render_pass_instance_active = false;
553 }
554
555 for (id, access) in task_node.accesses.iter() {
556 let access = ResourceAccess {
557 queue_family_index,
558 ..*access
559 };
560 builder.resource_access(node_index, id, access);
561
562 if id.is::<Swapchain>() {
563 *last_swapchain_accesses
564 .get_or_insert(unsafe { id.parametrize() }, node_index) = node_index;
565 }
566 }
567
568 builder.submissions.last_mut().unwrap().last_node_index = node_index;
569
570 prev_queue_family_index = queue_family_index;
571 }
572
573 if !last_swapchain_accesses.is_empty() {
574 let present_queue = present_queue.expect("expected to be given a present queue");
575
576 for (&swapchain_id, &node_index) in last_swapchain_accesses.iter() {
577 builder.swapchain_present(node_index, swapchain_id, present_queue);
578 }
579 }
580
581 Ok((builder, last_swapchain_accesses))
582 }
583}
584
585struct IntermediateRepresentationBuilder {
586 submissions: Vec<SubmissionState>,
587 nodes: Vec<NodeState>,
588 prev_accesses: Vec<ResourceAccess>,
589 prev_node_indices: Vec<NodeIndex>,
590 semaphore_count: usize,
591 render_passes: Vec<RenderPassState>,
592 is_render_pass_instance_active: bool,
593 pre_present_queue_family_ownership_transfers: Vec<Id<Swapchain>>,
594}
595
596struct SubmissionState {
597 initial_barriers: Vec<super::MemoryBarrier>,
598 first_node_index: NodeIndex,
599 last_node_index: NodeIndex,
600}
601
602#[derive(Clone, Default)]
603struct NodeState {
604 submission_index: usize,
605 wait_acquire: Vec<(Id<Swapchain>, PipelineStages)>,
606 wait_semaphores: Vec<SemaphoreIndex>,
607 start_barriers: Vec<super::MemoryBarrier>,
608 subpass: Option<Subpass>,
609 clear_attachments: Vec<Id>,
610 end_barriers: Vec<super::MemoryBarrier>,
611 signal_semaphores: Vec<SemaphoreIndex>,
612 signal_pre_present: Vec<(Id<Swapchain>, PipelineStages)>,
613 signal_present: Vec<(Id<Swapchain>, PipelineStages)>,
614}
615
616#[derive(Clone, Copy)]
617struct Subpass {
618 render_pass_index: RenderPassIndex,
619 subpass_index: usize,
620}
621
622struct RenderPassState {
623 framebuffer_id: Id<Framebuffer>,
624 attachments: LinearMap<Id, AttachmentState>,
625 subpasses: Vec<SubpassState>,
626 first_node_index: NodeIndex,
627 last_node_index: NodeIndex,
628 clear_node_indices: Vec<NodeIndex>,
629}
630
631struct AttachmentState {
632 first_node_index: NodeIndex,
633 first_access: ResourceAccess,
634 last_access: ResourceAccess,
635 has_reads: bool,
636 index: u32,
637 clear: bool,
638 format: Format,
639 component_mapping: ComponentMapping,
640 mip_level: u32,
641 base_array_layer: u32,
642}
643
644struct SubpassState {
645 input_attachments: LinearMap<Id, (u32, ImageLayout)>,
646 color_attachments: LinearMap<Id, (u32, ImageLayout)>,
647 depth_stencil_attachment: Option<(Id, ImageLayout)>,
648 accesses: LinearMap<Id, ResourceAccess>,
649}
650
651impl IntermediateRepresentationBuilder {
652 fn new(node_capacity: u32, resource_capacity: u32) -> Self {
653 IntermediateRepresentationBuilder {
654 submissions: Vec::new(),
655 nodes: vec![NodeState::default(); node_capacity as usize],
656 prev_accesses: vec![ResourceAccess::default(); resource_capacity as usize],
657 prev_node_indices: vec![0; resource_capacity as usize],
658 semaphore_count: 0,
659 render_passes: Vec::new(),
660 is_render_pass_instance_active: false,
661 pre_present_queue_family_ownership_transfers: Vec::new(),
662 }
663 }
664
665 fn subpass(
666 &mut self,
667 node_index: NodeIndex,
668 accesses: &ResourceAccesses,
669 attachments: &Attachments,
670 ) {
671 let is_same_render_pass = self.is_render_pass_instance_active
672 && is_render_pass_mergeable(self.render_passes.last().unwrap(), accesses, attachments);
673
674 if !is_same_render_pass {
675 self.render_passes
676 .push(RenderPassState::new(attachments.framebuffer_id, node_index));
677 }
678
679 let render_pass_state = self.render_passes.last_mut().unwrap();
680
681 let is_same_subpass = render_pass_state
682 .subpasses
683 .last()
684 .is_some_and(|subpass_state| {
685 is_subpass_mergeable(subpass_state, accesses, attachments)
686 });
687
688 if !is_same_subpass {
689 render_pass_state
690 .subpasses
691 .push(SubpassState::from_task_node(accesses, attachments));
692 }
693
694 let subpass_state = render_pass_state.subpasses.last_mut().unwrap();
695
696 for (id, access) in accesses.iter() {
697 let current_access = subpass_state
698 .accesses
699 .get_or_insert_with(id, Default::default);
700 current_access.stage_mask |= access.stage_mask;
701 current_access.access_mask |= access.access_mask;
702 }
703
704 for (&id, attachment_info) in attachments.iter() {
705 let access = *accesses.get(id.erase()).unwrap();
706 let attachment_state =
707 render_pass_state
708 .attachments
709 .get_or_insert_with(id, || AttachmentState {
710 first_node_index: node_index,
711 first_access: access,
712 last_access: ResourceAccess::default(),
713 has_reads: false,
714 index: attachment_info.index,
715 clear: attachment_info.clear,
716 format: attachment_info.format,
717 component_mapping: attachment_info.component_mapping,
718 mip_level: attachment_info.mip_level,
719 base_array_layer: attachment_info.base_array_layer,
720 });
721 attachment_state.last_access = access;
722 attachment_state.has_reads |= access.access_mask.contains_reads();
723
724 if attachment_info.clear {
725 if attachment_state.first_node_index == node_index {
729 if !render_pass_state.clear_node_indices.contains(&node_index) {
730 render_pass_state.clear_node_indices.push(node_index);
731 }
732 } else {
733 let node_state = &mut self.nodes[node_index as usize];
734
735 if !node_state.clear_attachments.contains(&id) {
736 node_state.clear_attachments.push(id);
737 }
738 }
739 }
740 }
741
742 render_pass_state.last_node_index = node_index;
743
744 self.nodes[node_index as usize].subpass = Some(Subpass {
745 subpass_index: render_pass_state.subpasses.len() - 1,
746 render_pass_index: self.render_passes.len() - 1,
747 });
748 }
749
750 fn resource_access(&mut self, node_index: NodeIndex, id: Id, access: ResourceAccess) {
751 let prev_access = self.prev_accesses[id.index() as usize];
752 let prev_node_index = self.prev_node_indices[id.index() as usize];
753 let mut barriered = true;
754
755 if prev_access.stage_mask.is_empty() {
756 if id.is::<Swapchain>() {
757 self.swapchain_acquire(node_index, unsafe { id.parametrize() }, access);
758 } else if id.is::<Image>() {
759 self.initial_image_layout_transition(id, access);
760 } else if access.access_mask.contains_reads() {
761 self.initial_memory_barrier(id, access);
762 }
763 } else if prev_access.queue_family_index != access.queue_family_index {
764 if id.is_exclusive() {
765 self.queue_family_ownership_release(prev_node_index, id, access);
766 self.queue_family_ownership_acquire(node_index, id, access);
767 } else {
768 let prev_access = &mut self.prev_accesses[id.index() as usize];
769 prev_access.stage_mask = PipelineStages::empty();
770 prev_access.access_mask = AccessFlags::empty();
771
772 if prev_access.image_layout != access.image_layout {
773 self.image_layout_transition(node_index, id, access);
774 }
775 }
776 } else if self.is_render_pass_instance_active
777 && self.nodes[prev_node_index as usize]
778 .subpass
779 .is_some_and(|subpass| subpass.render_pass_index == self.render_passes.len() - 1)
780 {
781 } else if prev_access.image_layout != access.image_layout {
783 self.image_layout_transition(node_index, id, access);
784 } else if prev_access.access_mask.contains_writes() {
785 self.memory_barrier(node_index, id, access);
786 } else if access.access_mask.contains_writes() {
787 self.execution_barrier(node_index, id, access);
788 } else {
789 barriered = false;
790 }
791
792 let prev_access = &mut self.prev_accesses[id.index() as usize];
793 let prev_node_index = &mut self.prev_node_indices[id.index() as usize];
794
795 if barriered {
796 *prev_access = access;
797 *prev_node_index = node_index;
798 } else {
799 prev_access.stage_mask |= access.stage_mask;
800 prev_access.access_mask |= access.access_mask;
801
802 let prev_barrier = self.nodes[*prev_node_index as usize]
803 .start_barriers
804 .iter_mut()
805 .find(|barrier| barrier.resource == id)
806 .unwrap_or_else(|| {
807 self.submissions
808 .last_mut()
809 .unwrap()
810 .initial_barriers
811 .iter_mut()
812 .find(|barrier| barrier.resource == id)
813 .unwrap()
814 });
815 prev_barrier.dst_stage_mask |= access.stage_mask;
816 prev_barrier.dst_access_mask |= access.access_mask;
817 }
818 }
819
820 fn swapchain_acquire(
821 &mut self,
822 node_index: NodeIndex,
823 swapchain_id: Id<Swapchain>,
824 access: ResourceAccess,
825 ) {
826 let src = ResourceAccess {
827 stage_mask: access.stage_mask,
828 access_mask: AccessFlags::empty(),
829 image_layout: ImageLayout::Undefined,
830 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
831 };
832 let dst = ResourceAccess {
833 stage_mask: access.stage_mask,
834 access_mask: access.access_mask,
835 image_layout: access.image_layout,
836 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
837 };
838
839 self.memory_barrier_inner(node_index, swapchain_id.erase(), src, dst, false);
840
841 let submission_state = self.submissions.last().unwrap();
842
843 self.nodes[submission_state.first_node_index as usize]
844 .wait_acquire
845 .push((swapchain_id, access.stage_mask));
846 }
847
848 fn initial_image_layout_transition(&mut self, id: Id, access: ResourceAccess) {
849 self.initial_memory_barrier(id, access);
850 }
851
852 fn initial_memory_barrier(&mut self, id: Id, access: ResourceAccess) {
853 let submission_state = self.submissions.last_mut().unwrap();
854
855 submission_state
856 .initial_barriers
857 .push(super::MemoryBarrier {
858 src_stage_mask: PipelineStages::empty(),
859 src_access_mask: AccessFlags::empty(),
860 dst_stage_mask: access.stage_mask,
861 dst_access_mask: access.access_mask,
862 old_layout: ImageLayout::Undefined,
863 new_layout: access.image_layout,
864 src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
865 dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
866 resource: id,
867 });
868 }
869
870 fn queue_family_ownership_release(
871 &mut self,
872 node_index: NodeIndex,
873 id: Id,
874 access: ResourceAccess,
875 ) {
876 debug_assert!(id.is_exclusive());
877
878 let prev_access = &mut self.prev_accesses[id.index() as usize];
879 let mut src = *prev_access;
880 let dst = ResourceAccess {
881 stage_mask: PipelineStages::empty(),
882 access_mask: AccessFlags::empty(),
883 ..access
884 };
885
886 if !prev_access.access_mask.contains_writes() {
887 src.access_mask = AccessFlags::empty();
888 }
889
890 debug_assert_ne!(src.queue_family_index, dst.queue_family_index);
891
892 self.memory_barrier_inner(node_index, id, src, dst, true);
893 }
894
895 fn queue_family_ownership_acquire(
896 &mut self,
897 node_index: NodeIndex,
898 id: Id,
899 access: ResourceAccess,
900 ) {
901 debug_assert!(id.is_exclusive());
902
903 let prev_access = self.prev_accesses[id.index() as usize];
904 let src = ResourceAccess {
905 stage_mask: PipelineStages::empty(),
906 access_mask: AccessFlags::empty(),
907 ..prev_access
908 };
909 let dst = access;
910
911 debug_assert_ne!(src.queue_family_index, dst.queue_family_index);
912
913 self.memory_barrier_inner(node_index, id, src, dst, false);
914 }
915
916 fn image_layout_transition(&mut self, node_index: NodeIndex, id: Id, access: ResourceAccess) {
917 debug_assert_ne!(
918 self.prev_accesses[id.index() as usize].image_layout,
919 access.image_layout,
920 );
921
922 self.memory_barrier(node_index, id, access);
923 }
924
925 fn memory_barrier(&mut self, node_index: NodeIndex, id: Id, access: ResourceAccess) {
926 let prev_access = self.prev_accesses[id.index() as usize];
927 let src = ResourceAccess {
928 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
929 ..prev_access
930 };
931 let dst = ResourceAccess {
932 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
933 ..access
934 };
935
936 self.memory_barrier_inner(node_index, id, src, dst, false);
937 }
938
939 fn execution_barrier(&mut self, node_index: NodeIndex, id: Id, access: ResourceAccess) {
940 let prev_access = self.prev_accesses[id.index() as usize];
941 let src = ResourceAccess {
942 access_mask: AccessFlags::empty(),
943 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
944 ..prev_access
945 };
946 let dst = ResourceAccess {
947 access_mask: AccessFlags::empty(),
948 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
949 ..access
950 };
951
952 debug_assert_eq!(prev_access.image_layout, access.image_layout);
953
954 self.memory_barrier_inner(node_index, id, src, dst, false);
955 }
956
957 fn memory_barrier_inner(
958 &mut self,
959 node_index: NodeIndex,
960 id: Id,
961 src: ResourceAccess,
962 dst: ResourceAccess,
963 is_end_barrier: bool,
964 ) {
965 let mut node_state = &mut self.nodes[node_index as usize];
966
967 if let Some(subpass) = node_state.subpass {
973 let render_pass_state = &self.render_passes[subpass.render_pass_index];
974 let moved_node_index = if is_end_barrier {
975 render_pass_state.last_node_index
976 } else {
977 render_pass_state.first_node_index
978 };
979 node_state = &mut self.nodes[moved_node_index as usize];
980 }
981
982 let barriers = if is_end_barrier {
983 &mut node_state.end_barriers
984 } else {
985 &mut node_state.start_barriers
986 };
987
988 barriers.push(super::MemoryBarrier {
989 src_stage_mask: src.stage_mask,
990 src_access_mask: src.access_mask,
991 dst_stage_mask: dst.stage_mask,
992 dst_access_mask: dst.access_mask,
993 old_layout: src.image_layout,
994 new_layout: dst.image_layout,
995 src_queue_family_index: src.queue_family_index,
996 dst_queue_family_index: dst.queue_family_index,
997 resource: id,
998 });
999 }
1000
1001 fn swapchain_present(
1002 &mut self,
1003 node_index: NodeIndex,
1004 swapchain_id: Id<Swapchain>,
1005 present_queue: &Queue,
1006 ) {
1007 let prev_access = self.prev_accesses[swapchain_id.index() as usize];
1008 let src = ResourceAccess {
1009 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
1010 ..prev_access
1011 };
1012
1013 let needs_queue_family_ownership_transfer = prev_access.queue_family_index
1014 != present_queue.queue_family_index()
1015 && swapchain_id.is_exclusive();
1016
1017 if needs_queue_family_ownership_transfer {
1018 self.queue_family_ownership_release(
1019 node_index,
1020 swapchain_id.erase(),
1021 ResourceAccess {
1022 stage_mask: PipelineStages::empty(),
1023 access_mask: AccessFlags::empty(),
1024 image_layout: ImageLayout::PresentSrc,
1025 queue_family_index: present_queue.queue_family_index(),
1026 },
1027 );
1028 } else {
1029 self.memory_barrier_inner(
1030 node_index,
1031 swapchain_id.erase(),
1032 src,
1033 ResourceAccess {
1034 stage_mask: PipelineStages::empty(),
1035 access_mask: AccessFlags::empty(),
1036 image_layout: ImageLayout::PresentSrc,
1037 queue_family_index: vk::QUEUE_FAMILY_IGNORED,
1038 },
1039 true,
1040 );
1041 }
1042
1043 let node_state = &self.nodes[node_index as usize];
1044 let submission_state = &self.submissions[node_state.submission_index];
1045
1046 if needs_queue_family_ownership_transfer {
1047 self.nodes[submission_state.last_node_index as usize]
1048 .signal_pre_present
1049 .push((swapchain_id, prev_access.stage_mask));
1050 self.pre_present_queue_family_ownership_transfers
1051 .push(swapchain_id);
1052 } else {
1053 self.nodes[submission_state.last_node_index as usize]
1054 .signal_present
1055 .push((swapchain_id, prev_access.stage_mask));
1056 }
1057 }
1058}
1059
1060impl SubmissionState {
1061 fn new(node_index: u32) -> Self {
1062 Self {
1063 initial_barriers: Vec::new(),
1064 first_node_index: node_index,
1065 last_node_index: node_index,
1066 }
1067 }
1068}
1069
1070impl RenderPassState {
1071 fn new(framebuffer: Id<Framebuffer>, node_index: NodeIndex) -> Self {
1072 RenderPassState {
1073 framebuffer_id: framebuffer,
1074 attachments: LinearMap::new(),
1075 subpasses: Vec::new(),
1076 first_node_index: node_index,
1077 last_node_index: node_index,
1078 clear_node_indices: Vec::new(),
1079 }
1080 }
1081}
1082
1083impl SubpassState {
1084 fn from_task_node(accesses: &ResourceAccesses, attachments: &Attachments) -> Self {
1085 SubpassState {
1086 input_attachments: attachments
1087 .input_attachments
1088 .iter()
1089 .map(|(id, attachment_info)| {
1090 let input_attachment_index = attachment_info.index;
1091 let image_layout = accesses.get(id.erase()).unwrap().image_layout;
1092
1093 (*id, (input_attachment_index, image_layout))
1094 })
1095 .collect(),
1096 color_attachments: attachments
1097 .color_attachments
1098 .iter()
1099 .map(|(id, attachment_info)| {
1100 let location = attachment_info.index;
1101 let image_layout = accesses.get(id.erase()).unwrap().image_layout;
1102
1103 (*id, (location, image_layout))
1104 })
1105 .collect(),
1106 depth_stencil_attachment: attachments.depth_stencil_attachment.as_ref().map(
1107 |(id, _)| {
1108 let image_layout = accesses.get(id.erase()).unwrap().image_layout;
1109
1110 (*id, image_layout)
1111 },
1112 ),
1113 accesses: accesses.inner.clone(),
1114 }
1115 }
1116}
1117
1118fn is_render_pass_mergeable(
1119 render_pass_state: &RenderPassState,
1120 accesses: &ResourceAccesses,
1121 attachments: &Attachments,
1122) -> bool {
1123 const ATTACHMENT_ACCESSES: AccessFlags = AccessFlags::INPUT_ATTACHMENT_READ
1124 .union(AccessFlags::COLOR_ATTACHMENT_READ)
1125 .union(AccessFlags::COLOR_ATTACHMENT_WRITE)
1126 .union(AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
1127 .union(AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE);
1128
1129 if render_pass_state.framebuffer_id != attachments.framebuffer_id {
1131 return false;
1132 }
1133
1134 if render_pass_state.attachments.keys().any(|id| {
1137 accesses
1138 .get(id.erase())
1139 .is_some_and(|access| !ATTACHMENT_ACCESSES.contains(access.access_mask))
1140 }) {
1141 return false;
1142 }
1143
1144 if attachments.keys().any(|id| {
1147 render_pass_state.subpasses.iter().any(|subpass_state| {
1148 subpass_state
1149 .accesses
1150 .get(&id.erase())
1151 .is_some_and(|access| !ATTACHMENT_ACCESSES.contains(access.access_mask))
1152 })
1153 }) {
1154 return false;
1155 }
1156
1157 true
1158}
1159
1160fn is_subpass_mergeable(
1161 subpass_state: &SubpassState,
1162 accesses: &ResourceAccesses,
1163 attachments: &Attachments,
1164) -> bool {
1165 if subpass_state.color_attachments.len() != attachments.color_attachments.len()
1171 || subpass_state.input_attachments.len() != attachments.input_attachments.len()
1172 || subpass_state.depth_stencil_attachment.is_some()
1173 != attachments.depth_stencil_attachment.is_some()
1174 {
1175 return false;
1176 }
1177
1178 for (id, &(location, image_layout)) in subpass_state.color_attachments.iter() {
1179 if !attachments
1180 .color_attachments
1181 .get(id)
1182 .is_some_and(|attachment_info| attachment_info.index == location)
1183 || accesses.get(id.erase()).unwrap().image_layout != image_layout
1184 {
1185 return false;
1186 }
1187 }
1188
1189 for (id, &(input_attachment_index, image_layout)) in subpass_state.input_attachments.iter() {
1190 if !attachments
1191 .input_attachments
1192 .get(id)
1193 .is_some_and(|attachment_info| attachment_info.index == input_attachment_index)
1194 || accesses.get(id.erase()).unwrap().image_layout != image_layout
1195 {
1196 return false;
1197 }
1198 }
1199
1200 if subpass_state.depth_stencil_attachment
1201 != attachments
1202 .depth_stencil_attachment
1203 .as_ref()
1204 .map(|(id, _)| (*id, accesses.get(id.erase()).unwrap().image_layout))
1205 {
1206 return false;
1207 }
1208
1209 true
1210}
1211
1212fn create_render_pass(
1213 resources: &super::Resources,
1214 render_pass_state: RenderPassState,
1215) -> Result<super::RenderPassState, CompileErrorKind> {
1216 const FRAMEBUFFER_SPACE_ACCESS_FLAGS: AccessFlags = AccessFlags::INPUT_ATTACHMENT_READ
1217 .union(AccessFlags::COLOR_ATTACHMENT_READ)
1218 .union(AccessFlags::COLOR_ATTACHMENT_WRITE)
1219 .union(AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ)
1220 .union(AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE);
1221
1222 let attachments = render_pass_state
1223 .attachments
1224 .iter()
1225 .map(|(&id, attachment_state)| {
1226 let resource_info = resources.get(id.erase()).unwrap();
1227 let is_resident = !resource_info
1228 .usage
1229 .contains(ImageUsage::TRANSIENT_ATTACHMENT);
1230
1231 AttachmentDescription {
1232 format: attachment_state.format,
1233 samples: resource_info.samples,
1234 load_op: if attachment_state.clear {
1235 AttachmentLoadOp::Clear
1236 } else if attachment_state.has_reads && is_resident {
1237 AttachmentLoadOp::Load
1238 } else {
1239 AttachmentLoadOp::DontCare
1240 },
1241 store_op: if is_resident {
1242 AttachmentStoreOp::Store
1243 } else {
1244 AttachmentStoreOp::DontCare
1245 },
1246 initial_layout: attachment_state.first_access.image_layout,
1247 final_layout: attachment_state.last_access.image_layout,
1248 ..Default::default()
1249 }
1250 })
1251 .collect();
1252
1253 let subpasses = render_pass_state
1254 .subpasses
1255 .iter()
1256 .map(|subpass_state| SubpassDescription {
1257 view_mask: 0,
1259 input_attachments: convert_attachments(
1260 &render_pass_state,
1261 &subpass_state.input_attachments,
1262 ),
1263 color_attachments: convert_attachments(
1264 &render_pass_state,
1265 &subpass_state.color_attachments,
1266 ),
1267 depth_stencil_attachment: subpass_state.depth_stencil_attachment.map(|(id, layout)| {
1268 AttachmentReference {
1269 attachment: render_pass_state.attachments.index_of(&id).unwrap() as u32,
1270 layout,
1271 ..Default::default()
1272 }
1273 }),
1274 preserve_attachments: render_pass_state
1275 .attachments
1276 .keys()
1277 .enumerate()
1278 .filter_map(|(attachment, &id)| {
1279 (!subpass_state.input_attachments.contains_key(&id)
1280 && !subpass_state.color_attachments.contains_key(&id)
1281 && !subpass_state
1282 .depth_stencil_attachment
1283 .is_some_and(|(x, _)| x == id))
1284 .then_some(attachment as u32)
1285 })
1286 .collect(),
1287 ..Default::default()
1288 })
1289 .collect();
1290
1291 let mut dependencies = Vec::<SubpassDependency>::new();
1292
1293 for (src_subpass_index, src_subpass_state) in render_pass_state.subpasses.iter().enumerate() {
1295 let src_subpass = src_subpass_index as u32;
1296
1297 for (dst_subpass_index, dst_subpass_state) in render_pass_state
1298 .subpasses
1299 .iter()
1300 .enumerate()
1301 .skip(src_subpass_index + 1)
1302 {
1303 let dst_subpass = dst_subpass_index as u32;
1304
1305 for (id, src_access) in src_subpass_state.accesses.iter() {
1306 let Some(dst_access) = dst_subpass_state.accesses.get(id) else {
1307 continue;
1308 };
1309 let src_access_mask;
1310 let dst_access_mask;
1311
1312 if src_access.access_mask.contains_writes() {
1313 src_access_mask = src_access.access_mask;
1314 dst_access_mask = dst_access.access_mask;
1315 } else if dst_access.access_mask.contains_writes() {
1316 src_access_mask = AccessFlags::empty();
1317 dst_access_mask = AccessFlags::empty();
1318 } else {
1319 continue;
1320 }
1321
1322 let dependency =
1323 get_or_insert_subpass_dependency(&mut dependencies, src_subpass, dst_subpass);
1324 dependency.src_stages |= src_access.stage_mask;
1325 dependency.src_access |= src_access_mask;
1326 dependency.dst_stages |= dst_access.stage_mask;
1327 dependency.dst_access |= dst_access_mask;
1328
1329 if !FRAMEBUFFER_SPACE_ACCESS_FLAGS.contains(src_access_mask) {
1334 dependency.dependency_flags = DependencyFlags::empty();
1335 }
1336 }
1337 }
1338 }
1339
1340 for (subpass_index, subpass_state) in render_pass_state.subpasses.iter().enumerate() {
1342 let subpass = subpass_index as u32;
1343
1344 for access in subpass_state.accesses.values() {
1345 let access_mask = access.access_mask;
1346
1347 if access_mask.contains(AccessFlags::INPUT_ATTACHMENT_READ) {
1348 if access_mask.contains(AccessFlags::COLOR_ATTACHMENT_WRITE) {
1349 let dependency =
1350 get_or_insert_subpass_dependency(&mut dependencies, subpass, subpass);
1351 dependency.src_stages |= PipelineStages::COLOR_ATTACHMENT_OUTPUT;
1352 dependency.dst_stages |= PipelineStages::FRAGMENT_SHADER;
1353 dependency.src_access |= AccessFlags::COLOR_ATTACHMENT_WRITE;
1354 dependency.dst_access |= AccessFlags::INPUT_ATTACHMENT_READ;
1355 } else if access_mask.contains(AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE) {
1356 let dependency =
1357 get_or_insert_subpass_dependency(&mut dependencies, subpass, subpass);
1358 dependency.src_stages |=
1359 PipelineStages::EARLY_FRAGMENT_TESTS | PipelineStages::LATE_FRAGMENT_TESTS;
1360 dependency.dst_stages |= PipelineStages::FRAGMENT_SHADER;
1361 dependency.src_access |= AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE;
1362 dependency.dst_access |= AccessFlags::INPUT_ATTACHMENT_READ;
1363 }
1364 }
1365 }
1366 }
1367
1368 let render_pass = RenderPass::new(
1369 resources.physical_resources.device().clone(),
1370 RenderPassCreateInfo {
1371 attachments,
1372 subpasses,
1373 dependencies,
1374 ..Default::default()
1375 },
1376 )
1377 .map_err(Validated::unwrap)
1379 .map_err(CompileErrorKind::VulkanError)?;
1380
1381 let attachments = render_pass_state
1382 .attachments
1383 .iter()
1384 .map(|(&id, attachment_state)| {
1385 let attachment = super::AttachmentState {
1386 index: attachment_state.index,
1387 format: attachment_state.format,
1388 component_mapping: attachment_state.component_mapping,
1389 mip_level: attachment_state.mip_level,
1390 base_array_layer: attachment_state.base_array_layer,
1391 };
1392
1393 (id, attachment)
1394 })
1395 .collect();
1396
1397 Ok(super::RenderPassState {
1398 render_pass,
1399 attachments,
1400 framebuffers: Vec::new(),
1401 clear_node_indices: render_pass_state.clear_node_indices,
1402 })
1403}
1404
1405fn convert_attachments(
1406 render_pass_state: &RenderPassState,
1407 attachments: &LinearMap<Id, (u32, ImageLayout)>,
1408) -> Vec<Option<AttachmentReference>> {
1409 let len = attachments.values().map(|(i, _)| *i + 1).max().unwrap_or(0);
1410 let mut attachments_out = vec![None; len as usize];
1411
1412 for (&id, &(index, layout)) in attachments.iter() {
1413 let attachment = &mut attachments_out[index as usize];
1414
1415 debug_assert!(attachment.is_none());
1416
1417 *attachment = Some(AttachmentReference {
1418 attachment: render_pass_state.attachments.index_of(&id).unwrap() as u32,
1419 layout,
1420 ..Default::default()
1421 });
1422 }
1423
1424 attachments_out
1425}
1426
1427fn get_or_insert_subpass_dependency(
1428 dependencies: &mut Vec<SubpassDependency>,
1429 src_subpass: u32,
1430 dst_subpass: u32,
1431) -> &mut SubpassDependency {
1432 let dependency_index = dependencies
1433 .iter_mut()
1434 .position(|dependency| {
1435 dependency.src_subpass == Some(src_subpass)
1436 && dependency.dst_subpass == Some(dst_subpass)
1437 })
1438 .unwrap_or_else(|| {
1439 let index = dependencies.len();
1440
1441 dependencies.push(SubpassDependency {
1442 src_subpass: Some(src_subpass),
1443 dst_subpass: Some(dst_subpass),
1444 dependency_flags: DependencyFlags::BY_REGION,
1445 ..Default::default()
1446 });
1447
1448 index
1449 });
1450
1451 &mut dependencies[dependency_index]
1452}
1453
1454struct FinalRepresentationBuilder {
1455 instructions: Vec<Instruction>,
1456 submissions: Vec<Submission>,
1457 barriers: Vec<super::MemoryBarrier>,
1458 clear_attachments: Vec<Id>,
1459 present_queue: Option<Arc<Queue>>,
1460 initial_barrier_range: Range<BarrierIndex>,
1461 has_flushed_submit: bool,
1462 should_flush_submit: bool,
1463 prev_barrier_index: usize,
1464 prev_subpass_index: usize,
1465}
1466
1467impl FinalRepresentationBuilder {
1468 fn new(present_queue: Option<&Arc<Queue>>) -> Self {
1469 FinalRepresentationBuilder {
1470 instructions: Vec::new(),
1471 submissions: Vec::new(),
1472 barriers: Vec::new(),
1473 clear_attachments: Vec::new(),
1474 present_queue: present_queue.cloned(),
1475 initial_barrier_range: 0..0,
1476 has_flushed_submit: true,
1477 should_flush_submit: false,
1478 prev_barrier_index: 0,
1479 prev_subpass_index: 0,
1480 }
1481 }
1482
1483 fn initial_pipeline_barrier(&mut self, barriers: &[super::MemoryBarrier]) {
1484 self.barriers.extend_from_slice(barriers);
1485 self.initial_barrier_range =
1486 self.prev_barrier_index as BarrierIndex..self.barriers.len() as BarrierIndex;
1487 self.prev_barrier_index = self.barriers.len();
1488 }
1489
1490 fn pipeline_barrier(&mut self, barriers: &[super::MemoryBarrier]) {
1491 self.barriers.extend_from_slice(barriers);
1492 }
1493
1494 fn wait_acquire(&mut self, swapchain_id: Id<Swapchain>, stage_mask: PipelineStages) {
1495 if !self.has_flushed_submit {
1496 self.flush_submit();
1497 }
1498
1499 self.instructions.push(Instruction::WaitAcquire {
1500 swapchain_id,
1501 stage_mask,
1502 });
1503 }
1504
1505 fn wait_semaphore(&mut self, semaphore_index: SemaphoreIndex) {
1506 if !self.has_flushed_submit {
1507 self.flush_submit();
1508 }
1509
1510 self.instructions.push(Instruction::WaitSemaphore {
1511 semaphore_index,
1512 stage_mask: PipelineStages::ALL_COMMANDS,
1513 });
1514 }
1515
1516 fn begin_render_pass(&mut self, render_pass_index: RenderPassIndex) {
1517 self.flush_barriers();
1518
1519 self.instructions
1520 .push(Instruction::BeginRenderPass { render_pass_index });
1521
1522 self.prev_subpass_index = 0;
1523 }
1524
1525 fn next_subpass(&mut self, subpass_index: usize) {
1526 if self.prev_subpass_index != subpass_index {
1527 self.instructions.push(Instruction::NextSubpass);
1528 self.prev_subpass_index = subpass_index;
1529 }
1530 }
1531
1532 fn execute_task(&mut self, node_index: NodeIndex) {
1533 self.flush_barriers();
1534
1535 self.instructions
1536 .push(Instruction::ExecuteTask { node_index });
1537
1538 self.has_flushed_submit = false;
1539 }
1540
1541 fn end_render_pass(&mut self) {
1542 self.instructions.push(Instruction::EndRenderPass);
1543 }
1544
1545 fn clear_attachments(
1546 &mut self,
1547 node_index: NodeIndex,
1548 render_pass_index: RenderPassIndex,
1549 clear_attachments: &[Id],
1550 ) {
1551 self.flush_barriers();
1552
1553 let clear_attachment_range_start = self.clear_attachments.len();
1554 self.clear_attachments.extend_from_slice(clear_attachments);
1555 self.instructions.push(Instruction::ClearAttachments {
1556 node_index,
1557 render_pass_index,
1558 clear_attachment_range: clear_attachment_range_start..self.clear_attachments.len(),
1559 });
1560 }
1561
1562 fn signal_semaphore(&mut self, semaphore_index: SemaphoreIndex) {
1563 self.instructions.push(Instruction::SignalSemaphore {
1564 semaphore_index,
1565 stage_mask: PipelineStages::ALL_COMMANDS,
1566 });
1567
1568 self.should_flush_submit = true;
1569 }
1570
1571 fn signal_present(&mut self, swapchain_id: Id<Swapchain>, stage_mask: PipelineStages) {
1572 self.instructions.push(Instruction::SignalPresent {
1573 swapchain_id,
1574 stage_mask,
1575 });
1576
1577 self.should_flush_submit = true;
1578 }
1579
1580 fn signal_pre_present(&mut self, swapchain_id: Id<Swapchain>, stage_mask: PipelineStages) {
1581 self.instructions.push(Instruction::SignalPrePresent {
1582 swapchain_id,
1583 stage_mask,
1584 });
1585
1586 self.should_flush_submit = true;
1587 }
1588
1589 fn pre_present_acquire_queue_family_ownership(
1590 &mut self,
1591 last_accesses: &[ResourceAccess],
1592 swapchain_id: Id<Swapchain>,
1593 ) {
1594 if !self.has_flushed_submit {
1595 self.flush_submit();
1596 }
1597
1598 self.instructions.push(Instruction::WaitPrePresent {
1599 swapchain_id,
1600 stage_mask: PipelineStages::ALL_COMMANDS,
1601 });
1602
1603 let last_access = last_accesses[swapchain_id.index() as usize];
1604
1605 self.barriers.push(super::MemoryBarrier {
1606 src_stage_mask: PipelineStages::empty(),
1607 src_access_mask: AccessFlags::empty(),
1608 dst_stage_mask: PipelineStages::empty(),
1609 dst_access_mask: AccessFlags::empty(),
1610 old_layout: last_access.image_layout,
1611 new_layout: ImageLayout::PresentSrc,
1612 src_queue_family_index: last_access.queue_family_index,
1613 dst_queue_family_index: self.present_queue.as_ref().unwrap().queue_family_index(),
1614 resource: swapchain_id.erase(),
1615 });
1616
1617 self.instructions.push(Instruction::SignalPresent {
1618 swapchain_id,
1619 stage_mask: PipelineStages::ALL_COMMANDS,
1620 });
1621 }
1622
1623 fn flush_barriers(&mut self) {
1624 if self.prev_barrier_index != self.barriers.len() {
1625 self.instructions.push(Instruction::PipelineBarrier {
1626 barrier_range: self.prev_barrier_index as BarrierIndex
1627 ..self.barriers.len() as BarrierIndex,
1628 });
1629 self.prev_barrier_index = self.barriers.len();
1630 }
1631 }
1632
1633 fn flush_submit(&mut self) {
1634 self.flush_barriers();
1635 self.instructions.push(Instruction::FlushSubmit);
1636 self.has_flushed_submit = true;
1637 self.should_flush_submit = false;
1638 }
1639
1640 fn submit(&mut self, queue: Arc<Queue>) {
1641 self.instructions.push(Instruction::Submit);
1642
1643 let prev_instruction_range_end = self
1644 .submissions
1645 .last()
1646 .map(|s| s.instruction_range.end)
1647 .unwrap_or(0);
1648 self.submissions.push(Submission {
1649 queue,
1650 initial_barrier_range: self.initial_barrier_range.clone(),
1651 instruction_range: prev_instruction_range_end..self.instructions.len(),
1652 });
1653 }
1654}
1655
1656impl<W: ?Sized> ExecutableTaskGraph<W> {
1657 #[inline]
1659 pub fn decompile(self) -> TaskGraph<W> {
1660 self.graph
1661 }
1662}
1663
1664#[derive(Clone, Debug)]
1668pub struct CompileInfo<'a> {
1669 pub queues: &'a [&'a Arc<Queue>],
1675
1676 pub present_queue: Option<&'a Arc<Queue>>,
1685
1686 pub flight_id: Id<Flight>,
1690
1691 pub _ne: crate::NonExhaustive<'a>,
1692}
1693
1694impl Default for CompileInfo<'_> {
1695 #[inline]
1696 fn default() -> Self {
1697 CompileInfo {
1698 queues: &[],
1699 present_queue: None,
1700 flight_id: Id::INVALID,
1701 _ne: crate::NE,
1702 }
1703 }
1704}
1705
1706pub struct CompileError<W: ?Sized> {
1710 pub graph: TaskGraph<W>,
1711 pub kind: CompileErrorKind,
1712}
1713
1714#[derive(Debug, PartialEq, Eq)]
1716pub enum CompileErrorKind {
1717 Unconnected,
1718 Cycle,
1719 InsufficientQueues,
1720 VulkanError(VulkanError),
1721}
1722
1723impl<W: ?Sized> CompileError<W> {
1724 fn new(graph: TaskGraph<W>, kind: CompileErrorKind) -> Self {
1725 CompileError { graph, kind }
1726 }
1727}
1728
1729impl<W: ?Sized> fmt::Debug for CompileError<W> {
1730 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1731 fmt::Debug::fmt(&self.kind, f)
1732 }
1733}
1734
1735impl<W: ?Sized> fmt::Display for CompileError<W> {
1736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1737 match self.kind {
1738 CompileErrorKind::Unconnected => f.write_str("the graph is not weakly connected"),
1739 CompileErrorKind::Cycle => f.write_str("the graph contains a directed cycle"),
1740 CompileErrorKind::InsufficientQueues => {
1741 f.write_str("the given queues are not sufficient for the requirements of a task")
1742 }
1743 CompileErrorKind::VulkanError(_) => f.write_str("a runtime error occurred"),
1744 }
1745 }
1746}
1747
1748impl<W: ?Sized> Error for CompileError<W> {
1749 fn source(&self) -> Option<&(dyn Error + 'static)> {
1750 match &self.kind {
1751 CompileErrorKind::VulkanError(err) => Some(err),
1752 _ => None,
1753 }
1754 }
1755}
1756
1757#[cfg(test)]
1758mod tests {
1759 use super::*;
1760 use crate::{
1761 graph::AttachmentInfo,
1762 resource::{AccessTypes, ImageLayoutType},
1763 tests::test_queues,
1764 };
1765 use std::marker::PhantomData;
1766 use vulkano::{
1767 buffer::BufferCreateInfo, image::ImageCreateInfo, swapchain::SwapchainCreateInfo,
1768 sync::Sharing,
1769 };
1770
1771 #[test]
1772 fn unconnected1() {
1773 let (resources, queues) = test_queues!();
1774
1775 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1783 graph
1784 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1785 .build();
1786 graph
1787 .create_task_node("B", QueueFamilyType::Compute, PhantomData)
1788 .build();
1789
1790 let err = unsafe {
1791 graph.compile(&CompileInfo {
1792 queues: &queues.iter().collect::<Vec<_>>(),
1793 ..Default::default()
1794 })
1795 }
1796 .unwrap_err();
1797
1798 assert_eq!(err.kind, CompileErrorKind::Unconnected);
1799 }
1800
1801 #[test]
1802 fn unconnected2() {
1803 let (resources, queues) = test_queues!();
1804
1805 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1819 let a = graph
1820 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1821 .build();
1822 let b = graph
1823 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
1824 .build();
1825 let c = graph
1826 .create_task_node("C", QueueFamilyType::Compute, PhantomData)
1827 .build();
1828 let d = graph
1829 .create_task_node("D", QueueFamilyType::Compute, PhantomData)
1830 .build();
1831 graph.add_edge(a, c).unwrap();
1832 graph.add_edge(b, d).unwrap();
1833
1834 let err = unsafe {
1835 graph.compile(&CompileInfo {
1836 queues: &queues.iter().collect::<Vec<_>>(),
1837 ..Default::default()
1838 })
1839 }
1840 .unwrap_err();
1841
1842 assert_eq!(err.kind, CompileErrorKind::Unconnected);
1843 }
1844
1845 #[test]
1846 fn unconnected3() {
1847 let (resources, queues) = test_queues!();
1848
1849 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1859 let a = graph
1860 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1861 .build();
1862 let b = graph
1863 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
1864 .build();
1865 let c = graph
1866 .create_task_node("C", QueueFamilyType::Graphics, PhantomData)
1867 .build();
1868 graph.add_edge(a, b).unwrap();
1869 graph.add_edge(b, c).unwrap();
1870 let d = graph
1871 .create_task_node("D", QueueFamilyType::Compute, PhantomData)
1872 .build();
1873 let e = graph
1874 .create_task_node("E", QueueFamilyType::Compute, PhantomData)
1875 .build();
1876 let f = graph
1877 .create_task_node("F", QueueFamilyType::Compute, PhantomData)
1878 .build();
1879 let g = graph
1880 .create_task_node("G", QueueFamilyType::Compute, PhantomData)
1881 .build();
1882 graph.add_edge(d, e).unwrap();
1883 graph.add_edge(d, g).unwrap();
1884 graph.add_edge(e, f).unwrap();
1885 graph.add_edge(e, g).unwrap();
1886 graph.add_edge(f, g).unwrap();
1887
1888 let err = unsafe {
1889 graph.compile(&CompileInfo {
1890 queues: &queues.iter().collect::<Vec<_>>(),
1891 ..Default::default()
1892 })
1893 }
1894 .unwrap_err();
1895
1896 assert_eq!(err.kind, CompileErrorKind::Unconnected);
1897 }
1898
1899 #[test]
1900 fn cycle1() {
1901 let (resources, queues) = test_queues!();
1902
1903 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1908 let a = graph
1909 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1910 .build();
1911 let b = graph
1912 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
1913 .build();
1914 let c = graph
1915 .create_task_node("C", QueueFamilyType::Graphics, PhantomData)
1916 .build();
1917 graph.add_edge(a, b).unwrap();
1918 graph.add_edge(b, c).unwrap();
1919 graph.add_edge(c, a).unwrap();
1920
1921 let err = unsafe {
1922 graph.compile(&CompileInfo {
1923 queues: &queues.iter().collect::<Vec<_>>(),
1924 ..Default::default()
1925 })
1926 }
1927 .unwrap_err();
1928
1929 assert_eq!(err.kind, CompileErrorKind::Cycle);
1930 }
1931
1932 #[test]
1933 fn cycle2() {
1934 let (resources, queues) = test_queues!();
1935
1936 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1945 let a = graph
1946 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1947 .build();
1948 let b = graph
1949 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
1950 .build();
1951 let c = graph
1952 .create_task_node("C", QueueFamilyType::Graphics, PhantomData)
1953 .build();
1954 let d = graph
1955 .create_task_node("D", QueueFamilyType::Compute, PhantomData)
1956 .build();
1957 let e = graph
1958 .create_task_node("E", QueueFamilyType::Compute, PhantomData)
1959 .build();
1960 let f = graph
1961 .create_task_node("F", QueueFamilyType::Compute, PhantomData)
1962 .build();
1963 graph.add_edge(a, b).unwrap();
1964 graph.add_edge(a, d).unwrap();
1965 graph.add_edge(b, c).unwrap();
1966 graph.add_edge(c, f).unwrap();
1967 graph.add_edge(d, e).unwrap();
1968 graph.add_edge(e, f).unwrap();
1969 graph.add_edge(f, a).unwrap();
1970
1971 let err = unsafe {
1972 graph.compile(&CompileInfo {
1973 queues: &queues.iter().collect::<Vec<_>>(),
1974 ..Default::default()
1975 })
1976 }
1977 .unwrap_err();
1978
1979 assert_eq!(err.kind, CompileErrorKind::Cycle);
1980 }
1981
1982 #[test]
1983 fn cycle3() {
1984 let (resources, queues) = test_queues!();
1985
1986 let mut graph = TaskGraph::<()>::new(&resources, 10, 0);
1997 let a = graph
1998 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
1999 .build();
2000 let b = graph
2001 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
2002 .build();
2003 let c = graph
2004 .create_task_node("C", QueueFamilyType::Graphics, PhantomData)
2005 .build();
2006 let d = graph
2007 .create_task_node("D", QueueFamilyType::Compute, PhantomData)
2008 .build();
2009 let e = graph
2010 .create_task_node("E", QueueFamilyType::Compute, PhantomData)
2011 .build();
2012 let f = graph
2013 .create_task_node("F", QueueFamilyType::Compute, PhantomData)
2014 .build();
2015 graph.add_edge(a, b).unwrap();
2016 graph.add_edge(a, d).unwrap();
2017 graph.add_edge(b, c).unwrap();
2018 graph.add_edge(c, d).unwrap();
2019 graph.add_edge(c, f).unwrap();
2020 graph.add_edge(d, c).unwrap();
2021 graph.add_edge(d, e).unwrap();
2022 graph.add_edge(f, b).unwrap();
2023
2024 let err = unsafe {
2025 graph.compile(&CompileInfo {
2026 queues: &queues.iter().collect::<Vec<_>>(),
2027 ..Default::default()
2028 })
2029 }
2030 .unwrap_err();
2031
2032 assert_eq!(err.kind, CompileErrorKind::Cycle);
2033 }
2034
2035 #[test]
2036 fn initial_pipeline_barrier() {
2037 let (resources, queues) = test_queues!();
2038
2039 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2040 let buffer = graph.add_buffer(&BufferCreateInfo::default());
2041 let image = graph.add_image(&ImageCreateInfo::default());
2042 let node1 = graph
2043 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2044 .buffer_access(buffer, AccessTypes::VERTEX_SHADER_UNIFORM_READ)
2045 .image_access(
2046 image,
2047 AccessTypes::FRAGMENT_SHADER_SAMPLED_READ,
2048 ImageLayoutType::General,
2049 )
2050 .build();
2051 let node2 = graph
2052 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2053 .buffer_access(buffer, AccessTypes::VERTEX_SHADER_SAMPLED_READ)
2054 .image_access(
2055 image,
2056 AccessTypes::FRAGMENT_SHADER_STORAGE_READ,
2057 ImageLayoutType::General,
2058 )
2059 .build();
2060 graph.add_edge(node1, node2).unwrap();
2061
2062 let graph = unsafe {
2063 graph.compile(&CompileInfo {
2064 queues: &queues.iter().collect::<Vec<_>>(),
2065 ..Default::default()
2066 })
2067 }
2068 .unwrap();
2069
2070 assert_matches_instructions!(
2071 graph,
2072 InitialPipelineBarrier {
2073 barriers: [
2074 {
2075 dst_stage_mask: VERTEX_SHADER,
2076 dst_access_mask: UNIFORM_READ | SHADER_SAMPLED_READ,
2077 new_layout: Undefined,
2078 resource: buffer,
2079 },
2080 {
2081 dst_stage_mask: FRAGMENT_SHADER,
2082 dst_access_mask: SHADER_SAMPLED_READ | SHADER_STORAGE_READ,
2083 new_layout: General,
2084 resource: image,
2085 },
2086 ],
2087 },
2088 ExecuteTask { node: node1 },
2089 ExecuteTask { node: node2 },
2090 FlushSubmit,
2091 Submit,
2092 );
2093 }
2094
2095 #[test]
2096 fn barrier1() {
2097 let (resources, queues) = test_queues!();
2098
2099 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2100 let buffer = graph.add_buffer(&BufferCreateInfo::default());
2101 let image = graph.add_image(&ImageCreateInfo::default());
2102 let node1 = graph
2103 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2104 .buffer_access(buffer, AccessTypes::FRAGMENT_SHADER_STORAGE_WRITE)
2105 .image_access(
2106 image,
2107 AccessTypes::FRAGMENT_SHADER_STORAGE_WRITE,
2108 ImageLayoutType::General,
2109 )
2110 .build();
2111 let node2 = graph
2112 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2113 .buffer_access(buffer, AccessTypes::INDIRECT_COMMAND_READ)
2114 .image_access(
2115 image,
2116 AccessTypes::FRAGMENT_SHADER_COLOR_INPUT_ATTACHMENT_READ,
2117 ImageLayoutType::General,
2118 )
2119 .build();
2120 let node3 = graph
2121 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2122 .buffer_access(buffer, AccessTypes::RAY_TRACING_SHADER_STORAGE_READ)
2123 .image_access(
2124 image,
2125 AccessTypes::RAY_TRACING_SHADER_COLOR_INPUT_ATTACHMENT_READ,
2126 ImageLayoutType::General,
2127 )
2128 .build();
2129 graph.add_edge(node1, node2).unwrap();
2130 graph.add_edge(node2, node3).unwrap();
2131
2132 let graph = unsafe {
2133 graph.compile(&CompileInfo {
2134 queues: &queues.iter().collect::<Vec<_>>(),
2135 ..Default::default()
2136 })
2137 }
2138 .unwrap();
2139
2140 assert_matches_instructions!(
2141 graph,
2142 ExecuteTask { node: node1 },
2143 PipelineBarrier {
2144 barriers: [
2145 {
2146 src_stage_mask: FRAGMENT_SHADER,
2147 src_access_mask: SHADER_STORAGE_WRITE,
2148 dst_stage_mask: DRAW_INDIRECT | RAY_TRACING_SHADER,
2149 dst_access_mask: INDIRECT_COMMAND_READ | SHADER_STORAGE_READ,
2150 old_layout: Undefined,
2151 new_layout: Undefined,
2152 resource: buffer,
2153 },
2154 {
2155 src_stage_mask: FRAGMENT_SHADER,
2156 src_access_mask: SHADER_STORAGE_WRITE,
2157 dst_stage_mask: FRAGMENT_SHADER | RAY_TRACING_SHADER,
2158 dst_access_mask: INPUT_ATTACHMENT_READ,
2159 old_layout: General,
2160 new_layout: General,
2161 resource: image,
2162 },
2163 ],
2164 },
2165 ExecuteTask { node: node2 },
2166 ExecuteTask { node: node3 },
2167 FlushSubmit,
2168 Submit,
2169 );
2170 }
2171
2172 #[test]
2173 fn barrier2() {
2174 let (resources, queues) = test_queues!();
2175
2176 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2177 let buffer = graph.add_buffer(&BufferCreateInfo::default());
2178 let image = graph.add_image(&ImageCreateInfo::default());
2179 let node1 = graph
2180 .create_task_node("", QueueFamilyType::Compute, PhantomData)
2181 .buffer_access(buffer, AccessTypes::COMPUTE_SHADER_STORAGE_WRITE)
2182 .image_access(
2183 image,
2184 AccessTypes::COPY_TRANSFER_WRITE,
2185 ImageLayoutType::General,
2186 )
2187 .build();
2188 let node2 = graph
2189 .create_task_node("", QueueFamilyType::Compute, PhantomData)
2190 .buffer_access(buffer, AccessTypes::COPY_TRANSFER_WRITE)
2191 .image_access(
2192 image,
2193 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
2194 ImageLayoutType::General,
2195 )
2196 .build();
2197 let node3 = graph
2198 .create_task_node("", QueueFamilyType::Compute, PhantomData)
2199 .buffer_access(buffer, AccessTypes::INDIRECT_COMMAND_READ)
2200 .image_access(
2201 image,
2202 AccessTypes::COMPUTE_SHADER_SAMPLED_READ,
2203 ImageLayoutType::General,
2204 )
2205 .build();
2206 graph.add_edge(node1, node2).unwrap();
2207 graph.add_edge(node2, node3).unwrap();
2208
2209 let graph = unsafe {
2210 graph.compile(&CompileInfo {
2211 queues: &queues.iter().collect::<Vec<_>>(),
2212 ..Default::default()
2213 })
2214 }
2215 .unwrap();
2216
2217 assert_matches_instructions!(
2218 graph,
2219 ExecuteTask { node: node1 },
2220 PipelineBarrier {
2221 barriers: [
2222 {
2223 src_stage_mask: COMPUTE_SHADER,
2224 src_access_mask: SHADER_STORAGE_WRITE,
2225 dst_stage_mask: COPY,
2226 dst_access_mask: TRANSFER_WRITE,
2227 old_layout: Undefined,
2228 new_layout: Undefined,
2229 resource: buffer,
2230 },
2231 {
2232 src_stage_mask: COPY,
2233 src_access_mask: TRANSFER_WRITE,
2234 dst_stage_mask: COMPUTE_SHADER,
2235 dst_access_mask: SHADER_STORAGE_WRITE,
2236 old_layout: General,
2237 new_layout: General,
2238 resource: image,
2239 },
2240 ],
2241 },
2242 ExecuteTask { node: node2 },
2243 PipelineBarrier {
2244 barriers: [
2245 {
2246 src_stage_mask: COPY,
2247 src_access_mask: TRANSFER_WRITE,
2248 dst_stage_mask: DRAW_INDIRECT,
2249 dst_access_mask: INDIRECT_COMMAND_READ,
2250 old_layout: Undefined,
2251 new_layout: Undefined,
2252 resource: buffer,
2253 },
2254 {
2255 src_stage_mask: COMPUTE_SHADER,
2256 src_access_mask: SHADER_STORAGE_WRITE,
2257 dst_stage_mask: COMPUTE_SHADER,
2258 dst_access_mask: SHADER_SAMPLED_READ,
2259 old_layout: General,
2260 new_layout: General,
2261 resource: image,
2262 },
2263 ],
2264 },
2265 ExecuteTask { node: node3 },
2266 FlushSubmit,
2267 Submit,
2268 );
2269 }
2270
2271 #[test]
2272 fn semaphore1() {
2273 let (resources, queues) = test_queues!();
2274
2275 if !has_compute_only_queue(&queues) {
2276 return;
2277 }
2278
2279 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2290 let a = graph
2291 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
2292 .build();
2293 let b = graph
2294 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
2295 .build();
2296 let c = graph
2297 .create_task_node("C", QueueFamilyType::Compute, PhantomData)
2298 .build();
2299 graph.add_edge(a, c).unwrap();
2300 graph.add_edge(b, c).unwrap();
2301
2302 let graph = unsafe {
2303 graph.compile(&CompileInfo {
2304 queues: &queues.iter().collect::<Vec<_>>(),
2305 ..Default::default()
2306 })
2307 }
2308 .unwrap();
2309
2310 assert_matches_instructions!(
2311 graph,
2312 ExecuteTask { node: b },
2313 SignalSemaphore {
2315 semaphore_index: semaphore1,
2316 stage_mask: ALL_COMMANDS,
2317 },
2318 FlushSubmit,
2319 ExecuteTask { node: a },
2320 SignalSemaphore {
2321 semaphore_index: semaphore2,
2322 stage_mask: ALL_COMMANDS,
2323 },
2324 FlushSubmit,
2325 Submit,
2326 WaitSemaphore {
2327 semaphore_index: semaphore1,
2328 stage_mask: ALL_COMMANDS,
2329 },
2330 WaitSemaphore {
2331 semaphore_index: semaphore2,
2332 stage_mask: ALL_COMMANDS,
2333 },
2334 ExecuteTask { node: c },
2335 FlushSubmit,
2336 Submit,
2337 );
2338 }
2339
2340 #[test]
2341 fn semaphore2() {
2342 let (resources, queues) = test_queues!();
2343
2344 if !has_compute_only_queue(&queues) {
2345 return;
2346 }
2347
2348 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2359 let a = graph
2360 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
2361 .build();
2362 let b = graph
2363 .create_task_node("B", QueueFamilyType::Compute, PhantomData)
2364 .build();
2365 let c = graph
2366 .create_task_node("C", QueueFamilyType::Compute, PhantomData)
2367 .build();
2368 graph.add_edge(a, b).unwrap();
2369 graph.add_edge(a, c).unwrap();
2370
2371 let graph = unsafe {
2372 graph.compile(&CompileInfo {
2373 queues: &queues.iter().collect::<Vec<_>>(),
2374 ..Default::default()
2375 })
2376 }
2377 .unwrap();
2378
2379 assert_matches_instructions!(
2380 graph,
2381 ExecuteTask { node: a },
2382 SignalSemaphore {
2384 semaphore_index: semaphore1,
2385 stage_mask: ALL_COMMANDS,
2386 },
2387 SignalSemaphore {
2388 semaphore_index: semaphore2,
2389 stage_mask: ALL_COMMANDS,
2390 },
2391 FlushSubmit,
2392 Submit,
2393 WaitSemaphore {
2394 semaphore_index: semaphore2,
2395 stage_mask: ALL_COMMANDS,
2396 },
2397 ExecuteTask { node: c },
2398 FlushSubmit,
2399 WaitSemaphore {
2400 semaphore_index: semaphore1,
2401 stage_mask: ALL_COMMANDS,
2402 },
2403 ExecuteTask { node: b },
2404 FlushSubmit,
2405 Submit,
2406 );
2407 }
2408
2409 #[test]
2410 fn semaphore3() {
2411 let (resources, queues) = test_queues!();
2412
2413 if !has_compute_only_queue(&queues) {
2414 return;
2415 }
2416
2417 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2428 let a = graph
2429 .create_task_node("A", QueueFamilyType::Graphics, PhantomData)
2430 .build();
2431 let b = graph
2432 .create_task_node("B", QueueFamilyType::Graphics, PhantomData)
2433 .build();
2434 let c = graph
2435 .create_task_node("C", QueueFamilyType::Compute, PhantomData)
2436 .build();
2437 let d = graph
2438 .create_task_node("D", QueueFamilyType::Compute, PhantomData)
2439 .build();
2440 let e = graph
2441 .create_task_node("E", QueueFamilyType::Graphics, PhantomData)
2442 .build();
2443 graph.add_edge(a, d).unwrap();
2444 graph.add_edge(a, e).unwrap();
2445 graph.add_edge(b, c).unwrap();
2446 graph.add_edge(c, d).unwrap();
2447 graph.add_edge(d, e).unwrap();
2448
2449 let graph = unsafe {
2450 graph.compile(&CompileInfo {
2451 queues: &queues.iter().collect::<Vec<_>>(),
2452 ..Default::default()
2453 })
2454 }
2455 .unwrap();
2456
2457 assert_matches_instructions!(
2459 graph,
2460 ExecuteTask { node: b },
2461 SignalSemaphore {
2462 semaphore_index: semaphore1,
2463 stage_mask: ALL_COMMANDS,
2464 },
2465 FlushSubmit,
2466 Submit,
2467 WaitSemaphore {
2468 semaphore_index: semaphore1,
2469 stage_mask: ALL_COMMANDS,
2470 },
2471 ExecuteTask { node: c },
2472 FlushSubmit,
2473 Submit,
2474 ExecuteTask { node: a },
2475 SignalSemaphore {
2476 semaphore_index: semaphore2,
2477 stage_mask: ALL_COMMANDS,
2478 },
2479 FlushSubmit,
2480 Submit,
2481 WaitSemaphore {
2482 semaphore_index: semaphore2,
2483 stage_mask: ALL_COMMANDS,
2484 },
2485 ExecuteTask { node: d },
2486 SignalSemaphore {
2487 semaphore_index: semaphore3,
2488 stage_mask: ALL_COMMANDS,
2489 },
2490 FlushSubmit,
2491 Submit,
2492 WaitSemaphore {
2493 semaphore_index: semaphore3,
2494 stage_mask: ALL_COMMANDS,
2495 },
2496 ExecuteTask { node: e },
2497 FlushSubmit,
2498 Submit,
2499 );
2500 }
2501
2502 #[test]
2503 fn render_pass1() {
2504 let (resources, queues) = test_queues!();
2505
2506 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2507 let color_image = graph.add_image(&ImageCreateInfo {
2508 format: Format::R8G8B8A8_UNORM,
2509 ..Default::default()
2510 });
2511 let depth_image = graph.add_image(&ImageCreateInfo {
2512 format: Format::D16_UNORM,
2513 ..Default::default()
2514 });
2515 let framebuffer = graph.add_framebuffer();
2516 let node1 = graph
2517 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2518 .framebuffer(framebuffer)
2519 .color_attachment(
2520 color_image,
2521 AccessTypes::COLOR_ATTACHMENT_READ | AccessTypes::COLOR_ATTACHMENT_WRITE,
2522 ImageLayoutType::Optimal,
2523 &AttachmentInfo {
2524 clear: true,
2525 ..Default::default()
2526 },
2527 )
2528 .build();
2529 let node2 = graph
2530 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2531 .framebuffer(framebuffer)
2532 .color_attachment(
2533 color_image,
2534 AccessTypes::COLOR_ATTACHMENT_WRITE,
2535 ImageLayoutType::Optimal,
2536 &AttachmentInfo::default(),
2537 )
2538 .depth_stencil_attachment(
2539 depth_image,
2540 AccessTypes::DEPTH_STENCIL_ATTACHMENT_READ,
2541 ImageLayoutType::Optimal,
2542 &AttachmentInfo {
2543 clear: true,
2544 ..Default::default()
2545 },
2546 )
2547 .build();
2548 graph.add_edge(node1, node2).unwrap();
2549
2550 let graph = unsafe {
2551 graph.compile(&CompileInfo {
2552 queues: &queues.iter().collect::<Vec<_>>(),
2553 ..Default::default()
2554 })
2555 }
2556 .unwrap();
2557
2558 assert_matches_instructions!(
2559 graph,
2560 BeginRenderPass,
2561 ExecuteTask { node: node1 },
2562 NextSubpass,
2563 ExecuteTask { node: node2 },
2564 EndRenderPass,
2565 FlushSubmit,
2566 Submit,
2567 );
2568 }
2569
2570 #[test]
2571 fn render_pass2() {
2572 let (resources, queues) = test_queues!();
2573
2574 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2575 let color_image = graph.add_image(&ImageCreateInfo {
2576 format: Format::R8G8B8A8_UNORM,
2577 ..Default::default()
2578 });
2579 let depth_image = graph.add_image(&ImageCreateInfo {
2580 format: Format::D16_UNORM,
2581 ..Default::default()
2582 });
2583 let framebuffer = graph.add_framebuffer();
2584 let node1 = graph
2585 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2586 .framebuffer(framebuffer)
2587 .color_attachment(
2588 color_image,
2589 AccessTypes::COLOR_ATTACHMENT_READ | AccessTypes::COLOR_ATTACHMENT_WRITE,
2590 ImageLayoutType::Optimal,
2591 &AttachmentInfo {
2592 clear: true,
2593 ..Default::default()
2594 },
2595 )
2596 .depth_stencil_attachment(
2597 depth_image,
2598 AccessTypes::DEPTH_STENCIL_ATTACHMENT_READ
2599 | AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
2600 ImageLayoutType::Optimal,
2601 &AttachmentInfo {
2602 clear: true,
2603 ..Default::default()
2604 },
2605 )
2606 .build();
2607 let node2 = graph
2608 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2609 .framebuffer(framebuffer)
2610 .input_attachment(
2611 color_image,
2612 AccessTypes::FRAGMENT_SHADER_COLOR_INPUT_ATTACHMENT_READ,
2613 ImageLayoutType::General,
2614 &AttachmentInfo {
2615 clear: true,
2616 ..Default::default()
2617 },
2618 )
2619 .color_attachment(
2620 color_image,
2621 AccessTypes::COLOR_ATTACHMENT_WRITE,
2622 ImageLayoutType::General,
2623 &AttachmentInfo {
2624 clear: true,
2625 ..Default::default()
2626 },
2627 )
2628 .depth_stencil_attachment(
2629 depth_image,
2630 AccessTypes::DEPTH_STENCIL_ATTACHMENT_READ,
2631 ImageLayoutType::Optimal,
2632 &AttachmentInfo {
2633 clear: true,
2634 ..Default::default()
2635 },
2636 )
2637 .build();
2638 graph.add_edge(node1, node2).unwrap();
2639
2640 let graph = unsafe {
2641 graph.compile(&CompileInfo {
2642 queues: &queues.iter().collect::<Vec<_>>(),
2643 ..Default::default()
2644 })
2645 }
2646 .unwrap();
2647
2648 assert_matches_instructions!(
2649 graph,
2650 BeginRenderPass,
2651 ExecuteTask { node: node1 },
2652 NextSubpass,
2653 ClearAttachments {
2654 node: node2,
2655 attachments: [color_image, depth_image],
2656 },
2657 ExecuteTask { node: node2 },
2658 EndRenderPass,
2659 FlushSubmit,
2660 Submit,
2661 );
2662 }
2663
2664 #[test]
2665 fn render_pass3() {
2666 let (resources, queues) = test_queues!();
2667
2668 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2669 let color_image = graph.add_image(&ImageCreateInfo {
2670 format: Format::R8G8B8A8_UNORM,
2671 ..Default::default()
2672 });
2673 let depth_image = graph.add_image(&ImageCreateInfo {
2674 format: Format::D16_UNORM,
2675 ..Default::default()
2676 });
2677 let framebuffer = graph.add_framebuffer();
2678 let node1 = graph
2679 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2680 .framebuffer(framebuffer)
2681 .color_attachment(
2682 color_image,
2683 AccessTypes::COLOR_ATTACHMENT_READ | AccessTypes::COLOR_ATTACHMENT_WRITE,
2684 ImageLayoutType::Optimal,
2685 &AttachmentInfo {
2686 clear: true,
2687 ..Default::default()
2688 },
2689 )
2690 .depth_stencil_attachment(
2691 depth_image,
2692 AccessTypes::DEPTH_STENCIL_ATTACHMENT_READ
2693 | AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
2694 ImageLayoutType::Optimal,
2695 &AttachmentInfo {
2696 clear: true,
2697 ..Default::default()
2698 },
2699 )
2700 .build();
2701 let node2 = graph
2702 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2703 .framebuffer(framebuffer)
2704 .color_attachment(
2705 color_image,
2706 AccessTypes::COLOR_ATTACHMENT_WRITE,
2707 ImageLayoutType::Optimal,
2708 &AttachmentInfo {
2709 clear: true,
2710 ..Default::default()
2711 },
2712 )
2713 .depth_stencil_attachment(
2714 depth_image,
2715 AccessTypes::DEPTH_STENCIL_ATTACHMENT_READ,
2716 ImageLayoutType::Optimal,
2717 &AttachmentInfo {
2718 clear: true,
2719 ..Default::default()
2720 },
2721 )
2722 .build();
2723 let node3 = graph
2724 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2725 .framebuffer(framebuffer)
2726 .input_attachment(
2727 color_image,
2728 AccessTypes::FRAGMENT_SHADER_COLOR_INPUT_ATTACHMENT_READ,
2729 ImageLayoutType::Optimal,
2730 &AttachmentInfo::default(),
2731 )
2732 .build();
2733 graph.add_edge(node1, node2).unwrap();
2734 graph.add_edge(node2, node3).unwrap();
2735
2736 let graph = unsafe {
2737 graph.compile(&CompileInfo {
2738 queues: &queues.iter().collect::<Vec<_>>(),
2739 ..Default::default()
2740 })
2741 }
2742 .unwrap();
2743
2744 assert_matches_instructions!(
2745 graph,
2746 BeginRenderPass,
2747 ExecuteTask { node: node1 },
2748 ClearAttachments {
2749 node: node2,
2750 attachments: [color_image, depth_image],
2751 },
2752 ExecuteTask { node: node2 },
2753 NextSubpass,
2754 ExecuteTask { node: node3 },
2755 EndRenderPass,
2756 FlushSubmit,
2757 Submit,
2758 );
2759 }
2760
2761 #[test]
2762 fn queue_family_ownership_transfer1() {
2763 let (resources, queues) = test_queues!();
2764
2765 if !has_compute_only_queue(&queues) {
2766 return;
2767 }
2768
2769 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2770 let buffer1 = graph.add_buffer(&BufferCreateInfo::default());
2771 let buffer2 = graph.add_buffer(&BufferCreateInfo::default());
2772 let image1 = graph.add_image(&ImageCreateInfo::default());
2773 let image2 = graph.add_image(&ImageCreateInfo::default());
2774 let compute_node = graph
2775 .create_task_node("", QueueFamilyType::Compute, PhantomData)
2776 .buffer_access(buffer1, AccessTypes::COMPUTE_SHADER_STORAGE_WRITE)
2777 .buffer_access(buffer2, AccessTypes::COMPUTE_SHADER_STORAGE_READ)
2778 .image_access(
2779 image1,
2780 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
2781 ImageLayoutType::Optimal,
2782 )
2783 .image_access(
2784 image2,
2785 AccessTypes::COMPUTE_SHADER_SAMPLED_READ,
2786 ImageLayoutType::Optimal,
2787 )
2788 .build();
2789 let graphics_node = graph
2790 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2791 .buffer_access(buffer1, AccessTypes::INDEX_READ)
2792 .buffer_access(buffer2, AccessTypes::VERTEX_SHADER_UNIFORM_READ)
2793 .image_access(
2794 image1,
2795 AccessTypes::VERTEX_SHADER_SAMPLED_READ,
2796 ImageLayoutType::General,
2797 )
2798 .image_access(
2799 image2,
2800 AccessTypes::FRAGMENT_SHADER_SAMPLED_READ,
2801 ImageLayoutType::General,
2802 )
2803 .build();
2804 graph.add_edge(compute_node, graphics_node).unwrap();
2805
2806 let graph = unsafe {
2807 graph.compile(&CompileInfo {
2808 queues: &queues.iter().collect::<Vec<_>>(),
2809 ..Default::default()
2810 })
2811 }
2812 .unwrap();
2813
2814 assert_matches_instructions!(
2815 graph,
2816 ExecuteTask { node: compute_node },
2817 SignalSemaphore {
2818 semaphore_index: semaphore,
2819 stage_mask: ALL_COMMANDS,
2820 },
2821 PipelineBarrier {
2822 barriers: [
2823 {
2824 src_stage_mask: COMPUTE_SHADER,
2825 src_access_mask: SHADER_STORAGE_WRITE,
2826 dst_stage_mask: ,
2827 dst_access_mask: ,
2828 old_layout: Undefined,
2829 new_layout: Undefined,
2830 resource: buffer1,
2831 },
2832 {
2833 src_stage_mask: COMPUTE_SHADER,
2834 src_access_mask: ,
2835 dst_stage_mask: ,
2836 dst_access_mask: ,
2837 old_layout: Undefined,
2838 new_layout: Undefined,
2839 resource: buffer2,
2840 },
2841 {
2842 src_stage_mask: COMPUTE_SHADER,
2843 src_access_mask: SHADER_STORAGE_WRITE,
2844 dst_stage_mask: ,
2845 dst_access_mask: ,
2846 old_layout: General,
2847 new_layout: General,
2848 resource: image1,
2849 },
2850 {
2851 src_stage_mask: COMPUTE_SHADER,
2852 src_access_mask: ,
2853 dst_stage_mask: ,
2854 dst_access_mask: ,
2855 old_layout: ShaderReadOnlyOptimal,
2856 new_layout: General,
2857 resource: image2,
2858 },
2859 ],
2860 },
2861 FlushSubmit,
2862 Submit,
2863 WaitSemaphore {
2864 semaphore_index: semaphore,
2865 stage_mask: ALL_COMMANDS,
2866 },
2867 PipelineBarrier {
2868 barriers: [
2869 {
2870 src_stage_mask: ,
2871 src_access_mask: ,
2872 dst_stage_mask: INDEX_INPUT,
2873 dst_access_mask: INDEX_READ,
2874 old_layout: Undefined,
2875 new_layout: Undefined,
2876 resource: buffer1,
2877 },
2878 {
2879 src_stage_mask: ,
2880 src_access_mask: ,
2881 dst_stage_mask: VERTEX_SHADER,
2882 dst_access_mask: UNIFORM_READ,
2883 old_layout: Undefined,
2884 new_layout: Undefined,
2885 resource: buffer2,
2886 },
2887 {
2888 src_stage_mask: ,
2889 src_access_mask: ,
2890 dst_stage_mask: VERTEX_SHADER,
2891 dst_access_mask: SHADER_SAMPLED_READ,
2892 old_layout: General,
2893 new_layout: General,
2894 resource: image1,
2895 },
2896 {
2897 src_stage_mask: ,
2898 src_access_mask: ,
2899 dst_stage_mask: FRAGMENT_SHADER,
2900 dst_access_mask: SHADER_SAMPLED_READ,
2901 old_layout: ShaderReadOnlyOptimal,
2902 new_layout: General,
2903 resource: image2,
2904 },
2905 ],
2906 },
2907 ExecuteTask { node: graphics_node },
2908 FlushSubmit,
2909 Submit,
2910 );
2911 }
2912
2913 #[test]
2914 fn queue_family_ownership_transfer2() {
2915 let (resources, queues) = test_queues!();
2916
2917 if !has_compute_only_queue(&queues) {
2918 return;
2919 }
2920
2921 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
2922 let sharing = Sharing::Concurrent(queues.iter().map(|q| q.queue_family_index()).collect());
2923 let buffer1 = graph.add_buffer(&BufferCreateInfo {
2924 sharing: sharing.clone(),
2925 ..Default::default()
2926 });
2927 let buffer2 = graph.add_buffer(&BufferCreateInfo {
2928 sharing: sharing.clone(),
2929 ..Default::default()
2930 });
2931 let image1 = graph.add_image(&ImageCreateInfo {
2932 sharing: sharing.clone(),
2933 ..Default::default()
2934 });
2935 let image2 = graph.add_image(&ImageCreateInfo {
2936 sharing: sharing.clone(),
2937 ..Default::default()
2938 });
2939 let compute_node = graph
2940 .create_task_node("", QueueFamilyType::Compute, PhantomData)
2941 .buffer_access(buffer1, AccessTypes::COMPUTE_SHADER_STORAGE_WRITE)
2942 .buffer_access(buffer2, AccessTypes::COMPUTE_SHADER_STORAGE_READ)
2943 .image_access(
2944 image1,
2945 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
2946 ImageLayoutType::Optimal,
2947 )
2948 .image_access(
2949 image2,
2950 AccessTypes::COMPUTE_SHADER_SAMPLED_READ,
2951 ImageLayoutType::Optimal,
2952 )
2953 .build();
2954 let graphics_node = graph
2955 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
2956 .buffer_access(buffer1, AccessTypes::INDEX_READ)
2957 .buffer_access(buffer2, AccessTypes::VERTEX_SHADER_UNIFORM_READ)
2958 .image_access(
2959 image1,
2960 AccessTypes::VERTEX_SHADER_SAMPLED_READ,
2961 ImageLayoutType::General,
2962 )
2963 .image_access(
2964 image2,
2965 AccessTypes::FRAGMENT_SHADER_SAMPLED_READ,
2966 ImageLayoutType::General,
2967 )
2968 .build();
2969 graph.add_edge(compute_node, graphics_node).unwrap();
2970
2971 let graph = unsafe {
2972 graph.compile(&CompileInfo {
2973 queues: &queues.iter().collect::<Vec<_>>(),
2974 ..Default::default()
2975 })
2976 }
2977 .unwrap();
2978
2979 assert_matches_instructions!(
2980 graph,
2981 ExecuteTask { node: compute_node },
2982 SignalSemaphore {
2983 semaphore_index: semaphore,
2984 stage_mask: ALL_COMMANDS,
2985 },
2986 FlushSubmit,
2987 Submit,
2988 WaitSemaphore {
2989 semaphore_index: semaphore,
2990 stage_mask: ALL_COMMANDS,
2991 },
2992 PipelineBarrier {
2993 barriers: [
2994 {
2995 src_stage_mask: ,
2996 src_access_mask: ,
2997 dst_stage_mask: FRAGMENT_SHADER,
2998 dst_access_mask: SHADER_SAMPLED_READ,
2999 old_layout: ShaderReadOnlyOptimal,
3000 new_layout: General,
3001 resource: image2,
3002 },
3003 ],
3004 },
3005 ExecuteTask { node: graphics_node },
3006 FlushSubmit,
3007 Submit,
3008 );
3009 }
3010
3011 #[test]
3012 fn queue_family_ownership_transfer3() {
3013 let (resources, queues) = test_queues!();
3014
3015 if !has_compute_only_queue(&queues) {
3016 return;
3017 }
3018
3019 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3020 let buffer1 = graph.add_buffer(&BufferCreateInfo::default());
3021 let buffer2 = graph.add_buffer(&BufferCreateInfo::default());
3022 let image1 = graph.add_image(&ImageCreateInfo::default());
3023 let image2 = graph.add_image(&ImageCreateInfo::default());
3024 let color_image = graph.add_image(&ImageCreateInfo {
3025 format: Format::R8G8B8A8_UNORM,
3026 ..Default::default()
3027 });
3028 let depth_image = graph.add_image(&ImageCreateInfo {
3029 format: Format::D16_UNORM,
3030 ..Default::default()
3031 });
3032 let framebuffer = graph.add_framebuffer();
3033 let compute_node = graph
3034 .create_task_node("", QueueFamilyType::Compute, PhantomData)
3035 .buffer_access(buffer1, AccessTypes::COMPUTE_SHADER_STORAGE_WRITE)
3036 .buffer_access(buffer2, AccessTypes::COMPUTE_SHADER_STORAGE_READ)
3037 .image_access(
3038 image1,
3039 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
3040 ImageLayoutType::Optimal,
3041 )
3042 .image_access(
3043 image2,
3044 AccessTypes::COMPUTE_SHADER_SAMPLED_READ,
3045 ImageLayoutType::Optimal,
3046 )
3047 .build();
3048 let graphics_node1 = graph
3049 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3050 .framebuffer(framebuffer)
3051 .color_attachment(
3052 color_image,
3053 AccessTypes::COLOR_ATTACHMENT_WRITE,
3054 ImageLayoutType::Optimal,
3055 &AttachmentInfo::default(),
3056 )
3057 .depth_stencil_attachment(
3058 depth_image,
3059 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3060 ImageLayoutType::Optimal,
3061 &AttachmentInfo::default(),
3062 )
3063 .build();
3064 let graphics_node2 = graph
3065 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3066 .framebuffer(framebuffer)
3067 .color_attachment(
3068 color_image,
3069 AccessTypes::COLOR_ATTACHMENT_WRITE,
3070 ImageLayoutType::Optimal,
3071 &AttachmentInfo::default(),
3072 )
3073 .input_attachment(
3074 depth_image,
3075 AccessTypes::FRAGMENT_SHADER_DEPTH_STENCIL_INPUT_ATTACHMENT_READ,
3076 ImageLayoutType::Optimal,
3077 &AttachmentInfo::default(),
3078 )
3079 .buffer_access(buffer1, AccessTypes::INDEX_READ)
3080 .buffer_access(buffer2, AccessTypes::VERTEX_SHADER_UNIFORM_READ)
3081 .image_access(
3082 image1,
3083 AccessTypes::VERTEX_SHADER_SAMPLED_READ,
3084 ImageLayoutType::General,
3085 )
3086 .image_access(
3087 image2,
3088 AccessTypes::FRAGMENT_SHADER_SAMPLED_READ,
3089 ImageLayoutType::General,
3090 )
3091 .build();
3092 graph.add_edge(compute_node, graphics_node1).unwrap();
3093 graph.add_edge(graphics_node1, graphics_node2).unwrap();
3094
3095 let graph = unsafe {
3096 graph.compile(&CompileInfo {
3097 queues: &queues.iter().collect::<Vec<_>>(),
3098 ..Default::default()
3099 })
3100 }
3101 .unwrap();
3102
3103 assert_matches_instructions!(
3104 graph,
3105 ExecuteTask { node: compute_node },
3106 SignalSemaphore {
3107 semaphore_index: semaphore,
3108 stage_mask: ALL_COMMANDS,
3109 },
3110 PipelineBarrier {
3111 barriers: [
3112 {
3113 src_stage_mask: COMPUTE_SHADER,
3114 src_access_mask: SHADER_STORAGE_WRITE,
3115 dst_stage_mask: ,
3116 dst_access_mask: ,
3117 old_layout: Undefined,
3118 new_layout: Undefined,
3119 resource: buffer1,
3120 },
3121 {
3122 src_stage_mask: COMPUTE_SHADER,
3123 src_access_mask: ,
3124 dst_stage_mask: ,
3125 dst_access_mask: ,
3126 old_layout: Undefined,
3127 new_layout: Undefined,
3128 resource: buffer2,
3129 },
3130 {
3131 src_stage_mask: COMPUTE_SHADER,
3132 src_access_mask: SHADER_STORAGE_WRITE,
3133 dst_stage_mask: ,
3134 dst_access_mask: ,
3135 old_layout: General,
3136 new_layout: General,
3137 resource: image1,
3138 },
3139 {
3140 src_stage_mask: COMPUTE_SHADER,
3141 src_access_mask: ,
3142 dst_stage_mask: ,
3143 dst_access_mask: ,
3144 old_layout: ShaderReadOnlyOptimal,
3145 new_layout: General,
3146 resource: image2,
3147 },
3148 ],
3149 },
3150 FlushSubmit,
3151 Submit,
3152 WaitSemaphore {
3153 semaphore_index: semaphore,
3154 stage_mask: ALL_COMMANDS,
3155 },
3156 PipelineBarrier {
3157 barriers: [
3158 {
3159 src_stage_mask: ,
3160 src_access_mask: ,
3161 dst_stage_mask: INDEX_INPUT,
3162 dst_access_mask: INDEX_READ,
3163 old_layout: Undefined,
3164 new_layout: Undefined,
3165 resource: buffer1,
3166 },
3167 {
3168 src_stage_mask: ,
3169 src_access_mask: ,
3170 dst_stage_mask: VERTEX_SHADER,
3171 dst_access_mask: UNIFORM_READ,
3172 old_layout: Undefined,
3173 new_layout: Undefined,
3174 resource: buffer2,
3175 },
3176 {
3177 src_stage_mask: ,
3178 src_access_mask: ,
3179 dst_stage_mask: VERTEX_SHADER,
3180 dst_access_mask: SHADER_SAMPLED_READ,
3181 old_layout: General,
3182 new_layout: General,
3183 resource: image1,
3184 },
3185 {
3186 src_stage_mask: ,
3187 src_access_mask: ,
3188 dst_stage_mask: FRAGMENT_SHADER,
3189 dst_access_mask: SHADER_SAMPLED_READ,
3190 old_layout: ShaderReadOnlyOptimal,
3191 new_layout: General,
3192 resource: image2,
3193 },
3194 ],
3195 },
3196 BeginRenderPass,
3197 ExecuteTask { node: graphics_node1 },
3198 NextSubpass,
3199 ExecuteTask { node: graphics_node2 },
3200 EndRenderPass,
3201 FlushSubmit,
3202 Submit,
3203 );
3204 }
3205
3206 #[test]
3207 fn queue_family_ownership_transfer4() {
3208 let (resources, queues) = test_queues!();
3209
3210 if !has_compute_only_queue(&queues) {
3211 return;
3212 }
3213
3214 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3215 let sharing = Sharing::Concurrent(queues.iter().map(|q| q.queue_family_index()).collect());
3216 let buffer1 = graph.add_buffer(&BufferCreateInfo {
3217 sharing: sharing.clone(),
3218 ..Default::default()
3219 });
3220 let buffer2 = graph.add_buffer(&BufferCreateInfo {
3221 sharing: sharing.clone(),
3222 ..Default::default()
3223 });
3224 let image1 = graph.add_image(&ImageCreateInfo {
3225 sharing: sharing.clone(),
3226 ..Default::default()
3227 });
3228 let image2 = graph.add_image(&ImageCreateInfo {
3229 sharing: sharing.clone(),
3230 ..Default::default()
3231 });
3232 let color_image = graph.add_image(&ImageCreateInfo {
3233 format: Format::R8G8B8A8_UNORM,
3234 ..Default::default()
3235 });
3236 let depth_image = graph.add_image(&ImageCreateInfo {
3237 format: Format::D16_UNORM,
3238 ..Default::default()
3239 });
3240 let framebuffer = graph.add_framebuffer();
3241 let compute_node = graph
3242 .create_task_node("", QueueFamilyType::Compute, PhantomData)
3243 .buffer_access(buffer1, AccessTypes::COMPUTE_SHADER_STORAGE_WRITE)
3244 .buffer_access(buffer2, AccessTypes::COMPUTE_SHADER_STORAGE_READ)
3245 .image_access(
3246 image1,
3247 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
3248 ImageLayoutType::Optimal,
3249 )
3250 .image_access(
3251 image2,
3252 AccessTypes::COMPUTE_SHADER_SAMPLED_READ,
3253 ImageLayoutType::Optimal,
3254 )
3255 .build();
3256 let graphics_node1 = graph
3257 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3258 .framebuffer(framebuffer)
3259 .color_attachment(
3260 color_image,
3261 AccessTypes::COLOR_ATTACHMENT_WRITE,
3262 ImageLayoutType::Optimal,
3263 &AttachmentInfo::default(),
3264 )
3265 .depth_stencil_attachment(
3266 depth_image,
3267 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3268 ImageLayoutType::Optimal,
3269 &AttachmentInfo::default(),
3270 )
3271 .build();
3272 let graphics_node2 = graph
3273 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3274 .framebuffer(framebuffer)
3275 .color_attachment(
3276 color_image,
3277 AccessTypes::COLOR_ATTACHMENT_WRITE,
3278 ImageLayoutType::Optimal,
3279 &AttachmentInfo::default(),
3280 )
3281 .input_attachment(
3282 depth_image,
3283 AccessTypes::FRAGMENT_SHADER_DEPTH_STENCIL_INPUT_ATTACHMENT_READ,
3284 ImageLayoutType::Optimal,
3285 &AttachmentInfo::default(),
3286 )
3287 .buffer_access(buffer1, AccessTypes::INDEX_READ)
3288 .buffer_access(buffer2, AccessTypes::VERTEX_SHADER_UNIFORM_READ)
3289 .image_access(
3290 image1,
3291 AccessTypes::VERTEX_SHADER_SAMPLED_READ,
3292 ImageLayoutType::General,
3293 )
3294 .image_access(
3295 image2,
3296 AccessTypes::FRAGMENT_SHADER_SAMPLED_READ,
3297 ImageLayoutType::General,
3298 )
3299 .build();
3300 graph.add_edge(compute_node, graphics_node1).unwrap();
3301 graph.add_edge(graphics_node1, graphics_node2).unwrap();
3302
3303 let graph = unsafe {
3304 graph.compile(&CompileInfo {
3305 queues: &queues.iter().collect::<Vec<_>>(),
3306 ..Default::default()
3307 })
3308 }
3309 .unwrap();
3310
3311 assert_matches_instructions!(
3312 graph,
3313 ExecuteTask {
3314 node: compute_node,
3315 },
3316 SignalSemaphore {
3317 semaphore_index: semaphore,
3318 stage_mask: ALL_COMMANDS,
3319 },
3320 FlushSubmit,
3321 Submit,
3322 WaitSemaphore {
3323 semaphore_index: semaphore,
3324 stage_mask: ALL_COMMANDS,
3325 },
3326 PipelineBarrier {
3327 barriers: [
3328 {
3329 src_stage_mask: ,
3330 src_access_mask: ,
3331 dst_stage_mask: FRAGMENT_SHADER,
3332 dst_access_mask: SHADER_SAMPLED_READ,
3333 old_layout: ShaderReadOnlyOptimal,
3334 new_layout: General,
3335 resource: image2,
3336 },
3337 ],
3338 },
3339 BeginRenderPass,
3340 ExecuteTask { node: graphics_node1 },
3341 NextSubpass,
3342 ExecuteTask { node: graphics_node2 },
3343 EndRenderPass,
3344 FlushSubmit,
3345 Submit,
3346 );
3347 }
3348
3349 #[test]
3350 fn swapchain1() {
3351 let (resources, queues) = test_queues!();
3352
3353 let queue_family_properties = resources
3354 .device()
3355 .physical_device()
3356 .queue_family_properties();
3357
3358 let present_queue = queues.iter().find(|q| {
3359 let queue_flags = queue_family_properties[q.queue_family_index() as usize].queue_flags;
3360
3361 queue_flags.contains(QueueFlags::GRAPHICS)
3362 });
3363
3364 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3365 let swapchain1 = graph.add_swapchain(&SwapchainCreateInfo::default());
3366 let swapchain2 = graph.add_swapchain(&SwapchainCreateInfo::default());
3367 let node = graph
3368 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3369 .image_access(
3370 swapchain1.current_image_id(),
3371 AccessTypes::COLOR_ATTACHMENT_WRITE,
3372 ImageLayoutType::Optimal,
3373 )
3374 .image_access(
3375 swapchain2.current_image_id(),
3376 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
3377 ImageLayoutType::Optimal,
3378 )
3379 .build();
3380
3381 let graph = unsafe {
3382 graph.compile(&CompileInfo {
3383 queues: &queues.iter().collect::<Vec<_>>(),
3384 present_queue: Some(present_queue.unwrap()),
3385 ..Default::default()
3386 })
3387 }
3388 .unwrap();
3389
3390 assert_matches_instructions!(
3391 graph,
3392 WaitAcquire {
3393 swapchain_id: swapchain1,
3394 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3395 },
3396 WaitAcquire {
3397 swapchain_id: swapchain2,
3398 stage_mask: COMPUTE_SHADER,
3399 },
3400 PipelineBarrier {
3401 barriers: [
3402 {
3403 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3404 src_access_mask: ,
3405 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3406 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3407 old_layout: Undefined,
3408 new_layout: ColorAttachmentOptimal,
3409 resource: swapchain1,
3410 },
3411 {
3412 src_stage_mask: COMPUTE_SHADER,
3413 src_access_mask: ,
3414 dst_stage_mask: COMPUTE_SHADER,
3415 dst_access_mask: SHADER_STORAGE_WRITE,
3416 old_layout: Undefined,
3417 new_layout: General,
3418 resource: swapchain2,
3419 },
3420 ],
3421 },
3422 ExecuteTask { node: node },
3423 SignalPresent {
3424 swapchain_id: swapchain1,
3425 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3426 },
3427 SignalPresent {
3428 swapchain_id: swapchain2,
3429 stage_mask: COMPUTE_SHADER,
3430 },
3431 PipelineBarrier {
3432 barriers: [
3433 {
3434 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3435 src_access_mask: COLOR_ATTACHMENT_WRITE,
3436 dst_stage_mask: ,
3437 dst_access_mask: ,
3438 old_layout: ColorAttachmentOptimal,
3439 new_layout: PresentSrc,
3440 resource: swapchain1,
3441 },
3442 {
3443 src_stage_mask: COMPUTE_SHADER,
3444 src_access_mask: SHADER_STORAGE_WRITE,
3445 dst_stage_mask: ,
3446 dst_access_mask: ,
3447 old_layout: General,
3448 new_layout: PresentSrc,
3449 resource: swapchain2,
3450 },
3451 ],
3452 },
3453 FlushSubmit,
3454 Submit,
3455 );
3456 }
3457
3458 #[test]
3459 fn swapchain2() {
3460 let (resources, queues) = test_queues!();
3461
3462 let queue_family_properties = resources
3463 .device()
3464 .physical_device()
3465 .queue_family_properties();
3466
3467 let present_queue = queues.iter().find(|q| {
3468 let queue_flags = queue_family_properties[q.queue_family_index() as usize].queue_flags;
3469
3470 queue_flags.contains(QueueFlags::COMPUTE) && !queue_flags.contains(QueueFlags::GRAPHICS)
3471 });
3472
3473 if !present_queue.is_some() {
3474 return;
3475 }
3476
3477 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3478 let concurrent_sharing =
3479 Sharing::Concurrent(queues.iter().map(|q| q.queue_family_index()).collect());
3480 let swapchain1 = graph.add_swapchain(&SwapchainCreateInfo::default());
3481 let swapchain2 = graph.add_swapchain(&SwapchainCreateInfo {
3482 image_sharing: concurrent_sharing.clone(),
3483 ..Default::default()
3484 });
3485 let swapchain3 = graph.add_swapchain(&SwapchainCreateInfo::default());
3486 let swapchain4 = graph.add_swapchain(&SwapchainCreateInfo {
3487 image_sharing: concurrent_sharing.clone(),
3488 ..Default::default()
3489 });
3490 let node = graph
3491 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3492 .image_access(
3493 swapchain1.current_image_id(),
3494 AccessTypes::COLOR_ATTACHMENT_WRITE,
3495 ImageLayoutType::Optimal,
3496 )
3497 .image_access(
3498 swapchain2.current_image_id(),
3499 AccessTypes::COLOR_ATTACHMENT_WRITE,
3500 ImageLayoutType::Optimal,
3501 )
3502 .image_access(
3503 swapchain3.current_image_id(),
3504 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
3505 ImageLayoutType::Optimal,
3506 )
3507 .image_access(
3508 swapchain4.current_image_id(),
3509 AccessTypes::COMPUTE_SHADER_STORAGE_WRITE,
3510 ImageLayoutType::Optimal,
3511 )
3512 .build();
3513
3514 let graph = unsafe {
3515 graph.compile(&CompileInfo {
3516 queues: &queues.iter().collect::<Vec<_>>(),
3517 present_queue: Some(present_queue.unwrap()),
3518 ..Default::default()
3519 })
3520 }
3521 .unwrap();
3522
3523 assert_matches_instructions!(
3524 graph,
3525 WaitAcquire {
3526 swapchain_id: swapchain1,
3527 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3528 },
3529 WaitAcquire {
3530 swapchain_id: swapchain2,
3531 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3532 },
3533 WaitAcquire {
3534 swapchain_id: swapchain3,
3535 stage_mask: COMPUTE_SHADER,
3536 },
3537 WaitAcquire {
3538 swapchain_id: swapchain4,
3539 stage_mask: COMPUTE_SHADER,
3540 },
3541 PipelineBarrier {
3542 barriers: [
3543 {
3544 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3545 src_access_mask: ,
3546 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3547 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3548 old_layout: Undefined,
3549 new_layout: ColorAttachmentOptimal,
3550 resource: swapchain1,
3551 },
3552 {
3553 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3554 src_access_mask: ,
3555 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3556 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3557 old_layout: Undefined,
3558 new_layout: ColorAttachmentOptimal,
3559 resource: swapchain2,
3560 },
3561 {
3562 src_stage_mask: COMPUTE_SHADER,
3563 src_access_mask: ,
3564 dst_stage_mask: COMPUTE_SHADER,
3565 dst_access_mask: SHADER_STORAGE_WRITE,
3566 old_layout: Undefined,
3567 new_layout: General,
3568 resource: swapchain3,
3569 },
3570 {
3571 src_stage_mask: COMPUTE_SHADER,
3572 src_access_mask: ,
3573 dst_stage_mask: COMPUTE_SHADER,
3574 dst_access_mask: SHADER_STORAGE_WRITE,
3575 old_layout: Undefined,
3576 new_layout: General,
3577 resource: swapchain4,
3578 },
3579 ],
3580 },
3581 ExecuteTask { node: node },
3582 SignalPrePresent {
3583 swapchain_id: swapchain1,
3584 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3585 },
3586 SignalPrePresent {
3587 swapchain_id: swapchain3,
3588 stage_mask: COMPUTE_SHADER,
3589 },
3590 SignalPresent {
3591 swapchain_id: swapchain2,
3592 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3593 },
3594 SignalPresent {
3595 swapchain_id: swapchain4,
3596 stage_mask: COMPUTE_SHADER,
3597 },
3598 PipelineBarrier {
3599 barriers: [
3600 {
3601 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3602 src_access_mask: COLOR_ATTACHMENT_WRITE,
3603 dst_stage_mask: ,
3604 dst_access_mask: ,
3605 old_layout: ColorAttachmentOptimal,
3606 new_layout: PresentSrc,
3607 resource: swapchain1,
3608 },
3609 {
3610 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3611 src_access_mask: COLOR_ATTACHMENT_WRITE,
3612 dst_stage_mask: ,
3613 dst_access_mask: ,
3614 old_layout: ColorAttachmentOptimal,
3615 new_layout: PresentSrc,
3616 resource: swapchain2,
3617 },
3618 {
3619 src_stage_mask: COMPUTE_SHADER,
3620 src_access_mask: SHADER_STORAGE_WRITE,
3621 dst_stage_mask: ,
3622 dst_access_mask: ,
3623 old_layout: General,
3624 new_layout: PresentSrc,
3625 resource: swapchain3,
3626 },
3627 {
3628 src_stage_mask: COMPUTE_SHADER,
3629 src_access_mask: SHADER_STORAGE_WRITE,
3630 dst_stage_mask: ,
3631 dst_access_mask: ,
3632 old_layout: General,
3633 new_layout: PresentSrc,
3634 resource: swapchain4,
3635 },
3636 ],
3637 },
3638 FlushSubmit,
3639 Submit,
3640 WaitPrePresent {
3641 swapchain_id: swapchain1,
3642 stage_mask: ALL_COMMANDS,
3643 },
3644 SignalPresent {
3645 swapchain_id: swapchain1,
3646 stage_mask: ALL_COMMANDS,
3647 },
3648 WaitPrePresent {
3649 swapchain_id: swapchain3,
3650 stage_mask: ALL_COMMANDS,
3651 },
3652 SignalPresent {
3653 swapchain_id: swapchain3,
3654 stage_mask: ALL_COMMANDS,
3655 },
3656 PipelineBarrier {
3657 barriers: [
3658 {
3659 src_stage_mask: ,
3660 src_access_mask: ,
3661 dst_stage_mask: ,
3662 dst_access_mask: ,
3663 old_layout: ColorAttachmentOptimal,
3664 new_layout: PresentSrc,
3665 resource: swapchain1,
3666 },
3667 {
3668 src_stage_mask: ,
3669 src_access_mask: ,
3670 dst_stage_mask: ,
3671 dst_access_mask: ,
3672 old_layout: General,
3673 new_layout: PresentSrc,
3674 resource: swapchain3,
3675 },
3676 ],
3677 },
3678 FlushSubmit,
3679 Submit,
3680 );
3681 }
3682
3683 #[test]
3684 fn swapchain3() {
3685 let (resources, queues) = test_queues!();
3686
3687 let queue_family_properties = resources
3688 .device()
3689 .physical_device()
3690 .queue_family_properties();
3691
3692 let present_queue = queues.iter().find(|q| {
3693 let queue_flags = queue_family_properties[q.queue_family_index() as usize].queue_flags;
3694
3695 queue_flags.contains(QueueFlags::GRAPHICS)
3696 });
3697
3698 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3699 let swapchain = graph.add_swapchain(&SwapchainCreateInfo {
3700 image_format: Format::R8G8B8A8_UNORM,
3701 ..Default::default()
3702 });
3703 let depth_image = graph.add_image(&ImageCreateInfo {
3704 format: Format::D16_UNORM,
3705 ..Default::default()
3706 });
3707 let framebuffer = graph.add_framebuffer();
3708 let node1 = graph
3709 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3710 .framebuffer(framebuffer)
3711 .depth_stencil_attachment(
3712 depth_image,
3713 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3714 ImageLayoutType::Optimal,
3715 &AttachmentInfo::default(),
3716 )
3717 .build();
3718 let node2 = graph
3719 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3720 .framebuffer(framebuffer)
3721 .color_attachment(
3722 swapchain.current_image_id(),
3723 AccessTypes::COLOR_ATTACHMENT_WRITE,
3724 ImageLayoutType::Optimal,
3725 &AttachmentInfo::default(),
3726 )
3727 .depth_stencil_attachment(
3728 depth_image,
3729 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3730 ImageLayoutType::Optimal,
3731 &AttachmentInfo::default(),
3732 )
3733 .build();
3734 let node3 = graph
3735 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3736 .framebuffer(framebuffer)
3737 .input_attachment(
3738 depth_image,
3739 AccessTypes::FRAGMENT_SHADER_DEPTH_STENCIL_INPUT_ATTACHMENT_READ,
3740 ImageLayoutType::Optimal,
3741 &AttachmentInfo::default(),
3742 )
3743 .build();
3744 graph.add_edge(node1, node2).unwrap();
3745 graph.add_edge(node2, node3).unwrap();
3746
3747 let graph = unsafe {
3748 graph.compile(&CompileInfo {
3749 queues: &queues.iter().collect::<Vec<_>>(),
3750 present_queue: Some(present_queue.unwrap()),
3751 ..Default::default()
3752 })
3753 }
3754 .unwrap();
3755
3756 assert_matches_instructions!(
3757 graph,
3758 WaitAcquire {
3759 swapchain_id: swapchain,
3760 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3761 },
3762 PipelineBarrier {
3763 barriers: [
3764 {
3765 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3766 src_access_mask: ,
3767 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3768 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3769 old_layout: Undefined,
3770 new_layout: ColorAttachmentOptimal,
3771 resource: swapchain,
3772 },
3773 ],
3774 },
3775 BeginRenderPass,
3776 ExecuteTask { node: node1 },
3777 NextSubpass,
3778 ExecuteTask { node: node2 },
3779 NextSubpass,
3780 ExecuteTask { node: node3 },
3781 EndRenderPass,
3782 SignalPresent {
3783 swapchain_id: swapchain,
3784 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3785 },
3786 PipelineBarrier {
3787 barriers: [
3788 {
3789 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3790 src_access_mask: COLOR_ATTACHMENT_WRITE,
3791 dst_stage_mask: ,
3792 dst_access_mask: ,
3793 old_layout: ColorAttachmentOptimal,
3794 new_layout: PresentSrc,
3795 resource: swapchain,
3796 },
3797 ],
3798 },
3799 FlushSubmit,
3800 Submit,
3801 );
3802 }
3803
3804 #[test]
3805 fn swapchain4() {
3806 let (resources, queues) = test_queues!();
3807
3808 let queue_family_properties = resources
3809 .device()
3810 .physical_device()
3811 .queue_family_properties();
3812
3813 let present_queue = queues.iter().find(|q| {
3814 let queue_flags = queue_family_properties[q.queue_family_index() as usize].queue_flags;
3815
3816 queue_flags.contains(QueueFlags::COMPUTE) && !queue_flags.contains(QueueFlags::GRAPHICS)
3817 });
3818
3819 if !present_queue.is_some() {
3820 return;
3821 }
3822
3823 let mut graph = TaskGraph::<()>::new(&resources, 10, 10);
3824 let concurrent_sharing =
3825 Sharing::Concurrent(queues.iter().map(|q| q.queue_family_index()).collect());
3826 let swapchain1 = graph.add_swapchain(&SwapchainCreateInfo {
3827 image_format: Format::R8G8B8A8_UNORM,
3828 ..SwapchainCreateInfo::default()
3829 });
3830 let swapchain2 = graph.add_swapchain(&SwapchainCreateInfo {
3831 image_format: Format::R8G8B8A8_UNORM,
3832 image_sharing: concurrent_sharing.clone(),
3833 ..Default::default()
3834 });
3835 let depth_image = graph.add_image(&ImageCreateInfo {
3836 format: Format::D16_UNORM,
3837 ..Default::default()
3838 });
3839 let framebuffer = graph.add_framebuffer();
3840 let node1 = graph
3841 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3842 .framebuffer(framebuffer)
3843 .depth_stencil_attachment(
3844 depth_image,
3845 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3846 ImageLayoutType::Optimal,
3847 &AttachmentInfo::default(),
3848 )
3849 .build();
3850 let node2 = graph
3851 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3852 .framebuffer(framebuffer)
3853 .color_attachment(
3854 swapchain1.current_image_id(),
3855 AccessTypes::COLOR_ATTACHMENT_WRITE,
3856 ImageLayoutType::Optimal,
3857 &AttachmentInfo::default(),
3858 )
3859 .color_attachment(
3860 swapchain2.current_image_id(),
3861 AccessTypes::COLOR_ATTACHMENT_WRITE,
3862 ImageLayoutType::Optimal,
3863 &AttachmentInfo {
3864 index: 1,
3865 ..Default::default()
3866 },
3867 )
3868 .depth_stencil_attachment(
3869 depth_image,
3870 AccessTypes::DEPTH_STENCIL_ATTACHMENT_WRITE,
3871 ImageLayoutType::Optimal,
3872 &AttachmentInfo::default(),
3873 )
3874 .build();
3875 let node3 = graph
3876 .create_task_node("", QueueFamilyType::Graphics, PhantomData)
3877 .framebuffer(framebuffer)
3878 .input_attachment(
3879 depth_image,
3880 AccessTypes::FRAGMENT_SHADER_DEPTH_STENCIL_INPUT_ATTACHMENT_READ,
3881 ImageLayoutType::Optimal,
3882 &AttachmentInfo::default(),
3883 )
3884 .build();
3885 graph.add_edge(node1, node2).unwrap();
3886 graph.add_edge(node2, node3).unwrap();
3887
3888 let graph = unsafe {
3889 graph.compile(&CompileInfo {
3890 queues: &queues.iter().collect::<Vec<_>>(),
3891 present_queue: Some(present_queue.unwrap()),
3892 ..Default::default()
3893 })
3894 }
3895 .unwrap();
3896
3897 assert_matches_instructions!(
3898 graph,
3899 WaitAcquire {
3900 swapchain_id: swapchain1,
3901 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3902 },
3903 WaitAcquire {
3904 swapchain_id: swapchain2,
3905 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3906 },
3907 PipelineBarrier {
3908 barriers: [
3909 {
3910 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3911 src_access_mask: ,
3912 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3913 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3914 old_layout: Undefined,
3915 new_layout: ColorAttachmentOptimal,
3916 resource: swapchain1,
3917 },
3918 {
3919 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3920 src_access_mask: ,
3921 dst_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3922 dst_access_mask: COLOR_ATTACHMENT_WRITE,
3923 old_layout: Undefined,
3924 new_layout: ColorAttachmentOptimal,
3925 resource: swapchain2,
3926 },
3927 ],
3928 },
3929 BeginRenderPass,
3930 ExecuteTask { node: node1 },
3931 NextSubpass,
3932 ExecuteTask { node: node2 },
3933 NextSubpass,
3934 ExecuteTask { node: node3 },
3935 EndRenderPass,
3936 SignalPrePresent {
3937 swapchain_id: swapchain1,
3938 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3939 },
3940 SignalPresent {
3941 swapchain_id: swapchain2,
3942 stage_mask: COLOR_ATTACHMENT_OUTPUT,
3943 },
3944 PipelineBarrier {
3945 barriers: [
3946 {
3947 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3948 src_access_mask: COLOR_ATTACHMENT_WRITE,
3949 dst_stage_mask: ,
3950 dst_access_mask: ,
3951 old_layout: ColorAttachmentOptimal,
3952 new_layout: PresentSrc,
3953 resource: swapchain1,
3954 },
3955 {
3956 src_stage_mask: COLOR_ATTACHMENT_OUTPUT,
3957 src_access_mask: COLOR_ATTACHMENT_WRITE,
3958 dst_stage_mask: ,
3959 dst_access_mask: ,
3960 old_layout: ColorAttachmentOptimal,
3961 new_layout: PresentSrc,
3962 resource: swapchain2,
3963 },
3964 ],
3965 },
3966 FlushSubmit,
3967 Submit,
3968 WaitPrePresent {
3969 swapchain_id: swapchain1,
3970 stage_mask: ALL_COMMANDS,
3971 },
3972 SignalPresent {
3973 swapchain_id: swapchain1,
3974 stage_mask: ALL_COMMANDS,
3975 },
3976 PipelineBarrier {
3977 barriers: [
3978 {
3979 src_stage_mask: ,
3980 src_access_mask: ,
3981 dst_stage_mask: ,
3982 dst_access_mask: ,
3983 old_layout: ColorAttachmentOptimal,
3984 new_layout: PresentSrc,
3985 resource: swapchain1,
3986 },
3987 ],
3988 },
3989 FlushSubmit,
3990 Submit,
3991 );
3992 }
3993
3994 fn has_compute_only_queue(queues: &[Arc<Queue>]) -> bool {
3995 let queue_family_properties = queues[0]
3996 .device()
3997 .physical_device()
3998 .queue_family_properties();
3999
4000 queues.iter().any(|q| {
4001 let queue_flags = queue_family_properties[q.queue_family_index() as usize].queue_flags;
4002
4003 queue_flags.contains(QueueFlags::COMPUTE) && !queue_flags.contains(QueueFlags::GRAPHICS)
4004 })
4005 }
4006
4007 struct MatchingState {
4008 submission_index: usize,
4009 instruction_index: usize,
4010 semaphores: foldhash::HashMap<&'static str, SemaphoreIndex>,
4011 }
4012
4013 macro_rules! assert_matches_instructions {
4014 (
4015 $graph:ident,
4016 $($arg:tt)+
4017 ) => {
4018 let mut state = MatchingState {
4019 submission_index: 0,
4020 instruction_index: 0,
4021 semaphores: Default::default(),
4022 };
4023 assert_matches_instructions!(@ $graph, state, $($arg)+);
4024 };
4025 (
4026 @
4027 $graph:ident,
4028 $state:ident,
4029 InitialPipelineBarrier {
4030 barriers: [
4031 $({
4032 dst_stage_mask: $($dst_stage:ident)|*,
4033 dst_access_mask: $($dst_access:ident)|*,
4034 new_layout: $new_layout:ident,
4035 resource: $resource:ident,
4036 },)*
4037 ],
4038 },
4039 $($arg:tt)*
4040 ) => {
4041 let submission = &$graph.submissions[$state.submission_index];
4042 let barrier_range = &submission.initial_barrier_range;
4043 let barrier_range = barrier_range.start as usize..barrier_range.end as usize;
4044 let barriers = &$graph.barriers[barrier_range];
4045
4046 #[allow(unused_mut)]
4047 let mut barrier_count = 0;
4048 $(
4049 let barrier = barriers
4050 .iter()
4051 .find(|barrier| barrier.resource == $resource.erase())
4052 .unwrap();
4053 assert_eq!(barrier.src_stage_mask, PipelineStages::empty());
4054 assert_eq!(barrier.src_access_mask, AccessFlags::empty());
4055 assert_eq!(
4056 barrier.dst_stage_mask,
4057 PipelineStages::empty() $(| PipelineStages::$dst_stage)*,
4058 );
4059 assert_eq!(
4060 barrier.dst_access_mask,
4061 AccessFlags::empty() $(| AccessFlags::$dst_access)*,
4062 );
4063 assert_eq!(barrier.old_layout, ImageLayout::Undefined);
4064 assert_eq!(barrier.new_layout, ImageLayout::$new_layout);
4065 barrier_count += 1;
4066 )*
4067 assert_eq!(barriers.len(), barrier_count);
4068
4069 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4070 };
4071 (
4072 @
4073 $graph:ident,
4074 $state:ident,
4075 WaitAcquire {
4076 swapchain_id: $swapchain_id:expr,
4077 stage_mask: $($stage:ident)|*,
4078 },
4079 $($arg:tt)*
4080 ) => {
4081 assert!(matches!(
4082 $graph.instructions[$state.instruction_index],
4083 Instruction::WaitAcquire {
4084 swapchain_id,
4085 stage_mask,
4086 } if swapchain_id == $swapchain_id
4087 && stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4088 ));
4089 $state.instruction_index += 1;
4090 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4091 };
4092 (
4093 @
4094 $graph:ident,
4095 $state:ident,
4096 WaitSemaphore {
4097 semaphore_index: $semaphore_index:ident,
4098 stage_mask: $($stage:ident)|*,
4099 },
4100 $($arg:tt)*
4101 ) => {
4102 assert!(matches!(
4103 $graph.instructions[$state.instruction_index],
4104 Instruction::WaitSemaphore {
4105 stage_mask,
4106 ..
4107 } if stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4108 ));
4109 let Instruction::WaitSemaphore { semaphore_index, .. } =
4110 &$graph.instructions[$state.instruction_index]
4111 else {
4112 unreachable!();
4113 };
4114
4115 assert_eq!(
4116 semaphore_index,
4117 $state.semaphores.get(stringify!($semaphore_index)).unwrap(),
4118 );
4119
4120 $state.instruction_index += 1;
4121 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4122 };
4123 (
4124 @
4125 $graph:ident,
4126 $state:ident,
4127 ExecuteTask {
4128 node: $node:expr $(,)?
4129 },
4130 $($arg:tt)*
4131 ) => {
4132 assert!(matches!(
4133 $graph.instructions[$state.instruction_index],
4134 Instruction::ExecuteTask { node_index } if node_index == $node.index(),
4135 ));
4136 $state.instruction_index += 1;
4137 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4138 };
4139 (
4140 @
4141 $graph:ident,
4142 $state:ident,
4143 PipelineBarrier {
4144 barriers: [
4145 $({
4146 src_stage_mask: $($src_stage:ident)|*,
4147 src_access_mask: $($src_access:ident)|*,
4148 dst_stage_mask: $($dst_stage:ident)|*,
4149 dst_access_mask: $($dst_access:ident)|*,
4150 old_layout: $old_layout:ident,
4151 new_layout: $new_layout:ident,
4152 resource: $resource:ident,
4153 },)+
4154 ],
4155 },
4156 $($arg:tt)*
4157 ) => {
4158 assert!(matches!(
4159 $graph.instructions[$state.instruction_index],
4160 Instruction::PipelineBarrier { .. },
4161 ));
4162 let Instruction::PipelineBarrier { barrier_range } =
4163 &$graph.instructions[$state.instruction_index]
4164 else {
4165 unreachable!();
4166 };
4167 let barrier_range = barrier_range.start as usize..barrier_range.end as usize;
4168 let barriers = &$graph.barriers[barrier_range];
4169
4170 let mut barrier_count = 0;
4171 $(
4172 let barrier = barriers
4173 .iter()
4174 .find(|barrier| barrier.resource == $resource.erase())
4175 .unwrap();
4176 assert_eq!(
4177 barrier.src_stage_mask,
4178 PipelineStages::empty() $(| PipelineStages::$src_stage)*,
4179 );
4180 assert_eq!(
4181 barrier.src_access_mask,
4182 AccessFlags::empty() $(| AccessFlags::$src_access)*,
4183 );
4184 assert_eq!(
4185 barrier.dst_stage_mask,
4186 PipelineStages::empty() $(| PipelineStages::$dst_stage)*,
4187 );
4188 assert_eq!(
4189 barrier.dst_access_mask,
4190 AccessFlags::empty() $(| AccessFlags::$dst_access)*,
4191 );
4192 assert_eq!(barrier.old_layout, ImageLayout::$old_layout);
4193 assert_eq!(barrier.new_layout, ImageLayout::$new_layout);
4194 barrier_count += 1;
4195 )+
4196 assert_eq!(barriers.len(), barrier_count);
4197
4198 $state.instruction_index += 1;
4199 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4200 };
4201 (
4202 @
4203 $graph:ident,
4204 $state:ident,
4205 BeginRenderPass,
4206 $($arg:tt)*
4207 ) => {
4208 assert!(matches!(
4209 $graph.instructions[$state.instruction_index],
4210 Instruction::BeginRenderPass { .. },
4211 ));
4212 $state.instruction_index += 1;
4213 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4214 };
4215 (
4216 @
4217 $graph:ident,
4218 $state:ident,
4219 NextSubpass,
4220 $($arg:tt)*
4221 ) => {
4222 assert!(matches!(
4223 $graph.instructions[$state.instruction_index],
4224 Instruction::NextSubpass,
4225 ));
4226 $state.instruction_index += 1;
4227 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4228 };
4229 (
4230 @
4231 $graph:ident,
4232 $state:ident,
4233 EndRenderPass,
4234 $($arg:tt)*
4235 ) => {
4236 assert!(matches!(
4237 $graph.instructions[$state.instruction_index],
4238 Instruction::EndRenderPass,
4239 ));
4240 $state.instruction_index += 1;
4241 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4242 };
4243 (
4244 @
4245 $graph:ident,
4246 $state:ident,
4247 ClearAttachments {
4248 node: $node:ident,
4249 attachments: [$($resource:ident),+ $(,)?],
4250 },
4251 $($arg:tt)*
4252 ) => {
4253 assert!(matches!(
4254 $graph.instructions[$state.instruction_index],
4255 Instruction::ClearAttachments { node_index, .. } if node_index == $node.index(),
4256 ));
4257 let Instruction::ClearAttachments { clear_attachment_range, .. } =
4258 &$graph.instructions[$state.instruction_index]
4259 else {
4260 unreachable!();
4261 };
4262 let clear_attachments = &$graph.clear_attachments[clear_attachment_range.clone()];
4263
4264 let mut clear_attachment_count = 0;
4265 $(
4266 assert!(clear_attachments.contains(&$resource.erase()));
4267 clear_attachment_count += 1;
4268 )+
4269 assert_eq!(clear_attachments.len(), clear_attachment_count);
4270
4271 $state.instruction_index += 1;
4272 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4273 };
4274 (
4275 @
4276 $graph:ident,
4277 $state:ident,
4278 SignalSemaphore {
4279 semaphore_index: $semaphore_index:ident,
4280 stage_mask: $($stage:ident)|*,
4281 },
4282 $($arg:tt)*
4283 ) => {
4284 assert!(matches!(
4285 $graph.instructions[$state.instruction_index],
4286 Instruction::SignalSemaphore {
4287 stage_mask,
4288 ..
4289 } if stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4290 ));
4291 let Instruction::SignalSemaphore { semaphore_index, .. } =
4292 &$graph.instructions[$state.instruction_index]
4293 else {
4294 unreachable!();
4295 };
4296
4297 assert!($state.semaphores.get(&stringify!($semaphore_index)).is_none());
4298 $state.semaphores.insert(stringify!($semaphore_index), *semaphore_index);
4299
4300 $state.instruction_index += 1;
4301 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4302 };
4303 (
4304 @
4305 $graph:ident,
4306 $state:ident,
4307 SignalPrePresent {
4308 swapchain_id: $swapchain_id:expr,
4309 stage_mask: $($stage:ident)|*,
4310 },
4311 $($arg:tt)*
4312 ) => {
4313 assert!(matches!(
4314 $graph.instructions[$state.instruction_index],
4315 Instruction::SignalPrePresent {
4316 swapchain_id,
4317 stage_mask,
4318 } if swapchain_id == $swapchain_id
4319 && stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4320 ));
4321 $state.instruction_index += 1;
4322 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4323 };
4324 (
4325 @
4326 $graph:ident,
4327 $state:ident,
4328 WaitPrePresent {
4329 swapchain_id: $swapchain_id:expr,
4330 stage_mask: $($stage:ident)|*,
4331 },
4332 $($arg:tt)*
4333 ) => {
4334 assert!(matches!(
4335 $graph.instructions[$state.instruction_index],
4336 Instruction::WaitPrePresent {
4337 swapchain_id,
4338 stage_mask,
4339 } if swapchain_id == $swapchain_id
4340 && stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4341 ));
4342 $state.instruction_index += 1;
4343 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4344 };
4345 (
4346 @
4347 $graph:ident,
4348 $state:ident,
4349 SignalPresent {
4350 swapchain_id: $swapchain_id:expr,
4351 stage_mask: $($stage:ident)|*,
4352 },
4353 $($arg:tt)*
4354 ) => {
4355 assert!(matches!(
4356 $graph.instructions[$state.instruction_index],
4357 Instruction::SignalPresent {
4358 swapchain_id,
4359 stage_mask,
4360 } if swapchain_id == $swapchain_id
4361 && stage_mask == PipelineStages::empty() $(| PipelineStages::$stage)*,
4362 ));
4363 $state.instruction_index += 1;
4364 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4365 };
4366 (
4367 @
4368 $graph:ident,
4369 $state:ident,
4370 FlushSubmit,
4371 $($arg:tt)*
4372 ) => {
4373 assert!(matches!(
4374 $graph.instructions[$state.instruction_index],
4375 Instruction::FlushSubmit,
4376 ));
4377 $state.instruction_index += 1;
4378 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4379 };
4380 (
4381 @
4382 $graph:ident,
4383 $state:ident,
4384 Submit,
4385 $($arg:tt)*
4386 ) => {
4387 assert!(matches!(
4388 $graph.instructions[$state.instruction_index],
4389 Instruction::Submit,
4390 ));
4391 $state.submission_index += 1;
4392 $state.instruction_index += 1;
4393 assert_matches_instructions!(@ $graph, $state, $($arg)*);
4394 };
4395 (
4396 @
4397 $graph:ident,
4398 $state:ident,
4399 ) => {
4400 assert_eq!($graph.submissions.len(), $state.submission_index);
4401 assert_eq!($graph.instructions.len(), $state.instruction_index);
4402 };
4403 }
4404 use assert_matches_instructions;
4405}