forked from 1b-agent/opencadstudio-upstream
perf: let one tessellation serve every background
Switching to a Layout re-tessellated the whole model — `memo_miss=186455`, 2034 ms — to arrive at geometry identical to what was already in the memo. The only thing that differed was colour. `local_wires_for` already knows this: it stores the raw colour and drops its background argument with `let _ = bg_color;`, noting that `Batches::finalize` applies `adapt_to_bg` last, "so the cache no longer has to rebuild on BACKGROUND / layout-switch". The tessellation memo's guard had not been told, and still mixed the background in, so every entry was discarded on a switch. `finalize` now records what its colour resolution consumed — the raw colour, the raw glyph colours, and the three flags it branches on — as `WireModel::bg_adapt`, and `resolve_colors_for_bg` replays it for another background. The guard drops the background, so a memo entry serves any layout. The resolution recomputes from the recorded raw colour rather than from the resolved one, which makes it idempotent and therefore safe over a set that mixes memo hits with freshly tessellated misses. Re-adapting the resolved colour would not be safe: `adapt_to_bg` snaps a near-white colour to pure black on a light background, and pure black comes back pure *white* on a dark one, losing the tint. A test pins that asymmetry so the raw colour is not optimised away later. The contract is tested against the real `finalize` rather than a copy of its logic: for every background pair, building under one and resolving for the other lands exactly where building under the other would have. `bg_adapt` is `None` for wires built anywhere else, and the resolution skips them, so nothing outside the block cache changes colour. It is also `None` when the resolution would be a no-op — a preserved colour with no canvas fill looks the same under every background — which keeps the common case at no cost. Ordering is unchanged: the pass runs inside the builder, before `append_scene_markers` and `apply_refedit_fade`, both of which have their own separate background dependency. `wires-build` gains `colors=` so the pass has a number of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e4f755e6f
commit
391a74a615
40 files changed
+308
-9
No files matched your search
+25
-9
@@ -1939,12 +1939,14 @@ pub struct Scene {
|
||||
/// Per-entity memo for the **resident** model wire set (`model_tile_wires_arc`,
|
||||
/// the one the main GPU render holds). Kept separate from `tess_memo` because
|
||||
/// the resident set is camera-INDEPENDENT (no view cull, no zoom LOD), so its
|
||||
/// guard depends only on anno-scale / background — it survives pan/zoom, and a
|
||||
/// guard depends only on anno-scale — it survives pan/zoom and layout switches, and a
|
||||
/// single-entity edit re-tessellates just the changed entity instead of the
|
||||
/// whole model. Sharing `tess_memo` would let the camera-dependent culled path
|
||||
/// thrash it on every zoom. (#perf)
|
||||
resident_tess_memo: RefCell<HashMap<Handle, Arc<Vec<WireModel>>>>,
|
||||
/// Guard hash for `resident_tess_memo` (anno-scale / bg only).
|
||||
/// Guard hash for `resident_tess_memo` (anno-scale only). The background
|
||||
/// is not part of it: it changes colours, not geometry, and
|
||||
/// `resolve_colors_for_bg` reapplies it to the assembled set.
|
||||
resident_tess_guard: std::cell::Cell<u64>,
|
||||
/// Per-mutation delta journal: which handles changed on each `geometry_epoch`
|
||||
/// bump. Bounded ring ([`GEOMETRY_JOURNAL_CAP`]); a derived cache replays the
|
||||
@@ -9013,10 +9015,11 @@ impl Scene {
|
||||
let memo_misses;
|
||||
let t_build = perf.then(iced::time::Instant::now);
|
||||
let mut wires: Vec<WireModel> = if memo_active {
|
||||
// Guard hash of everything tessellate_entity output depends on
|
||||
// Guard hash of everything tessellate_entity's GEOMETRY depends on
|
||||
// besides the entity itself. A mismatch (zoom/tol, view, anno,
|
||||
// offset, bg, entered viewport) means the memo is stale. For the
|
||||
// resident path wpp/view_aabb are None, so this collapses to anno/bg.
|
||||
// offset, entered viewport) means the memo is stale. For the
|
||||
// resident path wpp/view_aabb are None, so this collapses to anno.
|
||||
// The background is excluded on purpose — see the note below.
|
||||
let guard = {
|
||||
let mut g: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
let mut mix = |x: u64| g = g.rotate_left(13) ^ x;
|
||||
@@ -9029,9 +9032,13 @@ impl Scene {
|
||||
mix(anno.to_bits() as u64);
|
||||
mix(annotation_scale_handle.map(|handle| handle.value()).unwrap_or(0));
|
||||
mix(all_visible as u64);
|
||||
for c in bg {
|
||||
mix(c.to_bits() as u64);
|
||||
}
|
||||
// The background is deliberately NOT mixed in. It changes
|
||||
// colours, never geometry — `local_wires_for` discards it with
|
||||
// `let _ = bg_color;` and `Batches::finalize` applies it last —
|
||||
// so a memo entry built under one background serves any other
|
||||
// once `resolve_colors_for_bg` has run over the assembled set
|
||||
// below. Keying on it made a layout switch re-tessellate the
|
||||
// whole model to arrive at identical points.
|
||||
mix(avp.map(|h| h.value()).unwrap_or(0));
|
||||
// SDF glyph quads bake the atlas UV of each tile, so a growth or
|
||||
// a re-bake (which rescale / rewind every UV) makes memoized text
|
||||
@@ -9133,6 +9140,14 @@ impl Scene {
|
||||
};
|
||||
let build_ms = crate::perf::elapsed_ms(t_build);
|
||||
|
||||
// Resolve display colours for the live background. Memo hits were
|
||||
// built under whatever background was current then, misses under this
|
||||
// one; the pass recomputes both from their recorded raw colour, so a
|
||||
// mixed set lands in the same place either way.
|
||||
let t_colors = perf.then(iced::time::Instant::now);
|
||||
crate::scene::view::render::resolve_colors_for_bg(&mut wires, bg);
|
||||
let colors_ms = crate::perf::elapsed_ms(t_colors);
|
||||
|
||||
// Apply draw order via the cached index (O(1) block lookup).
|
||||
let t_sort = perf.then(iced::time::Instant::now);
|
||||
let mut sorted = false;
|
||||
@@ -9157,13 +9172,14 @@ impl Scene {
|
||||
}
|
||||
if perf {
|
||||
crate::perf_record!(
|
||||
"[perf] wires-build total={:.1}ms sort_cache={:.1} visible={:.1} blk_cache={:.1} \
|
||||
"[perf] wires-build total={:.1}ms sort_cache={:.1} visible={:.1} blk_cache={:.1} colors={:.1} \
|
||||
build={:.1} [classify={:.1} hits={:.1} tess={:.1} materialize={:.1}] sort={:.1}({}) \
|
||||
entities={} memo_hit={} memo_miss={} wires={} memo={}",
|
||||
crate::perf::elapsed_ms(t_fn),
|
||||
sort_cache_ms,
|
||||
visible_ms,
|
||||
blk_ms,
|
||||
colors_ms,
|
||||
build_ms,
|
||||
classify_ms,
|
||||
hits_ms,
|
||||
|
||||
Reference in new issue
Block a user