perf: eliminate per-frame re-tessellation and GPU re-upload in large files

Add two complementary render caches that make mouse navigation essentially
free regardless of file size:

Option A – Wire tessellation cache: entity_wires_arc() caches the
tessellated Vec<WireModel> keyed by geometry_epoch and returns an Arc,
so navigation frames (no preview wires) incur zero Vec clones and zero
tessellation work. Primitive::wires changed to Arc<Vec<WireModel>>.

Option B – GPU buffer cache: Pipeline stores cached_epoch; prepare()
skips all upload_wires/hatches/images/meshes calls when the epoch is
unchanged, writing only the 192-byte camera uniform instead.

geometry_epoch is a process-wide AtomicU64 incremented by every Scene
mutation (add/erase entity, selection, layer visibility, layout switch,
populate_*, grip, transform, copy, clear, preview wires). Using a global
counter rather than a per-scene counter prevents two tabs that happen to
have the same local bump count from sharing a cached_epoch value, which
was causing the first file's geometry to remain on screen after opening a
second file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-26 02:13:31 +03:00
1 parent 320312e81a
commit d1ecaf4695
5 files changed
+225 -20

No files matched your search

+36 -16
View File
@@ -9,6 +9,8 @@ use iced::mouse;
use iced::widget::shader::{self, Viewport};
use iced::{Rectangle, Size};
use std::sync::Arc;
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use super::pipeline::Pipeline;
use super::tessellate;
@@ -75,7 +77,7 @@ pub struct CameraState {
#[derive(Debug)]
pub struct Primitive {
pub(super) wires: Vec<WireModel>,
pub(super) wires: Arc<Vec<WireModel>>,
pub(super) hatches: Vec<HatchModel>,
/// Wipeout fills — rendered in a separate pass AFTER wires.
pub(super) wipeout_hatches: Vec<HatchModel>,
@@ -89,6 +91,7 @@ pub struct Primitive {
/// Background color used to clear the MSAA buffer at the start of each frame.
pub(super) bg_color: [f32; 4],
pub(super) show_viewcube: bool,
pub(super) geometry_epoch: u64,
}
// ── shader::Primitive impl ────────────────────────────────────────────────
@@ -116,11 +119,14 @@ impl shader::Primitive for Primitive {
pipeline.ensure_depth_texture(device, clip_size);
pipeline.viewcube.ensure_depth_texture(device, full_size);
pipeline.upload_uniforms(queue, &self.uniforms);
pipeline.upload_hatches(device, &self.hatches);
pipeline.upload_wipeouts(device, &self.wipeout_hatches);
pipeline.upload_images(device, queue, &self.images);
pipeline.upload_meshes(device, &self.meshes);
pipeline.upload_wires(device, &self.wires);
if self.geometry_epoch != pipeline.cached_epoch {
pipeline.upload_hatches(device, &self.hatches);
pipeline.upload_wipeouts(device, &self.wipeout_hatches);
pipeline.upload_images(device, queue, &self.images);
pipeline.upload_meshes(device, &self.meshes);
pipeline.upload_wires(device, &self.wires[..]);
pipeline.cached_epoch = self.geometry_epoch;
}
if self.show_viewcube {
pipeline.viewcube.upload(
queue,
@@ -226,11 +232,17 @@ impl Scene {
let cam = self.camera.borrow();
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
let mut all_wires = self.entity_wires();
if let Some(iw) = &self.interim_wire {
all_wires.push(iw.clone());
}
all_wires.extend(self.preview_wires.iter().cloned());
let entity_arc = self.entity_wires_arc();
let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
entity_arc
} else {
let mut v = (*entity_arc).clone();
if let Some(iw) = &self.interim_wire {
v.push(iw.clone());
}
v.extend(self.preview_wires.iter().cloned());
Arc::new(v)
};
let bg_color = if self.current_layout == "Model" {
self.bg_color
@@ -249,6 +261,7 @@ impl Scene {
hover_region,
bg_color,
show_viewcube,
geometry_epoch: self.geometry_epoch,
}
}
@@ -266,11 +279,17 @@ impl Scene {
None => return self.build_primitive(hover_region, bounds, false),
};
let mut all_wires = self.model_wires_for_viewport(vp_handle);
if let Some(iw) = &self.interim_wire {
all_wires.push(iw.clone());
}
all_wires.extend(self.preview_wires.iter().cloned());
let base_wires = self.model_wires_for_viewport(vp_handle);
let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
Arc::new(base_wires)
} else {
let mut v = base_wires;
if let Some(iw) = &self.interim_wire {
v.push(iw.clone());
}
v.extend(self.preview_wires.iter().cloned());
Arc::new(v)
};
Primitive {
wires: all_wires,
@@ -283,6 +302,7 @@ impl Scene {
hover_region,
bg_color: self.bg_color,
show_viewcube: false,
geometry_epoch: self.geometry_epoch,
}
}