perf: keep block geometry on the GPU across edits

Adding one entity to a drawing rebuilt every block group's geometry from
scratch. Measured on a 1.17 M-entity drawing with 11 380 block wires, the
per-edit upload split as:

    upload-split regular=80.4ms block=230.3ms

The block half is three quarters of it, and every byte of it was
identical to the previous frame: a new line is not a block reference.
`BlockWireGpu::from_wires` re-emitted each group's segments, re-expanded
them six-fold and re-uploaded the vertex buffers anyway.

That work depends only on the block definition being expanded, which
already has a stable identity: `render_instance.source_id`, minted once
when the block cache expands a definition and unchanged while that
expansion stays cached. Cache the vertex chunks and constants under
`(source_id, mesh_edge, colour)` -- colour is in the key because a
highlight tint changes the constants while leaving the vertices alone.

Only the per-instance placements are rebuilt, and they must be: each
carries its own draw depth and the depth map shifts on every add or
remove. They are also small, a few thousand placements against millions
of vertices.

Entries not seen in a build are dropped, so a block-cache rebuild that
mints fresh source ids does not strand old buffers on the GPU.

Measured: the block half falls from 230 ms to 7.4 ms, and the whole
per-edit upload from ~350 ms to ~118 ms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-05 09:16:12 +03:00
1 parent 13afc1479f
commit 071b4b6a87
2 files changed
+154 -52

No files matched your search

+8 -1
View File
@@ -213,6 +213,9 @@ pub struct Pipeline {
/// shared resident buffer.
pub(crate) gpu_wires: std::sync::Arc<Vec<WireGpu>>,
pub(crate) gpu_block_wires: std::sync::Arc<Vec<BlockWireGpu>>,
/// Block geometry that survives an edit, keyed by the definition it
/// expands. See `wire_gpu::BlockGeometryCache`.
pub(crate) block_geometry: wire_gpu::BlockGeometryCache,
/// Persistent per-entity wire instance arena (capability-selected format).
/// When active, `gpu_wires` is a thin wrapper over this arena's buffers and an
/// edit patches one entity's slab in place instead of rebuilding every wire.
@@ -2279,6 +2282,7 @@ impl Pipeline {
surface_format: format,
gpu_wires: std::sync::Arc::new(vec![]),
gpu_block_wires: std::sync::Arc::new(vec![]),
block_geometry: Default::default(),
wire_arena: None,
wire_arena_mesh: None,
wire_arena_fallback: std::sync::Arc::new(Vec::new()),
@@ -2337,7 +2341,7 @@ impl Pipeline {
/// `MultiPipeline::wire_buffer_cache`). Takes `&self` (reads only the const
/// bind-group layout) so the shared cache — not the slot — owns the result.
pub fn build_wire_buffers(
&self,
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[WireModel],
@@ -2397,6 +2401,7 @@ impl Pipeline {
depth_map,
None,
&self.block_wire_const_bgl,
Some(&mut self.block_geometry),
);
// Index handle → wire slots once, here, so the per-hover selection
@@ -2523,6 +2528,7 @@ impl Pipeline {
depth_map,
Some(tint),
&self.block_wire_const_bgl,
None,
)
} else {
Vec::new()
@@ -2534,6 +2540,7 @@ impl Pipeline {
depth_map,
Some(WireModel::HOVER),
&self.block_wire_const_bgl,
None,
));
self.gpu_selected_block_wires = block_gpu;
if let Some(started) = perf_started {