1dabd9c31e
- Updated stroke cache to use parallel processing for pixel checks. - Modified benchmark commands in notlar.txt to ensure consistent output and baseline comparisons. - Created a plan for merging criterion directories and optimizing benchmark performance, including pre-allocation of buffers and various optimization strategies. - Removed redundant output directory settings in benchmark files and ensured all benchmarks write to a single criterion directory. - Updated documentation to reflect changes in benchmark paths and configurations.
147 lines
5.5 KiB
Rust
147 lines
5.5 KiB
Rust
//! 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);
|