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

nordisk/facett

facett-map3d/tests/route_reaches_the_gpu.rs

//! **A route set after the first paint must reach the DEVICE** — the mesh-mutation cache
//! invalidation (`Map3D::mesh_mut`).
//!
//! # The defect this pins
//!
//! `WgpuVelloBackend::paint` builds its GPU payload behind `if self.cache.borrow().is_none()`
//! (backend.rs:683) — once, and again only after `RenderBackend::reset_cache`. Three
//! writers touched `Map3D::mesh`; ONE invalidated that cache (`set_mesh_for_test`) and two
//! did not:
//!
//! * `Map3D::set_route` — the route ribbon, the stop markers, the chevron train;
//! * `Map3D::apply_roof_accent` — the roof highlight.
//!
//! So a route set after the pane's first paint was **built, counted, published in
//! `state_json`, and never drawn on the GPU lane**. Every cheaper signal was green:
//! `triangle_count()` grew, `state_json.triangles` grew, `RenderStats::visible_triangles`
//! grew — because all three read the CPU-side mesh. Only the bytes on the device knew.
//!
//! # Why the oracle is the device buffer and nothing else
//!
//! This is the exact reason `visible_triangles()` cannot be the oracle here: on the GPU
//! backend it comes from `analyze_scene`, which projects `scene.mesh` on the CPU. It is a
//! measurement of the mesh, not of the upload, so it would have been GREEN throughout the
//! defect. The assertion is therefore `Map3dRenderer::vbuf_for_test().1` — the vertex count
//! the next `draw` will use, read out of the live renderer in the harness's own
//! `callback_resources`.
//!
//! Distinct from `tests/gpu_geometry_swap.rs`, which pins the seam one layer down: that
//! file proves the RENDERER replaces a payload it is handed with a new build stamp. This
//! one proves a new payload is ever BUILT AND HANDED OVER at all.
//!
//! Run (needs the real device, opens NO window):
//! ```text
//! env -u WAYLAND_DISPLAY -u DISPLAY \
//!   cargo test -p facett-map3d --features wgpu --test route_reaches_the_gpu -- --nocapture
//! ```
#![cfg(feature = "wgpu")]

use std::cell::RefCell;
use std::rc::Rc;

use facett_core::Theme;
use facett_map::Way;
use facett_map3d::gpu::Map3dRenderer;
use facett_map3d::Map3D;

const CANVAS: egui::Vec2 = egui::vec2(640.0, 480.0);

/// The scene's ways — held separately because `set_route` projects its waypoints against
/// the SAME footprint bbox the mesh was extruded from (`extrude::project_route`), so the
/// route and the buildings have to come from one set of ways or the ribbon lands nowhere.
fn ways() -> Vec<Way> {
    let theme = Theme::default();
    let ring = |lon: f64, lat: f64, h: f64| {
        vec![(lon - h, lat - h), (lon + h, lat - h), (lon + h, lat + h), (lon - h, lat + h), (lon - h, lat - h)]
    };
    (0..6_i64)
        .flat_map(|i| {
            let d = i as f64 * 0.0004;
            Way::from_lonlat(100 + i, &ring(18.0686 + d, 59.3293 + d, 0.00008), &[("building", "yes")], &theme)
        })
        .collect()
}

/// The vertex count the next device draw will use, read out of the live renderer.
fn device_draw_count(renderer: &egui_wgpu::RenderState) -> u32 {
    let g = renderer.renderer.read();
    let r = g.callback_resources.get::<Map3dRenderer>().expect("the map3d renderer is installed");
    r.vbuf_for_test().map_or(0, |(_, n)| n)
}

/// **THE RED LINE.** Paint, then set a route, then paint again: the device's draw count
/// must GROW, because the ribbon is real triangles.
///
/// RED: put `&mut self.mesh` back in `Map3D::set_route` (in place of `self.mesh_mut()`) —
/// the backend's payload cache is then never invalidated, the second paint hands the device
/// the first frame's payload again, and `after == before` while every CPU-side count has
/// moved.
#[test]
fn a_route_set_after_the_first_paint_reaches_the_device() {
    let setup = facett_core::render::gpu::adapter_wgpu::facett_wgpu_options().wgpu_setup;
    let rs = egui_kittest::wgpu::create_render_state(setup, Default::default());
    let info = rs.adapter.get_info();
    eprintln!("LOOK: adapter {} [{:?}/{:?}]", info.name, info.device_type, info.backend);
    facett_map3d::gpu::install_renderer(&rs);
    // Kept so the test can read the renderer the harness is about to own.
    let probe = rs.clone();

    let ways = ways();
    let map = Rc::new(RefCell::new(Map3D::from_geometry("route probe", &ways).with_gpu(true)));
    assert!(map.borrow().gpu_lane_selected(), "fixture: the wgpu lane is selected");

    let drive = map.clone();
    let mut harness = egui_kittest::Harness::builder()
        .with_size(CANVAS)
        .renderer(egui_kittest::wgpu::WgpuTestRenderer::from_render_state(rs))
        .build_ui(move |ui| {
            facett_core::set_theme(ui.ctx(), Theme::default());
            facett_core::Facet::ui(&mut *drive.borrow_mut(), ui);
        });

    // ── FRAME(S) 1: the payload is built by the layout pass and uploaded by the render.
    for _ in 0..3 {
        harness.step();
        harness.render().expect("the wgpu backend produced a frame");
    }
    let before = device_draw_count(&probe);
    let tris_before = map.borrow().triangle_count();
    eprintln!("LOOK: before the route — device draw count {before}, mesh triangles {tris_before}");

    // NON-VACUITY, three ways. Without all three, `after > before` could be true of
    // nothing, or of a pane the GPU never touched.
    assert!(before > 0, "nothing was uploaded at all, so there is no baseline to grow from");
    assert_eq!(
        map.borrow().lane_flag(),
        facett_core::render::lane::LaneFlag::Gpu,
        "the GPU lane must actually be the one painting, or the device payload is irrelevant"
    );

    // ── THE ROUTE, set AFTER the first paint — the whole point.
    let bb_lon = 18.0686;
    let bb_lat = 59.3293;
    map.borrow_mut().set_route(
        &ways,
        &[
            (bb_lon as f32, bb_lat as f32),
            ((bb_lon + 0.0010) as f32, (bb_lat + 0.0010) as f32),
            ((bb_lon + 0.0020) as f32, (bb_lat + 0.0006) as f32),
        ],
    );
    let tris_after_mesh = map.borrow().triangle_count();
    assert!(
        tris_after_mesh > tris_before,
        "fixture: the route must add real triangles to the mesh ({tris_before} -> \
         {tris_after_mesh}); otherwise this test would pass on a route that was never built"
    );

    // ── FRAME(S) 2.
    for _ in 0..3 {
        harness.step();
        harness.render().expect("the wgpu backend produced a frame");
    }
    let after = device_draw_count(&probe);
    eprintln!("LOOK: after the route  — device draw count {after}, mesh triangles {tris_after_mesh}");

    assert!(
        after > before,
        "the route reached the MESH ({tris_before} -> {tris_after_mesh} triangles) and not \
         the DEVICE: the draw count is still {after} against {before}. The backend caches \
         its GPU payload behind `cache.borrow().is_none()` (backend.rs:683), so a mesh \
         mutation that skips `Map3D::mesh_mut`'s `reset_cache` is invisible on the GPU lane \
         forever — every count green, no pixel."
    );
}