From 65eba3887a951287966281caf1693418c2a5e0a7 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 5 Jun 2026 09:23:52 +0300 Subject: [PATCH] perf(scene): memoize Face3D wire split across frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_primitive ran split_face3d_wires every frame — for each wire a string-parse + document handle lookup + clone, to separate Face3D wires from the rest — even when a pan had just reused the tessellation. The split is content-only, so memoize it by the Model-tile wire content id: a pan-reuse (same id) reuses the split, and the common no-overlay frame returns the cached `other` Arc with zero clone. Preview/interim frames clone once to append. Non-tile paths split inline as before. Completes the cheap-pan path: a pure pan now reuses the CPU tessellation, the Face3D split, and the GPU wire upload — only the view_proj uniform and per-frame scissors recompute. Co-Authored-By: Claude Opus 4.8 --- ROADMAP.md | 7 +++++++ src/scene/mod.rs | 8 ++++++++ src/scene/render.rs | 33 ++++++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9e6bb3afc..25686367e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -176,6 +176,13 @@ outright — no re-tessellation, `tess ms` drops to ~0 on the PERF HUD. Zoom exactly as before; the cull margin is unchanged, so miss cost is identical (no zoom regression). +**Partial (landed): per-frame split.** `build_primitive` ran +`split_face3d_wires` every frame — an O(N) per-wire handle lookup + clone to +separate Face3D wires — even on a pan that reused the tessellation. It's now +memoized by the tile's wire content id, and the non-overlay frame reuses the +`other` Arc with no clone at all. So a pan reuses tessellation, the split, and +the GPU upload; only the uniform + scissors update per frame. + **Partial (landed): hover.** `set_hover_highlight` no longer bumps the geometry epoch (a full re-tessellation) when the hovered entity is already selected — the effective highlight set `selected ∪ {hover}` is then unchanged, so the diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 2985bf340..ca905bc85 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -752,6 +752,13 @@ pub struct Scene { /// never collide with a real [`WIRE_CONTENT_GEN`] id; incremented every /// use so the GPU always sees a fresh id and re-uploads. pub(crate) wire_force_nonce: std::cell::Cell, + /// Memoized `(face3d, other)` split of the Model-tile wire set, keyed by + /// its [`WIRE_CONTENT_GEN`] id. `split_face3d_wires` is an O(N) per-wire + /// handle lookup + clone that otherwise re-runs every frame; a pan that + /// reuses the tessellation (same id) reuses this split too. + #[allow(clippy::type_complexity)] + split_cache: + RefCell>, Arc>)>>, } impl Scene { @@ -813,6 +820,7 @@ impl Scene { last_tess_wires: std::cell::Cell::new(0), last_model_wire_gen: std::cell::Cell::new(0), wire_force_nonce: std::cell::Cell::new(0), + split_cache: RefCell::new(None), } } diff --git a/src/scene/render.rs b/src/scene/render.rs index 734594258..ef637b39f 100644 --- a/src/scene/render.rs +++ b/src/scene/render.rs @@ -633,11 +633,34 @@ impl Scene { n | (1u64 << 63) } }; - let (face3d_wires, other_wires) = split_face3d_wires(&base_arc, &self.document); - let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() { - Arc::new(other_wires) + // Split Face3D wires from the rest. The split is content-only (keyed + // by the tile's wire id), so on a pan-reuse it's memoized rather than + // re-walking every wire (handle lookup + clone) each frame. Non-tile + // paths have no stable id and split inline as before. + let (face3d_wires, other_arc) = match tile_wire_gen { + Some(gen) => { + let cached = { + let c = self.split_cache.borrow(); + c.as_ref().filter(|(g, ..)| *g == gen).map(|(_, f, o)| (f.clone(), o.clone())) + }; + cached.unwrap_or_else(|| { + let (f, o) = split_face3d_wires(&base_arc, &self.document); + let (fa, oa) = (Arc::new(f), Arc::new(o)); + *self.split_cache.borrow_mut() = Some((gen, fa.clone(), oa.clone())); + (fa, oa) + }) + } + None => { + let (f, o) = split_face3d_wires(&base_arc, &self.document); + (Arc::new(f), Arc::new(o)) + } + }; + // Reuse the cached `other` Arc directly when no live preview/interim + // wires need appending; otherwise clone once to extend. + let all_wires = if !has_overlay { + other_arc } else { - let mut v = other_wires; + let mut v = (*other_arc).clone(); if let Some(iw) = &self.interim_wire { v.push(iw.clone()); } @@ -705,7 +728,7 @@ impl Scene { Some(ViewportData { wires: all_wires, - face3d_wires: Arc::new(face3d_wires), + face3d_wires, draw_depths: self.draw_depth_map(), hatches, wipeout_hatches,