Commit:
9aa5c3cParent:
00780b4Add a render-path benchmark example
cargo run -p render --release --example bench reports pipeline and render-only frame times at the dev rig's full size (256x212 depth, ~59k splats, 640x480 target), so numbers from a real GPU can be compared directly against the software-Vulkan (lavapipe) container.
README.md
+2
-0
diff --git a/README.md b/README.md
index 852cba6..5d796cd 100644
@@ -51,6 +51,8 @@ recordings can be converted into that shape; synthetic ones are
generated for dev/CI.
- `cargo run -p sink` — the viewer (needs a GPU, or lavapipe/Vulkan)
- `cargo run -p render --release --example bench` — pipeline/render
frame times (`vulkaninfo --summary` shows which adapter is in use)
- `cargo test --workspace` — unit, GPU-integration, and headless UI tests
## Configuration
render/Cargo.toml
+4
-1
diff --git a/render/Cargo.toml b/render/Cargo.toml
index 138da11..a2b97e9 100644
@@ -14,7 +14,10 @@ glam.workspace = true
pollster.workspace = true
rig.workspace = true
#[dev-dependencies]
[dev-dependencies]
replay.workspace = true
splatgen.workspace = true
tokio = { workspace = true }
[[example]]
name = "bench"
render/examples/bench.rs
+82
-0
diff --git a/render/examples/bench.rs b/render/examples/bench.rs
new file mode 100644
index 0000000..fe576f5
@@ -0,0 +1,82 @@
//! 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(())
}