fewer btrees

This commit is contained in:
janis 2026-10-25 01:36:34 +02:00
parent 6a41cccf8b
commit 51a2e69f74
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
2 changed files with 61 additions and 45 deletions

View file

@ -1,5 +1,6 @@
use std::{ use std::{
collections::{BTreeMap, BTreeSet, HashMap}, cmp::Reverse,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, VecDeque},
sync::Arc, sync::Arc,
}; };
@ -103,9 +104,11 @@ impl ResourceInterference {
/// Deterministic Kahn topological-sort state for command dependencies. /// Deterministic Kahn topological-sort state for command dependencies.
struct Kahn { struct Kahn {
edges: Vec<BTreeSet<usize>>, edges: Vec<Vec<usize>>,
indegree: Vec<usize>, indegree: Vec<usize>,
ready: BTreeSet<usize>, ready: BinaryHeap<Reverse<usize>>,
// TODO: bitvec
is_ready: Vec<bool>,
emitted: usize, emitted: usize,
total: usize, total: usize,
} }
@ -113,23 +116,34 @@ struct Kahn {
impl Kahn { impl Kahn {
fn new(node_count: usize) -> Self { fn new(node_count: usize) -> Self {
Self { Self {
edges: vec![BTreeSet::new(); node_count], edges: vec![Vec::new(); node_count],
indegree: vec![0; node_count], indegree: vec![0; node_count],
ready: (0..node_count).collect(), ready: (0..node_count).map(Reverse).collect(),
is_ready: vec![true; node_count],
emitted: 0, emitted: 0,
total: node_count, total: node_count,
} }
} }
fn add_edge(&mut self, from: usize, to: usize) { fn add_edge(&mut self, from: usize, to: usize) {
if self.edges[from].insert(to) { let edges = &mut self.edges[from];
if let Err(index) = edges.binary_search(&to) {
edges.insert(index, to);
self.indegree[to] += 1; self.indegree[to] += 1;
self.ready.remove(&to); if self.is_ready[to] {
self.is_ready[to] = false;
}
} }
} }
fn pop_ready(&mut self) -> Option<usize> { fn pop_ready(&mut self) -> Option<usize> {
self.ready.pop_first() while let Some(Reverse(node)) = self.ready.pop() {
if self.is_ready[node] {
self.is_ready[node] = false;
return Some(node);
}
}
None
} }
fn complete(&mut self, node: usize) { fn complete(&mut self, node: usize) {
@ -137,7 +151,8 @@ impl Kahn {
for &dependent in &self.edges[node] { for &dependent in &self.edges[node] {
self.indegree[dependent] -= 1; self.indegree[dependent] -= 1;
if self.indegree[dependent] == 0 { if self.indegree[dependent] == 0 {
self.ready.insert(dependent); self.is_ready[dependent] = true;
self.ready.push(Reverse(dependent));
} }
} }
} }
@ -149,7 +164,7 @@ impl Kahn {
pub struct RenderGraph { pub struct RenderGraph {
next_id: u64, next_id: u64,
resources: BTreeMap<ResourceId, ResourceDesc>, resources: Vec<ResourceDesc>,
pub commands: CommandList, pub commands: CommandList,
} }
@ -157,7 +172,7 @@ impl RenderGraph {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
next_id: 0, next_id: 0,
resources: BTreeMap::new(), resources: Vec::new(),
commands: CommandList::new(), commands: CommandList::new(),
} }
} }
@ -165,7 +180,7 @@ impl RenderGraph {
fn add_resource(&mut self, desc: ResourceDesc) -> ResourceId { fn add_resource(&mut self, desc: ResourceDesc) -> ResourceId {
let id = ResourceId(self.next_id); let id = ResourceId(self.next_id);
self.next_id = self.next_id.checked_add(1).expect("resource id overflow"); self.next_id = self.next_id.checked_add(1).expect("resource id overflow");
self.resources.insert(id, desc); self.resources.push(desc);
id id
} }
@ -225,14 +240,14 @@ impl RenderGraph {
} }
struct GraphCompiler<'a> { struct GraphCompiler<'a> {
resources: &'a BTreeMap<ResourceId, ResourceDesc>, resources: &'a [ResourceDesc],
accesses: &'a [Vec<crate::recorder::ResourceAccess>], accesses: &'a [Vec<crate::recorder::ResourceAccess>],
command_count: usize, command_count: usize,
} }
impl<'a> GraphCompiler<'a> { impl<'a> GraphCompiler<'a> {
fn new( fn new(
resources: &'a BTreeMap<ResourceId, ResourceDesc>, resources: &'a [ResourceDesc],
accesses: &'a [Vec<crate::recorder::ResourceAccess>], accesses: &'a [Vec<crate::recorder::ResourceAccess>],
command_count: usize, command_count: usize,
) -> Self { ) -> Self {
@ -277,7 +292,7 @@ impl<'a> GraphCompiler<'a> {
fn validate_resources(&self) -> Result<(), GraphError> { fn validate_resources(&self) -> Result<(), GraphError> {
for command_accesses in self.accesses { for command_accesses in self.accesses {
for access in command_accesses { for access in command_accesses {
if !self.resources.contains_key(&access.resource) { if access.resource.index() >= self.resources.len() {
return Err(GraphError::UnknownResource(access.resource)); return Err(GraphError::UnknownResource(access.resource));
} }
} }
@ -313,7 +328,7 @@ impl<'a> GraphCompiler<'a> {
continue; continue;
} }
let desc = &self.resources[&current.resource]; let desc = &self.resources[current.resource.0 as usize];
let initial = desc.initial_state(); let initial = desc.initial_state();
let (old_layout, new_layout) = Self::layouts_from_state(initial, current.scope); let (old_layout, new_layout) = Self::layouts_from_state(initial, current.scope);
let layout_transition = old_layout != new_layout && new_layout.is_some(); let layout_transition = old_layout != new_layout && new_layout.is_some();
@ -397,12 +412,13 @@ impl<'a> GraphCompiler<'a> {
ids.sort_by_key(|id| lifetimes[id].first_command); ids.sort_by_key(|id| lifetimes[id].first_command);
for id in ids { for id in ids {
let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists"); let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists");
let size = match &self.resources[&id] { let desc = &self.resources[id.0 as usize];
let size = match desc {
ResourceDesc::CreatedBuffer(desc) => desc.size, ResourceDesc::CreatedBuffer(desc) => desc.size,
ResourceDesc::CreatedTexture(desc) => texture_size_estimate(desc), ResourceDesc::CreatedTexture(desc) => texture_size_estimate(desc),
ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0, ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0,
}; };
if self.resources[&id].is_created() if desc.is_created()
&& let Some((offset, last_use, capacity)) = slots && let Some((offset, last_use, capacity)) = slots
.iter_mut() .iter_mut()
.find(|(_, last, cap)| !interference.interferes(*last, id) && *cap >= size) .find(|(_, last, cap)| !interference.interferes(*last, id) && *cap >= size)
@ -412,7 +428,7 @@ impl<'a> GraphCompiler<'a> {
let _ = capacity; let _ = capacity;
} else { } else {
lifetime.memory_offset = next_offset; lifetime.memory_offset = next_offset;
if self.resources[&id].is_created() { if desc.is_created() {
slots.push((next_offset, id, size)); slots.push((next_offset, id, size));
next_offset = next_offset.saturating_add(size); next_offset = next_offset.saturating_add(size);
} }
@ -429,7 +445,7 @@ impl Default for RenderGraph {
} }
pub struct CompiledGraph { pub struct CompiledGraph {
resources: BTreeMap<ResourceId, ResourceDesc>, resources: Vec<ResourceDesc>,
resolved: HashMap<ResourceId, ResolvedResource>, resolved: HashMap<ResourceId, ResolvedResource>,
commands: CommandList, commands: CommandList,
order: Vec<CommandIndex>, order: Vec<CommandIndex>,
@ -464,7 +480,8 @@ impl CompiledGraph {
/// of a Vulkan device, while command recording requires this resolution step /// of a Vulkan device, while command recording requires this resolution step
/// for any created resources used by commands. /// for any created resources used by commands.
pub fn resolve(&mut self, device: &Device) -> renderer_resources::Result<()> { pub fn resolve(&mut self, device: &Device) -> renderer_resources::Result<()> {
for (&id, desc) in &self.resources { for (index, desc) in self.resources.iter().enumerate() {
let id = ResourceId(index as u64);
if self.resolved.contains_key(&id) { if self.resolved.contains_key(&id) {
continue; continue;
} }
@ -529,7 +546,8 @@ impl CompiledGraph {
&mut self, &mut self,
recorder: &mut CommandRecorder<'_>, recorder: &mut CommandRecorder<'_>,
) -> Result<(), RecordError> { ) -> Result<(), RecordError> {
for (&id, desc) in &self.resources { for (index, desc) in self.resources.iter().enumerate() {
let id = ResourceId(index as u64);
if !self.lifetimes.contains_key(&id) { if !self.lifetimes.contains_key(&id) {
continue; continue;
} }
@ -801,6 +819,7 @@ mod tests {
fn kahn_emits_dag_in_deterministic_order() { fn kahn_emits_dag_in_deterministic_order() {
let mut kahn = Kahn::new(4); let mut kahn = Kahn::new(4);
kahn.add_edge(0, 2); kahn.add_edge(0, 2);
kahn.add_edge(0, 2);
kahn.add_edge(1, 2); kahn.add_edge(1, 2);
kahn.add_edge(2, 3); kahn.add_edge(2, 3);
@ -867,29 +886,20 @@ mod tests {
#[test] #[test]
fn aliasing_reuses_non_interfering_slot_and_separates_interfering_resources() { fn aliasing_reuses_non_interfering_slot_and_separates_interfering_resources() {
let resources = BTreeMap::from([ let resources = vec![
(
ResourceId(0),
ResourceDesc::CreatedBuffer(GraphBufferDesc { ResourceDesc::CreatedBuffer(GraphBufferDesc {
size: 8, size: 8,
..Default::default() ..Default::default()
}), }),
),
(
ResourceId(1),
ResourceDesc::CreatedBuffer(GraphBufferDesc { ResourceDesc::CreatedBuffer(GraphBufferDesc {
size: 8, size: 8,
..Default::default() ..Default::default()
}), }),
),
(
ResourceId(2),
ResourceDesc::CreatedBuffer(GraphBufferDesc { ResourceDesc::CreatedBuffer(GraphBufferDesc {
size: 8, size: 8,
..Default::default() ..Default::default()
}), }),
), ];
]);
let accesses = vec![ let accesses = vec![
vec![resource_access_for(ResourceId(0))], vec![resource_access_for(ResourceId(0))],
vec![ vec![

View file

@ -72,6 +72,12 @@ impl Default for GraphTextureDesc {
#[repr(transparent)] #[repr(transparent)]
pub struct ResourceId(pub u64); pub struct ResourceId(pub u64);
impl ResourceId {
pub(crate) fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Buffer(pub ResourceId); pub struct Buffer(pub ResourceId);