Files
hcie-rust-v3.05/hcie-engine-api/benches/below_cache_reuse.rs
T
Your Name 1dabd9c31e
mandatory-regression-gate / deterministic-tests (push) Waiting to run
mandatory-regression-gate / protected-performance-path (push) Waiting to run
Optimize stroke cache and benchmark configurations
- 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.
2026-07-25 05:20:12 +03:00

147 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Criterion benchmark: `below_cache` reuse across consecutive strokes.
//!
//! ## Purpose
//! Verifies that painting multiple strokes on the **same layer** reuses the
//! pre-built `below_cache` (composite of all layers below the active one)
//! instead of rebuilding it from scratch on every `begin_stroke()`.
//!
//! On a 4K 10-layer document the below-layer composite costs ~1525 ms.
//! If `below_cache_dirty` is incorrectly set to `true` after every
//! `end_stroke()`, each subsequent stroke pays that cost again.
//!
//! ## Logic & Workflow
//! 1. Creates a 3840×2160, 10-layer document (all filled).
//! 2. **first_stroke**: Benchmarks a full stroke including below_cache build.
//! 3. **second_stroke_same_layer**: Benchmarks a stroke on the same layer
//! where `below_cache` should be reused (much cheaper `begin_stroke`).
//!
//! When the cache works correctly, the second stroke's `begin_stroke()` is
//! essentially free (just a dirty-flag check), while the first one must
//! composite 9 below-layers into a 33 MB buffer.
//!
//! ## Arguments & Returns
//! Standard criterion benchmark — run with:
//! ```bash
//! cargo bench -p hcie-engine-api --bench below_cache_reuse
//! ```
//!
//! ## Side Effects / Dependencies
//! Allocates ~400 MB (10 layers × 4K RGBA + below_cache + composite_scratch).
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.
const LAYERS: usize = 10;
/// Builds a 10-layer 4K document. The top layer is the active drawing target.
///
/// **Purpose:** Creates a worst-case scenario where the below-layer composite
/// is expensive (9 filled layers below the active one).
/// **Returns:** `(Engine, top_layer_id)`.
fn setup_10layer_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)
}
/// Performs a short stroke: begin → 5 segments → composite → end.
///
/// **Purpose:** Exercises the full `begin_stroke()` (below_cache build/reuse)
/// through `end_stroke()` lifecycle.
/// **Arguments:** `engine` — mutable engine, `layer_id` — target layer,
/// `offset` — slight position offset to avoid degenerate repeat painting.
/// **Side Effects:** Mutates layer pixels, triggers composite, commits history.
fn do_stroke(engine: &mut Engine, layer_id: u64, offset: f32) {
let cx = W as f32 / 2.0 + offset;
let cy = H as f32 / 2.0 + offset;
engine.begin_stroke(layer_id, cx, cy);
for i in 1..=5 {
let d = i as f32 * 8.0;
engine.stroke_to(layer_id, cx + d, cy + d, 0.8);
}
let (_region, ptr, _size) = engine.render_composite_region();
assert!(!ptr.is_null());
engine.end_stroke(layer_id);
std::thread::sleep(std::time::Duration::from_millis(10));
engine.commit_pending_history();
}
/// Benchmark group: below_cache build (first stroke) vs reuse (second stroke).
///
/// **Purpose:** Detects regressions where `below_cache_dirty` is incorrectly
/// set to `true` in `end_stroke()` or where `below_cache` is dropped to `None`,
/// forcing a full 9-layer re-composite on every stroke.
///
/// **Logic & Workflow:**
/// - `first_stroke_cache_build`: On a fresh engine, `begin_stroke()` must
/// build the below_cache from scratch (composite 9 layers → 33 MB buffer).
/// - `second_stroke_cache_reuse`: After the first stroke on the same layer,
/// `begin_stroke()` should find `below_cache_dirty == false` and
/// `below_cache_active_idx == current`, skipping the entire composite.
fn bench_below_cache_reuse(c: &mut Criterion) {
let mut group = c.benchmark_group("below_cache_reuse");
// ── First stroke: cache must be built from scratch ──
group.bench_function("first_stroke_cache_build", |b| {
b.iter_with_setup(
|| setup_10layer_engine(),
|(mut engine, layer_id)| {
do_stroke(&mut engine, layer_id, 0.0);
},
);
});
// ── Second stroke on same layer: cache should be reused ──
group.bench_function("second_stroke_cache_reuse", |b| {
let (mut engine, layer_id) = setup_10layer_engine();
// Build the cache with the first stroke
do_stroke(&mut engine, layer_id, 0.0);
let mut offset = 0.0_f32;
b.iter(|| {
offset += 5.0;
do_stroke(&mut engine, layer_id, offset);
});
});
group.finish();
}
criterion_group!(benches, bench_below_cache_reuse);
criterion_main!(benches);