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

//! **perf/v2.3 — the GPU tail of korp's mousewheel→GPU TRACE line, 3D lane.**
//!
//! Two observables, read through the ONE stats seam
//! (`facett_core::render::lane::clock_stats_of` /
//! `facett_map3d::gpu::lane_clock_stats`):
//!
//! * **Upload wall clock** — `Map3dRenderer::upload` notes `{last_upload_us,
//!   last_upload_bytes, uploads_total}` per replace-on-change event. The byte count
//!   is asserted with EQUALITY against the payload fed (mesh vertices + water grid +
//!   water indices; LAW 2 — forced red recorded below), and the generation gate is
//!   asserted the other way round: re-sending an unchanged payload is NOT an upload
//!   event.
//! * **In-GPU pass time** — the TIMESTAMP_QUERY bracket `Map3dPaintCallback::prepare`
//!   wraps around everything it records (shadow → G-buffer/SSAO → reflection → sky →
//!   main → water → post), driven here through the REAL egui path: a kittest wgpu
//!   harness with the renderer installed, exactly `gpu_depth`'s posture. The
//!   kittest-default device requests NO features, which is the natural
//!   forced-unavailable arm (`gpu_timestamps = Some(false)`, `last_gpu_pass_us =
//!   None`, never `0`); a second render state requests the features where the
//!   adapter has them and a sample must land `> 0` within a few frames.
//!
//! Adapters are printed for every arm — a number measured on llvmpipe must say so.
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test gpu_clock -- --nocapture`

#![cfg(feature = "wgpu")]

use std::sync::Mutex;

use egui_kittest::wgpu::{create_render_state, default_wgpu_setup, WgpuTestRenderer};
use egui_kittest::Harness;
use facett_core::render::gpu::PASS_CLOCK_FEATURES;
use facett_core::Facet as _;
use facett_map::Way;
use facett_map3d::extrude::{Mesh3, WaterMesh};
use facett_map3d::gpu::{
    build_gpu_geometry, build_gpu_geometry_with_water, lane_clock_stats, lane_name,
    proof_wgpu_setup, Map3dRenderer, Vertex,
};
use facett_map3d::{Map3D, ViewPreset};

/// Both tests write the same process-global lane record; serialised so one test's
/// uploads cannot smear the other's byte-count deltas.
static LANE_LOCK: Mutex<()> = Mutex::new(());

/// One render state, adapter printed (the tree's convention — a number measured on a
/// CPU rasteriser and reported as a GPU number is exactly the kind of claim this
/// suite keeps having to retract).
fn render_state(setup: egui_wgpu::WgpuSetup) -> egui_wgpu::RenderState {
    let rs = create_render_state(setup, Default::default());
    let info = rs.adapter.get_info();
    println!("  adapter: {} [{:?}/{:?}]", info.name, info.device_type, info.backend);
    rs
}

/// A one-quad mesh whose bytes are decided by `seed` (the `gpu_geometry_swap`
/// fixture shape — small on purpose; the byte accounting is exact regardless of
/// size).
fn mesh(seed: f32) -> Mesh3 {
    let mut m = Mesh3::default();
    let up = [0.0, 1.0, 0.0];
    for p in [
        [-0.5 + seed, 0.0, -0.5],
        [0.5 + seed, 0.0, -0.5],
        [0.5 + seed, 0.0, 0.5],
        [-0.5 + seed, 0.0, 0.5],
    ] {
        m.positions.push(p);
        m.normals.push(up);
    }
    m.indices.extend_from_slice(&[0, 1, 2, 0, 2, 3]);
    let c = egui::Color32::from_rgb((seed * 40.0) as u8 + 10, 80, 200);
    m.colors.push(c);
    m.colors.push(c);
    m
}

/// The exact bytes `upload` hands the device: the expanded mesh vertices, plus the
/// water grid's positions (3×f32 each — `WaterVertex` is position-only) and u32
/// indices. Uniform/target allocations are capacity, not payload.
fn payload_bytes(geo: &facett_map3d::gpu::GpuGeometry) -> u64 {
    (geo.vertices.len() * std::mem::size_of::<Vertex>()
        + geo.water_positions.len() * std::mem::size_of::<[f32; 3]>()
        + geo.water_indices.len() * std::mem::size_of::<u32>()) as u64
}

/// **Upload events carry wall-time and the EXACT payload byte count — and the
/// generation gate keeps a re-sent payload from counting as one.**
///
/// Forced red (LAW 2), 2026-08-12, on the kittest default adapter: with the
/// `uploaded_bytes += (water verts + indices)` line removed from
/// `Map3dRenderer::upload` the equality failed verbatim:
///
/// ```text
/// assertion `left == right` failed: the upload event's byte count equals the payload fed
///   left: 240
///  right: 312
/// ```
///
/// i.e. the accounting silently dropped the water grid — the class of hole the
/// equality (not `> 0`) assertion exists to catch. Restored, both sides read 312.
#[test]
fn an_upload_event_notes_wall_time_and_the_exact_bytes_fed() {
    let _g = LANE_LOCK.lock().unwrap();
    let rs = render_state(proof_wgpu_setup().unwrap_or_else(default_wgpu_setup));
    let mut r = Map3dRenderer::new(&rs.device, wgpu::TextureFormat::Rgba8UnormSrgb);

    let water = WaterMesh {
        positions: vec![[-1.0, 0.0, -1.0], [1.0, 0.0, -1.0], [1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]],
        indices: vec![0, 1, 2, 0, 2, 3],
    };
    let geo = build_gpu_geometry_with_water(&mesh(0.0), &water);
    let expected = payload_bytes(&geo);
    assert!(
        geo.water_positions.len() == 4 && expected > 0,
        "fixture sanity: water must be part of the payload or the accounting proof is partial"
    );

    let before = lane_clock_stats();
    r.upload_for_test(&rs.device, &geo);
    let after = lane_clock_stats();
    assert_eq!(after.uploads_total, before.uploads_total + 1, "one upload event counted");
    assert_eq!(
        after.last_upload_bytes, expected,
        "the upload event's byte count equals the payload fed"
    );
    assert!(after.last_upload_us > 0, "a real upload takes measurable wall time");

    // The generation gate: the SAME stamp re-sent (the every-frame shape) moves no
    // bytes and must not count — `uploads_total` counts re-bakes, not frames.
    r.upload_for_test(&rs.device, &geo);
    assert_eq!(
        lane_clock_stats().uploads_total,
        after.uploads_total,
        "re-sending an unchanged generation is not an upload event"
    );

    // A REBUILT scene is one.
    let geo2 = build_gpu_geometry(&mesh(3.0));
    r.upload_for_test(&rs.device, &geo2);
    let last = lane_clock_stats();
    assert_eq!(last.uploads_total, after.uploads_total + 1, "a new generation counts");
    assert_eq!(last.last_upload_bytes, payload_bytes(&geo2), "and carries ITS byte count");
}

/// The adversarial two-building scene from `gpu_depth`, GPU lane on — the proven
/// posture in which `Map3dPaintCallback::prepare` really runs under kittest.
fn scene() -> Map3D {
    fn building(id: i64, ring: &[(f64, f64)], col: egui::Color32) -> Way {
        let mut r: Vec<(f64, f64)> = ring.to_vec();
        if let Some(&f) = r.first() {
            r.push(f);
        }
        Way::filled(id, "building", r, col, egui::Color32::from_rgb(20, 20, 20), 1.0)
    }
    let far = building(
        1,
        &[(9.50, 47.12), (9.56, 47.12), (9.56, 47.15), (9.50, 47.15)],
        egui::Color32::from_rgb(40, 80, 240),
    );
    let near = building(
        2,
        &[(9.52, 47.10), (9.54, 47.10), (9.54, 47.115), (9.52, 47.115)],
        egui::Color32::from_rgb(240, 50, 40),
    );
    let mut m = Map3D::from_ways("clock", &[near, far], 0.35);
    m.spin = false;
    m.set_clock(0.0);
    m.view_preset(ViewPreset::Front);
    m.cam.goal_elevation = 0.18;
    m.cam.goal_distance = 2.4;
    m.cam.goal_target = facett_map3d::V3::new(0.0, 0.15, 0.0);
    m.cam.settle();
    m.with_gpu(true)
}

/// Render `frames` real egui frames through the installed renderer — each
/// `harness.render()` runs the full egui-wgpu pass, including
/// `Map3dPaintCallback::prepare` (where the clock lives) and the submit that lets
/// the readback ring advance.
fn drive(rs: egui_wgpu::RenderState, frames: usize) {
    facett_map3d::gpu::install_renderer(&rs);
    let mut view = scene();
    let mut harness = Harness::builder()
        .with_size(egui::vec2(420.0, 420.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);
        });
    for _ in 0..frames {
        harness.run_steps(1);
        let _ = harness.render().expect("wgpu render");
    }
}

/// **The two honesty arms of the 3D pass clock, through the real egui path.**
///
/// Forced red (LAW 2), 2026-08-12: with the `clock.begin(...)` call removed from
/// `Map3dPaintCallback::prepare` (probe never runs, timestamps never written) the
/// featureless arm failed verbatim:
///
/// ```text
/// the kittest-default device requests no features, so the probe must record Some(false)
///   left: None
///  right: Some(false)
/// ```
///
/// — the flag stayed `None` ("never probed"), proving the assertion tracks the
/// probe actually running inside `prepare`, not just the enum's default.
#[test]
fn pass_time_is_none_without_the_feature_and_micros_with_it() {
    let _g = LANE_LOCK.lock().unwrap();

    // ── Arm 1: the kittest default — its DeviceDescriptor requests NO features, so
    // even an adapter that has timestamps yields a device that cannot measure. The
    // natural "device requested without the feature" arm.
    println!("  featureless arm:");
    drive(render_state(default_wgpu_setup()), 4);
    assert!(
        facett_core::render::lane::frames_of(lane_name()) > 0,
        "precondition: prepare must have encoded frames, or nothing was measured at all"
    );
    let s = lane_clock_stats();
    assert_eq!(
        s.gpu_timestamps,
        Some(false),
        "the kittest-default device requests no features, so the probe must record Some(false)"
    );
    assert_eq!(s.last_gpu_pass_us, None, "an unmeasurable device reports None, not 0");

    // ── Arm 2: the features requested where the adapter carries them.
    let mut setup = proof_wgpu_setup().unwrap_or_else(default_wgpu_setup);
    if let egui_wgpu::WgpuSetup::CreateNew(create) = &mut setup {
        create.device_descriptor = std::sync::Arc::new(|adapter: &wgpu::Adapter| {
            wgpu::DeviceDescriptor {
                label: Some("map3d_gpu_clock"),
                required_features: adapter.features() & PASS_CLOCK_FEATURES,
                required_limits: wgpu::Limits {
                    max_texture_dimension_2d: 8192,
                    ..wgpu::Limits::default()
                },
                ..Default::default()
            }
        });
    }
    println!("  real-timestamp arm:");
    let rs = render_state(setup);
    if !rs.device.features().contains(PASS_CLOCK_FEATURES) {
        println!(
            "  [honest skip] this adapter does not offer TIMESTAMP_QUERY(+INSIDE_ENCODERS) — \
             the real-timestamp arm cannot run here (the featureless arm above still proved None)"
        );
        return;
    }
    // A fresh renderer instance (fresh probe) against the featured device; up to 12
    // real frames for the non-blocking ring to surface a sample.
    drive(rs, 12);
    let s = lane_clock_stats();
    assert_eq!(s.gpu_timestamps, Some(true), "the probe on a featured device says real");
    let us = s
        .last_gpu_pass_us
        .unwrap_or_else(|| panic!("a pass sample must land within 12 frames: {s:?}"));
    assert!(us > 0, "a resolved sample is strictly positive by construction");
    println!("  3D pass window (shadow→post): {us} µs");
}