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,
};
use ash::vk;
use bytemuck::Contiguous;
use itertools::Itertools;
use petgraph::{
graph::NodeIndex,
@ -391,6 +392,7 @@ impl RenderGraph {
}
}
}
let now = std::time::Instant::now();
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum PassNode {
@ -443,7 +445,7 @@ impl RenderGraph {
}
// gather references to resources.
let (references, intervals) = {
let (references, intervals) = util::timed("build reference and interval trees", || {
let mut references = BTreeSet::<GraphRef>::new();
// interval for each resource from (first pass referencing, last pass referencing)
@ -539,13 +541,10 @@ impl RenderGraph {
.or_insert((PassNode::Last, PassNode::Last));
}
eprintln!("references: {references:#?}");
eprintln!("intervals: {intervals:#?}");
(references, intervals)
};
});
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
enum Barrier {
Logical,
Execution {
@ -587,7 +586,7 @@ impl RenderGraph {
let pass_count = self.pass_descs.len() as u32;
// build graph from references.
let mut dag = {
let mut dag = util::timed("construct dag", || {
let mut edges = Vec::new();
intervals.iter().for_each(|(&rid, &(from, to))| {
@ -740,12 +739,6 @@ impl RenderGraph {
}
// make all writes available
if !to_make_available.is_empty() {
tracing::debug!(
"making available {:?} for {:?} on {:?}",
to_make_available,
rid,
a.pass
);
edges.push((
(write, a.pass),
(
@ -836,9 +829,9 @@ impl RenderGraph {
}
});
let mut dag = petgraph::graph::DiGraph::new();
let root = dag.add_node(PassNode::First);
let output = dag.add_node(PassNode::Last);
let mut dag = petgraph::stable_graph::StableDiGraph::new();
dag.add_node(PassNode::First);
dag.add_node(PassNode::Last);
for i in 0..self.pass_descs.len() {
dag.add_node(PassNode::Pass(i));
}
@ -864,41 +857,29 @@ impl RenderGraph {
}
}
#[cfg(any(debug_assertions, test))]
std::fs::write(
"render_graph2.dot",
&format!(
"{:?}",
petgraph::dot::Dot::with_attr_getters(
&dag,
&[],
&|_graph, edgeref| {
format!(
"label = \"{},{:#?}\"",
edgeref.weight().0.as_u32(),
edgeref.weight().1,
)
},
&|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
)
),
)
.expect("writing render_graph repr");
// #[cfg(any(debug_assertions, test))]
// std::fs::write(
// "render_graph2.dot",
// &format!(
// "{:?}",
// petgraph::dot::Dot::with_attr_getters(
// &dag,
// &[],
// &|_graph, edgeref| {
// format!(
// "label = \"{},{:#?}\"",
// edgeref.weight().0.as_u32(),
// edgeref.weight().1,
// )
// },
// &|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
// )
// ),
// )
// .expect("writing render_graph repr");
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.
// 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.
// 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 top_dag = dag.clone();
let mut root_barriers = BTreeMap::new();
// create topological map of DAG from sink to source
loop {
let (sinks, passes): (Vec<_>, Vec<_>) = top_dag
let (sinks, passes): (Vec<_>, Vec<_>) = dag
.externals(petgraph::Direction::Outgoing)
.filter(|&id| id != root)
.filter_map(|id| top_dag.node_weight(id).cloned().map(|idx| (id, idx)))
//.filter(|&id| id != root)
.filter_map(|id| dag.node_weight(id).cloned().map(|idx| (id, idx)))
.unzip();
if sinks.is_empty() {
@ -1087,26 +904,95 @@ impl RenderGraph {
let mut barriers = BTreeMap::new();
for &sink in &sinks {
top_dag
.edges_directed(sink, petgraph::Direction::Incoming)
dag.edges_directed(sink, petgraph::Direction::Incoming)
.for_each(|edge| {
let (rid, (before, after)) = edge.weight();
let (rid, barrier) = edge.weight();
// initial access is transitioned at the beginning
// this affects imported resources only.
if edge.source() == root {
&mut root_barriers
} else {
&mut barriers
let before_and_after = match *barrier {
Barrier::Logical => None,
Barrier::Execution { src, dst } => Some((
Access {
stage: src,
..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
@ -1123,13 +1009,12 @@ impl RenderGraph {
topological_map.push((passes, barriers));
}
topological_map.push((vec![], root_barriers));
//tracing::debug!("mapping: {topological_map:#?}");
// I don't think this can currently happen with the way passes are added.
top_dag.remove_node(root);
if top_dag.node_count() > 0 {
eprintln!("dag: {top_dag:?}");
dag.remove_node(0.into());
if dag.node_count() > 0 {
eprintln!("dag: {dag:?}");
panic!("dag is cyclic!");
}