facett-graph3d/tests/gpu_cloud.rs
//! **The LIVE GPU cloud paint lane** (feature `wgpu`) — proves that a graph opted into
//! [`Graph3D::with_gpu`] draws its node/edge cloud through the instanced `egui_wgpu`
//! callback ([`facett_graph3d::graph_gpu::cloud`]) instead of egui tessellating every
//! disc on the CPU — the fix for the single-core rotation paint wall the rotate bench
//! measures.
//!
//! RED-when-broken: the callback must render a **non-blank** frame end-to-end (the
//! instanced draw landed in the shared offscreen and the blit composited it into egui's
//! pass). If the callback path silently paints nothing (renderer not installed, blit
//! broken, empty instance buffers), the content ratio collapses to ~0 and this fails.
//!
//! Run: `cargo test -p facett-graph3d --features wgpu --test gpu_cloud`
//! (needs a GPU/`lavapipe`; degrades to a skip if no adapter — never a false fail.)
#![cfg(feature = "wgpu")]
use egui_kittest::Harness;
use egui_kittest::wgpu::{create_render_state, default_wgpu_setup, WgpuTestRenderer};
use egui::Color32;
use facett_core::Facet;
use facett_graph3d::{Graph3D, Layout3D};
/// Fraction of pixels far from the modal (≈background) colour — the "did real content
/// draw?" measure (a blank pane scores ~0).
fn content_ratio(img: &image::RgbaImage) -> f64 {
let mut hist = std::collections::HashMap::<u32, u32>::new();
for p in img.pixels() {
let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
*hist.entry(key).or_default() += 1;
}
let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
let total = (img.width() * img.height()) as usize;
let mut content = 0usize;
for p in img.pixels() {
let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
content += 1;
}
}
content as f64 / total as f64
}
fn sample(n: usize) -> Graph3D {
let nodes: Vec<(String, Color32)> = (0..n).map(|i| (format!("n{i}"), Color32::from_rgb(40, 210, 230))).collect();
let edges: Vec<(usize, usize)> = (0..n).map(|i| (i, (i + 1) % n)).collect();
Graph3D::new(nodes, edges, Layout3D::Sphere)
}
/// INJECT-ASSERT: a 4 000-node graph opted into the GPU cloud lane renders a non-blank
/// frame through the instanced callback + shared offscreen blit. Green ⇒ the 30k-scale
/// cloud draws on the GPU, not via single-core egui tessellation.
#[test]
fn gpu_cloud_renders_nonblank_via_instanced_callback() {
let rs = create_render_state(default_wgpu_setup(), Default::default());
facett_graph3d::graph_gpu::cloud::install_cloud_renderer(&rs);
let mut view = sample(4_000).with_clock(1.0).with_gpu(true);
let mut harness = Harness::builder()
.with_size(egui::vec2(480.0, 480.0))
.renderer(WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| {
facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
view.ui(ui);
});
harness.run_steps(3);
let img = harness.render().expect("wgpu render produced an image");
let ratio = content_ratio(&img);
eprintln!("[graph3d-gpu-cloud] content ratio {ratio:.4}");
assert!(
ratio > 0.01,
"GPU cloud must render non-blank via the instanced callback + offscreen blit (got {ratio:.4})"
);
// ATTRIBUTION, paired with the pixels above: the shared lane registry
// (`facett_core::render::lane` — what an **About box row** reads) must have counted
// the frames this render encoded, and must name the lane that did it. A content
// ratio alone cannot say WHO painted; korp installs this very renderer beside the
// map's, so if it stayed silent the app's whole GPU-lane answer would be `unknown`.
let lanes = facett_core::render::lane::lanes();
let cloud = lanes
.iter()
.find(|l| l.short_name() == "CloudRenderer")
.unwrap_or_else(|| panic!("the cloud lane must be registered: {lanes:?}"));
assert!(
cloud.frames > 0,
"the instanced scene was encoded and the registry did not count it — an About box would \
report this lane as installed-but-UNUSED while it is plainly painting: {cloud:?}"
);
assert!(
matches!(
facett_core::render::lane::lane_use(),
facett_core::render::lane::GpuLaneUse::InUse { .. }
),
"with a lane that just painted, the fold must read IN USE: {:?}",
facett_core::render::lane::lane_use()
);
}
/// **RED-when-broken: the fail-safe that keeps a 3D graph from going BLANK.**
///
/// `Graph3D::with_gpu(true)` makes the view SKIP the CPU tessellation of the
/// whole node/edge cloud, because the instanced GPU lane is supposed to draw it
/// instead. If the host opted in but never called `install_cloud_renderer`, the
/// paint callback finds no renderer and draws nothing — and the CPU painter is
/// already skipped. The pane renders **black**, which is exactly the "3D graphs
/// are missing" symptom, and neither oracle would blame the missing install.
///
/// The lane is therefore gated on the install flag. This test proves the gate:
/// with the renderer NOT installed, a `with_gpu(true)` graph must still
/// tessellate real vertices through the CPU painter.
///
/// It runs GPU-less (no adapter needed) — the whole point is the no-renderer
/// path — and must therefore run BEFORE any test in this file installs the
/// renderer. It builds its own graph, so it cannot be perturbed by the parity
/// test's fixture; the process-global install flag is the one shared thing, and
/// the assertion below only holds while it is unset.
#[test]
fn opting_into_gpu_without_installing_the_renderer_still_draws_on_the_cpu() {
// Guard: if another test in this binary already installed the renderer, the
// premise of this test is gone — skip rather than assert a false green.
if facett_graph3d::graph_gpu::cloud::cloud_renderer_installed() {
eprintln!("[gpu_cloud] renderer already installed in this process — fail-safe path not exercised");
return;
}
let mut cpu = sample(600).with_clock(1.0);
let cpu_report = facett_core::harness::headless_render(&mut cpu);
assert!(cpu_report.vertices > 0, "baseline: the CPU painter draws the cloud");
let mut opted_in = sample(600).with_clock(1.0).with_gpu(true);
let gpu_report = facett_core::harness::headless_render(&mut opted_in);
assert!(
gpu_report.vertices > 0,
"FAIL-SAFE: with_gpu(true) and NO installed renderer must fall back to the CPU painter, \
not render a blank pane (vertices={})",
gpu_report.vertices
);
// And it falls back to the SAME picture — the fallback is the golden path.
assert_eq!(
gpu_report.vertices, cpu_report.vertices,
"the fallback is the unchanged CPU path, vertex-for-vertex"
);
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-graph3d",
"gpu_optin_without_renderer_falls_back_to_cpu",
gpu_report.vertices > 0 && gpu_report.vertices == cpu_report.vertices,
&format!("cpu_vertices={} optedin_vertices={}", cpu_report.vertices, gpu_report.vertices),
);
}