facett-graph3d/tests/cpu_paint_budget.rs
//! **The CPU-painter fallback must stay bounded when the cloud gets big.**
//!
//! The instanced GPU cloud draws the whole cloud as instances, which is what let the
//! demo's graph3d go from 140 nodes to 100000. But `draw_cloud` is deliberately
//! fail-SAFE: with no `RenderState`, no installed renderer, or on wasm it returns
//! false and the CPU painter draws instead — tessellating a disc plus a glow orb per
//! node and a line per edge, on ONE core. That is the measured 33.75 ms/frame wall at
//! 30000 nodes; at 100000 it would be seconds per frame, i.e. raising the data would
//! have traded a hollow pane for a frozen one.
//!
//! So the fallback carries a budget, exactly like the OSM-2D point layer:
//! [`CPU_PAINT_MAX_NODES`] / [`CPU_PAINT_MAX_EDGES`]. Above it the CPU pass paints a
//! strided SAMPLE; below it the stride is 1 and the paint is byte-identical to
//! before, which is what keeps every existing snapshot golden valid.
use facett_core::harness;
use facett_graph3d::{dataset, Graph3D, Layout3D, CPU_PAINT_MAX_EDGES, CPU_PAINT_MAX_NODES};
fn built(n: usize) -> Graph3D {
let d = dataset::generate(&dataset::GraphSpec::sized(n));
Graph3D::new(d.nodes, d.edges, Layout3D::Force)
}
/// A SMALL cloud paints in full — stride 1, so every golden is unchanged.
#[test]
fn a_small_cloud_is_painted_in_full() {
let mut g = built(1_000);
assert!(g.nodes.len() < CPU_PAINT_MAX_NODES);
let r = harness::headless_render(&mut g);
assert_eq!(r.state["cpu_paint_stride"][0], 1, "no node decimation under budget");
assert_eq!(r.state["cpu_paint_stride"][1], 1, "no edge decimation under budget");
assert!(r.drew(), "and it really drew");
}
/// **RED-when-broken.** A LARGE cloud on the CPU lane must bound its own per-frame
/// tessellation: the painted node count is capped by the budget however big the
/// dataset is. Without the stride this is `nodes.len()` and the frame is seconds long.
#[test]
fn a_large_cloud_bounds_its_cpu_tessellation() {
let mut g = built(100_000);
let n = g.nodes.len();
let e = g.edges.len();
assert!(n > CPU_PAINT_MAX_NODES * 4, "the input really is large ({n})");
let r = harness::headless_render(&mut g);
let ns = r.state["cpu_paint_stride"][0].as_u64().unwrap() as usize;
let es = r.state["cpu_paint_stride"][1].as_u64().unwrap() as usize;
assert!(ns > 1, "a 100k cloud strides its CPU node pass, got {ns}");
let painted_nodes = n.div_ceil(ns);
let painted_edges = e.div_ceil(es);
assert!(
painted_nodes <= CPU_PAINT_MAX_NODES,
"CPU node pass is bounded: {painted_nodes} <= {CPU_PAINT_MAX_NODES}"
);
assert!(
painted_edges <= CPU_PAINT_MAX_EDGES,
"CPU edge pass is bounded: {painted_edges} <= {CPU_PAINT_MAX_EDGES}"
);
// Bounded is not blank: a real sample still draws.
assert!(painted_nodes > CPU_PAINT_MAX_NODES / 2, "…and it is a full-value sample");
assert!(r.drew(), "the pane is not empty");
}
/// The budget bounds the frame's ACTUAL tessellation, not just a reported number —
/// vertex count at 100k must stay in the same league as at 12k, not 8x it.
#[test]
fn the_budget_bounds_the_real_vertex_count() {
let small = harness::headless_render(&mut built(CPU_PAINT_MAX_NODES)).vertices;
let huge = harness::headless_render(&mut built(100_000)).vertices;
assert!(
huge <= small * 3,
"a 100k cloud tessellates no more than ~3x a budget-sized one ({huge} vs {small})"
);
}
/// **The two budgets are not the same number, and a host must not confuse them.**
///
/// [`CPU_PAINT_MAX_NODES`] is what the CPU *fallback* tessellates per frame;
/// [`VIEWER_MAX_NODES`] is what the VIEWER is exercised at. korp read the first as its
/// 3D `node_ceiling` and so declined every graph over 12 000 nodes — a graph this pane
/// draws, which is exactly what the test above proves. The guard is that the viewer
/// number is strictly the larger of the two AND that the pane really draws there, so
/// the constant can never quietly collapse back onto the fallback's budget.
#[test]
fn the_viewer_ceiling_is_not_the_cpu_fallback_budget() {
const _: () = assert!(
facett_graph3d::VIEWER_MAX_NODES > CPU_PAINT_MAX_NODES,
"the viewer ceiling (VIEWER_MAX_NODES) must exceed the CPU fallback budget \
(CPU_PAINT_MAX_NODES) — a host that cannot tell them apart declines graphs the pane draws"
);
// And it is a size the pane actually reaches: render AT the ceiling and require a
// bounded, non-blank frame. A constant nobody rendered at is a promise, not a fact.
let mut g = built(facett_graph3d::VIEWER_MAX_NODES);
let r = harness::headless_render(&mut g);
assert!(r.drew(), "the pane draws at VIEWER_MAX_NODES");
let ns = r.state["cpu_paint_stride"][0].as_u64().unwrap() as usize;
assert!(
g.nodes.len().div_ceil(ns) <= CPU_PAINT_MAX_NODES,
"…and the CPU lane stays inside its own budget while doing it"
);
}