use alloc::collections::BinaryHeap;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Reverse;
use hashbrown::HashMap;
use super::builder::{GraphBuilder, ResourceVersion};
use super::passes::PassId;
use super::types::{
BarrierOp, BufferDesc, GraphResourceClass, PassKind, PassRange, ReadStages, ResourceId,
ResourceOrigin, ResourceState, TextureDesc,
};
#[derive(Debug, Clone, PartialEq)]
pub enum GraphError {
MissingPresenter,
MultiplePresenters(usize),
MissingProducer {
pass: PassId,
resource_label: &'static str,
version: u32,
},
Cycle,
}
impl core::fmt::Display for GraphError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
GraphError::MissingPresenter => {
write!(
f,
"no pass declared presents(); the graph has no terminal node"
)
}
GraphError::MultiplePresenters(n) => {
write!(f, "{} passes declared presents(); only one is allowed", n)
}
GraphError::MissingProducer {
pass,
resource_label,
version,
} => write!(
f,
"pass {:?} reads {} v{} but no pass writes that version",
pass, resource_label, version
),
GraphError::Cycle => write!(f, "cycle in render-graph read/write edges"),
}
}
}
impl core::error::Error for GraphError {}
#[derive(Debug, Clone)]
pub struct CompiledPass {
pub id: PassId,
pub kind: PassKind,
pub reads: Vec<ResourceVersion>,
pub writes: Vec<ResourceVersion>,
pub presents: bool,
pub barriers_before: Vec<BarrierOp>,
}
#[derive(Debug, Clone)]
pub struct CompiledResource {
pub label: &'static str,
pub origin: ResourceOrigin,
pub lifetime: PassRange,
pub tex_desc: Option<TextureDesc>,
pub(crate) buf_desc: Option<BufferDesc>,
}
impl CompiledResource {
pub fn class(&self) -> Option<GraphResourceClass> {
if let Some(desc) = self.tex_desc {
return Some(GraphResourceClass::for_texture_usage(desc.usage));
}
self.buf_desc
.map(|desc| GraphResourceClass::for_buffer_usage(desc.usage))
}
}
#[derive(Debug, Clone)]
pub struct CompiledGraph {
pub passes: Vec<CompiledPass>,
pub resources: Vec<CompiledResource>,
}
impl CompiledGraph {
#[cfg(test)]
pub(crate) fn pass_barriers_for(
&self,
pass: &CompiledPass,
allow: &[&str],
) -> Vec<(&'static str, BarrierOp)> {
pass.barriers_before
.iter()
.filter_map(|op| {
let label = self.resources[op.resource_index()].label;
allow.contains(&label).then_some((label, *op))
})
.collect()
}
}
impl GraphBuilder {
pub(crate) fn compile(self) -> Result<CompiledGraph, GraphError> {
let GraphBuilder {
resources,
mut passes,
} = self;
let n_passes = passes.len();
let n_resources = resources.len();
let presenters: Vec<usize> = passes
.iter()
.enumerate()
.filter(|(_, p)| p.presents)
.map(|(i, _)| i)
.collect();
match presenters.len() {
0 => return Err(GraphError::MissingPresenter),
1 => {}
n => return Err(GraphError::MultiplePresenters(n)),
}
let mut writer_of: HashMap<(ResourceId, u32), usize> = HashMap::new();
for (i, pass) in passes.iter().enumerate() {
for w in &pass.writes {
writer_of.insert((w.resource, w.version), i);
}
}
for pass in passes.iter() {
for r in &pass.reads {
if writer_of.contains_key(&(r.resource, r.version)) {
continue;
}
let decl = &resources[r.resource.index()];
let implicit_producer = r.version == 0 && decl.origin() == ResourceOrigin::Imported;
if !implicit_producer {
return Err(GraphError::MissingProducer {
pass: pass.id,
resource_label: decl.label(),
version: r.version,
});
}
}
}
let mut edges: Vec<Vec<usize>> = vec![Vec::new(); n_passes];
let mut in_degree: Vec<usize> = vec![0; n_passes];
let add_edge =
|from: usize, to: usize, edges: &mut [Vec<usize>], in_degree: &mut [usize]| {
if from != to {
edges[from].push(to);
in_degree[to] += 1;
}
};
let mut readers_of: HashMap<(ResourceId, u32), Vec<usize>> = HashMap::new();
for (i, pass) in passes.iter().enumerate() {
for r in &pass.reads {
readers_of
.entry((r.resource, r.version))
.or_default()
.push(i);
}
}
for (pass_idx, pass) in passes.iter().enumerate() {
for r in &pass.reads {
if let Some(&w) = writer_of.get(&(r.resource, r.version)) {
add_edge(w, pass_idx, &mut edges, &mut in_degree);
}
}
for w in &pass.writes {
if w.version > 1
&& let Some(&prev_writer) = writer_of.get(&(w.resource, w.version - 1))
{
add_edge(prev_writer, pass_idx, &mut edges, &mut in_degree);
}
}
for w in &pass.writes {
if w.version > 0
&& let Some(readers) = readers_of.get(&(w.resource, w.version - 1))
{
for &reader in readers {
add_edge(reader, pass_idx, &mut edges, &mut in_degree);
}
}
}
}
let mut ready: BinaryHeap<Reverse<usize>> = (0..n_passes)
.filter(|&i| in_degree[i] == 0)
.map(Reverse)
.collect();
let mut order: Vec<usize> = Vec::with_capacity(n_passes);
while let Some(Reverse(idx)) = ready.pop() {
order.push(idx);
for &neighbor in &edges[idx] {
in_degree[neighbor] -= 1;
if in_degree[neighbor] == 0 {
ready.push(Reverse(neighbor));
}
}
}
if order.len() != n_passes {
return Err(GraphError::Cycle);
}
let mut compiled_passes: Vec<CompiledPass> = order
.iter()
.map(|&orig_idx| {
let decl = &mut passes[orig_idx];
CompiledPass {
id: decl.id,
kind: decl.kind,
reads: core::mem::take(&mut decl.reads),
writes: core::mem::take(&mut decl.writes),
presents: decl.presents,
barriers_before: Vec::new(),
}
})
.collect();
derive_barriers(&mut compiled_passes, n_resources);
let mut lifetimes: Vec<Option<PassRange>> = vec![None; n_resources];
for (sorted_idx, pass) in compiled_passes.iter().enumerate() {
for v in pass.writes.iter().chain(pass.reads.iter()) {
let i = v.resource.index();
let merged = match lifetimes[i] {
None => PassRange {
first: sorted_idx,
last: sorted_idx,
},
Some(PassRange { first, .. }) => PassRange {
first,
last: sorted_idx,
},
};
lifetimes[i] = Some(merged);
}
}
let compiled_resources: Vec<CompiledResource> = resources
.into_iter()
.enumerate()
.map(|(i, decl)| {
let lifetime = lifetimes[i].unwrap_or(PassRange { first: 0, last: 0 });
CompiledResource {
label: decl.label(),
origin: decl.origin(),
lifetime,
tex_desc: decl.texture_desc(),
buf_desc: decl.buffer_desc(),
}
})
.collect();
Ok(CompiledGraph {
passes: compiled_passes,
resources: compiled_resources,
})
}
}
fn derive_barriers(passes: &mut [CompiledPass], n_resources: usize) {
#[derive(Copy, Clone)]
enum Eff {
Write,
Read(ReadStages),
}
let mut timeline: Vec<Vec<(usize, Eff)>> = (0..n_resources).map(|_| Vec::new()).collect();
for (i, pass) in passes.iter().enumerate() {
let stage = ReadStages::for_pass_kind(pass.kind);
let mut access: HashMap<ResourceId, Eff> = HashMap::new();
for r in &pass.reads {
access.entry(r.resource).or_insert(Eff::Read(stage));
}
for w in &pass.writes {
access.insert(w.resource, Eff::Write);
}
let mut touched: Vec<(ResourceId, Eff)> = access.into_iter().collect();
touched.sort_by_key(|(r, _)| r.0);
for (res, eff) in touched {
timeline[res.index()].push((i, eff));
}
}
for (r_idx, entries) in timeline.iter().enumerate() {
let resource = ResourceId(r_idx as u32);
let mut state = ResourceState::Undefined;
let mut run_stages = ReadStages::empty();
for (k, &(pass_idx, eff)) in entries.iter().enumerate() {
match eff {
Eff::Write => {
let read_stages = if state == ResourceState::Read {
run_stages
} else {
ReadStages::empty()
};
passes[pass_idx].barriers_before.push(BarrierOp {
resource,
from: state,
to: ResourceState::Write,
read_stages,
});
state = ResourceState::Write;
run_stages = ReadStages::empty();
}
Eff::Read(_) => {
if state != ResourceState::Read {
let mut run = ReadStages::empty();
for &(_, e) in entries[k..].iter() {
match e {
Eff::Read(s) => run = run.union(s),
Eff::Write => break,
}
}
passes[pass_idx].barriers_before.push(BarrierOp {
resource,
from: state,
to: ResourceState::Read,
read_stages: run,
});
state = ResourceState::Read;
run_stages = run;
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::super::types::{BufferUsage, PixelFormat, TextureDesc, TextureSize, TextureUsage};
use super::*;
use crate::render::render_graph::builder::GraphBuilder;
use crate::render::render_graph::passes::PassId;
use crate::render::render_graph::types::{BufferDesc, PassKind};
fn tex() -> TextureDesc {
TextureDesc::texture_2d(
TextureSize::Drawable,
TextureSize::Drawable,
PixelFormat::Rgba16Float,
TextureUsage::SHADER_READ | TextureUsage::RENDER_TARGET,
)
}
fn buf() -> BufferDesc {
BufferDesc {
size_bytes: None,
usage: BufferUsage::STORAGE,
}
}
#[test]
fn linear_chain_toposorts_in_declared_order() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let u = b.create_texture("u", tex());
let t1 = b
.add_pass(PassId::Shadow, PassKind::Render)
.write_texture(t);
let u1 = {
let mut p = b.add_pass(PassId::Main, PassKind::Render);
p.read_texture(t1);
p.write_texture(u)
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(u1)
.presents();
let g = b.compile().expect("graph compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Shadow, PassId::Main, PassId::Composite]);
}
#[test]
fn diamond_toposorts_with_stable_tiebreak() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let u = b.create_texture("u", tex());
let v = b.create_texture("v", tex());
let t1 = b
.add_pass(PassId::Shadow, PassKind::Render)
.write_texture(t);
let u1 = {
let mut p = b.add_pass(PassId::Main, PassKind::Render);
p.read_texture(t1);
p.write_texture(u)
};
let v1 = {
let mut p = b.add_pass(PassId::SsaoKernel, PassKind::Render);
p.read_texture(t1);
p.write_texture(v)
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(u1)
.read_texture(v1)
.presents();
let g = b.compile().expect("graph compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Shadow,
PassId::Main,
PassId::SsaoKernel,
PassId::Composite,
]
);
}
#[test]
fn read_modify_write_chain_orders_correctly() {
let mut b = GraphBuilder::new();
let hdr = b.create_texture("hdr", tex());
let v1 = b
.add_pass(PassId::Main, PassKind::Render)
.write_texture(hdr);
let v2 = {
let mut p = b.add_pass(PassId::Decals, PassKind::Render);
p.read_texture(v1);
p.write_texture(v1)
};
let v3 = {
let mut p = b.add_pass(PassId::Fog, PassKind::Render);
p.read_texture(v2);
p.write_texture(v2)
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(v3)
.presents();
let g = b.compile().expect("graph compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![PassId::Main, PassId::Decals, PassId::Fog, PassId::Composite,]
);
}
#[test]
fn war_exposes_cross_rmw_cycle() {
let mut b = GraphBuilder::new();
let x = b.create_texture("x", tex());
let y = b.create_texture("y", tex());
let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
let _x2 = {
let mut p = b.add_pass(PassId::Decals, PassKind::Render);
p.read_texture(y1);
p.write_texture(x1)
};
let _y2 = {
let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
p.read_texture(x1);
p.write_texture(y1)
};
b.add_pass(PassId::Composite, PassKind::Render).presents();
match b.compile() {
Err(GraphError::Cycle) => {}
other => panic!("expected Cycle, got {:?}", other),
}
}
#[test]
fn mutual_write_cycle_errors() {
let mut b = GraphBuilder::new();
let x = b.create_texture("x", tex());
let y = b.create_texture("y", tex());
let x1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(x);
let y1 = b.add_pass(PassId::Fog, PassKind::Render).write_texture(y);
{
let mut p = b.add_pass(PassId::Decals, PassKind::Render);
p.read_texture(super::super::types::TextureHandle {
resource: y1.resource,
version: 2,
});
p.write_texture(x1);
}
{
let mut p = b.add_pass(PassId::SsaoBlur, PassKind::Render);
p.read_texture(super::super::types::TextureHandle {
resource: x1.resource,
version: 2,
});
p.write_texture(y1);
}
b.add_pass(PassId::Composite, PassKind::Render).presents();
match b.compile() {
Err(GraphError::Cycle) => {}
other => panic!("expected Cycle, got {:?}", other),
}
}
#[test]
fn war_edges_pin_reader_before_writer() {
let mut b = GraphBuilder::new();
let hdr = b.create_texture("hdr_resolve", tex());
let hdr_v1 = b
.add_pass(PassId::Main, PassKind::Render)
.write_texture(hdr);
let _hdr_v2 = b
.add_pass(PassId::Decals, PassKind::Render)
.write_texture(hdr_v1);
b.add_pass(PassId::AutoExposure, PassKind::Compute)
.read_texture(hdr_v1);
b.add_pass(PassId::Composite, PassKind::Render).presents();
let g = b.compile().expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(
order,
vec![
PassId::Main,
PassId::AutoExposure,
PassId::Decals,
PassId::Composite,
]
);
}
#[test]
fn imported_v0_read_does_not_error() {
let mut b = GraphBuilder::new();
let env = b.import_texture("env", tex());
let scene = b.create_texture("scene", tex());
{
let mut p = b.add_pass(PassId::Main, PassKind::Render);
p.read_texture(env);
p.write_texture(scene);
}
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(super::super::types::TextureHandle {
resource: scene.resource,
version: 1,
})
.presents();
let g = b.compile().expect("imported v0 read should compile");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Main, PassId::Composite]);
}
#[test]
fn transient_v0_read_still_errors() {
let mut b = GraphBuilder::new();
let t = b.create_texture("scratch", tex());
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t)
.presents();
match b.compile() {
Err(GraphError::MissingProducer {
pass: PassId::Composite,
resource_label: "scratch",
version: 0,
}) => {}
other => panic!("expected MissingProducer for transient v0, got {:?}", other),
}
}
#[test]
fn missing_producer_errors() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let phantom = super::super::types::TextureHandle {
resource: t.resource,
version: 1,
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(phantom)
.presents();
match b.compile() {
Err(GraphError::MissingProducer {
pass: PassId::Composite,
resource_label: "t",
version: 1,
}) => {}
other => panic!("expected MissingProducer, got {:?}", other),
}
}
#[test]
fn no_presenter_errors() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
match b.compile() {
Err(GraphError::MissingPresenter) => {}
other => panic!("expected MissingPresenter, got {:?}", other),
}
}
#[test]
fn multiple_presenters_errors() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
{
let mut p = b.add_pass(PassId::Main, PassKind::Render);
p.presents();
let _ = p.write_texture(t);
}
b.add_pass(PassId::Composite, PassKind::Render).presents();
match b.compile() {
Err(GraphError::MultiplePresenters(2)) => {}
other => panic!("expected MultiplePresenters(2), got {:?}", other),
}
}
#[test]
fn barriers_emit_on_state_transitions() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t1)
.presents();
let g = b.compile().expect("compiles");
assert_eq!(g.passes[0].barriers_before.len(), 1);
assert_eq!(
g.passes[0].barriers_before[0].from,
ResourceState::Undefined
);
assert_eq!(g.passes[0].barriers_before[0].to, ResourceState::Write);
assert_eq!(g.passes[1].barriers_before.len(), 1);
assert_eq!(g.passes[1].barriers_before[0].from, ResourceState::Write);
assert_eq!(g.passes[1].barriers_before[0].to, ResourceState::Read);
}
#[test]
fn pass_barriers_for_filters_by_label() {
let mut b = GraphBuilder::new();
let keep = b.create_texture("keep", tex());
let skip = b.create_texture("skip", tex());
let (keep1, skip1) = {
let mut p = b.add_pass(PassId::Main, PassKind::Render);
(p.write_texture(keep), p.write_texture(skip))
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(keep1)
.read_texture(skip1)
.presents();
let g = b.compile().expect("compiles");
let main = &g.passes[0];
let kept = g.pass_barriers_for(main, &["keep"]);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].0, "keep");
assert_eq!(kept[0].1.source_state(), ResourceState::Undefined);
assert_eq!(kept[0].1.to_state(), ResourceState::Write);
let composite = &g.passes[1];
let kept = g.pass_barriers_for(composite, &["keep"]);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].1.source_state(), ResourceState::Write);
assert_eq!(kept[0].1.to_state(), ResourceState::Read);
assert!(g.pass_barriers_for(main, &[]).is_empty());
assert!(g.pass_barriers_for(main, &["nope"]).is_empty());
}
#[test]
fn consecutive_reads_coalesce_no_barriers() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
b.add_pass(PassId::Decals, PassKind::Render)
.read_texture(t1);
b.add_pass(PassId::Fog, PassKind::Render).read_texture(t1);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t1)
.presents();
let g = b.compile().expect("compiles");
assert_eq!(g.passes[0].barriers_before.len(), 1);
assert_eq!(g.passes[1].barriers_before.len(), 1);
assert_eq!(g.passes[2].barriers_before.len(), 0);
assert_eq!(g.passes[3].barriers_before.len(), 0);
}
#[test]
fn lifetime_intervals_span_first_write_to_last_read() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let unrelated = b.create_texture("u", tex());
let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
b.add_pass(PassId::Decals, PassKind::Render)
.read_texture(t1);
b.add_pass(PassId::Fog, PassKind::Render)
.write_texture(unrelated);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t1)
.presents();
let g = b.compile().expect("compiles");
let t_idx = t.resource.index();
let u_idx = unrelated.resource.index();
assert_eq!(g.resources[t_idx].lifetime.first, 0);
assert_eq!(g.resources[t_idx].lifetime.last, 3);
assert_eq!(g.resources[u_idx].lifetime.first, 2);
assert_eq!(g.resources[u_idx].lifetime.last, 2);
}
#[test]
fn buffer_dep_edges_work_too() {
let mut b = GraphBuilder::new();
let draw_args = b.create_buffer("draw_args", buf());
let scene = b.create_texture("scene", tex());
let args1 = b
.add_pass(PassId::Cull, PassKind::Compute)
.write_buffer(draw_args);
let scene1 = {
let mut p = b.add_pass(PassId::Main, PassKind::Render);
p.read_buffer(args1);
p.write_texture(scene)
};
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(scene1)
.presents();
let g = b.compile().expect("compiles");
let order: Vec<PassId> = g.passes.iter().map(|p| p.id).collect();
assert_eq!(order, vec![PassId::Cull, PassId::Main, PassId::Composite]);
}
fn find(g: &CompiledGraph, id: PassId) -> &CompiledPass {
g.passes.iter().find(|p| p.id == id).expect("pass present")
}
#[test]
fn mixed_stage_read_run_unions_consumer_stages() {
let mut b = GraphBuilder::new();
let hdr = b.create_texture("hdr", tex());
let hdr_v1 = b
.add_pass(PassId::Main, PassKind::Render)
.write_texture(hdr);
b.add_pass(PassId::AutoExposure, PassKind::Compute)
.read_texture(hdr_v1);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(hdr_v1)
.presents();
let g = b.compile().expect("compiles");
let ae = find(&g, PassId::AutoExposure);
assert_eq!(ae.barriers_before.len(), 1);
assert_eq!(ae.barriers_before[0].source_state(), ResourceState::Write);
assert_eq!(ae.barriers_before[0].to_state(), ResourceState::Read);
let rs = ae.barriers_before[0].read_stages();
assert!(rs.contains(ReadStages::COMPUTE));
assert!(rs.contains(ReadStages::FRAGMENT));
assert_eq!(find(&g, PassId::Composite).barriers_before.len(), 0);
}
#[test]
fn fragment_read_carries_only_fragment_stage() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t1)
.presents();
let g = b.compile().expect("compiles");
let comp = find(&g, PassId::Composite);
assert_eq!(comp.barriers_before.len(), 1);
let rs = comp.barriers_before[0].read_stages();
assert!(rs.contains(ReadStages::FRAGMENT));
assert!(!rs.contains(ReadStages::COMPUTE));
}
#[test]
fn compute_read_carries_only_compute_stage() {
let mut b = GraphBuilder::new();
let args = b.create_buffer("draw_args", buf());
let scene = b.create_texture("scene", tex());
let args1 = b
.add_pass(PassId::Cull, PassKind::Compute)
.write_buffer(args);
b.add_pass(PassId::AutoExposure, PassKind::Compute)
.read_buffer(args1);
{
let mut p = b.add_pass(PassId::Composite, PassKind::Render);
let _ = p.write_texture(scene);
p.presents();
}
let g = b.compile().expect("compiles");
let ae = find(&g, PassId::AutoExposure);
assert_eq!(ae.barriers_before.len(), 1);
let rs = ae.barriers_before[0].read_stages();
assert!(rs.contains(ReadStages::COMPUTE));
assert!(!rs.contains(ReadStages::FRAGMENT));
}
#[test]
fn war_barrier_carries_prior_read_run_stage_union() {
let mut b = GraphBuilder::new();
let hdr = b.create_texture("hdr", tex());
let v1 = b
.add_pass(PassId::Main, PassKind::Render)
.write_texture(hdr);
b.add_pass(PassId::AutoExposure, PassKind::Compute)
.read_texture(v1);
b.add_pass(PassId::Fog, PassKind::Render).read_texture(v1);
let v2 = b
.add_pass(PassId::SsaoBlur, PassKind::Render)
.write_texture(v1);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(v2)
.presents();
let g = b.compile().expect("compiles");
let blur = find(&g, PassId::SsaoBlur);
assert_eq!(blur.barriers_before.len(), 1);
assert_eq!(blur.barriers_before[0].source_state(), ResourceState::Read);
assert_eq!(blur.barriers_before[0].to_state(), ResourceState::Write);
let rs = blur.barriers_before[0].read_stages();
assert!(rs.contains(ReadStages::COMPUTE));
assert!(rs.contains(ReadStages::FRAGMENT));
}
#[test]
fn producer_write_barrier_has_empty_read_stages() {
let mut b = GraphBuilder::new();
let t = b.create_texture("t", tex());
let t1 = b.add_pass(PassId::Main, PassKind::Render).write_texture(t);
b.add_pass(PassId::Composite, PassKind::Render)
.read_texture(t1)
.presents();
let g = b.compile().expect("compiles");
let main = find(&g, PassId::Main);
assert_eq!(main.barriers_before.len(), 1);
assert_eq!(main.barriers_before[0].to_state(), ResourceState::Write);
assert!(main.barriers_before[0].read_stages().is_empty());
}
}