feat: add performance benchmarks for composite buffer pooling and effects pipeline skipping
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
//! Criterion benchmark: `composite_scratch` buffer pooling guard.
|
||||
//!
|
||||
//! ## Purpose
|
||||
//! Verifies that the ~33 MB `composite_scratch` buffer inside
|
||||
//! `render_composite_region()` is allocated once and reused across calls.
|
||||
//! If a regression replaces the pooled `Option<Vec<u8>>` with a fresh
|
||||
//! `Vec::new()` on every call, this benchmark detects the allocation storm.
|
||||
//!
|
||||
//! ## Logic & Workflow
|
||||
//! 1. Creates a 3840×2160, 10-layer document with all layers filled.
|
||||
//! 2. Performs a stroke + composite to warm the scratch buffer.
|
||||
//! 3. **cold_composite**: Measures a `render_composite_region()` call on a
|
||||
//! fresh engine where `composite_scratch` is `None`.
|
||||
//! 4. **warm_composite**: Measures the same call after the buffer has been
|
||||
//! allocated (should be faster — no allocation, just memset + composite).
|
||||
//!
|
||||
//! ## Arguments & Returns
|
||||
//! Standard criterion benchmark — run with:
|
||||
//! ```bash
|
||||
//! cargo bench -p hcie-engine-api --bench composite_scratch_pooling
|
||||
//! ```
|
||||
//!
|
||||
//! ## Side Effects / Dependencies
|
||||
//! Allocates ~400 MB (10 layers × 4K RGBA + scratch + tile caches).
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use hcie_engine_api::{BrushStyle, BrushTip, Engine};
|
||||
|
||||
/// Canvas dimensions — 4K UHD.
|
||||
const W: u32 = 3840;
|
||||
const H: u32 = 2160;
|
||||
/// Number of raster layers to create (worst-case workload).
|
||||
const LAYERS: usize = 10;
|
||||
|
||||
/// Builds a multi-layer 4K document ready for compositing.
|
||||
///
|
||||
/// **Purpose:** Creates the worst-case composite scenario with every layer
|
||||
/// filled so tile and pixel compositing operate on real data.
|
||||
/// **Returns:** `(Engine, top_layer_id)`.
|
||||
fn setup_multilayer_engine() -> (Engine, u64) {
|
||||
let mut engine = Engine::new(W, H);
|
||||
|
||||
let bg_id = engine.active_layer_id();
|
||||
engine.set_active_layer(bg_id);
|
||||
engine.draw_filled_rect_rgba(0, 0, W, H, [240, 240, 240, 255]);
|
||||
|
||||
let mut layer_ids = vec![bg_id];
|
||||
for i in 1..LAYERS {
|
||||
let id = engine.add_layer(&format!("Layer {}", i));
|
||||
engine.set_active_layer(id);
|
||||
let color = match i % 4 {
|
||||
0 => [180, 80, 80, 255],
|
||||
1 => [80, 180, 80, 255],
|
||||
2 => [80, 80, 180, 255],
|
||||
_ => [160, 140, 100, 255],
|
||||
};
|
||||
engine.draw_filled_rect_rgba(0, 0, W, H, color);
|
||||
layer_ids.push(id);
|
||||
}
|
||||
|
||||
let top_id = *layer_ids.last().unwrap();
|
||||
engine.set_active_layer(top_id);
|
||||
|
||||
let mut tip = BrushTip::default();
|
||||
tip.style = BrushStyle::Round;
|
||||
tip.size = 24.0;
|
||||
tip.opacity = 1.0;
|
||||
tip.hardness = 0.85;
|
||||
tip.spacing = 0.1;
|
||||
engine.set_brush_tip(tip);
|
||||
engine.set_color([220, 60, 60, 255]);
|
||||
engine.set_eraser(false);
|
||||
|
||||
(engine, top_id)
|
||||
}
|
||||
|
||||
/// Forces a dirty region and composites it.
|
||||
///
|
||||
/// **Purpose:** Exercises the `render_composite_region()` path including
|
||||
/// scratch buffer allocation/reuse, tile compositing, and dirty rect handling.
|
||||
/// **Arguments:** `engine` — mutable engine, `layer_id` — target layer.
|
||||
/// **Side Effects:** Paints a short stroke segment to create a dirty region,
|
||||
/// then calls `render_composite_region()`.
|
||||
fn stroke_and_composite(engine: &mut Engine, layer_id: u64) {
|
||||
let cx = W as f32 / 2.0;
|
||||
let cy = H as f32 / 2.0;
|
||||
engine.begin_stroke(layer_id, cx, cy);
|
||||
engine.stroke_to(layer_id, cx + 50.0, cy + 50.0, 0.8);
|
||||
let (region, ptr, _size) = engine.render_composite_region();
|
||||
assert!(!ptr.is_null());
|
||||
let _ = region;
|
||||
engine.end_stroke(layer_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
engine.commit_pending_history();
|
||||
}
|
||||
|
||||
/// Benchmark group: cold vs warm `composite_scratch` allocation.
|
||||
///
|
||||
/// **Purpose:** Detects regressions where `composite_scratch` is dropped or
|
||||
/// reallocated on every `render_composite_region()` call. When pooling works,
|
||||
/// the warm benchmark avoids a ~33 MB allocation and should be measurably faster.
|
||||
///
|
||||
/// **Logic & Workflow:**
|
||||
/// - `cold_composite`: Fresh engine, `composite_scratch = None`. First call
|
||||
/// allocates the buffer.
|
||||
/// - `warm_composite`: Engine with pre-allocated scratch buffer. Measures
|
||||
/// pure composite cost without allocation overhead.
|
||||
fn bench_composite_scratch_pooling(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("composite_scratch_pooling");
|
||||
|
||||
// ── Cold: composite on a fresh engine (scratch allocation included) ──
|
||||
group.bench_function("cold_composite", |b| {
|
||||
b.iter_with_setup(
|
||||
|| setup_multilayer_engine(),
|
||||
|(mut engine, layer_id)| {
|
||||
stroke_and_composite(&mut engine, layer_id);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// ── Warm: composite on a pre-warmed engine (scratch already pooled) ──
|
||||
group.bench_function("warm_composite", |b| {
|
||||
let (mut engine, layer_id) = setup_multilayer_engine();
|
||||
// Warm up: allocate the scratch buffer
|
||||
stroke_and_composite(&mut engine, layer_id);
|
||||
|
||||
b.iter(|| {
|
||||
// Paint a new segment and composite — scratch should be reused
|
||||
let cx = W as f32 / 2.0;
|
||||
let cy = H as f32 / 2.0;
|
||||
engine.begin_stroke(layer_id, cx, cy);
|
||||
engine.stroke_to(layer_id, cx + 30.0, cy - 30.0, 0.7);
|
||||
let (region, ptr, _size) = engine.render_composite_region();
|
||||
assert!(!ptr.is_null());
|
||||
let _ = region;
|
||||
engine.end_stroke(layer_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
engine.commit_pending_history();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_composite_scratch_pooling);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user