perf(render): share one GPU wire buffer across slots by content id
Each pipeline slot owned its own gpu_wires: every paper viewport and every Model tile rebuilt an identical resident wire set into its own GPU vertex buffers and its own handle index. With N content viewports drawing one shared tessellation that is N copies of the (potentially huge) wire buffers in VRAM plus N batch+index builds on every content change — the issue-131 memory exposure multiplied by the viewport count. A wgpu::Buffer is internally ref-counted, so sharing a WireGpu by Arc shares its GPU allocation. Make Pipeline.gpu_wires an Arc<Vec<WireGpu>> (and wire_handle_index an Arc<map>), turn upload_wires into build_wire_buffers(&self) that returns those Arcs, and add MultiPipeline.wire_buffer_cache keyed by wire_content_id. prepare() builds an entry once on a cache miss and hands Arc clones to every slot with that id — so N viewports (and, since one MultiPipeline backs all panes, N panes) upload the wire vertices exactly once between them. Camera uniforms and scissor stay per-slot, so the draw still issues one call per viewport with the right view; only the geometry is deduplicated. The shared Vec is never mutated in place — only reassigned on a content change — so the sharing is sound. The cache drops entries no slot still references (Arc::strong_count == 1) once it passes a small bound, so a live buffer (held by any pane) is never evicted. Model multi-tile gets the same dedup for free, and two panes showing the same model now share buffers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f39a2d3126
commit
0f61a2e582
2 changed files with 73 additions and 12 deletions
|
|
@ -357,7 +357,35 @@ impl shader::Primitive for Primitive {
|
|||
// independent of the `cur_key` block so a preview/interim wire change
|
||||
// still uploads even when the camera didn't move.
|
||||
if vp.wire_content_id != inner.cached_wire_id {
|
||||
inner.upload_wires(device, &vp.wires[..], &vp.draw_depths);
|
||||
// Share one copy of the resident wire buffers across every slot
|
||||
// (and every pane — one MultiPipeline backs them all) rendering
|
||||
// this content id: build on a cache miss, then hand out Arc
|
||||
// clones. Two paper viewports showing the same model, or four
|
||||
// Model tiles, upload the wire vertices once between them.
|
||||
// `.cloned()` releases the immutable cache borrow before the
|
||||
// miss branch takes a mutable one.
|
||||
let cached = pipeline.wire_buffer_cache.get(&vp.wire_content_id).cloned();
|
||||
let built = match cached {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
let entry =
|
||||
inner.build_wire_buffers(device, &vp.wires[..], &vp.draw_depths);
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
// Evict entries no slot still holds (only the cache
|
||||
// references them). An entry drawn by any pane keeps a
|
||||
// strong count ≥ 2, so this never drops live geometry.
|
||||
if pipeline.wire_buffer_cache.len() > 16 {
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.retain(|_, (w, _)| std::sync::Arc::strong_count(w) > 1);
|
||||
}
|
||||
entry
|
||||
}
|
||||
};
|
||||
inner.gpu_wires = built.0;
|
||||
inner.wire_handle_index = built.1;
|
||||
inner.cached_wire_id = vp.wire_content_id;
|
||||
}
|
||||
// Selection xray overlay — rebuilt when the selection changes or the
|
||||
|
|
|
|||
Loading…
Reference in a new issue