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/bloom_off_costs_nothing.rs

//! **Bloom OFF must cost three fewer passes AND change no pixel** (needs `--features wgpu`).
//!
//! GFX_V2 §V2.9b, "still open": *"bloom's bright-pass + 2 blurs run even when bloom is
//! OFF — `facett-map3d/src/gpu.rs:1901-1903`; only the composite intensity is zeroed."*
//! True as written. `write_uniforms` set `params.z = 0.0` and `record` then ran
//! bright → blur H → blur V anyway, three fullscreen passes (one full-res 4-tap read, two
//! half-res 9-tap) whose entire product was multiplied by zero one pass later.
//!
//! `record` now takes `bloom_on` and skips them. That is a **claim about pixels**, not
//! just about cost, and the claim is the risky half: the composite still SAMPLES
//! `bloom_a`, so skipping the passes leaves it holding whatever the last bloom-on frame
//! wrote. The argument that this is safe is that `bloom.wgsl` reaches that texture only
//! through `scene + bloom * U.params.z`, and `params.z` is an exact `0.0`; the bright
//! pass's output is `soft_threshold` of finite HDR radiance, so `bloom_a` is always
//! finite and finite × 0.0 is 0.0. There is no NaN path.
//!
//! An argument is not a measurement, so this file measures it, and the measurement is
//! built to be able to fail:
//!
//! * **The bloom texture is DELIBERATELY DIRTIED.** One renderer draws a bright bloom-ON
//!   frame first, then the same renderer draws bloom-OFF. If stale glow could leak, this
//!   is the run in which it leaks. A fresh renderer drawing only bloom-OFF is the
//!   control, and the two must be **byte-identical**.
//! * **Non-vacuity, both ends.** The bloom-ON frame must differ from the bloom-OFF frame
//!   (otherwise "identical" is a statement about a renderer that draws nothing), and the
//!   pass counts must differ by exactly three.
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test bloom_off_costs_nothing -- --nocapture`
#![cfg(feature = "wgpu")]

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use egui_kittest::wgpu::{create_render_state, default_wgpu_setup, WgpuTestRenderer};
use egui_kittest::Harness;
use facett_core::Facet as _;
use facett_map::Way;
use facett_map3d::{Map3D, SunSky, ViewPreset};

/// Tall bright blocks under a strong sun — a scene with genuine HDR highlights, so the
/// bright-pass has something above threshold to find. A scene the bloom chain finds
/// nothing in would make "bloom on == bloom off" trivially true.
fn blocks() -> Vec<Way> {
    let (lon0, lat0) = (9.50_f64, 47.10_f64);
    let theme = facett_core::Theme::default();
    let tags = vec![
        ("building".to_string(), "yes".to_string()),
        ("height".to_string(), "120".to_string()),
    ];
    let mut ways = Vec::new();
    let mut id = 1i64;
    for r in 0..3 {
        for c in 0..3 {
            let bx = lon0 + c as f64 * 0.0018;
            let by = lat0 + r as f64 * 0.0018;
            let b = 0.0012;
            let ring = [(bx, by), (bx + b, by), (bx + b, by + b), (bx, by + b), (bx, by)];
            ways.push(Way::from_lonlat(id, &ring, &tags, &theme).unwrap());
            id += 1;
        }
    }
    ways
}

fn scene(bloom: bool) -> Map3D {
    let sun = SunSky {
        dir: SunSky::from_angles(0.7, 0.6).dir,
        sun_color: [1.0, 0.97, 0.90],
        // Hot sun: pushes highlights well above 1.0 in the Rgba16Float offscreen, which
        // is the only regime in which bloom is visible at all.
        sun_intensity: 3.2,
        sky_color: [0.55, 0.62, 0.80],
        ambient: 0.5,
    };
    let mut m = Map3D::from_ways("bloomcost", &blocks(), 0.4)
        .with_gpu(true)
        .with_sun(sun)
        .with_bloom(bloom);
    m.spin = false;
    m.set_clock(0.0);
    m.view_preset(ViewPreset::Isometric);
    m.cam.goal_distance = 2.6;
    m.cam.settle();
    m
}

/// Render one frame. When `dirty_first` is set, the SAME renderer draws several bloom-ON
/// frames before the bloom-OFF frame that is captured — so `bloom_a` is full of real glow
/// when the skip happens.
fn render(bloom: bool, dirty_first: bool) -> (image::RgbaImage, Option<u32>) {
    let rs = create_render_state(
        facett_map3d::gpu::proof_wgpu_setup().unwrap_or_else(default_wgpu_setup),
        Default::default(),
    );
    {
        // ATTRIBUTION: `create_render_state` hard-fails with no adapter, so reaching this
        // line means a device answered — say which, because a proof that silently ran on
        // llvmpipe is evidence about llvmpipe.
        let info = rs.adapter.get_info();
        eprintln!(
            "[bloom_off] device: {} ({:?}, {:?}) bloom={bloom} dirty_first={dirty_first}",
            info.name, info.backend, info.device_type
        );
    }
    // ── THE TEMPORAL LANE IS OFF FOR THIS FILE, and that is isolation rather than
    // convenience. The claim measured below is "a bloom-OFF frame is byte-identical
    // whether or not `bloom_a` holds stale glow". With TAA on, a frame is a function of
    // the accumulated history as well as of this frame's geometry, and the `dirty_first`
    // arm deliberately renders five MORE frames than the control — so the two arms sit at
    // different jitter phases with different histories and can never be byte-identical,
    // for reasons that have nothing to do with the glow chain. MEASURED with TAA on:
    // 84 235 of 230 400 px differ. Switching the temporal lane off is what makes the
    // comparison a statement about bloom again — and it also keeps the pass counts here
    // (10 on / 7 off) exactly the ones this file was written against, since `Off` records
    // neither the velocity pass nor the resolve. The assertion that matters is on their
    // DIFFERENCE of three either way.
    facett_map3d::gpu::install_renderer_with_taa_for_test(&rs, facett_map3d::gpu::TaaMode::Off);
    let probe = rs.clone();

    // The live bloom flag the frame closure reads, so ONE renderer can draw both states.
    let live = Arc::new(AtomicBool::new(if dirty_first { true } else { bloom }));
    let flag = live.clone();
    let mut view = scene(bloom);
    let mut harness = Harness::builder()
        .with_size(egui::vec2(480.0, 480.0))
        .renderer(WgpuTestRenderer::from_render_state(rs))
        .build_ui(move |ui| {
            view.set_bloom(flag.load(Ordering::Relaxed));
            facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
            view.ui(ui);
        });

    if dirty_first {
        // Four bloom-ON frames: bloom_a now holds real, bright, blurred glow.
        harness.run_steps(4);
        let _ = harness.render().expect("wgpu render (dirtying pass)");
        live.store(bloom, Ordering::Relaxed);
    }
    harness.run_steps(4);
    let img = harness.render().expect("wgpu render");
    (img, facett_map3d::gpu::post_passes_recorded(&probe))
}

#[test]
fn skipping_the_glow_chain_costs_three_passes_and_changes_no_pixel() {
    let (on_img, on_n) = render(true, false);
    let (off_img, off_n) = render(false, false);
    let (dirty_img, dirty_n) = render(false, true);

    let on_n = on_n.expect("the bloom-ON frame installed a renderer (Some(n) != None)");
    let off_n = off_n.expect("the bloom-OFF frame installed a renderer");
    let dirty_n = dirty_n.expect("the dirtied bloom-OFF frame installed a renderer");
    eprintln!("EMIT-BLOOMCOST post_passes: on={on_n} off={off_n} off_after_on={dirty_n}");

    for (name, img) in [("on", &on_img), ("off", &off_img), ("dirty", &dirty_img)] {
        image::DynamicImage::ImageRgba8((*img).clone())
            .save(format!("/tmp/map3d_bloomcost_{name}.png"))
            .ok();
    }

    // ── THE COST. Exactly three passes, which is bright + blur H + blur V. ──
    // "Exactly", not ">=": a skip that also dropped the composite would be the
    // 2026-08-05 untonemapped-plan-view regression coming back through another door.
    assert!(on_n >= 4, "the bloom-ON chain must record its four passes ({on_n})");
    assert_eq!(
        on_n - off_n,
        3,
        "bloom OFF must skip EXACTLY the three glow passes (on={on_n}, off={off_n}); the \
         composite is the display transform and always runs"
    );
    assert_eq!(off_n, dirty_n, "the pass count does not depend on what ran before it");

    // ── THE PIXELS. The claim that makes the skip a no-op rather than an optimisation. ──
    // A fresh bloom-OFF frame and a bloom-OFF frame drawn by a renderer whose bloom
    // texture is full of glow must be byte-identical.
    let differing = off_img
        .pixels()
        .zip(dirty_img.pixels())
        .filter(|(a, b)| a.0 != b.0)
        .count();
    let total = (off_img.width() * off_img.height()) as usize;
    eprintln!(
        "EMIT-BLOOMCOST stale-bloom leak: {differing} of {total} px ({:.4}%)",
        100.0 * differing as f64 / total as f64
    );
    assert_eq!(
        differing, 0,
        "a bloom-OFF frame drawn after bloom-ON frames differs from a fresh one in \
         {differing} of {total} px — stale `bloom_a` content is reaching the composite, so \
         skipping the glow passes is NOT the no-op `record`'s doc claims"
    );

    // ── NON-VACUITY: bloom must actually DO something in this scene, or the whole file
    //    is a statement about a renderer that draws the same frame either way. ──
    let bloom_delta = on_img
        .pixels()
        .zip(off_img.pixels())
        .filter(|(a, b)| a.0 != b.0)
        .count();
    eprintln!(
        "EMIT-BLOOMCOST bloom on-vs-off: {bloom_delta} of {total} px ({:.2}%)",
        100.0 * bloom_delta as f64 / total as f64
    );
    assert!(
        bloom_delta * 200 > total,
        "bloom must change more than 0.5% of the frame in this scene ({bloom_delta} of \
         {total}) — otherwise 'off is identical to off' proves nothing about the glow chain"
    );

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "bloom_off_skips_three_passes_pixel_identically",
        differing == 0 && on_n - off_n == 3,
        &format!("blob=/tmp/map3d_bloomcost_off.png on={on_n} off={off_n} leak_px={differing}"),
    );
}