it works (magic)

This commit is contained in:
Janis 2025-01-06 04:40:20 +01:00
parent efd73fce43
commit 107c43ee77

View file

@ -15,6 +15,7 @@ use crate::{
SwapchainFrame, SwapchainFrame,
}; };
use ash::vk; use ash::vk;
use bytemuck::Contiguous;
use itertools::Itertools; use itertools::Itertools;
use petgraph::{ use petgraph::{
graph::NodeIndex, graph::NodeIndex,
@ -391,6 +392,7 @@ impl RenderGraph {
} }
} }
} }
let now = std::time::Instant::now();
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum PassNode { enum PassNode {
@ -443,7 +445,7 @@ impl RenderGraph {
} }
// gather references to resources. // gather references to resources.
let (references, intervals) = { let (references, intervals) = util::timed("build reference and interval trees", || {
let mut references = BTreeSet::<GraphRef>::new(); let mut references = BTreeSet::<GraphRef>::new();
// interval for each resource from (first pass referencing, last pass referencing) // interval for each resource from (first pass referencing, last pass referencing)
@ -539,13 +541,10 @@ impl RenderGraph {
.or_insert((PassNode::Last, PassNode::Last)); .or_insert((PassNode::Last, PassNode::Last));
} }
eprintln!("references: {references:#?}");
eprintln!("intervals: {intervals:#?}");
(references, intervals) (references, intervals)
}; });
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
enum Barrier { enum Barrier {
Logical, Logical,
Execution { Execution {
@ -587,7 +586,7 @@ impl RenderGraph {
let pass_count = self.pass_descs.len() as u32; let pass_count = self.pass_descs.len() as u32;
// build graph from references. // build graph from references.
let mut dag = { let mut dag = util::timed("construct dag", || {
let mut edges = Vec::new(); let mut edges = Vec::new();
intervals.iter().for_each(|(&rid, &(from, to))| { intervals.iter().for_each(|(&rid, &(from, to))| {
@ -740,12 +739,6 @@ impl RenderGraph {
} }
// make all writes available // make all writes available
if !to_make_available.is_empty() { if !to_make_available.is_empty() {
tracing::debug!(
"making available {:?} for {:?} on {:?}",
to_make_available,
rid,
a.pass
);
edges.push(( edges.push((
(write, a.pass), (write, a.pass),
( (
@ -836,9 +829,9 @@ impl RenderGraph {
} }
}); });
let mut dag = petgraph::graph::DiGraph::new(); let mut dag = petgraph::stable_graph::StableDiGraph::new();
let root = dag.add_node(PassNode::First); dag.add_node(PassNode::First);
let output = dag.add_node(PassNode::Last); dag.add_node(PassNode::Last);
for i in 0..self.pass_descs.len() { for i in 0..self.pass_descs.len() {
dag.add_node(PassNode::Pass(i)); dag.add_node(PassNode::Pass(i));
} }
@ -864,41 +857,29 @@ impl RenderGraph {
} }
} }
#[cfg(any(debug_assertions, test))] // #[cfg(any(debug_assertions, test))]
std::fs::write( // std::fs::write(
"render_graph2.dot", // "render_graph2.dot",
&format!( // &format!(
"{:?}", // "{:?}",
petgraph::dot::Dot::with_attr_getters( // petgraph::dot::Dot::with_attr_getters(
&dag, // &dag,
&[], // &[],
&|_graph, edgeref| { // &|_graph, edgeref| {
format!( // format!(
"label = \"{},{:#?}\"", // "label = \"{},{:#?}\"",
edgeref.weight().0.as_u32(), // edgeref.weight().0.as_u32(),
edgeref.weight().1, // edgeref.weight().1,
) // )
}, // },
&|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) } // &|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
) // )
), // ),
) // )
.expect("writing render_graph repr"); // .expect("writing render_graph repr");
dag dag
}; });
let now = std::time::Instant::now();
let mut dag = petgraph::stable_graph::StableDiGraph::new();
let root = dag.add_node(PassNode::First);
let mut last_write: BTreeMap<GraphResourceId, (NodeIndex, Access)> = self
.resources
.keys()
.filter_map(|id| self.accesses.get(id).map(|access| (*id, (root, *access))))
.collect::<BTreeMap<_, _>>();
let mut last_read: BTreeMap<GraphResourceId, (NodeIndex, Access)> = BTreeMap::new();
// TODO: rewrite finding edges properly. // TODO: rewrite finding edges properly.
// finding out if this graph is cyclical is actually non-trivial // finding out if this graph is cyclical is actually non-trivial
@ -906,178 +887,14 @@ impl RenderGraph {
// this could be resolved by copying resource 1 before the write pass. // this could be resolved by copying resource 1 before the write pass.
// tl;dr: write-after-read makes this all more complicated // tl;dr: write-after-read makes this all more complicated
// insert edges between write->read edges of 2 passes
for (i, pass) in self.pass_descs.iter().enumerate() {
let node = dag.add_node(PassNode::Pass(i));
let mut read_accesses = BTreeMap::new();
for (rid, access) in &pass.reads {
read_accesses
.entry(*rid)
.and_modify(|a| {
// a single pass must not read one image twice with different layouts
*a = *a | *access;
})
.or_insert(*access);
}
for (rid, after) in read_accesses {
if let Some(&(other, before)) = last_write.get(&rid) {
tracing::trace!("adding edge between {other:?} and {node:?} for {rid:?} with ({before:?} -> {after:?})");
dag.add_edge(other, node, (rid, (before, after)));
}
last_read.insert(rid, (node, after));
}
let mut write_accesses = BTreeMap::new();
for (rid, access) in &pass.writes {
write_accesses
.entry(*rid)
.and_modify(|a| {
// a single pass must not write one image twice with different layouts
*a = *a | *access;
})
.or_insert(*access);
}
for (rid, after) in write_accesses {
last_write.insert(rid, (node, after));
if let Some(&(other, read)) = last_read.get(&rid)
&& other != node
{
tracing::trace!("adding edge between {other:?} and {node:?} for {rid:?} with ({read:?} -> {after:?}) (WaR)");
dag.add_edge(other, node, (rid, (read, after)));
}
}
}
// pseudo pass for tracking outputs
let output = dag.add_node(PassNode::Last);
for (id, (node, access)) in self
.outputs
.iter()
.filter_map(|&id| last_write.get(&id).cloned().map(|node| (id, node)))
{
dag.add_edge(
node,
output,
(
id,
(
access,
// make output writes available
Access {
stage: vk::PipelineStageFlags2::NONE,
mask: vk::AccessFlags2::empty(),
..access
},
),
),
);
}
// prune dead nodes
loop {
let sinks = dag
.externals(petgraph::Direction::Outgoing)
.filter(|idx| idx != &output)
.collect::<Vec<_>>();
if sinks.is_empty() {
break;
}
for sink in sinks {
dag.remove_node(sink);
}
}
// handle layout additional transitions
let edges = dag
.node_references()
.map(|(source, _)| {
let mut per_resourcelayout_multimap: BTreeMap<
(GraphResourceId, Option<vk::ImageLayout>),
Vec<(Access, NodeIndex)>,
> = BTreeMap::new();
let mut resources = BTreeSet::new();
dag.edges_directed(source, petgraph::Direction::Outgoing)
.for_each(|edge| {
let (rid, (_, after)) = edge.weight();
let target = edge.target();
let key = (*rid, after.layout);
let item = (*after, target);
resources.insert(*rid);
per_resourcelayout_multimap
.entry(key)
.and_modify(|list| list.push(item))
.or_insert(vec![item]);
});
let mut edges = vec![];
for resource in resources {
for (a, b) in per_resourcelayout_multimap
.range(
(resource, None)
..=(resource, Some(vk::ImageLayout::from_raw(i32::MAX))),
)
.tuple_windows()
{
let a = a.1;
let b = b.1;
// create new edge between all members of (a) and (b).
// topological mapping will fold all transitions into one.
for i in 0..a.len().max(b.len()) {
let from = a.get(i).unwrap_or(a.last().unwrap());
let to = b.get(i).unwrap_or(b.last().unwrap());
let edge = ((from.1, to.1), (resource, (from.0, to.0)));
edges.push(edge);
}
}
}
edges
})
.flatten()
.collect::<Vec<_>>();
for ((from, to), edge) in edges {
tracing::trace!(
"adding additional edge between {from:?} and {to:?} for {:?} with ({:?} -> {:?})",
edge.0,
edge.1 .0,
edge.1 .1
);
dag.add_edge(from, to, edge);
}
// #[cfg(any(debug_assertions, test))]
// std::fs::write(
// "render_graph.dot",
// &format!(
// "{:?}",
// petgraph::dot::Dot::with_attr_getters(
// &dag,
// &[],
// &|_graph, edgeref| { format!("label = \"{:?}\"", edgeref.weight()) },
// &|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
// )
// ),
// )
// .expect("writing render_graph repr");
let mut topological_map = Vec::new(); let mut topological_map = Vec::new();
let mut top_dag = dag.clone();
let mut root_barriers = BTreeMap::new();
// create topological map of DAG from sink to source // create topological map of DAG from sink to source
loop { loop {
let (sinks, passes): (Vec<_>, Vec<_>) = top_dag let (sinks, passes): (Vec<_>, Vec<_>) = dag
.externals(petgraph::Direction::Outgoing) .externals(petgraph::Direction::Outgoing)
.filter(|&id| id != root) //.filter(|&id| id != root)
.filter_map(|id| top_dag.node_weight(id).cloned().map(|idx| (id, idx))) .filter_map(|id| dag.node_weight(id).cloned().map(|idx| (id, idx)))
.unzip(); .unzip();
if sinks.is_empty() { if sinks.is_empty() {
@ -1087,26 +904,95 @@ impl RenderGraph {
let mut barriers = BTreeMap::new(); let mut barriers = BTreeMap::new();
for &sink in &sinks { for &sink in &sinks {
top_dag dag.edges_directed(sink, petgraph::Direction::Incoming)
.edges_directed(sink, petgraph::Direction::Incoming)
.for_each(|edge| { .for_each(|edge| {
let (rid, (before, after)) = edge.weight(); let (rid, barrier) = edge.weight();
// initial access is transitioned at the beginning let before_and_after = match *barrier {
// this affects imported resources only. Barrier::Logical => None,
if edge.source() == root { Barrier::Execution { src, dst } => Some((
&mut root_barriers Access {
} else { stage: src,
&mut barriers ..Access::empty()
},
Access {
stage: dst,
..Access::empty()
},
)),
Barrier::LayoutTransition {
src: (src, from),
dst: (dst, to),
} => Some((
Access {
stage: src,
layout: Some(from),
..Access::empty()
},
Access {
stage: dst,
layout: Some(to),
..Access::empty()
},
)),
Barrier::MakeAvailable {
src: (stage, mask),
dst,
} => Some((
Access {
stage,
mask,
..Access::empty()
},
Access {
stage: dst,
..Access::empty()
},
)),
Barrier::MakeVisible {
src,
dst: (stage, mask),
} => Some((
Access {
stage: src,
..Access::empty()
},
Access {
stage,
mask,
..Access::empty()
},
)),
Barrier::MemoryBarrier {
src: (src_stage, src_mask),
dst: (dst_stage, dst_mask),
} => Some((
Access {
stage: src_stage,
mask: src_mask,
..Access::empty()
},
Access {
stage: dst_stage,
mask: dst_mask,
..Access::empty()
},
)),
};
if let Some((before, after)) = before_and_after {
// initial access is transitioned at the beginning
// this affects imported resources only.
barriers
.entry(*rid)
.and_modify(|(from, to)| {
*from = *from | before;
*to = *to | after;
})
.or_insert((before, after));
} }
.entry(*rid)
.and_modify(|(from, to)| {
*from = *from | *before;
*to = *to | *after;
})
.or_insert((*before, *after));
}); });
top_dag.remove_node(sink); dag.remove_node(sink);
} }
let passes = passes let passes = passes
@ -1123,13 +1009,12 @@ impl RenderGraph {
topological_map.push((passes, barriers)); topological_map.push((passes, barriers));
} }
topological_map.push((vec![], root_barriers));
//tracing::debug!("mapping: {topological_map:#?}"); //tracing::debug!("mapping: {topological_map:#?}");
// I don't think this can currently happen with the way passes are added. // I don't think this can currently happen with the way passes are added.
top_dag.remove_node(root); dag.remove_node(0.into());
if top_dag.node_count() > 0 { if dag.node_count() > 0 {
eprintln!("dag: {top_dag:?}"); eprintln!("dag: {dag:?}");
panic!("dag is cyclic!"); panic!("dag is cyclic!");
} }