forked from 1b-agent/opencadstudio-upstream
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:
1 parent
b68989768d
commit
03c64e3d38
4 files changed
+176
-33
No files matched your search
+69
-33
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user