1use alloc::collections::BinaryHeap;
20use alloc::vec;
21use alloc::vec::Vec;
22use core::cmp::Reverse;
23use hashbrown::HashMap;
24
25use super::builder::{GraphBuilder, ResourceVersion};
26use super::passes::PassId;
27use super::types::{
28 BarrierOp, BufferDesc, GraphResourceClass, PassKind, PassRange, ReadStages, ResourceId,
29 ResourceOrigin, ResourceState, TextureDesc,
30};
31
32#[derive(Debug, Clone, PartialEq)]
35pub enum GraphError {
36 MissingPresenter,
39 MultiplePresenters(usize),
42 MissingProducer {
48 pass: PassId,
49 resource_label: &'static str,
50 version: u32,
51 },
52 Cycle,
55}
56
57impl core::fmt::Display for GraphError {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 match self {
60 GraphError::MissingPresenter => {
61 write!(
62 f,
63 "no pass declared presents(); the graph has no terminal node"
64 )
65 }
66 GraphError::MultiplePresenters(n) => {
67 write!(f, "{} passes declared presents(); only one is allowed", n)
68 }
69 GraphError::MissingProducer {
70 pass,
71 resource_label,
72 version,
73 } => write!(
74 f,
75 "pass {:?} reads {} v{} but no pass writes that version",
76 pass, resource_label, version
77 ),
78 GraphError::Cycle => write!(f, "cycle in render-graph read/write edges"),
79 }
80 }
81}
82
83impl core::error::Error for GraphError {}
84
85#[derive(Debug, Clone)]
89pub struct CompiledPass {
90 pub id: PassId,
92 pub kind: PassKind,
94 pub reads: Vec<ResourceVersion>,
96 pub writes: Vec<ResourceVersion>,
98 pub presents: bool,
100 pub barriers_before: Vec<BarrierOp>,
102}
103
104#[derive(Debug, Clone)]
108pub struct CompiledResource {
109 pub label: &'static str,
111 pub origin: ResourceOrigin,
113 pub lifetime: PassRange,
115 pub tex_desc: Option<TextureDesc>,
119 pub(crate) buf_desc: Option<BufferDesc>,
122}
123
124impl CompiledResource {
125 pub fn class(&self) -> Option<GraphResourceClass> {
130 if let Some(desc) = self.tex_desc {
131 return Some(GraphResourceClass::for_texture_usage(desc.usage));
132 }
133 self.buf_desc
134 .map(|desc| GraphResourceClass::for_buffer_usage(desc.usage))
135 }
136}
137
138#[derive(Debug, Clone)]
142pub struct CompiledGraph {
143 pub passes: Vec<CompiledPass>,
145 pub resources: Vec<CompiledResource>,
147}
148
149impl CompiledGraph {
150 #[cfg(test)]
157 pub(crate) fn pass_barriers_for(
158 &self,
159 pass: &CompiledPass,
160 allow: &[&str],
161 ) -> Vec<(&'static str, BarrierOp)> {
162 pass.barriers_before
163 .iter()
164 .filter_map(|op| {
165 let label = self.resources[op.resource_index()].label;
166 allow.contains(&label).then_some((label, *op))
167 })
168 .collect()
169 }
170}
171
172impl GraphBuilder {
173 pub(crate) fn compile(self) -> Result<CompiledGraph, GraphError> {
178 let GraphBuilder {
179 resources,
180 mut passes,
181 } = self;
182 let n_passes = passes.len();
183 let n_resources = resources.len();
184
185 let presenters: Vec<usize> = passes
187 .iter()
188 .enumerate()
189 .filter(|(_, p)| p.presents)
190 .map(|(i, _)| i)
191 .collect();
192 match presenters.len() {
193 0 => return Err(GraphError::MissingPresenter),
194 1 => {}
195 n => return Err(GraphError::MultiplePresenters(n)),
196 }
197
198 let mut writer_of: HashMap<(ResourceId, u32), usize> = HashMap::new();
200 for (i, pass) in passes.iter().enumerate() {
201 for w in &pass.writes {
202 writer_of.insert((w.resource, w.version), i);
206 }
207 }
208
209 for pass in passes.iter() {
216 for r in &pass.reads {
217 if writer_of.contains_key(&(r.resource, r.version)) {
218 continue;
219 }
220 let decl = &resources[r.resource.index()];
221 let implicit_producer = r.version == 0 && decl.origin() == ResourceOrigin::Imported;
222 if !implicit_producer {
223 return Err(GraphError::MissingProducer {
224 pass: pass.id,
225 resource_label: decl.label(),
226 version: r.version,
227 });
228 }
229 }
230 }
231
232 let mut edges: Vec<Vec<usize>> = vec![Vec::new(); n_passes];
235 let mut in_degree: Vec<usize> = vec![0; n_passes];
236 let add_edge =
237 |from: usize, to: usize, edges: &mut [Vec<usize>], in_degree: &mut [usize]| {
238 if from != to {
239 edges[from].push(to);
240 in_degree[to] += 1;
241 }
242 };
243
244 let mut readers_of: HashMap<(ResourceId, u32), Vec<usize>> = HashMap::new();
248 for (i, pass) in passes.iter().enumerate() {
249 for r in &pass.reads {
250 readers_of
251 .entry((r.resource, r.version))
252 .or_default()
253 .push(i);
254 }
255 }
256
257 for (pass_idx, pass) in passes.iter().enumerate() {
258 for r in &pass.reads {
261 if let Some(&w) = writer_of.get(&(r.resource, r.version)) {
262 add_edge(w, pass_idx, &mut edges, &mut in_degree);
263 }
264 }
265 for w in &pass.writes {
270 if w.version > 1
271 && let Some(&prev_writer) = writer_of.get(&(w.resource, w.version - 1))
272 {
273 add_edge(prev_writer, pass_idx, &mut edges, &mut in_degree);
274 }
275 }
276 for w in &pass.writes {
285 if w.version > 0
286 && let Some(readers) = readers_of.get(&(w.resource, w.version - 1))
287 {
288 for &reader in readers {
289 add_edge(reader, pass_idx, &mut edges, &mut in_degree);
290 }
291 }
292 }
293 }
294
295 let mut ready: BinaryHeap<Reverse<usize>> = (0..n_passes)
299 .filter(|&i| in_degree[i] == 0)
300 .map(Reverse)
301 .collect();
302 let mut order: Vec<usize> = Vec::with_capacity(n_passes);
303 while let Some(Reverse(idx)) = ready.pop() {
304 order.push(idx);
305 for &neighbor in &edges[idx] {
306 in_degree[neighbor] -= 1;
307 if in_degree[neighbor] == 0 {
308 ready.push(Reverse(neighbor));
309 }
310 }
311 }
312 if order.len() != n_passes {
313 return Err(GraphError::Cycle);
314 }
315
316 let mut compiled_passes: Vec<CompiledPass> = order
318 .iter()
319 .map(|&orig_idx| {
320 let decl = &mut passes[orig_idx];
323 CompiledPass {
324 id: decl.id,
325 kind: decl.kind,
326 reads: core::mem::take(&mut decl.reads),
327 writes: core::mem::take(&mut decl.writes),
328 presents: decl.presents,
329 barriers_before: Vec::new(),
330 }
331 })
332 .collect();
333
334 derive_barriers(&mut compiled_passes, n_resources);
336
337 let mut lifetimes: Vec<Option<PassRange>> = vec![None; n_resources];
339 for (sorted_idx, pass) in compiled_passes.iter().enumerate() {
340 for v in pass.writes.iter().chain(pass.reads.iter()) {
341 let i = v.resource.index();
342 let merged = match lifetimes[i] {
343 None => PassRange {
344 first: sorted_idx,
345 last: sorted_idx,
346 },
347 Some(PassRange { first, .. }) => PassRange {
348 first,
349 last: sorted_idx,
350 },
351 };
352 lifetimes[i] = Some(merged);
353 }
354 }
355
356 let compiled_resources: Vec<CompiledResource> = resources
357 .into_iter()
358 .enumerate()
359 .map(|(i, decl)| {
360 let lifetime = lifetimes[i].unwrap_or(PassRange { first: 0, last: 0 });
364 CompiledResource {
365 label: decl.label(),
366 origin: decl.origin(),
367 lifetime,
368 tex_desc: decl.texture_desc(),
369 buf_desc: decl.buffer_desc(),
370 }
371 })
372 .collect();
373
374 Ok(CompiledGraph {
375 passes: compiled_passes,
376 resources: compiled_resources,
377 })
378 }
379}
380
381fn derive_barriers(passes: &mut [CompiledPass], n_resources: usize) {
400 #[derive(Copy, Clone)]
403 enum Eff {
404 Write,
405 Read(ReadStages),
406 }
407
408 let mut timeline: Vec<Vec<(usize, Eff)>> = (0..n_resources).map(|_| Vec::new()).collect();
413 for (i, pass) in passes.iter().enumerate() {
414 let stage = ReadStages::for_pass_kind(pass.kind);
415 let mut access: HashMap<ResourceId, Eff> = HashMap::new();
416 for r in &pass.reads {
417 access.entry(r.resource).or_insert(Eff::Read(stage));
418 }
419 for w in &pass.writes {
420 access.insert(w.resource, Eff::Write);
421 }
422 let mut touched: Vec<(ResourceId, Eff)> = access.into_iter().collect();
426 touched.sort_by_key(|(r, _)| r.0);
427 for (res, eff) in touched {
428 timeline[res.index()].push((i, eff));
429 }
430 }
431
432 for (r_idx, entries) in timeline.iter().enumerate() {
435 let resource = ResourceId(r_idx as u32);
436 let mut state = ResourceState::Undefined;
437 let mut run_stages = ReadStages::empty();
438
439 for (k, &(pass_idx, eff)) in entries.iter().enumerate() {
440 match eff {
441 Eff::Write => {
442 let read_stages = if state == ResourceState::Read {
453 run_stages
456 } else {
457 ReadStages::empty()
460 };
461 passes[pass_idx].barriers_before.push(BarrierOp {
462 resource,
463 from: state,
464 to: ResourceState::Write,
465 read_stages,
466 });
467 state = ResourceState::Write;
468 run_stages = ReadStages::empty();
469 }
470 Eff::Read(_) => {
471 if state != ResourceState::Read {
472 let mut run = ReadStages::empty();
475 for &(_, e) in entries[k..].iter() {
476 match e {
477 Eff::Read(s) => run = run.union(s),
478 Eff::Write => break,
479 }
480 }
481 passes[pass_idx].barriers_before.push(BarrierOp {
482 resource,
483 from: state,
484 to: ResourceState::Read,
485 read_stages: run,
486 });
487 state = ResourceState::Read;
488 run_stages = run;
489 }
490 }
493 }
494 }
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::super::types::{BufferUsage, PixelFormat, TextureDesc, TextureSize, TextureUsage};
501 use super::*;
502 use crate::render_graph::builder::GraphBuilder;
503 use crate::render_graph::passes::PassId;
504 use crate::render_graph::types::{BufferDesc, PassKind};
505
506 fn tex() -> TextureDesc {
507 TextureDesc::texture_2d(
508 TextureSize::Drawable,
509 TextureSize::Drawable,
510 PixelFormat::Rgba16Float,
511 TextureUsage::SHADER_READ | TextureUsage::RENDER_TARGET,
512 )
513 }
514
515 fn buf() -> BufferDesc {
516 BufferDesc {
517 size_bytes: None,
518 usage: BufferUsage::STORAGE,
519 }
520 }
521
522 #[test]
523 fn linear_chain_toposorts_in_declared_order() {
524 let mut b = GraphBuilder::new();
526 let t = b.create_texture("t", tex());
527 let u = b.create_texture("u", tex());
528
529 let t1 = b
530 .add_pass(PassId::Shadow, PassKind::Render)
531 .write_texture(t);
532 let u1 = {
533 let mut p = b.add_pass(PassId::Main, PassKind::Render);
534 p.read_texture(t1);
535 p.write_texture(u)
536 };
537 b.add_pass(PassId::Composite, PassKind::Render)
538 .read_texture(u1)
539 .presents();
540
541 let g = b.compile().expect("graph compiles");
542 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
543 assert_eq!(order, vec![PassId::Shadow, PassId::Main, PassId::Composite]);
544 }
545
546 #[test]
547 fn diamond_toposorts_with_stable_tiebreak() {
548 let mut b = GraphBuilder::new();
552 let t = b.create_texture("t", tex());
553 let u = b.create_texture("u", tex());
554 let v = b.create_texture("v", tex());
555
556 let t1 = b
557 .add_pass(PassId::Shadow, PassKind::Render)
558 .write_texture(t);
559 let u1 = {
560 let mut p = b.add_pass(PassId::Main, PassKind::Render);
561 p.read_texture(t1);
562 p.write_texture(u)
563 };
564 let v1 = {
565 let mut p = b.add_pass(PassId::SsaoKernel, PassKind::Render);
566 p.read_texture(t1);
567 p.write_texture(v)
568 };
569 b.add_pass(PassId::Composite, PassKind::Render)
570 .read_texture(u1)
571 .read_texture(v1)
572 .presents();
573
574 let g = b.compile().expect("graph compiles");
575 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
576 assert_eq!(
581 order,
582 vec![
583 PassId::Shadow,
584 PassId::Main,
585 PassId::SsaoKernel,
586 PassId::Composite,
587 ]
588 );
589 }
590
591 #[test]
592 fn read_modify_write_chain_orders_correctly() {
593 let mut b = GraphBuilder::new();
597 let hdr = b.create_texture("hdr", tex());
598
599 let v1 = b
600 .add_pass(PassId::Main, PassKind::Render)
601 .write_texture(hdr);
602 let v2 = {
603 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
604 p.read_texture(v1);
605 p.write_texture(v1)
606 };
607 let v3 = {
608 let mut p = b.add_pass(PassId::Fog, PassKind::Render);
609 p.read_texture(v2);
610 p.write_texture(v2)
611 };
612 b.add_pass(PassId::Composite, PassKind::Render)
613 .read_texture(v3)
614 .presents();
615
616 let g = b.compile().expect("graph compiles");
617 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
618 assert_eq!(
619 order,
620 vec![PassId::Main, PassId::Decals, PassId::Fog, PassId::Composite,]
621 );
622 }
623
624 #[test]
625 fn war_exposes_cross_rmw_cycle() {
626 let mut b = GraphBuilder::new();
636 let x = b.create_texture("x", tex());
637 let y = b.create_texture("y", tex());
638
639 let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
640 let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
641
642 let _x2 = {
643 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
644 p.read_texture(y1);
645 p.write_texture(x1)
646 };
647 let _y2 = {
648 let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
649 p.read_texture(x1);
650 p.write_texture(y1)
651 };
652 b.add_pass(PassId::Composite, PassKind::Render).presents();
653
654 match b.compile() {
655 Err(GraphError::Cycle) => {}
656 other => panic!("expected Cycle, got {:?}", other),
657 }
658 }
659
660 #[test]
661 fn mutual_write_cycle_errors() {
662 let mut b = GraphBuilder::new();
667 let x = b.create_texture("x", tex());
668 let y = b.create_texture("y", tex());
669
670 let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
672 let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
673
674 {
680 let mut p = b.add_pass(PassId::Decals, PassKind::Render);
681 p.read_texture(super::super::types::TextureHandle {
683 resource: y1.resource,
684 version: 2,
685 });
686 p.write_texture(x1);
687 }
688 {
689 let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
690 p.read_texture(super::super::types::TextureHandle {
691 resource: x1.resource,
692 version: 2,
693 });
694 p.write_texture(y1);
695 }
696 b.add_pass(PassId::Composite, PassKind::Render).presents();
697
698 match b.compile() {
699 Err(GraphError::Cycle) => {}
700 other => panic!("expected Cycle, got {:?}", other),
701 }
702 }
703
704 #[test]
705 fn war_edges_pin_reader_before_writer() {
706 let mut b = GraphBuilder::new();
717 let hdr = b.create_texture("hdr_resolve", tex());
718
719 let hdr_v1 = b
720 .add_pass(PassId::Main, PassKind::Render)
721 .write_texture(hdr);
722
723 let _hdr_v2 = b
726 .add_pass(PassId::Decals, PassKind::Render)
727 .write_texture(hdr_v1);
728
729 b.add_pass(PassId::AutoExposure, PassKind::Compute)
730 .read_texture(hdr_v1);
731
732 b.add_pass(PassId::Composite, PassKind::Render).presents();
733
734 let g = b.compile().expect("compiles");
735 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
736 assert_eq!(
739 order,
740 vec![
741 PassId::Main,
742 PassId::AutoExposure,
743 PassId::Decals,
744 PassId::Composite,
745 ]
746 );
747 }
748
749 #[test]
750 fn imported_v0_read_does_not_error() {
751 let mut b = GraphBuilder::new();
757 let env = b.import_texture("env", tex());
758 let scene = b.create_texture("scene", tex());
759 {
760 let mut p = b.add_pass(PassId::Main, PassKind::Render);
761 p.read_texture(env);
762 p.write_texture(scene);
763 }
764 b.add_pass(PassId::Composite, PassKind::Render)
765 .read_texture(super::super::types::TextureHandle {
766 resource: scene.resource,
767 version: 1,
768 })
769 .presents();
770
771 let g = b.compile().expect("imported v0 read should compile");
772 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
773 assert_eq!(order, vec![PassId::Main, PassId::Composite]);
774 }
775
776 #[test]
777 fn transient_v0_read_still_errors() {
778 let mut b = GraphBuilder::new();
782 let t = b.create_texture("scratch", tex());
783 b.add_pass(PassId::Composite, PassKind::Render)
784 .read_texture(t)
785 .presents();
786 match b.compile() {
787 Err(GraphError::MissingProducer {
788 pass: PassId::Composite,
789 resource_label: "scratch",
790 version: 0,
791 }) => {}
792 other => panic!("expected MissingProducer for transient v0, got {:?}", other),
793 }
794 }
795
796 #[test]
797 fn missing_producer_errors() {
798 let mut b = GraphBuilder::new();
800 let t = b.create_texture("t", tex());
801
802 let phantom = super::super::types::TextureHandle {
805 resource: t.resource,
806 version: 1,
807 };
808 b.add_pass(PassId::Composite, PassKind::Render)
809 .read_texture(phantom)
810 .presents();
811
812 match b.compile() {
813 Err(GraphError::MissingProducer {
814 pass: PassId::Composite,
815 resource_label: "t",
816 version: 1,
817 }) => {}
818 other => panic!("expected MissingProducer, got {:?}", other),
819 }
820 }
821
822 #[test]
823 fn no_presenter_errors() {
824 let mut b = GraphBuilder::new();
825 let t = b.create_texture("t", tex());
826 b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
827 match b.compile() {
829 Err(GraphError::MissingPresenter) => {}
830 other => panic!("expected MissingPresenter, got {:?}", other),
831 }
832 }
833
834 #[test]
835 fn multiple_presenters_errors() {
836 let mut b = GraphBuilder::new();
837 let t = b.create_texture("t", tex());
838 {
839 let mut p = b.add_pass(PassId::Main, PassKind::Render);
840 p.presents();
841 let _ = p.write_texture(t);
842 }
843 b.add_pass(PassId::Composite, PassKind::Render).presents();
844 match b.compile() {
845 Err(GraphError::MultiplePresenters(2)) => {}
846 other => panic!("expected MultiplePresenters(2), got {:?}", other),
847 }
848 }
849
850 #[test]
851 fn barriers_emit_on_state_transitions() {
852 let mut b = GraphBuilder::new();
856 let t = b.create_texture("t", tex());
857 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
858 b.add_pass(PassId::Composite, PassKind::Render)
859 .read_texture(t1)
860 .presents();
861
862 let g = b.compile().expect("compiles");
863 assert_eq!(g.passes[0].barriers_before.len(), 1);
865 assert_eq!(
866 g.passes[0].barriers_before[0].from,
867 ResourceState::Undefined
868 );
869 assert_eq!(g.passes[0].barriers_before[0].to, ResourceState::Write);
870 assert_eq!(g.passes[1].barriers_before.len(), 1);
872 assert_eq!(g.passes[1].barriers_before[0].from, ResourceState::Write);
873 assert_eq!(g.passes[1].barriers_before[0].to, ResourceState::Read);
874 }
875
876 #[test]
877 fn pass_barriers_for_filters_by_label() {
878 let mut b = GraphBuilder::new();
882 let keep = b.create_texture("keep", tex());
883 let skip = b.create_texture("skip", tex());
884 let (keep1, skip1) = {
885 let mut p = b.add_pass(PassId::Main, PassKind::Render);
886 (p.write_texture(keep), p.write_texture(skip))
887 };
888 b.add_pass(PassId::Composite, PassKind::Render)
889 .read_texture(keep1)
890 .read_texture(skip1)
891 .presents();
892
893 let g = b.compile().expect("compiles");
894 let main = &g.passes[0];
896 let kept = g.pass_barriers_for(main, &["keep"]);
897 assert_eq!(kept.len(), 1);
898 assert_eq!(kept[0].0, "keep");
899 assert_eq!(kept[0].1.source_state(), ResourceState::Undefined);
900 assert_eq!(kept[0].1.to_state(), ResourceState::Write);
901 let composite = &g.passes[1];
903 let kept = g.pass_barriers_for(composite, &["keep"]);
904 assert_eq!(kept.len(), 1);
905 assert_eq!(kept[0].1.source_state(), ResourceState::Write);
906 assert_eq!(kept[0].1.to_state(), ResourceState::Read);
907 assert!(g.pass_barriers_for(main, &[]).is_empty());
909 assert!(g.pass_barriers_for(main, &["nope"]).is_empty());
910 }
911
912 #[test]
913 fn consecutive_reads_coalesce_no_barriers() {
914 let mut b = GraphBuilder::new();
917 let t = b.create_texture("t", tex());
918 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
919 b.add_pass(PassId::Decals, PassKind::Render)
920 .read_texture(t1);
921 b.add_pass(PassId::Fog, PassKind::Render).read_texture(t1);
922 b.add_pass(PassId::Composite, PassKind::Render)
923 .read_texture(t1)
924 .presents();
925
926 let g = b.compile().expect("compiles");
927 assert_eq!(g.passes[0].barriers_before.len(), 1);
929 assert_eq!(g.passes[1].barriers_before.len(), 1);
931 assert_eq!(g.passes[2].barriers_before.len(), 0);
933 assert_eq!(g.passes[3].barriers_before.len(), 0);
935 }
936
937 #[test]
938 fn lifetime_intervals_span_first_write_to_last_read() {
939 let mut b = GraphBuilder::new();
942 let t = b.create_texture("t", tex());
943 let unrelated = b.create_texture("u", tex());
944
945 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
946 b.add_pass(PassId::Decals, PassKind::Render)
947 .read_texture(t1);
948 b.add_pass(PassId::Fog, PassKind::Render)
949 .write_texture(unrelated);
950 b.add_pass(PassId::Composite, PassKind::Render)
951 .read_texture(t1)
952 .presents();
953
954 let g = b.compile().expect("compiles");
955 let t_idx = t.resource.index();
956 let u_idx = unrelated.resource.index();
957 assert_eq!(g.resources[t_idx].lifetime.first, 0);
958 assert_eq!(g.resources[t_idx].lifetime.last, 3);
959 assert_eq!(g.resources[u_idx].lifetime.first, 2);
960 assert_eq!(g.resources[u_idx].lifetime.last, 2);
961 }
962
963 #[test]
964 fn buffer_dep_edges_work_too() {
965 let mut b = GraphBuilder::new();
967 let draw_args = b.create_buffer("draw_args", buf());
968 let scene = b.create_texture("scene", tex());
969
970 let args1 = b
971 .add_pass(PassId::Cull, PassKind::Compute)
972 .write_buffer(draw_args);
973 let scene1 = {
974 let mut p = b.add_pass(PassId::Main, PassKind::Render);
975 p.read_buffer(args1);
976 p.write_texture(scene)
977 };
978 b.add_pass(PassId::Composite, PassKind::Render)
979 .read_texture(scene1)
980 .presents();
981
982 let g = b.compile().expect("compiles");
983 let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
984 assert_eq!(order, vec![PassId::Cull, PassId::Main, PassId::Composite]);
985 }
986
987 fn find(g: &CompiledGraph, id: PassId) -> &CompiledPass {
988 g.passes.iter().find(|p| p.id == id).expect("pass present")
989 }
990
991 #[test]
992 fn mixed_stage_read_run_unions_consumer_stages() {
993 let mut b = GraphBuilder::new();
998 let hdr = b.create_texture("hdr", tex());
999 let hdr_v1 = b
1000 .add_pass(PassId::Main, PassKind::Render)
1001 .write_texture(hdr);
1002 b.add_pass(PassId::AutoExposure, PassKind::Compute)
1003 .read_texture(hdr_v1);
1004 b.add_pass(PassId::Composite, PassKind::Render)
1005 .read_texture(hdr_v1)
1006 .presents();
1007
1008 let g = b.compile().expect("compiles");
1009 let ae = find(&g, PassId::AutoExposure);
1012 assert_eq!(ae.barriers_before.len(), 1);
1013 assert_eq!(ae.barriers_before[0].source_state(), ResourceState::Write);
1014 assert_eq!(ae.barriers_before[0].to_state(), ResourceState::Read);
1015 let rs = ae.barriers_before[0].read_stages();
1016 assert!(rs.contains(ReadStages::COMPUTE));
1017 assert!(rs.contains(ReadStages::FRAGMENT));
1018 assert_eq!(find(&g, PassId::Composite).barriers_before.len(), 0);
1020 }
1021
1022 #[test]
1023 fn fragment_read_carries_only_fragment_stage() {
1024 let mut b = GraphBuilder::new();
1027 let t = b.create_texture("t", tex());
1028 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
1029 b.add_pass(PassId::Composite, PassKind::Render)
1030 .read_texture(t1)
1031 .presents();
1032
1033 let g = b.compile().expect("compiles");
1034 let comp = find(&g, PassId::Composite);
1035 assert_eq!(comp.barriers_before.len(), 1);
1036 let rs = comp.barriers_before[0].read_stages();
1037 assert!(rs.contains(ReadStages::FRAGMENT));
1038 assert!(!rs.contains(ReadStages::COMPUTE));
1039 }
1040
1041 #[test]
1042 fn compute_read_carries_only_compute_stage() {
1043 let mut b = GraphBuilder::new();
1047 let args = b.create_buffer("draw_args", buf());
1048 let scene = b.create_texture("scene", tex());
1049 let args1 = b
1050 .add_pass(PassId::Cull, PassKind::Compute)
1051 .write_buffer(args);
1052 b.add_pass(PassId::AutoExposure, PassKind::Compute)
1053 .read_buffer(args1);
1054 {
1055 let mut p = b.add_pass(PassId::Composite, PassKind::Render);
1056 let _ = p.write_texture(scene);
1057 p.presents();
1058 }
1059
1060 let g = b.compile().expect("compiles");
1061 let ae = find(&g, PassId::AutoExposure);
1062 assert_eq!(ae.barriers_before.len(), 1);
1063 let rs = ae.barriers_before[0].read_stages();
1064 assert!(rs.contains(ReadStages::COMPUTE));
1065 assert!(!rs.contains(ReadStages::FRAGMENT));
1066 }
1067
1068 #[test]
1069 fn war_barrier_carries_prior_read_run_stage_union() {
1070 let mut b = GraphBuilder::new();
1074 let hdr = b.create_texture("hdr", tex());
1075 let v1 = b
1076 .add_pass(PassId::Main, PassKind::Render)
1077 .write_texture(hdr);
1078 b.add_pass(PassId::AutoExposure, PassKind::Compute)
1079 .read_texture(v1);
1080 b.add_pass(PassId::Fog, PassKind::Render).read_texture(v1);
1081 let v2 = b
1082 .add_pass(PassId::SsaoBlur, PassKind::Render)
1083 .write_texture(v1);
1084 b.add_pass(PassId::Composite, PassKind::Render)
1085 .read_texture(v2)
1086 .presents();
1087
1088 let g = b.compile().expect("compiles");
1089 let blur = find(&g, PassId::SsaoBlur);
1090 assert_eq!(blur.barriers_before.len(), 1);
1091 assert_eq!(blur.barriers_before[0].source_state(), ResourceState::Read);
1092 assert_eq!(blur.barriers_before[0].to_state(), ResourceState::Write);
1093 let rs = blur.barriers_before[0].read_stages();
1094 assert!(rs.contains(ReadStages::COMPUTE));
1095 assert!(rs.contains(ReadStages::FRAGMENT));
1096 }
1097
1098 #[test]
1099 fn producer_write_barrier_has_empty_read_stages() {
1100 let mut b = GraphBuilder::new();
1103 let t = b.create_texture("t", tex());
1104 let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
1105 b.add_pass(PassId::Composite, PassKind::Render)
1106 .read_texture(t1)
1107 .presents();
1108
1109 let g = b.compile().expect("compiles");
1110 let main = find(&g, PassId::Main);
1111 assert_eq!(main.barriers_before.len(), 1);
1112 assert_eq!(main.barriers_before[0].to_state(), ResourceState::Write);
1113 assert!(main.barriers_before[0].read_stages().is_empty());
1114 }
1115}