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

nordisk/facett

facett-map/tests/hairline_lane_off.rs

//! **The retained `LineList` hairline lane must cost a shipped frame nothing.**
//! (stinky `mapdraw-4`.)
//!
//! `MapPaintCallback::paint` draws the fill triangles, the fill outlines and the
//! pretty-line quads. It has never drawn `renderer.chunks` — the 24 B `Vertex`
//! hairline stream the quad lane superseded — and three `#[allow(dead_code)]`
//! attributes in `gpu/mod.rs` say so in words. Retaining the lane as a one-line revert
//! path is a defensible call. Baking it, uploading it to VRAM, compute-culling it 60
//! times a second and (since the baked-vertex tier landed) writing it to the on-disk
//! blob is not.
//!
//! MEASURED on the 20 000-way Sweden payload while it was live: **618 030 `Vertex` =
//! 14.83 MB geom + 14.83 MB compact + 0.40 MB meta = 30.07 MB, 14.4 % of the map's
//! 208 MB of VRAM**, and 198 cull workgroups a frame whose output — the compacted
//! buffer and the `indirect_buf` counter the cull atomically bumps — was read by
//! nothing.
//!
//! # What is asserted, and why it is the applied output
//!
//! Three facts, at the three places the lane used to cost something:
//!
//! 1. **the bake** — `GpuGeometry::vertices`/`way_meta` come back empty from a real
//!    way payload, so `build_gpu_geometry` no longer expands the stream;
//! 2. **VRAM** — `chunk_count()` is 0 and `chunk_buffer_bytes()` is empty after a real
//!    `upload_for_test` on a real device, which is the only honest measure of "not
//!    uploaded";
//! 3. **the dispatch** — this needs no assertion of its own and gets one anyway by
//!    construction: `run_cull`'s first loop is `for chunk in self.chunks.iter()...`, so
//!    an empty census IS a skipped dispatch. Fact 2 is therefore fact 3.
//!
//! And the other end, without which every one of those would pass on a renderer that
//! uploads NOTHING: the live quad lane must still hold VRAM for the same payload. A
//! "fix" that emptied both lanes would draw a blank map and satisfy 1–3.
//!
//! # RED PROVEN
//!
//! Flip `facett_map::gpu::HAIRLINE_LANE` to `true` and both tests fail with the real
//! figures — that is the measurement quoted in the commit, taken on this fixture on
//! this box.
//!
//! Run: `cargo test -p facett-map --features wgpu --test hairline_lane_off`
#![cfg(feature = "wgpu")]

use facett_map::Way;
use facett_map::gpu::{GpuMapRenderer, HAIRLINE_LANE, build_gpu_geometry, ways_origin};

/// The ONE GPU gate for this crate's device-backed tests (`tests/common/mod.rs`).
mod common;

/// `n` roads with three points each — the shape the served basemap carries.
fn roads(n: usize) -> Vec<Way> {
    let c = egui::Color32::from_rgb(0xE8, 0xB0, 0x30);
    (0..n)
        .map(|i| {
            let lon = 9.0 + i as f64 * 0.01;
            Way::line(i as i64, "road", vec![(lon, 47.0), (lon + 0.005, 47.002), (lon + 0.009, 47.0)], c, 5.0)
        })
        .collect()
}

/// (1) The bake does not expand the stream at all.
#[test]
fn the_hairline_stream_is_not_baked() {
    let ways = roads(500);
    let origin = ways_origin(ways.iter()).expect("non-empty fixture has an origin");
    let geo = build_gpu_geometry(ways.iter().map(|w| (w, w.style.lod as u32)), origin);

    // The premise, first: this payload really does produce geometry, so "empty" below
    // means "the hairline stream was skipped" and not "the fixture baked nothing".
    assert!(
        !geo.line_vertices.is_empty() && !geo.line_meta.is_empty(),
        "premise: the fixture must bake real quad geometry ({} vertices, {} metas)",
        geo.line_vertices.len(),
        geo.line_meta.len()
    );

    assert!(
        geo.vertices.is_empty() && geo.way_meta.is_empty(),
        "the hairline stream must not be baked (HAIRLINE_LANE = {HAIRLINE_LANE}): {} Vertex \
         ({} B) + {} GpuWayMeta ({} B) built for a lane paint() never draws",
        geo.vertices.len(),
        geo.vertices.len() * std::mem::size_of::<facett_map::gpu::Vertex>(),
        geo.way_meta.len(),
        geo.way_meta.len() * std::mem::size_of::<facett_map::gpu::GpuWayMeta>(),
    );
}

/// (2)+(3) It holds no VRAM and is therefore dispatched over zero times — while the
/// lane that DOES draw still holds the payload.
#[test]
fn the_hairline_lane_holds_no_vram_and_the_quad_lane_still_does() {
    let Some((device, queue)) = common::device_or_skip(
        "the_hairline_lane_holds_no_vram_and_the_quad_lane_still_does",
        "the dead hairline lane holds no VRAM",
    ) else {
        return;
    };

    let ways = roads(500);
    let origin = ways_origin(ways.iter()).expect("non-empty fixture has an origin");
    let geo = build_gpu_geometry(ways.iter().map(|w| (w, w.style.lod as u32)), origin);

    let mut r = GpuMapRenderer::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb);
    // A small budget so a live payload is several chunks — an all-fits-in-one census
    // could not tell "no chunks" from "one chunk".
    r.set_max_buffer_size_for_test(64 * 1024);
    r.upload_for_test(&device, &queue, &geo);

    let dead: u64 = r.chunk_buffer_bytes().iter().map(|(g, m, c)| g + m + c).sum();
    let live: u64 = r.line_chunk_buffer_bytes().iter().map(|(s, m, c)| s + m + c).sum();
    eprintln!(
        "[hairline] dead lane: {} chunks / {dead} B | live quad lane: {} chunks / {live} B",
        r.chunk_count(),
        r.line_chunk_count()
    );

    // THE OTHER END, first — without it an upload that landed nothing would pass.
    assert!(
        r.line_chunk_count() > 1 && live > 0,
        "premise: the quad lane must still hold this payload in VRAM, split by the budget \
         ({} chunks, {live} B) — else this test cannot tell a gated lane from a dead renderer",
        r.line_chunk_count()
    );

    assert_eq!(
        r.chunk_count(),
        0,
        "the hairline lane uploaded {} chunks ({dead} B of VRAM) that paint() never draws, and \
         run_cull dispatches over every one of them, every frame",
        r.chunk_count()
    );
    assert!(r.chunk_buffer_bytes().is_empty(), "and its per-chunk buffer census must be empty, not just short");
}