use crate::coarse::WideTilesBbox;
use crate::filter_effects::Filter;
use crate::kurbo::Affine;
use alloc::vec;
use alloc::vec::Vec;
use hashbrown::HashMap;
#[derive(Debug, Clone)]
pub struct RenderGraph {
pub nodes: Vec<RenderNode>,
pub edges: Vec<RenderEdge>,
next_node_id: NodeId,
node_execution_order: Vec<NodeId>,
root_node: Option<NodeId>,
has_filters: bool,
}
#[derive(Debug, Clone)]
pub enum DependencyKind {
Sequential {
layer_id: LayerId,
},
}
impl Default for RenderGraph {
fn default() -> Self {
Self::new()
}
}
impl RenderGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
next_node_id: 0,
node_execution_order: Vec::new(),
root_node: None,
has_filters: false,
}
}
pub fn clear(&mut self) {
self.nodes.clear();
self.edges.clear();
self.next_node_id = 0;
self.node_execution_order.clear();
self.root_node = None;
self.has_filters = false;
}
pub fn add_node(&mut self, kind: RenderNodeKind) -> NodeId {
let id = self.next_node_id;
self.next_node_id += 1;
if matches!(kind, RenderNodeKind::RootLayer { .. }) {
self.root_node = Some(id);
}
if matches!(kind, RenderNodeKind::FilterLayer { .. }) {
self.has_filters = true;
}
self.nodes.push(RenderNode { id, kind });
id
}
pub fn add_edge(&mut self, from: NodeId, to: NodeId, kind: DependencyKind) {
self.edges.push(RenderEdge { from, to, kind });
}
pub fn record_node_for_execution(&mut self, node_id: NodeId) {
self.node_execution_order.push(node_id);
}
pub fn has_filters(&self) -> bool {
self.has_filters
}
pub fn execution_order(&self) -> impl Iterator<Item = NodeId> + '_ {
self.node_execution_order
.iter()
.copied()
.chain(self.root_node)
}
#[allow(
dead_code,
reason = "we don't use currently topological sort but will in the future"
)]
pub fn topological_sort(&self) -> Vec<NodeId> {
let mut in_degree = vec![0; self.nodes.len()];
let mut adj_list: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for edge in &self.edges {
adj_list.entry(edge.from).or_default().push(edge.to);
in_degree[edge.to] += 1;
}
let mut queue: Vec<NodeId> = (0..self.nodes.len())
.filter(|&i| in_degree[i] == 0)
.collect();
let mut result = Vec::new();
while let Some(node_id) = queue.pop() {
result.push(node_id);
if let Some(neighbors) = adj_list.get(&node_id) {
for &neighbor in neighbors {
in_degree[neighbor] -= 1;
if in_degree[neighbor] == 0 {
queue.push(neighbor);
}
}
}
}
assert_eq!(
result.len(),
self.nodes.len(),
"Cycle detected in render graph"
);
result
}
}
#[derive(Debug, Clone)]
pub struct RenderNode {
pub id: NodeId,
pub kind: RenderNodeKind,
}
impl RenderNode {
pub fn is_empty(&self) -> bool {
match &self.kind {
RenderNodeKind::RootLayer { wtile_bbox, .. } => wtile_bbox.is_empty(),
RenderNodeKind::FilterLayer { wtile_bbox, .. } => wtile_bbox.is_empty(),
}
}
}
#[derive(Debug, Clone)]
pub struct RenderEdge {
pub from: NodeId,
pub to: NodeId,
pub kind: DependencyKind,
}
#[derive(Debug, Clone)]
pub enum RenderNodeKind {
RootLayer {
layer_id: LayerId,
wtile_bbox: WideTilesBbox,
},
FilterLayer {
layer_id: LayerId,
filter: Filter,
wtile_bbox: WideTilesBbox,
transform: Affine,
},
}
pub type LayerId = u32;
pub type NodeId = usize;
#[cfg(test)]
mod tests {
use super::*;
use crate::color::AlphaColor;
#[test]
fn new_creates_empty_graph() {
let graph = RenderGraph::new();
assert_eq!(graph.nodes.len(), 0);
assert_eq!(graph.edges.len(), 0);
assert!(!graph.has_filters());
assert!(graph.root_node.is_none());
}
#[test]
fn clear_resets_to_empty_state() {
let mut graph = RenderGraph::new();
graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
graph.record_node_for_execution(1);
graph.clear();
assert_eq!(graph.nodes.len(), 0);
assert_eq!(graph.edges.len(), 0);
assert_eq!(graph.next_node_id, 0);
assert!(graph.root_node.is_none());
assert!(!graph.has_filters());
assert_eq!(graph.execution_order().count(), 0);
}
#[test]
fn clear_preserves_capacity() {
let mut graph = RenderGraph::new();
let from = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
let to = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 1,
wtile_bbox: dummy_bbox(),
});
graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });
let nodes_capacity = graph.nodes.capacity();
let edges_capacity = graph.edges.capacity();
graph.clear();
assert!(graph.nodes.capacity() >= nodes_capacity);
assert!(graph.edges.capacity() >= edges_capacity);
}
#[test]
fn add_node_returns_sequential_ids() {
let mut graph = RenderGraph::new();
let id0 = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
let id1 = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
assert_eq!(id0, 0);
assert_eq!(id1, 1);
}
#[test]
fn add_node_tracks_root() {
let mut graph = RenderGraph::new();
let root_id = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
assert_eq!(graph.root_node, Some(root_id));
}
#[test]
fn add_node_filter_sets_has_filters() {
let mut graph = RenderGraph::new();
graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
assert!(graph.has_filters());
}
#[test]
fn add_node_root_does_not_set_has_filters() {
let mut graph = RenderGraph::new();
graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
assert!(!graph.has_filters());
}
#[test]
fn add_edge_creates_dependency() {
let mut graph = RenderGraph::new();
let from = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let to = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
graph.add_edge(from, to, DependencyKind::Sequential { layer_id: 1 });
assert_eq!(graph.edges.len(), 1);
assert_eq!(graph.edges[0].from, from);
assert_eq!(graph.edges[0].to, to);
}
#[test]
fn add_edge_multiple_from_same_node() {
let mut graph = RenderGraph::new();
let from = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let to1 = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 2,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let to2 = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
graph.add_edge(from, to1, DependencyKind::Sequential { layer_id: 1 });
graph.add_edge(from, to2, DependencyKind::Sequential { layer_id: 1 });
assert_eq!(graph.edges.len(), 2);
}
#[test]
fn execution_order_includes_recorded_nodes() {
let mut graph = RenderGraph::new();
let filter = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
graph.record_node_for_execution(filter);
let order: Vec<_> = graph.execution_order().collect();
assert_eq!(order, vec![filter]);
}
#[test]
fn execution_order_includes_only_root_when_present() {
let mut graph = RenderGraph::new();
let root = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
let order: Vec<_> = graph.execution_order().collect();
assert_eq!(order, vec![root]);
}
#[test]
fn execution_order_root_comes_last() {
let mut graph = RenderGraph::new();
let filter1 = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let filter2 = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 2,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let root = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
graph.record_node_for_execution(filter2);
graph.record_node_for_execution(filter1);
let order: Vec<_> = graph.execution_order().collect();
assert_eq!(order, vec![filter2, filter1, root]);
}
#[test]
fn topological_sort_diamond_dependency() {
let mut graph = RenderGraph::new();
let bottom = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 3,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let left = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 1,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let right = graph.add_node(RenderNodeKind::FilterLayer {
layer_id: 2,
filter: dummy_filter(),
wtile_bbox: dummy_bbox(),
transform: Affine::IDENTITY,
});
let top = graph.add_node(RenderNodeKind::RootLayer {
layer_id: 0,
wtile_bbox: dummy_bbox(),
});
graph.add_edge(bottom, left, DependencyKind::Sequential { layer_id: 3 });
graph.add_edge(bottom, right, DependencyKind::Sequential { layer_id: 3 });
graph.add_edge(left, top, DependencyKind::Sequential { layer_id: 1 });
graph.add_edge(right, top, DependencyKind::Sequential { layer_id: 2 });
let sorted = graph.topological_sort();
assert_eq!(sorted, vec![bottom, right, left, top]);
}
fn dummy_filter() -> Filter {
Filter::from_primitive(crate::filter_effects::FilterPrimitive::Flood {
color: AlphaColor::from_rgba8(0, 0, 0, 255),
})
}
fn dummy_bbox() -> WideTilesBbox {
WideTilesBbox::ZERO
}
}