facett-map/tests/fill_edge_aa.rs
//! **A filled area's OWN edge is anti-aliased with the outline layer OFF** — GG audit
//! row 32 (only with `--features wgpu`).
//!
//! # The claim this file was written against, and what the frame said
//!
//! `tests/fill_outline_aa.rs` proves the RING around a filled way carries a coverage
//! ramp. Audit #32 says that ring is the only anti-aliasing a filled area has — *"fill
//! triangles have NO AA; the 0.5 px outline IS the AA"* — and prescribes an SDF edge
//! distance per fill vertex, or 4× MSAA on the offscreen target. If the premise held, a
//! shoreline with no outline would be a HARD edge: water, paper, and nothing between.
//!
//! MEASURED on t14s / Radeon 890M (RADV, integrated, Vulkan), 2026-09-06, at facett
//! `05bd892f` with nothing in the lane changed: a diamond lake (every shore diagonal to
//! the pixel grid) rendered through the lane with a fully transparent ring colour:
//!
//! ```text
//! [GG32] outline OFF: 415 shore px, 3 distinct shore colours, 3 coverage levels
//! (bins/16: [0,0,0,0, 103, 0,0,0, 207, 0,0,0, 105, 0,0,0,0])
//! ```
//!
//! **Three levels, at exactly ¼, ½ and ¾ water.** That is the offscreen target's
//! `MAP_MSAA_SAMPLES = 4` resolving the fill triangles — lane GG #2 put the whole
//! offscreen pair at 4× for per-sample depth, and the fill pipeline draws into it
//! through `map_msaa_state()`, so a triangle's true edge has been rasterised at four
//! samples since that commit. The audit's premise expired with GG #2, and its two cures
//! are not equivalent: the sample count is already there (GG #2's own row says 4× alone
//! changed nothing FOR ROADS, because `line.wgsl` rasterises a quad WIDER than the road
//! and hides the coverage in alpha — a fill triangle's edge IS its geometric edge, so
//! the rasteriser sees it directly and no `sample_mask` is needed). A per-vertex edge
//! distance would raise the ramp from 4 levels to 256, but an ear-clip triangle does not
//! know which of its edges is a boundary, so it is a new vertex format or a fringe
//! strip — not a cheaper cure, and not one this measurement asks for.
//!
//! # What is pinned, and why it is pixels
//!
//! The 4× floor: at least THREE distinct coverage levels along a diagonal shore with
//! the outline off. Nothing else in the suite looks at a fill edge without its ring, so
//! a future change that drops the target or the fill pipeline off `MAP_MSAA_SAMPLES` —
//! or hands the fill fragment a `sample_mask` — would only have been caught by the
//! road-lane oracles, indirectly. A control keeps the outline ON and checks the water
//! and the ring's ramp are what they were.
//!
//! # Why this is its own binary
//!
//! `facett_map::gpu::install_renderer` latches a PROCESS-GLOBAL `renderer_installed()`.
//! `fill_outline_aa.rs` renders a CPU arm through a plain kittest `RenderState` with no
//! renderer in it, relying on that latch being `false`; a test in the same binary that
//! installed first made the CPU arm hand its fills to a callback with no renderer and
//! draw nothing (ink 57 369 → 19 343 px, MEASURED). One binary per install policy.
//!
//! Run: `cargo test -p facett-map --features wgpu --test fill_edge_aa -- --nocapture`
#![cfg(feature = "wgpu")]
use facett_map::layer::{LatLon, MapFacet, MapTheme, MapView, WaysLayer};
use facett_map::Way;
mod common;
const CANVAS: egui::Vec2 = egui::vec2(600.0, 600.0);
/// The least number of distinct partial-coverage levels a diagonal shore must show with
/// the outline off: `MAP_MSAA_SAMPLES − 1`, the ¼ / ½ / ¾ steps a 4× resolve can put a
/// pixel at. Written as a number rather than the constant so a change to the constant
/// has to come here and re-measure.
const MSAA_FLOOR_LEVELS: usize = 3;
/// A square rotated 45° in `(lon, lat)`, tagged water so it classifies `area` and takes
/// the fill lane. Mercator stretches y by ~1.96x at this latitude, so the half-widths
/// below put the shore near 38° on screen: diagonal, and NOT the 45° a sample grid can
/// be symmetric about.
///
/// `outline == false` turns the ring layer OFF the one way the lane can be asked to: a
/// fully transparent ring colour. `line.wgsl` discards a fragment whose alpha is below
/// `MIN_COVERAGE_ALPHA`, so a transparent outline paints nothing, blends nothing and
/// claims no depth — the fill is alone on the shore. The fill colour is untouched.
fn diamond(outline: bool) -> Way {
let (lon, lat) = (18.0620, 59.3272);
let (w, h) = (0.010, 0.004);
let r = vec![(lon, lat + h), (lon + w, lat), (lon, lat - h), (lon - w, lat), (lon, lat + h)];
let tags = [("natural".to_string(), "water".to_string())];
let mut way = Way::from_lonlat_with(1, &r, &tags, &facett_map::MapPalette::cartographic(false))
.expect("a 5-vertex closed ring is a way");
assert!(way.fill.is_some(), "fixture: the diamond must be a FILL, or there is no shore to measure");
if !outline {
way.style.color = egui::Color32::TRANSPARENT;
way.stroke = egui::Color32::TRANSPARENT;
}
way
}
fn facet(ways: Vec<Way>) -> MapFacet {
let view = MapView::new(LatLon::new(59.3272, 18.0620), 14.0, (CANVAS.x, CANVAS.y));
MapFacet::new("fill-edge-aa", view, MapTheme::default())
.with_layer(Box::new(WaysLayer::new("osm-ways", ways)))
}
/// One lane render plus the still-frame controls read beside it.
struct Probe {
img: image::RgbaImage,
/// Frames the GPU lane really encoded — 0 would mean the CPU painter drew this.
lane_frames: u64,
/// `offscreen_target_allocs()` after the first frame and after the last (GG #37).
allocs: (u64, u64),
/// `cull_dispatch_count()` after the first frame and after the last (GG #13).
culls: (u64, u64),
/// Harness wall time per frame over the still frames after the first — egui,
/// the lane and the readback together, NOT the GPU pass alone.
ms_per_frame: f64,
/// The last blit's wall time (`last_blit_us`) and the lane clock's last cull pass
/// where the device has timestamp queries.
blit_us: u64,
cull_pass_us: Option<u64>,
}
/// Render `frames` frames with `GpuMapRenderer` installed and read the controls off
/// the same `RenderState` the harness draws through. `RenderState` is `Arc`-backed, so
/// the clone kept here sees the renderer the callback installed.
fn probe_gpu(mut f: MapFacet, frames: usize) -> Probe {
let _turn = common::one_gpu::hold();
let before = facett_core::render::lane::lane_use().frames();
let setup = facett_core::render::gpu::adapter_wgpu::facett_wgpu_options().wgpu_setup;
let rs = egui_kittest::wgpu::create_render_state(setup, Default::default());
facett_map::gpu::install_renderer(&rs);
let seam = rs.clone();
let culls = move || {
seam.renderer
.read()
.callback_resources
.get::<facett_map::gpu::GpuMapRenderer>()
.map_or(0, facett_map::gpu::GpuMapRenderer::cull_dispatch_count)
};
let mut h = egui_kittest::Harness::builder()
.with_size(CANVAS)
.renderer(egui_kittest::wgpu::WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| f.ui(ui));
let mut img = None;
let mut first = (0, 0);
let mut t0 = std::time::Instant::now();
for i in 0..frames {
img = Some(h.render().expect("the wgpu backend produced a frame"));
h.step();
if i == 0 {
first = (facett_map::gpu::offscreen_target_allocs(), culls());
t0 = std::time::Instant::now();
}
}
let ms_per_frame = t0.elapsed().as_secs_f64() * 1000.0 / (frames.max(2) - 1) as f64;
Probe {
img: img.expect("at least one frame"),
lane_frames: facett_core::render::lane::lane_use().frames() - before,
allocs: (first.0, facett_map::gpu::offscreen_target_allocs()),
culls: (first.1, culls()),
ms_per_frame,
blit_us: facett_map::gpu::last_blit_us(),
cull_pass_us: facett_map::gpu::lane_clock_stats().last_gpu_pass_us,
}
}
/// How many units of water a pixel carries, as the projection of `p − paper` onto
/// `water − paper`; `None` when the pixel is off that axis (the outline's own hue, an
/// overlay) by more than `OFF_AXIS` per channel.
fn water_share(p: [u8; 4], paper: [u8; 4], water: [u8; 4]) -> Option<f32> {
const OFF_AXIS: f32 = 10.0;
let d: [f32; 3] = std::array::from_fn(|i| f32::from(water[i]) - f32::from(paper[i]));
let q: [f32; 3] = std::array::from_fn(|i| f32::from(p[i]) - f32::from(paper[i]));
let dd: f32 = d.iter().map(|v| v * v).sum();
let t = (0..3).map(|i| q[i] * d[i]).sum::<f32>() / dd;
let residual = (0..3).map(|i| (q[i] - t * d[i]).abs()).fold(0.0_f32, f32::max);
(residual <= OFF_AXIS).then_some(t)
}
/// What the shoreline census hands back.
struct Shore {
/// Distinct coverage levels among ON-AXIS partial pixels (bins of a sixteenth).
levels: usize,
/// The bin histogram those levels were counted from.
hist: [u32; 17],
/// Distinct RGB triples over the whole shore, ring hue included.
triples: usize,
/// Shore pixels: neither paper nor water, inside the central window.
px: usize,
}
/// **The shoreline census.** Every pixel in the central window that is neither paper
/// nor water is a shore pixel — the diamond is the only ink there, and the window
/// (60..540 both ways) is clear of the lane flag and badge overlays in the corners.
///
/// A hard edge has NO on-axis partial pixel: every pixel is paper or water, and
/// `levels` is 0. A 4× resolve can put a pixel at ¼, ½ or ¾ water — three levels.
fn shoreline(img: &image::RgbaImage) -> Shore {
let paper = px_at(img, 0.05, 0.95);
let water = px_at(img, 0.5, 0.5);
assert!(!near(paper, water, 8), "fixture: the pane centre must be water, not paper ({water:?} vs {paper:?})");
let mut hist = [0u32; 17];
let mut triples = std::collections::HashSet::new();
let mut px = 0usize;
for y in 60..540u32 {
for x in 60..540u32 {
let p = img.get_pixel(x, y).0;
if near(p, paper, 2) || near(p, water, 2) {
continue;
}
px += 1;
triples.insert([p[0], p[1], p[2]]);
if let Some(t) = water_share(p, paper, water)
&& (0.03..=0.97).contains(&t)
{
hist[(t * 16.0).round() as usize] += 1;
}
}
}
Shore { levels: hist.iter().filter(|c| **c > 0).count(), hist, triples: triples.len(), px }
}
fn px_at(img: &image::RgbaImage, fx: f32, fy: f32) -> [u8; 4] {
let x = ((fx * img.width() as f32) as u32).min(img.width() - 1);
let y = ((fy * img.height() as f32) as u32).min(img.height() - 1);
img.get_pixel(x, y).0
}
fn near(a: [u8; 4], b: [u8; 4], tol: i32) -> bool {
(0..3).all(|i| (i32::from(a[i]) - i32::from(b[i])).abs() <= tol)
}
fn save(img: &image::RgbaImage, name: &str) {
let path = format!("{}/scratch/cargo/fill_edge_aa_{name}.png", std::env::var("HOME").unwrap_or_default());
let _ = image::DynamicImage::ImageRgba8(img.clone()).save(&path);
eprintln!("LOOK: saved {path} ({}x{})", img.width(), img.height());
}
/// A synthetic 600×600 frame of the diamond, `paper` outside and `water` inside, with the
/// coverage of a pixel decided by `samples` points across it: 1 is a hard edge (the
/// pixel centre alone), 4 is a 4× resolve. Only for proving the census below can tell
/// the two apart — the lane's own frame is what the oracles measure.
fn synthetic(samples: u32, paper: [u8; 4], water: [u8; 4]) -> image::RgbaImage {
let (cx, cy, hw, hh) = (300.0_f32, 300.0_f32, 116.0_f32, 91.0_f32);
let inside = |x: f32, y: f32| ((x - cx) / hw).abs() + ((y - cy) / hh).abs() <= 1.0;
let mut img = image::RgbaImage::new(600, 600);
for (x, y, p) in img.enumerate_pixels_mut() {
let mut hit = 0u32;
for i in 0..samples {
for j in 0..samples {
let (sx, sy) = ((i as f32 + 0.5) / samples as f32, (j as f32 + 0.5) / samples as f32);
hit += u32::from(inside(x as f32 + sx, y as f32 + sy));
}
}
let t = hit as f32 / (samples * samples) as f32;
*p = image::Rgba(std::array::from_fn(|k| {
(f32::from(paper[k]) + t * (f32::from(water[k]) - f32::from(paper[k]))).round() as u8
}));
}
img
}
/// **The census is not vacuous**: on a HARD diagonal edge it counts zero levels, and on
/// a multi-sample one it counts the steps. Without this the oracle's `>= 3` could be
/// met by a census that mis-bins paper.
#[test]
fn the_census_sees_a_hard_edge_as_zero_levels_and_a_resolved_one_as_steps() {
let (paper, water) = ([246, 242, 233, 255], [18, 58, 90, 255]);
// A hard edge has NO pixel that is neither paper nor water — the census must see an
// empty shore, not a shore of two colours.
let hard = shoreline(&synthetic(1, paper, water));
assert_eq!((hard.px, hard.levels), (0, 0), "a hard edge binned a shore: {} px, {:?}", hard.px, hard.hist);
let msaa = shoreline(&synthetic(2, paper, water));
assert!(msaa.px > 200, "the resolved diamond has a shore ({} px)", msaa.px);
assert_eq!(msaa.levels, MSAA_FLOOR_LEVELS, "a 2×2 resolve has three steps: {:?}", msaa.hist);
}
/// **THE ORACLE (GG audit row 32): a diagonal shoreline is anti-aliased with the
/// outline layer OFF.** The fill triangles carry their own edge coverage; the ring
/// stroke is not the only thing that does.
#[test]
fn a_fill_edge_is_anti_aliased_with_the_outline_layer_off() {
if common::headless_device("fill_edge_aa").is_none() {
return;
}
let off = probe_gpu(facet(vec![diamond(false)]), 4);
save(&off.img, "outline_off");
let s = shoreline(&off.img);
eprintln!(
"[GG32] outline OFF: {} shore px, {} distinct shore colours, {} coverage levels (bins/16: {:?}) | \
lane frames {} | culls {:?} allocs {:?} | harness {:.2} ms/frame, blit {} us, cull pass {:?} us",
s.px, s.triples, s.levels, s.hist, off.lane_frames, off.culls, off.allocs, off.ms_per_frame, off.blit_us,
off.cull_pass_us
);
// NON-VACUITY: the GPU lane drew it, and a shore exists to be measured.
assert!(off.lane_frames > 0, "the GPU lane encoded no frame — this is the CPU painter, not the device");
assert!(s.px > 200, "only {} shore pixels: the diamond is not on the pane where the census looks", s.px);
// The still-frame controls did not move: one cull per lane frame (the whole-payload
// path culls every frame it encodes; GG #13's skip is the tile lane's) and NO
// offscreen target allocated after the first frame (GG #37).
assert_eq!(off.culls.1 - off.culls.0, off.lane_frames - 1, "extra cull dispatches on still frames");
assert_eq!(off.allocs.0, off.allocs.1, "an offscreen target was allocated on a still frame");
// THE CLAIM. A hard edge is water and paper and nothing between: zero levels. The
// 4× target puts a shore pixel at ¼, ½ or ¾ — three, and that floor is what is held.
assert!(
s.levels >= MSAA_FLOOR_LEVELS,
"the shoreline has {} partial-coverage level(s) with the outline off (bins/16 {:?}); {} shore \
pixels in {} colours. The fill triangles are no longer resolving through the 4× offscreen \
target (`MAP_MSAA_SAMPLES`, `map_msaa_state()` on `fill_pipeline`) — audit #32's hard edge \
is back.",
s.levels, s.hist, s.px, s.triples
);
}
/// **CONTROL: the same shore with the outline ON is what it was.** The interior is the
/// same water, the ring still carries its own ramp (many more colours than the fill's
/// four steps), and it can only WIDEN the shore over the fill's own edge, never narrow it.
#[test]
fn the_shore_with_the_outline_on_keeps_its_ramp_and_its_water() {
if common::headless_device("fill_edge_aa_control").is_none() {
return;
}
let on = probe_gpu(facet(vec![diamond(true)]), 4);
let off = probe_gpu(facet(vec![diamond(false)]), 4);
save(&on.img, "outline_on");
let a = shoreline(&on.img);
let b = shoreline(&off.img);
eprintln!(
"[GG32] outline ON: {} shore px, {} distinct shore colours, {} on-axis coverage levels (bins/16: {:?}) \
| OFF: {} px, {} levels | harness {:.2} vs {:.2} ms/frame | blit {} vs {} us",
a.px, a.triples, a.levels, a.hist, b.px, b.levels, on.ms_per_frame, off.ms_per_frame, on.blit_us, off.blit_us
);
assert!(on.lane_frames > 0 && off.lane_frames > 0, "both renders must be the GPU lane");
assert_eq!(px_at(&on.img, 0.5, 0.5), px_at(&off.img, 0.5, 0.5), "the outline must not change the water itself");
assert!(
a.triples >= 8,
"the ring lost its coverage ramp: only {} distinct colours along the shore with the outline on",
a.triples
);
assert!(a.px >= b.px, "the shore is NARROWER with the outline on ({} < {} px)", a.px, b.px);
assert_eq!(on.allocs.0, on.allocs.1, "an offscreen target was allocated on a still frame");
}