1use alloc::collections::BinaryHeap;
21use alloc::vec;
22use alloc::vec::Vec;
23use core::cmp::Reverse;
24use hashbrown::HashMap;
25
26use super::builder::{GraphBuilder, ResourceVersion};
27use super::passes::PassId;
28use super::types::{
29 BarrierOp, BufferDesc, GraphResourceClass, PassKind, PassRange, ReadStages, ResourceId,
30 ResourceOrigin, ResourceState, TextureDesc,
31};
32
33#[derive(Debug, Clone, PartialEq)]
36pub enum GraphError {
37 MissingPresenter,
40 MultiplePresenters(usize),
43 MissingProducer {
49 pass: PassId,
50 resource_label: &'static str,
51 version: u32,
52 },
53 Cycle,
56}
57
58impl core::fmt::Display for GraphError {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 match self {
61 GraphError::MissingPresenter => {
62 write!(
63 f,
64 "no pass declared presents(); the graph has no terminal node"
65 )
66 }
67 GraphError::MultiplePresenters(n) => {
68 write!(f, "{} passes declared presents(); only one is allowed", n)
69 }
70 GraphError::MissingProducer {
71 pass,
72 resource_label,
73 version,
74 } => write!(
75 f,
76 "pass {:?} reads {} v{} but no pass writes that version",
77 pass, resource_label, version
78 ),
79 GraphError::Cycle => write!(f, "cycle in render-graph read/write edges"),
80 }
81 }
82}
83
84impl core::error::Error for GraphError {}
85
86#[derive(Debug, Clone)]
90pub struct CompiledPass {
91 pub id: PassId,
93 pub kind: PassKind,
95 pub reads: Vec<ResourceVersion>,
97 pub writes: Vec<ResourceVersion>,
99 pub presents: bool,
101 pub barriers_before: Vec<BarrierOp>,
103}
104
105#[derive(Debug, Clone)]
109pub struct CompiledResource {
110 pub label: &'static str,
112 pub origin: ResourceOrigin,
114 pub lifetime: PassRange,
116 pub tex_desc: Option<TextureDesc>,
120 pub(crate) buf_desc: Option<BufferDesc>,
123}
124
125impl CompiledResource {
126 pub fn class(&self) -> Option<GraphResourceClass> {
131 if let Some(desc) = self.tex_desc {
132 return Some(GraphResourceClass::for_texture_usage(desc.usage));
133 }
134 self.buf_desc
135 .map(|desc| GraphResourceClass::for_buffer_usage(desc.usage))
136 }
137}
138
139#[derive(Debug, Clone)]
143pub struct CompiledGraph {
144 pub passes: Vec<CompiledPass>,
146 pub resources: Vec<CompiledResource>,
148}
149
150impl CompiledGraph {
151 #[cfg(test)]
158 pub(crate) fn pass_barriers_for(
159 &self,
160 pass: &CompiledPass,
161 allow: &[&str],
162 ) -> Vec<(&'static str, BarrierOp)> {
163 pass.barriers_before
164 .iter()
165 .filter_map(|op| {
166 let label = self.resources[op.resource_index()].label;
167 allow.contains(&label).then_some((label, *op))
168 })
169 .collect()
170 }
171}
172
173impl GraphBuilder {
174 pub(crate) fn compile(self) -> Result<CompiledGraph, GraphError> {
179 let GraphBuilder { resources, passes } = self;
180 let n_passes = passes.len();
181 let n_resources = resources.len();
182
183 let presenters: Vec<usize> = passes
185 .iter()
186 .enumerate()
187 .filter(|(_, p)| p.presents)
188 .map(|(i, _)| i)
189 .collect();
190 match presenters.len() {
191 0 => return Err(GraphError::MissingPresenter),
192 1 => {}
193 n => return Err(GraphError::MultiplePresenters(n)),
194 }
195
196 let mut writer_of: HashMap<(ResourceId, u32), usize> = HashMap::new();
198 for (i, pass) in passes.iter().enumerate() {
199 for w in &pass.writes {
200 writer_of.insert((w.resource, w.version), i);
204 }
205 }
206
207 for pass in passes.iter() {
214 for r in &pass.reads {
215 if writer_of.contains_key(&(r.resource, r.version)) {
216 continue;
217 }
218 let decl = &resources[r.resource.index()];
219 let implicit_producer = r.version == 0 && decl.origin() == ResourceOrigin::Imported;
220 if !implicit_producer {
221 return Err(GraphError::MissingProducer {
222 pass: pass.id,
223 resource_label: decl.label(),
224 version: r.version,
225 });
226 }
227 }
228 }
229
230 let mut edges: Vec<Vec<usize>> = vec![Vec::new(); n_passes];
233 let mut in_degree: Vec<usize> = vec![0; n_passes];
234 let add_edge =
235 |from: usize, to: usize, edges: &mut [Vec<usize>], in_degree: &mut [usize]| {
236 if from != to {
237 edges[from].push(to);
238 in_degree[to] += 1;
239 }
240 };
241
242 let mut readers_of: HashMap<(ResourceId, u32), Vec<usize>> = HashMap::new();
246 for (i, pass) in passes.iter().enumerate() {
247 for r in &pass.reads {
248 readers_of
249 .entry((r.resource, r.version))
250 .or_default()
251 .push(i);
252 }
253 }
254
255 for (pass_idx, pass) in passes.iter().enumerate() {
256 for r in &pass.reads {
259 if let Some(&w) = writer_of.get(&(r.resource, r.version)) {
260 add_edge(w, pass_idx, &mut edges, &mut in_degree);
261 }
262 }
263 for w in &pass.writes {
268 if w.version > 1
269 && let Some(&prev_writer) = writer_of.get(&(w.resource, w.version - 1))
270 {
271 add_edge(prev_writer, pass_idx, &mut edges, &mut in_degree);
272 }
273 }
274 for w in &pass.writes {
283 if w.version > 0
284 && let Some(readers) = readers_of.get(&(w.resource, w.version - 1))
285 {
286 for &reader in readers {
287 add_edge(reader, pass_idx, &mut edges, &mut in_degree);
288 }
289 }
290 }
291 }
292
293 let mut ready: BinaryHeap<Reverse<usize>> = (0..n_passes)
297 .filter(|&i| in_degree[i] == 0)
298 .map(Reverse)
299 .collect();
300 let mut order: Vec<usize> = Vec::with_capacity(n_passes);
301 while let Some(Reverse(idx)) = ready.pop() {
302 order.push(idx);
303 for &neighbor in &edges[idx] {
304 in_degree[neighbor] -= 1;
305 if in_degree[neighbor] == 0 {
306 ready.push(Reverse(neighbor));
307 }
308 }
309 }
310 if order.len() != n_passes {
311 return Err(GraphError::Cycle);
312 }
313
314 let mut compiled_passes: Vec<CompiledPass> = order
316 .iter()
317 .map(|&orig_idx| {
318 let decl = &passes[orig_idx];
319 CompiledPass {
320 id: decl.id,
321 kind: decl.kind,
322 reads: decl.reads.clone(),
323 writes: decl.writes.clone(),
324 presents: decl.presents,
325 barriers_before: Vec::new(),
326 }
327 })
328 .collect();
329
330 derive_barriers(&mut compiled_passes, n_resources);
332
333 let mut lifetimes: Vec<Option<PassRange>> = vec![None; n_resources];
335 for (sorted_idx, pass) in compiled_passes.iter().enumerate() {
336 for v in pass.writes.iter().chain(pass.reads.iter()) {
337 let i = v.resource.index();
338 let merged = match lifetimes[i] {
339 None => PassRange {
340 first: sorted_idx,
341 last: sorted_idx,
342 },
343 Some(PassRange { first, .. }) => PassRange {
344 first,
345 last: sorted_idx,
346 },
347 };
348 lifetimes[i] = Some(merged);
349 }
350 }
351
352 let compiled_resources: Vec<CompiledResource> = resources
353 .into_iter()
354 .enumerate()
355 .map(|(i, decl)| {
356 let lifetime = lifetimes[i].unwrap_or(PassRange { first: 0, last: 0 });
360 CompiledResource {
361 label: decl.label(),
362 origin: decl.origin(),
363 lifetime,
364 tex_desc: decl.texture_desc(),
365 buf_desc: decl.buffer_desc(),
366 }
367 })
368 .collect();
369
370 Ok(CompiledGraph {
371 passes: compiled_passes,
372 resources: compiled_resources,
373 })
374 }
375}
376
377fn derive_barriers(passes: &mut [CompiledPass], n_resources: usize) {
396 #[derive(Copy, Clone)]
399 enum Eff {
400 Write,
401 Read(ReadStages),
402 }
403
404 let mut timeline: Vec<Vec<(usize, Eff)>> = (0..n_resources).map(|_| Vec::new()).collect();
409 for (i, pass) in passes.iter().enumerate() {
410 let stage = ReadStages::for_pass_kind(pass.kind);
411 let mut access: HashMap<ResourceId, Eff> = HashMap::new();
412 for r in &pass.reads {
413 access.entry(r.resource).or_insert(Eff::Read(stage));
414 }
415 for w in &pass.writes {
416 access.insert(w.resource, Eff::Write);
417 }
418 let mut touched: Vec<(ResourceId, Eff)> = access.into_iter().collect();
422 touched.sort_by_key(|(r, _)| r.0);
423 for (res, eff) in touched {
424 timeline[res.index()].push((i, eff));
425 }
426 }
427
428 for (r_idx, entries) in timeline.iter().enumerate() {
431 let resource = ResourceId(r_idx as u32);
432 let mut state = ResourceState::Undefined;
433 let mut run_stages = ReadStages::empty();
434
435 for (k, &(pass_idx, eff)) in entries.iter().enumerate() {
436 match eff {
437 Eff::Write => {
438 let read_stages = if state == ResourceState::Read {
449 run_stages
452 } else {
453 ReadStages::empty()
456 };
457 passes[pass_idx].barriers_before.push(BarrierOp {
458 resource,
459 from: state,
460 to: ResourceState::Write,
461 read_stages,
462 });
463 state = ResourceState::Write;
464 run_stages = ReadStages::empty();
465 }
466 Eff::Read(_) => {
467 if state != ResourceState::Read {
468 let mut run = ReadStages::empty();
471 for &(_, e) in entries[k..].iter() {
472 match e {
473 Eff::Read(s) => run = run.union(s),
474 Eff::Write => break,
475 }
476 }
477 passes[pass_idx].barriers_before.push(BarrierOp {
478 resource,
479 from: state,
480 to: ResourceState::Read,
481 read_stages: run,
482 });
483 state = ResourceState::Read;
484 run_stages = run;
485 }
486 }
489 }
490 }
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::super::types::{BufferUsage, PixelFormat, TextureDesc, TextureSize, TextureUsage};
497 use super::*;
498 use crate::render_graph::builder::GraphBuilder;
499 use crate::render_graph::passes::PassId;
500 use crate::render_graph::types::{BufferDesc, PassKind};
501
502 fn tex() -> TextureDesc {
503 TextureDesc::texture_2d(
504 TextureSize::Drawable,
505 TextureSize::Drawable,
506 PixelFormat::Rgba16Float,
507 TextureUsage::SHADER_READ | TextureUsage::RENDER_TARGET,
508 )
509 }
510
511 fn buf() -> BufferDesc {
512 BufferDesc {
513 size_bytes: None,
514 usage: BufferUsage::STORAGE,
515 }
516 }
517
518 #[test]
519 fn linear_chain_toposorts_in_declared_order() {
520 let mut b = GraphBuilder::new();
522 let t = b.create_texture("t", tex());
523 let u = b.create_texture("u", tex());
524
525 let t1 = b
526 .add_pass(PassId::Shadow, PassKind::Render)
527 .write_texture(t);
528 let u1 = {
529 let mut p = b.add_pass(PassId::Main, PassKind::Render);
530 p.read_texture(t1);
531 p.write_texture(u)
532 };
533 b.add_pass(PassId::Composite, PassKind::Render)
534 .read_texture(u1)
535 .presents();
536
537 let g = b.compile().expect("graph compiles");
538 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
539 assert_eq!(order, vec![PassId::Shadow, PassId::Main, PassId::Composite]);
540 }
541
542 #[test]
543 fn diamond_toposorts_with_stable_tiebreak() {
544 let mut b = GraphBuilder::new();
548 let t = b.create_texture("t", tex());
549 let u = b.create_texture("u", tex());
550 let v = b.create_texture("v", tex());
551
552 let t1 = b
553 .add_pass(PassId::Shadow, PassKind::Render)
554 .write_texture(t);
555 let u1 = {
556 let mut p = b.add_pass(PassId::Main, PassKind::Render);
557 p.read_texture(t1);
558 p.write_texture(u)
559 };
560 let v1 = {
561 let mut p = b.add_pass(PassId::SsaoKernel, PassKind::Render);
562 p.read_texture(t1);
563 p.write_texture(v)
564 };
565 b.add_pass(PassId::Composite, PassKind::Render)
566 .read_texture(u1)
567 .read_texture(v1)
568 .presents();
569
570 let g = b.compile().expect("graph compiles");
571 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
572 assert_eq!(
577 order,
578 vec![
579 PassId::Shadow,
580 PassId::Main,
581 PassId::SsaoKernel,
582 PassId::Composite,
583 ]
584 );
585 }
586
587 #[test]
588 fn read_modify_write_chain_orders_correctly() {
589 let mut b = GraphBuilder::new();
593 let hdr = b.create_texture("hdr", tex());
594
595 let v1 = b
596 .add_pass(PassId::Main, PassKind::Render)
597 .write_texture(hdr);
598 let v2 = {
599 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
600 p.read_texture(v1);
601 p.write_texture(v1)
602 };
603 let v3 = {
604 let mut p = b.add_pass(PassId::Fog, PassKind::Render);
605 p.read_texture(v2);
606 p.write_texture(v2)
607 };
608 b.add_pass(PassId::Composite, PassKind::Render)
609 .read_texture(v3)
610 .presents();
611
612 let g = b.compile().expect("graph compiles");
613 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
614 assert_eq!(
615 order,
616 vec![PassId::Main, PassId::Decals, PassId::Fog, PassId::Composite,]
617 );
618 }
619
620 #[test]
621 fn war_exposes_cross_rmw_cycle() {
622 let mut b = GraphBuilder::new();
632 let x = b.create_texture("x", tex());
633 let y = b.create_texture("y", tex());
634
635 let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
636 let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
637
638 let _x2 = {
639 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
640 p.read_texture(y1);
641 p.write_texture(x1)
642 };
643 let _y2 = {
644 let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
645 p.read_texture(x1);
646 p.write_texture(y1)
647 };
648 b.add_pass(PassId::Composite, PassKind::Render).presents();
649
650 match b.compile() {
651 Err(GraphError::Cycle) => {}
652 other => panic!("expected Cycle, got {:?}", other),
653 }
654 }
655
656 #[test]
657 fn mutual_write_cycle_errors() {
658 let mut b = GraphBuilder::new();
663 let x = b.create_texture("x", tex());
664 let y = b.create_texture("y", tex());
665
666 let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
668 let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
669
670 {
676 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
677 p.read_texture(super::super::types::TextureHandle {
679 resource: y1.resource,
680 version: 2,
681 });
682 p.write_texture(x1);
683 }
684 {
685 let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
686 p.read_texture(super::super::types::TextureHandle {
687 resource: x1.resource,
688 version: 2,
689 });
690 p.write_texture(y1);
691 }
692 b.add_pass(PassId::Composite, PassKind::Render).presents();
693
694 match b.compile() {
695 Err(GraphError::Cycle) => {}
696 other => panic!("expected Cycle, got {:?}", other),
697 }
698 }
699
700 #[test]
701 fn war_edges_pin_reader_before_writer() {
702 let mut b = GraphBuilder::new();
713 let hdr = b.create_texture("hdr_resolve", tex());
714
715 let hdr_v1 = b
716 .add_pass(PassId::Main, PassKind::Render)
717 .write_texture(hdr);
718
719 let _hdr_v2 = b
722 .add_pass(PassId::Decals, PassKind::Render)
723 .write_texture(hdr_v1);
724
725 b.add_pass(PassId::AutoExposure, PassKind::Compute)
726 .read_texture(hdr_v1);
727
728 b.add_pass(PassId::Composite, PassKind::Render).presents();
729
730 let g = b.compile().expect("compiles");
731 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
732 assert_eq!(
735 order,
736 vec![
737 PassId::Main,
738 PassId::AutoExposure,
739 PassId::Decals,
740 PassId::Composite,
741 ]
742 );
743 }
744
745 #[test]
746 fn imported_v0_read_does_not_error() {
747 let mut b = GraphBuilder::new();
753 let env = b.import_texture("env", tex());
754 let scene = b.create_texture("scene", tex());
755 {
756 let mut p = b.add_pass(PassId::Main, PassKind::Render);
757 p.read_texture(env);
758 p.write_texture(scene);
759 }
760 b.add_pass(PassId::Composite, PassKind::Render)
761 .read_texture(super::super::types::TextureHandle {
762 resource: scene.resource,
763 version: 1,
764 })
765 .presents();
766
767 let g = b.compile().expect("imported v0 read should compile");
768 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
769 assert_eq!(order, vec![PassId::Main, PassId::Composite]);
770 }
771
772 #[test]
773 fn transient_v0_read_still_errors() {
774 let mut b = GraphBuilder::new();
778 let t = b.create_texture("scratch", tex());
779 b.add_pass(PassId::Composite, PassKind::Render)
780 .read_texture(t)
781 .presents();
782 match b.compile() {
783 Err(GraphError::MissingProducer {
784 pass: PassId::Composite,
785 resource_label: "scratch",
786 version: 0,
787 }) => {}
788 other => panic!("expected MissingProducer for transient v0, got {:?}", other),
789 }
790 }
791
792 #[test]
793 fn missing_producer_errors() {
794 let mut b = GraphBuilder::new();
796 let t = b.create_texture("t", tex());
797
798 let phantom = super::super::types::TextureHandle {
801 resource: t.resource,
802 version: 1,
803 };
804 b.add_pass(PassId::Composite, PassKind::Render)
805 .read_texture(phantom)
806 .presents();
807
808 match b.compile() {
809 Err(GraphError::MissingProducer {
810 pass: PassId::Composite,
811 resource_label: "t",
812 version: 1,
813 }) => {}
814 other => panic!("expected MissingProducer, got {:?}", other),
815 }
816 }
817
818 #[test]
819 fn no_presenter_errors() {
820 let mut b = GraphBuilder::new();
821 let t = b.create_texture("t", tex());
822 b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
823 match b.compile() {
825 Err(GraphError::MissingPresenter) => {}
826 other => panic!("expected MissingPresenter, got {:?}", other),
827 }
828 }
829
830 #[test]
831 fn multiple_presenters_errors() {
832 let mut b = GraphBuilder::new();
833 let t = b.create_texture("t", tex());
834 {
835 let mut p = b.add_pass(PassId::Main, PassKind::Render);
836 p.presents();
837 let _ = p.write_texture(t);
838 }
839 b.add_pass(PassId::Composite, PassKind::Render).presents();
840 match b.compile() {
841 Err(GraphError::MultiplePresenters(2)) => {}
842 other => panic!("expected MultiplePresenters(2), got {:?}", other),
843 }
844 }
845
846 #[test]
847 fn barriers_emit_on_state_transitions() {
848 let mut b = GraphBuilder::new();
852 let t = b.create_texture("t", tex());
853 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
854 b.add_pass(PassId::Composite, PassKind::Render)
855 .read_texture(t1)
856 .presents();
857
858 let g = b.compile().expect("compiles");
859 assert_eq!(g.passes[0].barriers_before.len(), 1);
861 assert_eq!(
862 g.passes[0].barriers_before[0].from,
863 ResourceState::Undefined
864 );
865 assert_eq!(g.passes[0].barriers_before[0].to, ResourceState::Write);
866 assert_eq!(g.passes[1].barriers_before.len(), 1);
868 assert_eq!(g.passes[1].barriers_before[0].from, ResourceState::Write);
869 assert_eq!(g.passes[1].barriers_before[0].to, ResourceState::Read);
870 }
871
872 #[test]
873 fn pass_barriers_for_filters_by_label() {
874 let mut b = GraphBuilder::new();
878 let keep = b.create_texture("keep", tex());
879 let skip = b.create_texture("skip", tex());
880 let (keep1, skip1) = {
881 let mut p = b.add_pass(PassId::Main, PassKind::Render);
882 (p.write_texture(keep), p.write_texture(skip))
883 };
884 b.add_pass(PassId::Composite, PassKind::Render)
885 .read_texture(keep1)
886 .read_texture(skip1)
887 .presents();
888
889 let g = b.compile().expect("compiles");
890 let main = &g.passes[0];
892 let kept = g.pass_barriers_for(main, &["keep"]);
893 assert_eq!(kept.len(), 1);
894 assert_eq!(kept[0].0, "keep");
895 assert_eq!(kept[0].1.source_state(), ResourceState::Undefined);
896 assert_eq!(kept[0].1.to_state(), ResourceState::Write);
897 let composite = &g.passes[1];
899 let kept = g.pass_barriers_for(composite, &["keep"]);
900 assert_eq!(kept.len(), 1);
901 assert_eq!(kept[0].1.source_state(), ResourceState::Write);
902 assert_eq!(kept[0].1.to_state(), ResourceState::Read);
903 assert!(g.pass_barriers_for(main, &[]).is_empty());
905 assert!(g.pass_barriers_for(main, &["nope"]).is_empty());
906 }
907
908 #[test]
909 fn consecutive_reads_coalesce_no_barriers() {
910 let mut b = GraphBuilder::new();
913 let t = b.create_texture("t", tex());
914 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
915 b.add_pass(PassId::Decals, PassKind::Render)
916 .read_texture(t1);
917 b.add_pass(PassId::Fog, PassKind::Render).read_texture(t1);
918 b.add_pass(PassId::Composite, PassKind::Render)
919 .read_texture(t1)
920 .presents();
921
922 let g = b.compile().expect("compiles");
923 assert_eq!(g.passes[0].barriers_before.len(), 1);
925 assert_eq!(g.passes[1].barriers_before.len(), 1);
927 assert_eq!(g.passes[2].barriers_before.len(), 0);
929 assert_eq!(g.passes[3].barriers_before.len(), 0);
931 }
932
933 #[test]
934 fn lifetime_intervals_span_first_write_to_last_read() {
935 let mut b = GraphBuilder::new();
938 let t = b.create_texture("t", tex());
939 let unrelated = b.create_texture("u", tex());
940
941 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
942 b.add_pass(PassId::Decals, PassKind::Render)
943 .read_texture(t1);
944 b.add_pass(PassId::Fog, PassKind::Render)
945 .write_texture(unrelated);
946 b.add_pass(PassId::Composite, PassKind::Render)
947 .read_texture(t1)
948 .presents();
949
950 let g = b.compile().expect("compiles");
951 let t_idx = t.resource.index();
952 let u_idx = unrelated.resource.index();
953 assert_eq!(g.resources[t_idx].lifetime.first, 0);
954 assert_eq!(g.resources[t_idx].lifetime.last, 3);
955 assert_eq!(g.resources[u_idx].lifetime.first, 2);
956 assert_eq!(g.resources[u_idx].lifetime.last, 2);
957 }
958
959 #[test]
960 fn buffer_dep_edges_work_too() {
961 let mut b = GraphBuilder::new();
963 let draw_args = b.create_buffer("draw_args", buf());
964 let scene = b.create_texture("scene", tex());
965
966 let args1 = b
967 .add_pass(PassId::Cull, PassKind::Compute)
968 .write_buffer(draw_args);
969 let scene1 = {
970 let mut p = b.add_pass(PassId::Main, PassKind::Render);
971 p.read_buffer(args1);
972 p.write_texture(scene)
973 };
974 b.add_pass(PassId::Composite, PassKind::Render)
975 .read_texture(scene1)
976 .presents();
977
978 let g = b.compile().expect("compiles");
979 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
980 assert_eq!(order, vec![PassId::Cull, PassId::Main, PassId::Composite]);
981 }
982
983 fn find(g: &CompiledGraph, id: PassId) -> &CompiledPass {
984 g.passes.iter().find(|p| p.id == id).expect("pass present")
985 }
986
987 #[test]
988 fn mixed_stage_read_run_unions_consumer_stages() {
989 let mut b = GraphBuilder::new();
994 let hdr = b.create_texture("hdr", tex());
995 let hdr_v1 = b
996 .add_pass(PassId::Main, PassKind::Render)
997 .write_texture(hdr);
998 b.add_pass(PassId::AutoExposure, PassKind::Compute)
999 .read_texture(hdr_v1);
1000 b.add_pass(PassId::Composite, PassKind::Render)
1001 .read_texture(hdr_v1)
1002 .presents();
1003
1004 let g = b.compile().expect("compiles");
1005 let ae = find(&g, PassId::AutoExposure);
1008 assert_eq!(ae.barriers_before.len(), 1);
1009 assert_eq!(ae.barriers_before[0].source_state(), ResourceState::Write);
1010 assert_eq!(ae.barriers_before[0].to_state(), ResourceState::Read);
1011 let rs = ae.barriers_before[0].read_stages();
1012 assert!(rs.contains(ReadStages::COMPUTE));
1013 assert!(rs.contains(ReadStages::FRAGMENT));
1014 assert_eq!(find(&g, PassId::Composite).barriers_before.len(), 0);
1016 }
1017
1018 #[test]
1019 fn fragment_read_carries_only_fragment_stage() {
1020 let mut b = GraphBuilder::new();
1023 let t = b.create_texture("t", tex());
1024 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
1025 b.add_pass(PassId::Composite, PassKind::Render)
1026 .read_texture(t1)
1027 .presents();
1028
1029 let g = b.compile().expect("compiles");
1030 let comp = find(&g, PassId::Composite);
1031 assert_eq!(comp.barriers_before.len(), 1);
1032 let rs = comp.barriers_before[0].read_stages();
1033 assert!(rs.contains(ReadStages::FRAGMENT));
1034 assert!(!rs.contains(ReadStages::COMPUTE));
1035 }
1036
1037 #[test]
1038 fn compute_read_carries_only_compute_stage() {
1039 let mut b = GraphBuilder::new();
1043 let args = b.create_buffer("draw_args", buf());
1044 let scene = b.create_texture("scene", tex());
1045 let args1 = b
1046 .add_pass(PassId::Cull, PassKind::Compute)
1047 .write_buffer(args);
1048 b.add_pass(PassId::AutoExposure, PassKind::Compute)
1049 .read_buffer(args1);
1050 {
1051 let mut p = b.add_pass(PassId::Composite, PassKind::Render);
1052 let _ = p.write_texture(scene);
1053 p.presents();
1054 }
1055
1056 let g = b.compile().expect("compiles");
1057 let ae = find(&g, PassId::AutoExposure);
1058 assert_eq!(ae.barriers_before.len(), 1);
1059 let rs = ae.barriers_before[0].read_stages();
1060 assert!(rs.contains(ReadStages::COMPUTE));
1061 assert!(!rs.contains(ReadStages::FRAGMENT));
1062 }
1063
1064 #[test]
1065 fn war_barrier_carries_prior_read_run_stage_union() {
1066 let mut b = GraphBuilder::new();
1070 let hdr = b.create_texture("hdr", tex());
1071 let v1 = b
1072 .add_pass(PassId::Main, PassKind::Render)
1073 .write_texture(hdr);
1074 b.add_pass(PassId::AutoExposure, PassKind::Compute)
1075 .read_texture(v1);
1076 b.add_pass(PassId::Fog, PassKind::Render).read_texture(v1);
1077 let v2 = b
1078 .add_pass(PassId::SsaoBlur, PassKind::Render)
1079 .write_texture(v1);
1080 b.add_pass(PassId::Composite, PassKind::Render)
1081 .read_texture(v2)
1082 .presents();
1083
1084 let g = b.compile().expect("compiles");
1085 let blur = find(&g, PassId::SsaoBlur);
1086 assert_eq!(blur.barriers_before.len(), 1);
1087 assert_eq!(blur.barriers_before[0].source_state(), ResourceState::Read);
1088 assert_eq!(blur.barriers_before[0].to_state(), ResourceState::Write);
1089 let rs = blur.barriers_before[0].read_stages();
1090 assert!(rs.contains(ReadStages::COMPUTE));
1091 assert!(rs.contains(ReadStages::FRAGMENT));
1092 }
1093
1094 #[test]
1095 fn producer_write_barrier_has_empty_read_stages() {
1096 let mut b = GraphBuilder::new();
1099 let t = b.create_texture("t", tex());
1100 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
1101 b.add_pass(PassId::Composite, PassKind::Render)
1102 .read_texture(t1)
1103 .presents();
1104
1105 let g = b.compile().expect("compiles");
1106 let main = find(&g, PassId::Main);
1107 assert_eq!(main.barriers_before.len(), 1);
1108 assert_eq!(main.barriers_before[0].to_state(), ResourceState::Write);
1109 assert!(main.barriers_before[0].read_stages().is_empty());
1110 }
1111}