perf: skip the wire partition when a patch cannot change its output

`arena-post partition=55.2ms blocks=5.9ms curves=2.5ms` — the partition is
almost the whole of the 73 ms an arena patch spends after patching, and it
walks all 236 956 resident wires to serve a one-entity change.

Its three consumers here — the instanced, circle and ellipse uploads —
depend only on wires that are block references or analytical curves. A
patch that touches none of those cannot change any of them, so the walk is
skipped and the uploads already on the slot are kept.

Two things make that sound, and both are checked rather than assumed:

**Depths hold still.** The instances bake a draw depth.
`inserted_draw_depth_label` allocates into a reserved gap instead of
renumbering, so adding an entity leaves every other label alone — the
existing doc comment says so and there is now a test. A rebuild from
scratch *can* move them, so `draw_depth_generation` bumps only there, and
a slot reuses its uploads only while that generation is unchanged.

**Contributors are remembered.** `partition_wires` records which handles
fed those three outputs, and the skip requires that no changed handle is
among them and that no changed handle's new wires would join them. That
catches an entity entering the set and an entity leaving it. The
membership test is shared with the walk it decides to skip, so the two
cannot drift, and it is deliberately conservative: a wire that looks
analytical but fails extraction forces a needless re-partition, never a
stale upload.

`arena-post` gains `skipped=` so a run says how often this actually fires
rather than leaving it to be inferred from the timing.

Not verified on a device yet — the headless harness never reaches
`prepare`, so this needs a GUI run on the reproducer to confirm both the
saving and that nothing goes missing on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-08 16:42:38 +03:00
1 parent b68989768d
commit 03c64e3d38
4 files changed
+176 -33

No files matched your search

+69 -33
View File
@@ -149,6 +149,9 @@ pub struct ViewportData {
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
/// depth field (84 construction sites); the bias is looked up by handle
/// at GPU-upload time from this map instead.
/// See `Scene::draw_depth_generation`. Cached uploads that bake a depth are
/// only reusable while this is unchanged.
pub(in crate::scene) draw_depth_generation: u64,
pub(in crate::scene) draw_depths:
std::sync::Weak<rustc_hash::FxHashMap<u64, [f32; 2]>>,
pub(in crate::scene) hatches: Arc<Vec<HatchModel>>,
@@ -842,47 +845,79 @@ impl shader::Primitive for Primitive {
// A one-entity patch costs 73 ms in this block, and the
// three steps below are all O(resident set) rather than
// O(changes). Which of them dominates decides the fix.
// The partition walks the whole resident set, and on
// a patch it almost always produces the three uploads it
// produced last frame. Skip it when no changed entity
// feeds them and none did before.
//
// Safe because adding an entity does not disturb any
// other entity's draw depth: `inserted_draw_depth_label`
// allocates into the reserved label gap rather than
// renumbering, so instances already uploaded stay right.
let analytical_untouched = _patched
&& inner.partition_depth_generation == vp.draw_depth_generation
&& patch.is_some_and(|patch| {
patch.changes.iter().all(|(handle, _)| {
!inner.partition_contributors.contains(handle)
&& !patch.runs.get(handle).is_some_and(|run| {
run.iter()
.any(wire_arena::feeds_analytical_uploads)
})
})
});
let t_part = _perf.then(iced::time::Instant::now);
let partitioned =
wire_arena::partition_wires(&vp_wires, &draw_depths);
let partitioned = (!analytical_untouched)
.then(|| wire_arena::partition_wires(&vp_wires, &draw_depths));
let part_ms = t_part
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
let t_blk = _perf.then(iced::time::Instant::now);
inner.gpu_block_wires = std::sync::Arc::new(
inner.upload_block_wires(
device,
queue,
&partitioned.instanced,
&draw_depths,
&mut pipeline.block_geometry,
),
);
let blk_ms = t_blk
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
let t_curve = _perf.then(iced::time::Instant::now);
inner.gpu_circles = std::sync::Arc::new(
inner.upload_circles_from_instances(
device,
queue,
&partitioned.circle_instances,
),
);
inner.gpu_ellipses = std::sync::Arc::new(
inner.upload_ellipses_from_instances(
device,
queue,
&partitioned.ellipse_instances,
),
);
let mut blk_ms = 0.0f64;
let mut curve_ms = 0.0f64;
// `None` means the three uploads this slot holds are
// still the answer, so they are left alone.
if let Some(partitioned) = &partitioned {
inner.gpu_block_wires = std::sync::Arc::new(
inner.upload_block_wires(
device,
queue,
&partitioned.instanced,
&draw_depths,
&mut pipeline.block_geometry,
),
);
blk_ms = t_blk
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
let t_curve = _perf.then(iced::time::Instant::now);
inner.gpu_circles = std::sync::Arc::new(
inner.upload_circles_from_instances(
device,
queue,
&partitioned.circle_instances,
),
);
inner.gpu_ellipses = std::sync::Arc::new(
inner.upload_ellipses_from_instances(
device,
queue,
&partitioned.ellipse_instances,
),
);
inner
.partition_contributors
.clone_from(&partitioned.contributors);
inner.partition_depth_generation = vp.draw_depth_generation;
curve_ms = t_curve
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
}
if _perf {
crate::perf_record!(
"[perf] arena-post partition={part_ms:.1}ms blocks={blk_ms:.1}ms \
curves={:.1}ms wires={} instanced={} circles={} ellipses={}",
t_curve.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0),
curves={curve_ms:.1}ms skipped={} wires={} instanced={} circles={} ellipses={}",
partitioned.is_none(),
vp_wires.len(),
partitioned.instanced.len(),
partitioned.circle_instances.len(),
partitioned.ellipse_instances.len(),
partitioned.as_ref().map_or(0, |p| p.instanced.len()),
partitioned.as_ref().map_or(0, |p| p.circle_instances.len()),
partitioned.as_ref().map_or(0, |p| p.ellipse_instances.len()),
);
}
if _patched {
@@ -4259,6 +4294,7 @@ impl Scene {
face3d_wires,
text_verts,
preview_text_verts,
draw_depth_generation: self.draw_depth_generation(),
draw_depths: Arc::downgrade(&draw_depths),
hatches,
wipeout_hatches,