diff --git a/crates/renderer_rendergraph/src/graph.rs b/crates/renderer_rendergraph/src/graph.rs index de59057..92a48f3 100644 --- a/crates/renderer_rendergraph/src/graph.rs +++ b/crates/renderer_rendergraph/src/graph.rs @@ -1,6 +1,6 @@ use std::{ cmp::Reverse, - collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, VecDeque}, + collections::{BTreeSet, BinaryHeap, HashMap, VecDeque}, sync::Arc, }; @@ -64,41 +64,60 @@ impl ResourceLifetime { /// An edge means the two resources are live at the same command index. #[derive(Debug, Clone, Default)] pub struct ResourceInterference { - adj: BTreeMap>, + edges: Vec>, } impl ResourceInterference { /// Build a symmetric interference graph from inclusive resource lifetimes. pub fn from_lifetimes(lifetimes: &HashMap) -> Self { - let mut graph = Self::default(); - - let resources: Vec<_> = lifetimes + let mut max_id = ResourceId(0); + let mut resources: Vec<_> = lifetimes .iter() + .inspect(|(id, _)| { + if **id > max_id { + max_id = **id; + } + }) .map(|(&id, lifetime)| (id, *lifetime)) .collect(); - for (index, &(id, lifetime)) in resources.iter().enumerate() { - graph.adj.entry(id).or_default(); - for &(other_id, other) in &resources[index + 1..] { - if lifetime.overlaps(&other) { - graph.adj.entry(id).or_default().insert(other_id); - graph.adj.entry(other_id).or_default().insert(id); + let mut edges = vec![BTreeSet::new(); max_id.index() + 1]; + + resources.sort_unstable_by_key(|&(_, lifetime)| lifetime.first_command); + let mut resources = VecDeque::from(resources); + + let mut index = 0; + while index < resources.len() { + let (id, lifetime) = resources[index]; + + let mut j = 0; + while j < index { + if resources[j].1.last_command < lifetime.first_command { + resources.swap_remove_front(j); + index -= 1; + } else { + j += 1; } } + + index += 1; + + // resources is no longer sorted in the interval ..index, but because of the invariant `last_command >= first_command`, it is still partitioned by `a.last_command <= b.first_command`. + let active = resources + .partition_point(|&(_, other)| other.first_command <= lifetime.last_command); + + for &(other_id, _) in resources.range(..active) { + edges[id.index()].insert(other_id); + edges[other_id.index()].insert(id); + } } - graph + + Self { edges } } /// Returns whether both resources are live at any common command index. pub fn interferes(&self, a: ResourceId, b: ResourceId) -> bool { - self.adj - .get(&a) - .is_some_and(|neighbors| neighbors.contains(&b)) - } - - /// Returns the resources that overlap `id`'s inclusive lifetime interval. - pub fn neighbors(&self, id: ResourceId) -> Option<&BTreeSet> { - self.adj.get(&id) + self.edges[a.index()].contains(&b) } }