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