//! 4K multi-layer brush-stroke performance benchmark. //! //! ## Purpose //! Measures the per-segment cost of painting on a large (3840x2160), multi-layer //! document using the public `Engine` API. This benchmark is intentionally //! **non-gating** — it only prints timing information so engineers can compare //! before/after optimizations manually. It must never break the deterministic //! golden visual regression suite. //! //! ## Logic & Workflow //! 1. Creates a 3840x2160 document. //! 2. Fills 10 raster layers with semi-random opaque color so that every layer //! contributes to the composite (worst-case workload for the painter). //! 3. Selects the top layer and begins a brush stroke. //! 4. Issues 100 `stroke_to` calls across the canvas, calling //! `render_composite_region` after every segment to mimic the GUI render loop. //! 5. Ends the stroke and prints the average time per segment, per composite, //! and the total wall-clock time. //! //! ## Arguments & Returns //! This is a standard Rust test: `cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture`. //! It returns nothing and asserts only that the engine remains in a valid state. //! //! ## Side Effects / Dependencies //! - Allocates ~400MB of pixel/tile/mask buffers. //! - Uses `std::time::Instant` for measurement; results depend on the host CPU. use hcie_engine_api::{BrushStyle, BrushTip, Engine}; const W: u32 = 3840; const H: u32 = 2160; const LAYERS: usize = 10; const SEGMENTS: usize = 100; const COMPOSITE_EVERY_N_SEGMENTS: usize = 1; fn make_solid_color(r: u8, g: u8, b: u8) -> [u8; 4] { [r, g, b, 255] } #[test] fn benchmark_4k_stroke_on_multilayer_document() { let mut engine = Engine::new(W, H); // Fill the default background layer. let bg_id = engine.active_layer_id(); engine.set_active_layer(bg_id); engine.draw_filled_rect_rgba(0, 0, W, H, make_solid_color(240, 240, 240)); let mut layer_ids = vec![bg_id]; // Create and fill additional raster layers so every layer contributes. for i in 1..LAYERS { let id = engine.add_layer(&format!("Layer {}", i)); engine.set_active_layer(id); let color = match i % 4 { 0 => make_solid_color(180, 80, 80), 1 => make_solid_color(80, 180, 80), 2 => make_solid_color(80, 80, 180), _ => make_solid_color(160, 140, 100), }; engine.draw_filled_rect_rgba(0, 0, W, H, color); layer_ids.push(id); } // Switch to the top layer for painting. let top_id = *layer_ids.last().expect("at least one layer"); 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); // Start the stroke roughly in the center. let start_x = W as f32 / 2.0; let start_y = H as f32 / 2.0; engine.begin_stroke(top_id, start_x, start_y); let mut segment_times = Vec::with_capacity(SEGMENTS); let mut composite_times = Vec::with_capacity(SEGMENTS); let total_start = std::time::Instant::now(); for i in 0..SEGMENTS { let t = (i as f32) / SEGMENTS as f32; let angle = t * 6.28 * 2.0; let radius = 400.0 * t; let x = start_x + angle.cos() * radius; let y = start_y + angle.sin() * radius; let seg_start = std::time::Instant::now(); engine.stroke_to(top_id, x, y, 0.8); segment_times.push(seg_start.elapsed().as_micros() as f64); if (i + 1) % COMPOSITE_EVERY_N_SEGMENTS == 0 { let comp_start = std::time::Instant::now(); let (region, ptr, _size) = engine.render_composite_region(); // Ensure the engine actually produced a buffer pointer; do not // optimize away the call. assert!(!ptr.is_null()); let comp_us = comp_start.elapsed().as_micros() as f64; composite_times.push(comp_us); // Keep the region alive so the compiler doesn't discard it. let _ = region; } } engine.end_stroke(top_id); let total_us = total_start.elapsed().as_micros() as f64; let avg_segment_us = segment_times.iter().sum::() / segment_times.len().max(1) as f64; let avg_composite_us = composite_times.iter().sum::() / composite_times.len().max(1) as f64; let max_segment_us = segment_times.iter().copied().fold(0.0, f64::max); let max_composite_us = composite_times.iter().copied().fold(0.0, f64::max); println!("\n=== 4K Multi-Layer Stroke Benchmark ==="); println!( "Canvas: {}x{}, Layers: {}, Segments: {}", W, H, LAYERS, SEGMENTS ); println!("Average stroke_to: {:>8.1} us", avg_segment_us); println!("Max stroke_to: {:>8.1} us", max_segment_us); println!("Average composite: {:>8.1} us", avg_composite_us); println!("Max composite: {:>8.1} us", max_composite_us); println!( "Total wall time: {:>8.1} us ({:.2} s)", total_us, total_us / 1_000_000.0 ); println!( "Segments per second: {:>8.1}", SEGMENTS as f64 / (total_us / 1_000_000.0) ); // Soft sanity check: the engine must still report a valid top layer and the // document must be marked modified after painting. assert_eq!(engine.active_layer_id(), top_id); assert!(engine.is_modified()); }