use concinnity_core::render::render_graph::{CompiledGraph, PassQueue};
const TERMINAL_SLOTS: u64 = PassQueue::COUNT as u64;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) struct FrameEvents {
base: u64,
n_passes: usize,
}
impl FrameEvents {
pub(super) fn new(base: u64, n_passes: usize) -> Self {
Self { base, n_passes }
}
fn pass_value(&self, idx: usize) -> u64 {
self.base + 1 + idx as u64
}
fn terminal(&self, queue: PassQueue) -> u64 {
self.base + 1 + self.n_passes as u64 + queue.index() as u64
}
fn next_base(&self) -> u64 {
self.base + 1 + self.n_passes as u64 + TERMINAL_SLOTS
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(super) struct PassSync {
pub(super) waits: Vec<(PassQueue, u64)>,
pub(super) signals: Vec<u64>,
}
#[derive(Clone, Debug)]
pub(super) struct FramePlan {
passes: Vec<PassSync>,
terminals: [Option<u64>; PassQueue::COUNT],
next_base: u64,
}
impl FramePlan {
pub(super) fn pass(&self, idx: usize) -> &PassSync {
&self.passes[idx]
}
pub(super) fn terminal(&self, queue: PassQueue) -> Option<u64> {
self.terminals[queue.index()]
}
pub(super) fn next_base(&self) -> u64 {
self.next_base
}
}
pub(super) fn plan_frame(
graph: &CompiledGraph,
events: FrameEvents,
previous: [Option<u64>; PassQueue::COUNT],
) -> FramePlan {
let n = graph.passes.len();
let mut passes = vec![PassSync::default(); n];
let mut first = [None; PassQueue::COUNT];
let mut last = [None; PassQueue::COUNT];
for (i, pass) in graph.passes.iter().enumerate() {
let q = pass.queue.index();
first[q].get_or_insert(i);
last[q] = Some(i);
}
for queue in PassQueue::ALL {
let Some(i) = first[queue.index()] else {
continue;
};
for other in PassQueue::ALL {
if other == queue {
continue;
}
if let Some(value) = previous[other.index()] {
passes[i].waits.push((other, value));
}
}
}
for (i, pass) in graph.passes.iter().enumerate() {
for wait in &pass.waits_before {
passes[i]
.waits
.push((wait.producer_queue(), events.pass_value(wait.producer())));
}
if !pass.signals_after.is_empty() {
passes[i].signals.push(events.pass_value(i));
}
}
let mut terminals = [None; PassQueue::COUNT];
for queue in PassQueue::ALL {
if let Some(i) = last[queue.index()] {
let value = events.terminal(queue);
passes[i].signals.push(value);
terminals[queue.index()] = Some(value);
}
}
FramePlan {
passes,
terminals,
next_base: events.next_base(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::render::render_graph::{FrameGraphInputs, build_frame_graph};
use std::collections::BTreeSet;
fn loaded_graph() -> CompiledGraph {
let mut i = FrameGraphInputs::all_off();
i.hdr_sample_count = 1;
i.shadow_enabled = true;
i.bindless_cull_enabled = true;
i.two_pass_occlusion_enabled = true;
i.hiz_build_enabled = true;
i.clustering_enabled = true;
i.auto_exposure_enabled = true;
i.particles_enabled = true;
i.fog_enabled = true;
i.decals_enabled = true;
i.taa_enabled = true;
i.velocity_enabled = true;
build_frame_graph(&i).expect("the loaded graph compiles")
}
fn graphics_only_graph() -> CompiledGraph {
build_frame_graph(&FrameGraphInputs::all_off()).expect("the minimum graph compiles")
}
fn queue_of(graph: &CompiledGraph, idx: usize) -> PassQueue {
graph.passes[idx].queue
}
fn signals_on(graph: &CompiledGraph, plan: &FramePlan, queue: PassQueue) -> Vec<u64> {
(0..graph.passes.len())
.filter(|&i| queue_of(graph, i) == queue)
.flat_map(|i| plan.pass(i).signals.iter().copied())
.collect()
}
#[test]
fn a_loaded_graph_uses_both_queues() {
let graph = loaded_graph();
for queue in PassQueue::ALL {
assert!(
graph.passes.iter().any(|p| p.queue == queue),
"the loaded graph put nothing on {}",
queue.name()
);
}
assert!(
graph.passes.iter().any(|p| !p.waits_before.is_empty()),
"the loaded graph has no cross-queue wait to plan"
);
}
#[test]
fn signals_are_monotonic_per_queue() {
let graph = loaded_graph();
let plan = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
for queue in PassQueue::ALL {
let signals = signals_on(&graph, &plan, queue);
assert!(
signals.windows(2).all(|w| w[0] < w[1]),
"{} signals out of order: {signals:?}",
queue.name()
);
}
}
#[test]
fn every_wait_names_a_value_the_other_queue_signals() {
let graph = loaded_graph();
let previous = [Some(7), Some(9)];
let plan = plan_frame(&graph, FrameEvents::new(64, graph.passes.len()), previous);
let scheduled: [BTreeSet<u64>; PassQueue::COUNT] = PassQueue::ALL.map(|q| {
let mut set: BTreeSet<u64> = signals_on(&graph, &plan, q).into_iter().collect();
if let Some(v) = previous[q.index()] {
set.insert(v);
}
set
});
for i in 0..graph.passes.len() {
for &(queue, value) in &plan.pass(i).waits {
assert_ne!(
queue,
queue_of(&graph, i),
"pass {i} waits on its own queue's event"
);
assert!(
scheduled[queue.index()].contains(&value),
"pass {i} waits for {value} on {}, which nothing signals",
queue.name()
);
}
}
}
#[test]
fn a_wait_never_names_a_producer_recorded_later_on_its_queue() {
let graph = loaded_graph();
let plan = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
for i in 0..graph.passes.len() {
for &(queue, value) in &plan.pass(i).waits {
let signaled_before: Vec<u64> = (0..i)
.filter(|&j| queue_of(&graph, j) == queue)
.flat_map(|j| plan.pass(j).signals.iter().copied())
.collect();
assert!(
signaled_before.contains(&value),
"pass {i} waits for {value} on {}, signaled only later",
queue.name()
);
}
}
}
#[test]
fn the_frame_start_wait_names_the_previous_frame_terminal() {
let graph = loaded_graph();
let first = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
let previous = PassQueue::ALL.map(|q| first.terminal(q));
let second = plan_frame(
&graph,
FrameEvents::new(first.next_base(), graph.passes.len()),
previous,
);
for queue in PassQueue::ALL {
let head = (0..graph.passes.len())
.find(|&i| queue_of(&graph, i) == queue)
.expect("the loaded graph puts a pass on every queue");
for other in PassQueue::ALL.into_iter().filter(|&o| o != queue) {
let expected = first
.terminal(other)
.expect("the loaded graph puts a pass on every queue");
assert!(
second.pass(head).waits.contains(&(other, expected)),
"{}'s first pass does not wait on the previous {} terminal",
queue.name(),
other.name()
);
}
}
}
#[test]
fn consecutive_frames_reserve_disjoint_ascending_values() {
let graph = loaded_graph();
let first = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
let all_first: Vec<u64> = PassQueue::ALL
.iter()
.flat_map(|&q| signals_on(&graph, &first, q))
.collect();
assert!(
all_first.iter().all(|&v| v > 0 && v < first.next_base()),
"a value fell outside the frame's slice: {all_first:?}"
);
let second = plan_frame(
&graph,
FrameEvents::new(first.next_base(), graph.passes.len()),
PassQueue::ALL.map(|q| first.terminal(q)),
);
let smallest_second = PassQueue::ALL
.iter()
.flat_map(|&q| signals_on(&graph, &second, q))
.min()
.expect("the second frame signals something");
let largest_first = all_first.iter().copied().max().expect("nonempty");
assert!(
smallest_second > largest_first,
"frame 2 reuses values from frame 1 ({smallest_second} <= {largest_first})"
);
}
#[test]
fn terminals_are_the_last_signal_on_their_queue() {
let graph = loaded_graph();
let plan = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
for queue in PassQueue::ALL {
let signals = signals_on(&graph, &plan, queue);
assert_eq!(
signals.last().copied(),
plan.terminal(queue),
"{} does not end on its terminal",
queue.name()
);
}
}
#[test]
fn a_single_queue_graph_plans_no_cross_queue_work() {
let graph = graphics_only_graph();
let plan = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
assert!(plan.terminal(PassQueue::AsyncCompute).is_none());
assert!(plan.terminal(PassQueue::Graphics).is_some());
for i in 0..graph.passes.len() {
assert!(plan.pass(i).waits.is_empty(), "pass {i} waits on nothing");
}
}
#[test]
fn an_unused_queue_leaves_the_next_frames_frame_start_wait_off() {
let graph = graphics_only_graph();
let first = plan_frame(&graph, FrameEvents::new(0, graph.passes.len()), [None; 2]);
let second = plan_frame(
&graph,
FrameEvents::new(first.next_base(), graph.passes.len()),
PassQueue::ALL.map(|q| first.terminal(q)),
);
assert!(second.pass(0).waits.is_empty());
}
}