📄 render/examples/bench.rs
//! Render-path benchmark: frames per second through the real pipeline.
//!
//! Run it on your machine and compare against the software-Vulkan
//! (lavapipe) numbers from the dev container:
//!
//! ```text
//! cargo run -p render --release --example bench
//! vulkaninfo --summary   # which adapter you're actually on
//! ```
//!
//! Two numbers are reported per pass:
//!
//! * **pipeline** — the full per-frame cost as the viewer runs it:
//!   synthetic frame → splatgen (preprocess + surfels + proxy) → render;
//! * **render only** — re-rendering the same batch (the steering path:
//!   moving the virtual camera without advancing the frame).

use std::time::Instant;

use rig::DepthSource;

fn main() -> anyhow::Result<()> {
    // The development rig at full size: 256x212 depth (~54k splats
    // plus the proxy), rendered at webcam resolution.
    let rig = replay::synth::dev_rig();
    let virtual_camera = rig.virtual_camera(0.5, rig.left.intrinsics, rig.left.resolution);
    let renderer = render::SplatRenderer::default();
    let generator = splatgen::SplatGen::default();

    let mut raw = replay::synth::dev_source_with(&rig, replay::synth::SUBJECT, 30.0);
    let warmup = 5;
    let frames = 30;

    // Warm up: JIT shader compilation, allocator caches, the first
    // frames through the whole path.
    for _ in 0..warmup {
        let frame = raw.next_depth_frame()?;
        let batch = generator.generate(&frame, &rig);
        renderer.render(&batch, &virtual_camera)?;
    }

    // Full pipeline, per frame.
    let mut pipeline_ms = Vec::with_capacity(frames);
    let mut splats = 0;
    for _ in 0..frames {
        let start = Instant::now();
        let frame = raw.next_depth_frame()?;
        let batch = generator.generate(&frame, &rig);
        let (image, _) = renderer.render(&batch, &virtual_camera)?;
        pipeline_ms.push(start.elapsed().as_secs_f32() * 1e3);
        splats = batch.len();
        drop(image);
    }

    // Render only: the same batch, re-rendered (camera steering).
    let frame = raw.next_depth_frame()?;
    let batch = generator.generate(&frame, &rig);
    let mut render_ms = Vec::with_capacity(frames);
    for _ in 0..frames {
        let start = Instant::now();
        let (image, _) = renderer.render(&batch, &virtual_camera)?;
        render_ms.push(start.elapsed().as_secs_f32() * 1e3);
        drop(image);
    }

    let stats = |name: &str, times: &mut Vec<f32>| {
        times.sort_by(|a, b| a.partial_cmp(b).unwrap());
        let median = times[times.len() / 2];
        let mean = times.iter().sum::<f32>() / times.len() as f32;
        println!(
            "{name}: median {median:6.1} ms/frame ({:5.1} fps), mean {mean:6.1} ms",
            1000.0 / median,
        );
    };
    println!(
        "kinect-view bench: {} splats, {}x{} render target",
        splats, virtual_camera.resolution.x, virtual_camera.resolution.y,
    );
    stats("pipeline ", &mut pipeline_ms);
    stats("render   ", &mut render_ms);
    Ok(())
}