fix: let the wire arena serve drawings that contain blocks

Clicking to end a segment froze the UI until the whole drawing had been
re-uploaded. With block geometry cached the remaining cost is the regular
half, and on two drawings it dominates:

    113 k wires   regular=96ms  block=13ms  index=12ms   total 122ms
    198 k wires   regular=85ms  block= 7ms  index=25ms   total 118ms

It is rebuilt from scratch because `PersistentWireArena` -- the only
thing that patches wire buffers in place -- was disabled outright by:

    && !vp_wires.iter().any(|wire| wire.render_instance.is_some())

One block reference was enough; the reproducer has 11 363. The scene
already produces an incremental patch in 0.9 ms and the GPU side could
not use it.

Instanced wires are not something the arena has to handle: they are a
different batch type with their own pipeline. Partition them out, let the
arena own and patch the rest, and keep building `BlockWireGpu` for them.

The handle index is unaffected -- it maps into `vp_wires`, not into arena
slabs, and the patch path already maintains it.

Measured on the 198 k-wire drawing, per drawn segment:

    before   wire 350ms  (shared-fullupload)
    after    wire  12ms  (arena-patch)

with the whole click, including `bump_entities`, going from ~411 ms to
~20 ms.

Verified visually on drawings with and without blocks, through repeated
edits and undo/redo.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-05 09:18:50 +03:00
1 parent 071b4b6a87
commit fb64e76f04
2 files changed
+53 -3

No files matched your search

+23
View File
@@ -2340,6 +2340,29 @@ impl Pipeline {
/// copy across every slot that renders that content (see
/// `MultiPipeline::wire_buffer_cache`). Takes `&self` (reads only the const
/// bind-group layout) so the shared cache — not the slot — owns the result.
/// Batches for the instanced (block-reference) wires alone.
///
/// The arena serves the non-instanced wires and patches them in place;
/// block references are a different batch type and still rebuild, but
/// their geometry is cached by `source_id`, so that is cheap.
pub fn upload_block_wires(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[&WireModel],
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> Vec<BlockWireGpu> {
BlockWireGpu::from_wires(
device,
queue,
wires,
depth_map,
None,
&self.block_wire_const_bgl,
Some(&mut self.block_geometry),
)
}
pub fn build_wire_buffers(
&mut self,
device: &wgpu::Device,
+30 -3
View File
@@ -655,7 +655,14 @@ impl shader::Primitive for Primitive {
|| ((vp.wire_patch.is_some()
|| inner.wire_arena_id != u64::MAX)
&& packed_arena_owner))
&& !vp_wires.iter().any(|wire| wire.render_instance.is_some());
;
// Instanced wires used to disable the arena outright, and with
// it incremental patching: one block reference in the drawing
// meant every edit re-uploaded every wire, ~96 ms of the
// ~122 ms a click cost. They are simply a different batch type
// with their own pipeline, so the arena takes the rest and
// `BlockWireGpu` keeps them -- cheaply now that its geometry is
// cached by `source_id`.
if use_wire_arena {
use crate::scene::pipeline::wire_arena::{
self, PersistentWireArena as WireArena,
@@ -767,12 +774,16 @@ impl shader::Primitive for Primitive {
.iter()
.filter(|w| {
!w.points.is_empty()
&& w.render_instance.is_none()
&& !wire_arena::is_mesh_edge(w, &mesh_names)
})
.collect();
let mesh: Vec<&crate::scene::WireModel> = vp_wires
.iter()
.filter(|w| wire_arena::is_mesh_edge(w, &mesh_names))
.filter(|w| {
w.render_instance.is_none()
&& wire_arena::is_mesh_edge(w, &mesh_names)
})
.collect();
if !reg_ok {
inner.wire_arena = WireArena::build(
@@ -874,7 +885,23 @@ impl shader::Primitive for Primitive {
gpus.extend(arena.wire_gpus());
}
inner.gpu_wires = std::sync::Arc::new(gpus);
inner.gpu_block_wires = std::sync::Arc::new(Vec::new());
// The arena holds only the non-instanced wires;
// block references keep their own batch type. Their
// geometry is cached by `source_id`, so rebuilding them
// here is cheap against the ~96 ms of regular wires the
// arena now patches instead of re-uploading.
let instanced: Vec<&crate::scene::WireModel> = vp_wires
.iter()
.filter(|w| w.render_instance.is_some())
.collect();
inner.gpu_block_wires = std::sync::Arc::new(
inner.upload_block_wires(
device,
queue,
&instanced,
&draw_depths,
),
);
if _patched {
wire_arena::patch_handle_index(
&mut inner.wire_handle_index,