perf(scene): memoize Face3D wire split across frames

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-05 09:23:52 +03:00
1 parent 0f40cfa054
commit 65eba3887a
3 files changed
+43 -5

No files matched your search

+7
View File
@@ -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
+8
View File
@@ -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<u64>,
/// 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<Option<(u64, Arc<Vec<WireModel>>, Arc<Vec<WireModel>>)>>,
}
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),
}
}
+28 -5
View File
@@ -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,