Files
hcie-rust-v3.05/hcie-engine-api/benches/effects_skip.rs
T
Your Name 6b02f19624
mandatory-regression-gate / deterministic-tests (push) Has been cancelled
mandatory-regression-gate / protected-performance-path (push) Has been cancelled
feat: add performance benchmarks for composite buffer pooling and effects pipeline skipping
2026-07-24 17:29:33 +03:00

167 lines
6.0 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: conditional `effects_dirty` skip guard.
//!
//! ## Purpose
//! Verifies that `render_composite_region()` / `apply_effects_and_sync_tiles()`
//! skips the expensive effects pipeline for layers that have **no effects or
//! styles**. If a regression removes the `effects.is_empty() && styles.is_empty()`
//! early-exit or unconditionally sets `effects_dirty = true`, composite cost
//! on effect-free documents will increase dramatically.
//!
//! ## Logic & Workflow
//! 1. Creates a 3840×2160, 10-layer document (all filled, no effects).
//! 2. **no_effects**: Benchmarks `render_composite_region()` where every layer
//! has empty `effects` and `styles` vectors → the effects pipeline is
//! completely skipped.
//! 3. **with_one_effect**: Same document but one layer has a DropShadow style
//! → the effects pipeline runs for that single layer.
//!
//! When the conditional skip works, `no_effects` should be measurably faster
//! than `with_one_effect` because no `apply_layer_effects` calls are made.
//!
//! ## Arguments & Returns
//! Standard criterion benchmark — run with:
//! ```bash
//! cargo bench -p hcie-engine-api --bench effects_skip
//! ```
//!
//! ## Side Effects / Dependencies
//! Allocates ~400 MB (10 layers × 4K RGBA + composite buffers).
use criterion::{criterion_group, criterion_main, Criterion};
use hcie_engine_api::{BrushStyle, BrushTip, Engine, LayerStyle};
/// 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 with NO effects on any layer.
///
/// **Purpose:** Creates the baseline scenario where the effects pipeline
/// should be completely skipped during compositing.
/// **Returns:** `(Engine, top_layer_id, all_layer_ids)`.
fn setup_no_effects_engine() -> (Engine, u64, Vec<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, layer_ids)
}
/// Performs a stroke + composite cycle and returns.
///
/// **Purpose:** Creates a dirty region and composites it, exercising the
/// effects pipeline path.
/// **Arguments:** `engine` — mutable engine, `layer_id` — target layer,
/// `offset` — position variation.
/// **Side Effects:** Mutates layer pixels, triggers composite.
fn stroke_and_composite(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: effects-free composite vs single-effect composite.
///
/// **Purpose:** Detects regressions where the effects pipeline runs
/// unnecessarily on layers with no effects, causing wasted CPU cycles
/// (apply_layer_effects + 33 MB buffer clone per affected layer).
///
/// **Logic & Workflow:**
/// - `no_effects_composite`: All 10 layers have empty effects/styles.
/// `apply_effects_and_sync_tiles()` should skip the effects pass entirely.
/// - `with_one_dropshadow`: Layer 5 has a DropShadow style enabled.
/// The effects pipeline runs for that single layer but skips the other 9.
/// This should be slightly slower than `no_effects_composite` but not 10×
/// slower (which would indicate the skip logic is broken).
fn bench_effects_skip(c: &mut Criterion) {
let mut group = c.benchmark_group("effects_skip");
// ── No effects on any layer ──
group.bench_function("no_effects_composite", |b| {
let (mut engine, layer_id, _ids) = setup_no_effects_engine();
// Warm up composite scratch
stroke_and_composite(&mut engine, layer_id, 0.0);
let mut offset = 0.0_f32;
b.iter(|| {
offset += 3.0;
stroke_and_composite(&mut engine, layer_id, offset);
});
});
// ── One layer has a DropShadow effect ──
group.bench_function("with_one_dropshadow", |b| {
let (mut engine, layer_id, layer_ids) = setup_no_effects_engine();
// Add a DropShadow style to layer 5 (middle of the stack)
let target_layer_id = layer_ids[5];
engine.update_layer_style(
target_layer_id,
LayerStyle::DropShadow {
enabled: true,
opacity: 0.6,
angle: 135.0,
distance: 5.0,
spread: 0.0,
size: 10.0,
color: [0, 0, 0, 255],
blend_mode: hcie_engine_api::BlendMode::Normal,
},
);
// Warm up
stroke_and_composite(&mut engine, layer_id, 0.0);
let mut offset = 0.0_f32;
b.iter(|| {
offset += 3.0;
stroke_and_composite(&mut engine, layer_id, offset);
});
});
group.finish();
}
criterion_group!(benches, bench_effects_skip);
criterion_main!(benches);