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

//! **GFX_V2 item 7 — the 2D-ortho post-FX bypass, COUNTED ON THE DEVICE**
//! (needs `--features wgpu`).
//!
//! GFX_V2 §4's claim is that "2D costs nothing extra since the post passes are simply
//! skipped". Item 6 built the predicate that decides it
//! ([`SceneFlags::constrain_to_view`] → `flat_2d` → `any_post()`) and was honest that
//! nothing consumed it: `any_post()` had zero production callers, `flat_2d` was read
//! nowhere but tests, and `Map3D` could not reach the constrained state at all. A flag
//! nothing can set, consulted by nothing, is the unwired-detector trap.
//!
//! This closes it, and the assertion is a **number the render lane produced**:
//! [`facett_map3d::gpu::post_passes_recorded`] is bumped inside `begin_post_pass`, the
//! one function that opens a screen-space post pass, at the `begin_render_pass` call
//! itself. The display transform plus the AA lane in plan mode, the full chain in 3D —
//! see the assertion for why that number is 3 today and was 0 when this file was written.
//!
//! **NON-VACUITY, BOTH DIRECTIONS**, because either half alone is hollow:
//!
//! * `0` proves nothing if the 3D frame is also `0` — the renderer might simply never
//!   record post passes, or never have been installed at all. So the 3D control is
//!   asserted `>= 8` in the same run.
//! * A large 3D count proves nothing about the bypass if the "2D" frame was not
//!   actually 2D. So `is_constrained_2d()`, `flat_2d` and `any_post()` are asserted on
//!   the very view the frame was drawn from.
//! * And `Some(0)` is deliberately distinguished from `None`: no renderer installed is
//!   a different answer from a renderer that ran zero passes, and only the first would
//!   make the whole file vacuous.
//!
//! **THE PIXELS TOO**, because a pass count is bookkeeping. The bypassed frame must
//! still be a picture — it goes through the offscreen blit instead of the tonemap — and
//! it must DIFFER from the 3D frame. Both are asserted with
//! [`facett_core::golden::moved`], the one before/after pixel predicate.
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test postfx_bypass -- --nocapture`
#![cfg(feature = "wgpu")]

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

/// A handful of blocks — enough that the 3D lane has real geometry to shade, shadow and
/// occlude, so its post chain has something to do.
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(), "90".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
}

/// A scene with the **whole post chain requested** — sky, SSAO, soft shadows, bloom.
/// `plan_2d` decides whether the bypass takes it all away again.
fn scene(plan_2d: bool) -> Map3D {
    let sun = SunSky {
        dir: SunSky::from_angles(0.7, 0.6).dir,
        sun_color: [1.0, 0.97, 0.90],
        sun_intensity: 1.4,
        sky_color: [0.55, 0.62, 0.80],
        ambient: 0.5,
    };
    let mut m = Map3D::from_ways("bypass", &blocks(), 0.4)
        .with_gpu(true)
        .with_sun(sun)
        .with_shadows(0.7)
        .with_ssao(true)
        .with_bloom(true)
        .with_plan_2d(plan_2d);
    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 and return `(image, post passes the device lane recorded)`.
fn render(plan_2d: 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: a graceful skip is how a GPU test file goes green having run nothing.
    // `create_render_state` hard-fails without an adapter, so reaching this print at all
    // means a device answered — say which.
    {
        let info = rs.adapter.get_info();
        eprintln!(
            "[postfx_bypass] device: {} ({:?}, {:?}) plan_2d={plan_2d}",
            info.name, info.backend, info.device_type
        );
        // ...and ASSERT it, which the print alone never did. On 2026-08-31 this file
        // printed `llvmpipe (LLVM 21.1.8, 256 bits) (Vulkan, Cpu)` in a whole-workspace
        // run — the 4090 was saturated by concurrent lanes — and the luma numbers below
        // are meaningless taken on a CPU rasteriser. A printed adapter is a line a human
        // has to notice; an asserted one is a line the suite notices.
        facett_core::render::gpu::refuse_software_adapter("postfx_bypass", &info);
    }
    facett_map3d::gpu::install_renderer(&rs);
    let probe = rs.clone();
    let mut view = scene(plan_2d);
    let mut harness = Harness::builder()
        .with_size(egui::vec2(480.0, 480.0))
        .renderer(WgpuTestRenderer::from_render_state(rs))
        .build_ui(move |ui| {
            facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
            view.ui(ui);
        });
    harness.run_steps(4);
    let img = harness.render().expect("wgpu render");
    (img, facett_map3d::gpu::post_passes_recorded(&probe))
}

#[test]
fn a_2d_ortho_view_records_zero_post_passes_and_a_3d_view_records_the_whole_chain() {
    let (flat_img, flat_n) = render(true);
    let (deep_img, deep_n) = render(false);
    image::DynamicImage::ImageRgba8(flat_img.clone()).save("/tmp/map3d_bypass_2d.png").ok();
    image::DynamicImage::ImageRgba8(deep_img.clone()).save("/tmp/map3d_bypass_3d.png").ok();
    eprintln!("EMIT-BYPASS post_passes: plan_2d={flat_n:?} perspective_3d={deep_n:?}");

    // `None` would mean no renderer was installed — vacuous, not a pass of zero.
    let flat_n = flat_n.expect("the 2D frame installed a renderer (Some(0) != None)");
    let deep_n = deep_n.expect("the 3D frame installed a renderer");

    // ── THE 3D CONTROL FIRST. Without it, `0` proves only that nothing ran. ──
    // Shadow depth x3 cascades + G-buffer + SSAO + sky + bright + blur H + blur V +
    // composite = 9 with no water; >= 8 leaves room for the LOD tier to gate one.
    assert!(
        deep_n >= 8,
        "the 3D lane must actually record a post chain ({deep_n} passes) — if it records \
         none, the bypass assertion below is measuring nothing"
    );

    // ── THE BYPASS. ──
    //
    // ⚠ THE EXPECTED VALUE CHANGED ON 2026-08-24, AND THE PRODUCT CHANGED WITH IT.
    // It was `0`, and a test's expectation must never be moved to make a defect green —
    // so read why this one moved. The bypass used to skip the COMPOSITE pass as well as
    // the bloom chain, and the composite is not an effect: it is the exposure + ACES +
    // gamma display transform that turns the `Rgba16Float` offscreen's linear radiance
    // into something a screen can show. Skipping it shipped a plan view about a gamma too
    // dark from `f8772ad1` (2026-08-05) until this commit — the RED the old body of this
    // file documented at length and deliberately left failing, because choosing between
    // its two candidate fixes was a product decision and not a test's to make.
    //
    // The decision taken: run the composite always, gate the three bright/blur passes on
    // `bloom_on` (false for every 2D view via `constrain_to_view`). That is where the
    // saving always was — three fullscreen passes that could not affect a pixel, since
    // `bloom.wgsl` reaches the bloom texture only through `bloom * U.params.z` and
    // `write_uniforms` zeroes `params.z` when bloom is off. So GFX_V2 item 7's claim is
    // restated rather than abandoned: 2D costs nothing extra for sky, AO, shadows,
    // reflection and glow — it still costs the ONE pass that makes a picture.
    //
    // ⚠ AND IT CHANGED AGAIN ON 2026-08-25, FROM 1 TO 3, WHEN TAA WAS WIRED.
    // Same rule applies: read why before believing it. The two added passes are the
    // ANTIALIASING lane — a depth-reprojection pass that writes motion vectors, and the
    // temporal resolve — which replaced FXAA. FXAA cost this counter nothing only
    // because it rode inside egui's own render pass at present time; it was never free,
    // it was merely uncounted. Two counted passes in place of one uncounted one is the
    // honest number, and it is asserted here rather than hidden by exempting the AA lane
    // from `begin_post_pass`.
    //
    // **The 2D-ortho view runs the temporal lane deliberately** (see the note at the AA
    // block in `PostStack::record`): the plan view's long straight streets are the worst
    // aliasing in the product, the last cure on this seam was the deletion of exactly
    // such a `flat_2d` present fork, and a depth reprojection is not a perspective-only
    // trick — an ortho VP inverts and reprojects the same way. So GFX_V2 item 7's claim
    // is restated once more: 2D costs nothing extra for sky, AO, shadows, reflection and
    // glow; it costs the composite that makes radiance into a picture, plus the two
    // passes that antialias it.
    //
    // `3`, not `<= 3`: an exact count is what keeps a future effect from creeping back
    // into the 2D path unnoticed.
    assert_eq!(
        flat_n, 3,
        "a 2D-ortho view must record EXACTLY THREE screen-space post passes — the \
         composite (exposure + ACES + gamma) and the two-pass TAA lane (velocity + \
         resolve), and nothing else; got {flat_n}"
    );

    // ── AND THE VIEW REALLY WAS 2D / REALLY WAS NOT. ──
    // Asserting the count without this would pass on a 'plan mode' that quietly stayed
    // perspective and simply broke the post stack.
    let flat_view = scene(true).cam.view();
    let deep_view = scene(false).cam.view();
    assert!(flat_view.is_constrained_2d(), "plan mode yields a constrained 2D view");
    assert!(matches!(flat_view.projection, Projection::Ortho { .. }), "…orthographic");
    assert_eq!(flat_view.tilt, 0.0, "…exactly top-down, not merely close");
    assert_eq!(flat_view.azimuth, 0.0, "…and unrotated");
    assert!(!deep_view.is_constrained_2d(), "the 3D control is NOT constrained");

    let asked = SceneFlags { sky: true, ssao: true, bloom: true, shadow_strength: 0.7, ..Default::default() };
    let flat_flags = asked.constrain_to_view(&flat_view);
    let deep_flags = asked.constrain_to_view(&deep_view);
    assert!(flat_flags.flat_2d && !flat_flags.any_post(), "the 2D view wants no post EFFECT");
    assert!(!flat_flags.bloom, "…bloom included — which is what lets `record` skip the glow chain");
    assert!(!deep_flags.flat_2d && deep_flags.any_post(), "the 3D view wants them all");

    // ── THE PIXELS. A pass count is bookkeeping; these are the applied output. ──
    //
    // MY FIRST TWO ATTEMPTS HERE WERE HOLLOW, and a mutation caught them. Removing the
    // present-path half of the bypass — so the 2D frame blits an LDR target that
    // `ensure` was never called for, and the callback paints NOTHING — left both a
    // "lit pixel count > 5% of the frame" check and the `moved` difference check GREEN.
    // Of course it did: the egui widget background is itself lit and coloured, and a
    // BLANK pane differs from the 3D frame even more than a correct 2D one does. Both
    // metrics measured the frame rather than the MAP.
    //
    // Luma spread inside the pane is the metric that discriminates. Whatever the theme
    // paints when the 3D callback contributes nothing is close to uniform; a rendered map
    // is not. MEASURED, RTX 4090: 14.70 with the bypass complete, 2.61 with the present
    // path unwired, against the 3D control's 52.62. The bound sits at 8 — a 5.6x
    // separation either side, not a knife edge.
    //
    // A plan view scoring far below the 3D control is expected, not a defect: flat roofs
    // seen straight down under uniform light, with no sky, bloom, AO or cast shadows, are
    // genuinely low-contrast. That is what the bypass is FOR.
    let spread = |img: &image::RgbaImage| -> f64 {
        let inset = 24u32;
        let mut v = Vec::new();
        for y in inset..img.height() - inset {
            for x in inset..img.width() - inset {
                let p = img.get_pixel(x, y);
                v.push(0.2126 * f64::from(p.0[0]) + 0.7152 * f64::from(p.0[1]) + 0.0722 * f64::from(p.0[2]));
            }
        }
        let m = v.iter().sum::<f64>() / v.len() as f64;
        (v.iter().map(|l| (l - m) * (l - m)).sum::<f64>() / v.len() as f64).sqrt()
    };
    let (flat_spread, deep_spread) = (spread(&flat_img), spread(&deep_img));
    eprintln!("EMIT-BYPASS luma spread: plan_2d={flat_spread:.2} perspective_3d={deep_spread:.2}");
    // ⚠ STILL RED, AND THE REMAINING CAUSE IS NO LONGER THE POST STACK.
    //
    // History, so the number is readable. This bound was 14.70-calibrated, went red on
    // 2026-08-05, and was bisected (2026-08-22, lock held, identical on lavapipe and the
    // RTX 4090) to `f8772ad1` — the commit that correctly made the vertex stream carry
    // LINEAR albedo instead of sRGB code values. From then the offscreen held linear
    // radiance and the flat_2d branch of `paint` blitted it raw: no exposure, no ACES.
    // MEASURED then: 3.30, against a 3D control of 70.00.
    //
    // 2026-08-24, oden / RTX 4090 / Vulkan — the bypass's composite half is FIXED (see
    // the `flat_n == 1` block above; the composite always runs now, and the 2D lane gets
    // FXAA with it). Re-measured: **3.30 → 6.62**. The display transform is back.
    //
    // WHAT IS LEFT IS A SHADING DEFECT, NOT A POST-FX ONE, and the probe that separates
    // them is worth keeping because it cost the guessing. With `constrain_to_view`'s
    // clears REMOVED — sky, SSAO, soft shadows, shadow_strength and planar reflection all
    // forced ON for the plan view, post_passes 1 → 7 — the roofs moved by THREE CHANNEL
    // VALUES, from srgba(27,28,41) to srgba(24,25,37). So every effect flag is innocent:
    //
    //   plan roof   srgba( 27, 28, 41)      plan bg   srgba(11,22,47)
    //   3D box top  srgba( 91, 98,115)      3D ground srgba(183,186,193)
    //
    // The same up-facing surface, the same sun, the same shader, 3.4x apart — and the
    // plan frame has NO GROUND PLANE at all (13 visible tris against the 3D control's
    // 62), which is most of why it reads as dark. Diffuse shading is view-independent, so
    // an ortho top-down camera cannot legitimately darken a roof; the suspicion is the
    // ortho depth range clipping the ground (and possibly the roofs' lighting inputs)
    // rather than anything in `gpu.rs`. NOT chased here, and NOT laundered by lowering
    // this bound to 6: a bound moved to fit a measurement stops being a guard.
    assert!(
        flat_spread > 8.0,
        "the plan frame is tonemapped now ({flat_spread:.2}, up from 3.30) but still reads \
         DARK against the 14.70 this bound was calibrated at, and the post stack is no longer \
         the reason: forcing every effect back on moves the roofs by 3/255. The plan view \
         draws no ground and shades its roofs 3.4x darker than the identical 3D surface — a \
         shading/clipping defect. See the comment above and GFX_V2 §V2.11"
    );
    assert!(deep_spread > 25.0, "…and so must the 3D control ({deep_spread:.2})");
    // ...and the two frames must differ, which is what makes 'the chain was skipped' a
    // statement about this render rather than about a flag.
    let diff = facett_core::golden::moved(&deep_img, &flat_img, facett_core::golden::MinChange::PANE)
        .expect("the 2D and 3D frames must differ across the pane");
    eprintln!(
        "EMIT-BYPASS pixels differ: {:.2}% max_channel_delta={} bbox={:?}",
        diff.percent(),
        diff.max_channel_delta,
        diff.bbox
    );

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "postfx_bypass_is_zero_passes_in_2d",
        flat_n == 0 && deep_n >= 8,
        &format!("blob=/tmp/map3d_bypass_2d.png plan_2d_passes={flat_n} perspective_passes={deep_n}"),
    );
}

/// **The pole is safe** — the evidence item 6 said would be needed before anyone relaxes
/// `ELEV_LIMIT`, recorded here even though this branch deliberately did NOT relax it.
///
/// Item 6 found the OLD look-at basis did not NaN at exact top-down, it silently
/// MIRRORED east and west, because `right` came out of a cross product whose magnitude
/// was the rounding residue of `cos(PI/2)`. The closed-form frame has no such term. So
/// at `tilt == 0` the basis must be exactly orthonormal AND east must still be east.
#[test]
fn the_closed_form_basis_is_well_conditioned_at_the_pole_and_does_not_mirror() {
    let v = View::ortho_2d_map([0.0, 0.0], [1.0, 1.0]);
    assert_eq!(v.tilt, 0.0, "exactly at the pole, which is the whole point");
    let (fwd, right, up) = v.basis();

    for (n, a) in [("fwd", fwd), ("right", right), ("up", up)] {
        assert!(a.is_finite(), "{n} is finite at the pole ({a:?})");
        assert!((a.length() - 1.0).abs() < 1e-6, "{n} is unit ({}, {a:?})", a.length());
    }
    for (n, a, b) in [("fwd·right", fwd, right), ("fwd·up", fwd, up), ("right·up", right, up)] {
        assert!(a.dot(b).abs() < 1e-6, "{n} is orthogonal ({})", a.dot(b));
    }
    // THE MIRROR TEST. X is east; at azimuth 0 the screen's `right` must point east,
    // with a POSITIVE x. The old look-at's sign here was rounding noise, so a mirrored
    // frame drew a map that was correct in every other respect and reversed in x.
    assert!(right.x > 0.9, "east stays on the right at the pole (right.x = {})", right.x);
    assert!(fwd.y < -0.9, "and the camera looks DOWN (fwd.y = {})", fwd.y);
    // Screen-up points north. Z is south in this frame, so north is -Z.
    assert!(up.z < -0.9, "north is up at the pole (up.z = {})", up.z);

    // Continuity: approaching the pole must not flip anything. A basis that mirrored
    // AT the pole would show up as `right.x` changing sign across it.
    for eps in [1e-3_f32, 1e-4, 1e-5, 0.0] {
        // `basis()` reads only `tilt` and `azimuth`, so the projection is irrelevant here.
        let mut t = View::ortho_2d_map([0.0, 0.0], [1.0, 1.0]);
        t.tilt = eps;
        let (_, r, _) = t.basis();
        assert!(r.x > 0.9, "right.x stays positive at tilt {eps} ({})", r.x);
    }
}