perf: cache hatch/wipeout/image/mesh models per geometry epoch (F+G+H)

Add Arc<Vec<...>>-backed epoch caches for hatches, wipeouts, images, and
meshes — the same pattern used for wires. build_primitive() now returns
an O(1) Arc refcount bump on navigation frames instead of rebuilding
every collection from scratch.

Also wrap ImageModel.pixels in Arc<Vec<u8>> so cloning an ImageModel is
O(1) regardless of image resolution.

Navigation frames no longer copy pixel data, tessellated hatch boundaries,
or mesh geometry — all per-frame CPU work on unchanged geometry is now
eliminated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-26 11:34:52 +03:00
commit 81db3c37d0
5 changed files with 209 additions and 20 deletions

View file

@ -4,13 +4,15 @@
// from the RasterImage entity's insertion point, u/v vectors, and pixel size.
use std::path::Path;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct ImageModel {
/// Original file path (used for reload / display in properties).
pub file_path: String,
/// RGBA8 pixel data in row-major order.
pub pixels: Vec<u8>,
/// RGBA8 pixel data in row-major order. Arc-wrapped so cloning ImageModel
/// is O(1) — the pixel bytes are shared, not copied.
pub pixels: Arc<Vec<u8>>,
pub width: u32,
pub height: u32,
/// Opacity: 1.0 = opaque, 0.0 = transparent.
@ -47,7 +49,7 @@ impl ImageModel {
let opacity = 1.0 - img.fade as f32 / 100.0;
let (pixels, width, height) = load_pixels(&img.file_path)?;
Some(Self { file_path: img.file_path.clone(), pixels, width, height, opacity, corners })
Some(Self { file_path: img.file_path.clone(), pixels: Arc::new(pixels), width, height, opacity, corners })
}
}