forked from 1b-agent/opencadstudio-upstream
perf(render): unify model/paper/block spaces on one resident wire path
Model space held a full, camera-independent, un-culled wire set with a stable content id, so pan/zoom re-drew without re-tessellating or re-uploading and idle frames collapsed to a single blit. Paper did none of that: the sheet was camera-keyed (full re-tess every pan/zoom), content viewports re-baked per zoom step with their own frustum cull + LOD, and a per-frame wire_force_nonce forced a GPU re-upload every frame AND poisoned render_signature so paper never hit the single-blit scene cache. Route every space through one Scene::resident_wires_for(block, anno, frozen): the full, un-culled, LOD-free set per (block, ambient bg, anno, frozen) key, held resident and stamped with a stable WIRE_CONTENT_GEN id. Model tiles, BEDIT, the paper sheet (a thin border-drop + printable-area dressing over the resident base) and paper content viewports all share it. When annotation is inert (PSLTSCALE off and no annotative entity, cached per epoch) the per- viewport scale is normalized away so every viewport reuses one tessellation. Result: paper pan/zoom does zero re-tessellation and zero wire re-upload, and paper now hits the single-blit scene cache exactly like model. Deletes wire_force_nonce, the per-viewport height/view wire cache, and view_cull_aabb. Tradeoff (deliberate, matches model): no cull/LOD anywhere. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
6208b15cdb
commit
f39a2d3126
3 files changed
+223
-230
No files matched your search
+38
-45
@@ -848,10 +848,8 @@ impl Scene {
|
||||
use std::sync::Arc;
|
||||
{
|
||||
let cache = self.sdf_text_cache.borrow();
|
||||
if let Some((id, verts)) = cache.as_ref() {
|
||||
if *id == wire_content_id {
|
||||
return verts.clone();
|
||||
}
|
||||
if let Some(verts) = cache.get(&wire_content_id) {
|
||||
return verts.clone();
|
||||
}
|
||||
}
|
||||
let mut out: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||
@@ -861,7 +859,12 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
let verts = Arc::new(out);
|
||||
*self.sdf_text_cache.borrow_mut() = Some((wire_content_id, verts.clone()));
|
||||
let mut cache = self.sdf_text_cache.borrow_mut();
|
||||
// Ids change on rebuild, so old keys die naturally; cap bounds churn.
|
||||
if cache.len() > 8 {
|
||||
cache.clear();
|
||||
}
|
||||
cache.insert(wire_content_id, verts.clone());
|
||||
verts
|
||||
}
|
||||
|
||||
@@ -1003,64 +1006,54 @@ impl Scene {
|
||||
let us = (visible_w / full.width).clamp(0.0, 1.0);
|
||||
let vs = (visible_h / full.height).clamp(0.0, 1.0);
|
||||
|
||||
// Model tiles all share one resident, camera-independent wire set
|
||||
// (`model_tile_wires_arc` holds it static). `tile_wire_gen` is that
|
||||
// set's content id: stable across camera moves, so the GPU wire upload
|
||||
// and the Face3D split below are skipped every frame the geometry is
|
||||
// unchanged. The paper-space sources have no stable id and force a
|
||||
// re-upload.
|
||||
let (base_arc, tile_wire_gen) = if let Some(tile_idx) = inst.tile_idx {
|
||||
// EVERY wire source is resident + camera-independent now (unified
|
||||
// static-hold, `resident_wires_for`) and stamps its stable
|
||||
// [`WIRE_CONTENT_GEN`] id into `last_model_wire_gen` — Model tiles,
|
||||
// the paper sheet, content viewports and the pick composite alike. So
|
||||
// the GPU wire upload, the Face3D split and the SDF text gather below
|
||||
// are all skipped while the content is unchanged, and
|
||||
// `render_signature` stays stable so paper hits the single-blit scene
|
||||
// cache exactly like Model.
|
||||
let base_arc = if let Some(tile_idx) = inst.tile_idx {
|
||||
let aspect = if full.height > 0.0 {
|
||||
full.width / full.height
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let arc = self.model_tile_wires_arc(tile_idx, &inst.camera, aspect, full.height);
|
||||
(arc, Some(self.last_model_wire_gen.get()))
|
||||
self.model_tile_wires_arc(tile_idx, &inst.camera, aspect, full.height)
|
||||
} else if inst.paper_sheet {
|
||||
// The sheet renders the paper block's own entities + viewport
|
||||
// borders — NOT the projected viewport content (the GPU content
|
||||
// viewports draw that themselves).
|
||||
(self.paper_sheet_wires_arc(), None)
|
||||
self.paper_sheet_wires_arc()
|
||||
} else if inst.handle == acadrust::Handle::NULL {
|
||||
(self.entity_wires_arc(), None)
|
||||
self.entity_wires_arc()
|
||||
} else {
|
||||
(self.model_wires_for_viewport_arc(inst.handle, full.height), None)
|
||||
self.model_wires_for_viewport_arc(inst.handle, full.height)
|
||||
};
|
||||
// Wire-buffer content id for the upload gate. Preview / interim wires
|
||||
// are NOT part of this buffer anymore (they go in a separate per-frame
|
||||
// overlay buffer below), so the base id is the stable tile content gen
|
||||
// — a drag no longer re-uploads the whole base wire set every move.
|
||||
// Non-tile paths have no stable id and force a re-upload.
|
||||
let wire_content_id = match tile_wire_gen {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
let n = self.wire_force_nonce.get().wrapping_add(1);
|
||||
self.wire_force_nonce.set(n);
|
||||
n | (1u64 << 63)
|
||||
}
|
||||
};
|
||||
// overlay buffer below), so the base id is the source's stable content
|
||||
// gen — a drag or camera move never re-uploads the base wire set.
|
||||
let wire_content_id = self.last_model_wire_gen.get();
|
||||
// Split Face3D wires from the rest. The split is content-only (keyed
|
||||
// by the wire-set content id), so while the geometry is unchanged it's
|
||||
// memoized rather than re-walking every wire (handle lookup + clone)
|
||||
// each frame. Non-tile paths have no stable id and split inline.
|
||||
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 => {
|
||||
// each frame — for every source, since all ids are stable now.
|
||||
let (face3d_wires, other_arc) = {
|
||||
let cached = { self.split_cache.borrow().get(&wire_content_id).cloned() };
|
||||
cached.unwrap_or_else(|| {
|
||||
let (f, o) = split_face3d_wires(&base_arc, &self.document);
|
||||
(Arc::new(f), Arc::new(o))
|
||||
}
|
||||
let (fa, oa) = (Arc::new(f), Arc::new(o));
|
||||
let mut c = self.split_cache.borrow_mut();
|
||||
// Ids change on rebuild, so old keys die naturally; the cap
|
||||
// just bounds pathological churn.
|
||||
if c.len() > 8 {
|
||||
c.clear();
|
||||
}
|
||||
c.insert(wire_content_id, (fa.clone(), oa.clone()));
|
||||
(fa, oa)
|
||||
})
|
||||
};
|
||||
// Base wire set — the cached `other` Arc directly, never cloned to
|
||||
// append overlays. Preview / interim wires ride in their own small
|
||||
|
||||
Reference in new issue
Block a user