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