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

nordisk/facett

facett-flow/tests/gpu_host.rs

#![cfg(all(
    feature = "wgpu",
    feature = "boxes",
    feature = "arrows",
    feature = "status",
    feature = "rate"
))]
use facett_core::render::lane;
use facett_flow::{
    Animation, Edge, FlowScene, Graph, Node, PaintOpts, View,
    attr::{Rates, Sizes, Status, StatusTable},
    gpu,
};
use std::sync::{Arc, Mutex};
fn graph() -> Graph {
    Graph {
        nodes: vec![
            Node {
                id: "a".into(),
                label: "a".into(),
                group: "g".into(),
                position: [0.0, 0.0],
                kind: 0,
            },
            Node {
                id: "b".into(),
                label: "b".into(),
                group: "g".into(),
                position: [200.0, 0.0],
                kind: 2,
            },
        ],
        edges: vec![Edge {
            from: "a".into(),
            to: "b".into(),
        }],
    }
}
fn diff(a: &image::RgbaImage, b: &image::RgbaImage) -> usize {
    a.pixels().zip(b.pixels()).filter(|(a, b)| a != b).count()
}
#[test]
fn host_variants_pixels_cache_attributes_and_idle_eviction() {
    for ppp in [1_u32, 2] {
        run_variants(ppp);
    }
}
fn run_variants(ppp: u32) {
    if !["integrated", "discrete", "software"].contains(
        &std::env::var("FACETT_TEST_GPU")
            .unwrap_or_default()
            .as_str(),
    ) {
        eprintln!("SKIPPED: set FACETT_TEST_GPU to prove host variant pixels/cache on a device");
        return;
    }
    let _turn = facett_core::render::gputurn::hold();
    let setup = facett_core::render::gpu::adapter_wgpu::facett_wgpu_options().wgpu_setup;
    let rs = egui_kittest::wgpu::create_render_state(setup, Default::default());
    assert!(gpu::install_renderer(&rs));
    let data = Arc::new(Mutex::new((
        FlowScene::with_key(graph(), 88).unwrap(),
        PaintOpts {
            moving: false,
            ..Default::default()
        },
    )));
    let state = data.clone();
    let mut h = egui_kittest::Harness::builder()
        .with_size(egui::vec2(420.0, 240.0))
        .with_pixels_per_point(ppp as f32)
        .renderer(egui_kittest::wgpu::WgpuTestRenderer::from_render_state(
            rs.clone(),
        ))
        .build_ui(move |ui| {
            let d = state.lock().unwrap();
            let rect = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(420.0, 240.0));
            assert!(
                facett_flow::paint(
                    ui,
                    egui::Id::new("host-test"),
                    rect,
                    &d.0,
                    &View {
                        scale: 1.0,
                        offset: egui::vec2(100.0, 100.0)
                    },
                    &d.1
                )
                .gpu
            );
        });
    let frame = |h: &mut egui_kittest::Harness<'_>| {
        h.step();
        h.render().unwrap()
    };
    frame(&mut h);
    let plain = frame(&mut h);
    let bytes = lane::clock_stats_of(gpu::lane_name()).upload_bytes_total;
    assert_eq!(diff(&plain, &frame(&mut h)), 0);
    assert_eq!(
        bytes,
        lane::clock_stats_of(gpu::lane_name()).upload_bytes_total
    );
    data.lock()
        .unwrap()
        .0
        .set_status(StatusTable {
            nodes: vec![None; 2],
            edges: vec![None],
        })
        .unwrap();
    assert_eq!(
        diff(&plain, &frame(&mut h)),
        0,
        "all-None table must retain plain shader"
    );
    assert_eq!(
        bytes,
        lane::clock_stats_of(gpu::lane_name()).upload_bytes_total
    );
    data.lock()
        .unwrap()
        .0
        .set_sizes(Sizes {
            nodes: vec![[40.0, 30.0]; 2],
        })
        .unwrap();
    let boxes = frame(&mut h);
    // Explicit picking/pixel agreement: use the same world-space node interior
    // and curve midpoint returned by the host API, at both tested pixel densities.
    {
        let d = data.lock().unwrap();
        let view = View {
            scale: 1.0,
            offset: egui::vec2(100.0, 100.0),
        };
        for (world, expected) in [
            (egui::pos2(15.0, 10.0), facett_flow::Hit::Node(0)),
            (d.0.edge_point(0, 0.5).unwrap(), facett_flow::Hit::Edge(0)),
        ] {
            assert_eq!(d.0.hit(world, 0.5), Some(expected));
            let screen = view.screen(world);
            let sample = boxes.get_pixel(
                (screen.x * ppp as f32) as u32,
                (screen.y * ppp as f32) as u32,
            );
            assert_ne!(
                sample,
                boxes.get_pixel(50 * ppp, 50 * ppp),
                "picked {expected:?} must coincide with a lit pixel at {screen:?}"
            );
        }
    }

    assert!(diff(&plain, &boxes) > 600, "box area must be visible");
    assert_ne!(
        plain.get_pixel(115 * ppp, 110 * ppp),
        boxes.get_pixel(115 * ppp, 110 * ppp)
    );
    assert_eq!(
        boxes.get_pixel(80 * ppp, 85 * ppp),
        boxes.get_pixel(75 * ppp, 85 * ppp),
        "rounded-box outer corner must be transparent"
    );
    let before = lane::clock_stats_of(gpu::lane_name()).upload_bytes_total;
    data.lock()
        .unwrap()
        .0
        .set_status(StatusTable {
            nodes: vec![Some(Status::Error); 2],
            edges: vec![Some(Status::Slow)],
        })
        .unwrap();
    let status = frame(&mut h);
    assert!(diff(&boxes, &status) > 600);
    assert_eq!(
        lane::clock_stats_of(gpu::lane_name()).upload_bytes_total - before,
        40,
        "only two 16B nodes + one packed 8B edge attribute, no geometry"
    );
    data.lock().unwrap().0.set_arrows(true);
    let arrow = frame(&mut h);
    assert!(diff(&status, &arrow) > 10);
    let changed: Vec<_> = status
        .enumerate_pixels()
        .filter(|(x, y, p)| **p != *arrow.get_pixel(*x, *y))
        .map(|(x, y, _)| (x, y))
        .collect();
    assert!(
        changed
            .iter()
            .all(|&(x, y)| (272 * ppp..=280 * ppp).contains(&x)
                && (96 * ppp..=104 * ppp).contains(&y)),
        "arrow must end at consumer box side: {changed:?}"
    );

    {
        let mut d = data.lock().unwrap();
        d.0.set_rates(Rates {
            edges: vec![Some(0.0)],
        })
        .unwrap();
        d.1.moving = true;
        d.1.seconds = 0.0;
    }
    let dead = frame(&mut h);
    data.lock().unwrap().1.seconds = 0.43;
    assert_eq!(
        diff(&dead, &frame(&mut h)),
        0,
        "zero throughput must have neither trail nor packets"
    );
    {
        let mut d = data.lock().unwrap();
        d.0.set_rates(Rates {
            edges: vec![Some(1.0)],
        })
        .unwrap();
        d.1.seconds = 0.0;
    }
    let live = frame(&mut h);
    data.lock().unwrap().1.seconds = 0.43;
    assert!(diff(&live, &frame(&mut h)) > 10);
    #[cfg(feature = "lod")]
    {
        data.lock().unwrap().1.min_packet_px = 500.0;
        let culled = frame(&mut h);
        data.lock().unwrap().1.seconds = 0.65;
        assert_eq!(
            diff(&culled, &frame(&mut h)),
            0,
            "short-edge packets and trails culled under opt-in threshold"
        );
        data.lock().unwrap().1.min_packet_px = 0.0;
    }
    data.lock().unwrap().1.animation = Animation::External;
    let external = frame(&mut h);
    data.lock().unwrap().1.seconds = 0.85;
    assert_eq!(diff(&external, &frame(&mut h)), 0);
    let targets = gpu::residency(&rs).1;
    {
        let mut g = graph();
        g.nodes[0].position[1] += 20.0;
        data.lock().unwrap().0.replace(g).unwrap();
    }
    assert!(diff(&external, &frame(&mut h)) > 20);
    assert_eq!(gpu::residency(&rs).1, targets);
    gpu::evict_idle(&rs, u64::MAX);
    assert_eq!(gpu::residency(&rs).0, 0);
}