Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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),
    );
}