cad-editor/src/scene/pipeline/wire_arena.rs

413 lines
16 KiB
Rust
Raw Normal View History

perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
// Persistent per-entity wire instance arena (native, behind OCS_WIRE_GPU_PATCH).
//
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
// The normal wire path re-emits EVERY wire into a fresh instance buffer whenever
// the resident set's content id changes — so any edit on a drawing whose wires
// expand to millions of segments re-uploads the whole (hundreds-of-MB) buffer,
// which stalls for ~1s on a shared-memory GPU. This arena instead keeps one
// persistent instance buffer (plus its shared WireConst storage) laid out as
// per-entity *slabs*, so an edit only writes what changed:
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
//
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
// * Modify in place — a move / rotate / scale / colour change keeps the
// entity's segment count, so its slab is overwritten where it sits.
// * Add / a Modify whose segment count changed — bump-allocate a fresh slab at
// the tail (tombstone the old one); the instance buffer only grows by that
// entity.
// * Erase — tombstone the slab (blank instances that render nothing).
//
// Two correctness points make add/remove safe:
// * draw_depth_map normalises each entity's draw-order z-bias by the block's
// entity count, so ANY add/remove re-scales EVERY entity's bias. We keep a
// CPU mirror of the WireConst array and, on a structural change, refresh every
// slab's draw_depth and re-upload the (small, ~1 MB) const buffer — the huge
// instance buffer is untouched.
// * A tail-appended entity draws last, which only mis-orders alpha-blended /
// coincident wires. So when the set contains ANY transparent wire we bail to a
// full rebuild instead of appending. Opaque overlap resolves by the z-bias, so
// it is order-independent and safe to relocate.
//
// A tombstoned instance points at const slot 0 (a blank WireConst, half_width 0),
// so the shader expands it to a zero-area quad — no pixels. When tombstone waste
// or capacity is exceeded, `patch` returns false and the caller compacts via a
// full rebuild. Because a full rebuild is always the fallback, correctness never
// rides on the fast path.
//
// Scope: a SINGLE batch — the set must have no mesh-edge fills (which force the
// draw-order-preserving multi-batch split) and no per-wire scissor (paper content
// viewports). Mixed 2D/3D or scissored sets fall back to the batched path.
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
#![cfg(not(target_arch = "wasm32"))]
use super::wire_gpu::{emit_wire_native, wire_draw_depth, WireConst, WireGpu, WireInstance};
use crate::scene::model::wire_model::WireModel;
use crate::scene::ChangeKind;
use acadrust::Handle;
use iced::wgpu;
use rustc_hash::FxHashMap;
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// Spare capacity multiplier when (re)allocating, so a run of adds appends
/// without reallocating each time.
const HEADROOM_NUM: u64 = 3;
const HEADROOM_DEN: u64 = 2;
const MIN_INST_CAP: u64 = 4096;
const MIN_CONST_CAP: u64 = 1024;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
struct Slab {
inst_off: u32,
inst_len: u32,
const_off: u32,
const_len: u32,
}
pub struct WireArena {
inst_buf: wgpu::Buffer,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
inst_cap: u32,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
inst_tail: u32,
const_buf: wgpu::Buffer,
const_bind_group: std::sync::Arc<wgpu::BindGroup>,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
const_cap: u32,
const_tail: u32,
/// CPU mirror of the const buffer so a structural edit can refresh every
/// slab's draw_depth (denominator change) without re-emitting geometry.
consts_cpu: Vec<WireConst>,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
slabs: FxHashMap<Handle, Slab>,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// Tombstoned instances (blanked, not reclaimed) — past half the tail a patch
/// bails so the caller compacts with a full rebuild.
tombstoned: u32,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
}
fn handle_of(w: &WireModel) -> Option<Handle> {
crate::scene::Scene::handle_from_wire_name(&w.name)
}
/// True if the whole set is arena-eligible: a single batch that draws with no
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// viewport scissor — no mesh/solid fill and no per-wire scissor.
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
pub fn is_arena_eligible(wires: &[WireModel]) -> bool {
wires
.iter()
.all(|w| w.fill_tris.is_empty() && w.vp_scissor.is_none())
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// True when appending a new entity at the tail could change the image, so the
/// arena must fall back to a full rebuild instead of relocating a slab. Two
/// cases where draw order (not the z-bias) decides the winning pixel:
/// * transparency — alpha blends in submission order;
/// * a wire with NO draw-order depth — 3D solids (Solid3D / Region / Body /
/// Surface) are excluded from `draw_depth_map`, so their fallback edge wires
/// get draw_depth 0.0. Two coincident opaque such wires share a z-bias and
/// resolve by submission order, which a tail relocation would flip.
fn append_unsafe(wires: &[WireModel], depth_map: &FxHashMap<u64, f32>) -> bool {
wires.iter().any(|w| {
w.color[3] < 0.999
|| handle_of(w).map_or(true, |h| !depth_map.contains_key(&h.value()))
})
}
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
/// handle → wire-slot index for the selection / text-highlight overlays, built
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// from the resident Vec (independent of the arena's slab layout).
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
pub fn build_handle_index(wires: &[WireModel]) -> std::sync::Arc<FxHashMap<u64, Vec<u32>>> {
let mut index: FxHashMap<u64, Vec<u32>> = FxHashMap::default();
index.reserve(wires.len());
for (idx, w) in wires.iter().enumerate() {
if let Ok(h) = w.name.parse::<u64>() {
index.entry(h).or_default().push(idx as u32);
}
}
std::sync::Arc::new(index)
}
/// Group `wires` (draw-order sorted, entity-contiguous) into per-handle ranges.
fn handle_ranges(wires: &[WireModel]) -> Option<Vec<(Handle, usize, usize)>> {
let mut out: Vec<(Handle, usize, usize)> = Vec::new();
let mut i = 0;
while i < wires.len() {
let h = handle_of(&wires[i])?;
let mut j = i + 1;
while j < wires.len() && handle_of(&wires[j]) == Some(h) {
j += 1;
}
out.push((h, i, j));
i = j;
}
Some(out)
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
fn make_const_bg(
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
device: &wgpu::Device,
bgl: &wgpu::BindGroupLayout,
buf: &wgpu::Buffer,
) -> std::sync::Arc<wgpu::BindGroup> {
std::sync::Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("wire_arena.const.bg"),
layout: bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buf.as_entire_binding(),
}],
}))
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
fn alloc_inst(device: &wgpu::Device, cap: u64) -> wgpu::Buffer {
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("wire_arena.ibuf"),
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
size: cap * std::mem::size_of::<WireInstance>() as u64,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
fn alloc_const(device: &wgpu::Device, cap: u64) -> wgpu::Buffer {
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("wire_arena.cbuf"),
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
size: cap * std::mem::size_of::<WireConst>() as u64,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
fn blank_const() -> WireConst {
<WireConst as bytemuck::Zeroable>::zeroed()
}
/// A blank instance: zero-length segment at const slot 0 (half_width 0) — the
/// shader expands it to a zero-area quad, so it rasterises nothing.
fn blank_instance() -> WireInstance {
WireInstance {
pos_a: [0.0; 3],
pos_a_low: [0.0; 3],
pos_b: [0.0; 3],
pos_b_low: [0.0; 3],
distance_a: 0.0,
distance_b: 0.0,
wire_id: 0,
}
}
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
impl WireArena {
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
/// Build a fresh arena from the full resident set, or `None` if it isn't a
/// single scissor-free batch or a wire is unnamed (caller keeps the batched
/// path).
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
pub fn build(
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[WireModel],
depth_map: &FxHashMap<u64, f32>,
const_bgl: &wgpu::BindGroupLayout,
) -> Option<Self> {
if !is_arena_eligible(wires) {
return None;
}
let ranges = handle_ranges(wires)?;
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
// const slot 0 = blank tombstone target.
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let mut instances: Vec<WireInstance> = Vec::new();
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let mut consts_cpu: Vec<WireConst> = vec![blank_const()];
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let mut slabs: FxHashMap<Handle, Slab> = FxHashMap::default();
for (h, i, j) in ranges {
let inst_off = instances.len() as u32;
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let const_off = consts_cpu.len() as u32;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
for w in &wires[i..j] {
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let wire_id = consts_cpu.len() as u32;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let dd = wire_draw_depth(w, depth_map);
let (mut insts, cst) = emit_wire_native(w, wire_id, w.color, dd);
instances.append(&mut insts);
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
consts_cpu.push(cst);
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
}
slabs.insert(
h,
Slab {
inst_off,
inst_len: instances.len() as u32 - inst_off,
const_off,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
const_len: consts_cpu.len() as u32 - const_off,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
},
);
}
let inst_tail = instances.len() as u32;
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let const_tail = consts_cpu.len() as u32;
let inst_cap = ((inst_tail as u64 * HEADROOM_NUM / HEADROOM_DEN).max(MIN_INST_CAP)) as u32;
let const_cap =
((const_tail as u64 * HEADROOM_NUM / HEADROOM_DEN).max(MIN_CONST_CAP)) as u32;
let inst_buf = alloc_inst(device, inst_cap as u64);
let const_buf = alloc_const(device, const_cap as u64);
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
if inst_tail > 0 {
queue.write_buffer(&inst_buf, 0, bytemuck::cast_slice(&instances));
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
queue.write_buffer(&const_buf, 0, bytemuck::cast_slice(&consts_cpu));
let const_bind_group = make_const_bg(device, const_bgl, &const_buf);
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
Some(Self {
inst_buf,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
inst_cap,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
inst_tail,
const_buf,
const_bind_group,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
const_cap,
const_tail,
consts_cpu,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
slabs,
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
tombstoned: 0,
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
})
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
fn write_insts(&self, queue: &wgpu::Queue, off: u32, data: &[WireInstance]) {
if data.is_empty() {
return;
}
let sz = std::mem::size_of::<WireInstance>() as u64;
queue.write_buffer(&self.inst_buf, off as u64 * sz, bytemuck::cast_slice(data));
}
/// Apply the changed handles in place; returns false (⇒ full rebuild) when the
/// arena can't absorb the change: not eligible, a transparent append, a
/// capacity overflow, or too much tombstone waste.
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
pub fn patch(
&mut self,
queue: &wgpu::Queue,
changes: &[(Handle, ChangeKind)],
wires: &[WireModel],
depth_map: &FxHashMap<u64, f32>,
) -> bool {
if !is_arena_eligible(wires) {
return false;
}
let Some(ranges) = handle_ranges(wires) else {
return false;
};
let range_of: FxHashMap<Handle, (usize, usize)> =
ranges.into_iter().map(|(h, i, j)| (h, (i, j))).collect();
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let append_unsafe = append_unsafe(wires, depth_map);
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let mut structural = false;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
for &(h, kind) in changes {
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let new_range = range_of.get(&h).copied();
// Removed / now-hidden ⇒ tombstone the slab.
if matches!(kind, ChangeKind::Removed) || new_range.is_none() {
if let Some(slab) = self.slabs.remove(&h) {
let blanks = vec![blank_instance(); slab.inst_len as usize];
self.write_insts(queue, slab.inst_off, &blanks);
self.tombstoned += slab.inst_len;
structural = true;
}
continue;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let (i, j) = new_range.unwrap();
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let run = &wires[i..j];
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
// Emit into fresh, run-local const slots (patched to absolute below).
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let mut insts: Vec<WireInstance> = Vec::new();
let mut csts: Vec<WireConst> = Vec::new();
for w in run {
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let wire_id = csts.len() as u32;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
let dd = wire_draw_depth(w, depth_map);
let (mut wi, c) = emit_wire_native(w, wire_id, w.color, dd);
insts.append(&mut wi);
csts.push(c);
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
let inst_len = insts.len() as u32;
let const_len = csts.len() as u32;
let in_place = self
.slabs
.get(&h)
.map(|s| s.inst_len == inst_len && s.const_len == const_len)
.unwrap_or(false);
if in_place {
let (inst_off, const_off) = {
let s = self.slabs.get(&h).unwrap();
(s.inst_off, s.const_off)
};
for w in insts.iter_mut() {
w.wire_id += const_off;
}
self.write_insts(queue, inst_off, &insts);
for (k, c) in csts.iter().enumerate() {
self.consts_cpu[const_off as usize + k] = *c;
}
// Push the entity's consts to the GPU too — an in-place edit is
// NOT structural, so the whole-buffer refresh below won't run and
// a colour change would otherwise never reach the shader.
let csz = std::mem::size_of::<WireConst>() as u64;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
queue.write_buffer(
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
&self.const_buf,
const_off as u64 * csz,
bytemuck::cast_slice(&csts),
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
);
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
continue;
}
// Layout changed ⇒ append at the tail. Unsafe to relocate when the set
// resolves overlap by submission order (transparency, or a wire with
// no draw-order depth); fall back to a full rebuild instead.
if append_unsafe {
return false;
}
if self.inst_tail + inst_len > self.inst_cap
|| self.const_tail + const_len > self.const_cap
{
return false;
}
structural = true;
if let Some(s) = self.slabs.remove(&h) {
let blanks = vec![blank_instance(); s.inst_len as usize];
self.write_insts(queue, s.inst_off, &blanks);
self.tombstoned += s.inst_len;
}
let inst_off = self.inst_tail;
let const_off = self.const_tail;
for w in insts.iter_mut() {
w.wire_id += const_off;
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
self.write_insts(queue, inst_off, &insts);
for c in &csts {
self.consts_cpu.push(*c);
}
self.inst_tail += inst_len;
self.const_tail += const_len;
self.slabs.insert(
h,
Slab {
inst_off,
inst_len,
const_off,
const_len,
},
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
);
}
perf(render): arena handles add/remove too, not just in-place edits The in-place-only arena still fell back to a full batched rebuild on add/remove — re-uploading the entire wire instance buffer, which on a drawing whose wires expand to millions of segments (~265 MB) stalls ~1s per add/remove on a shared-memory GPU. Only move/edit avoided it. Add append/tombstone so add/remove patch the instance buffer too: a new or segment-count-changed entity bump-allocates a slab at the tail (buffers carry 1.5x headroom); an erased entity is tombstoned (blanked at const slot 0). The huge instance buffer only grows/blanks by the changed entity. Three correctness points make add/remove safe (each verified by an adversarial review, findings folded in): - draw_depth_map normalises each entity`s z-bias by the block entity count, so a structural change re-scales every entity`s bias. Keep a CPU mirror of the const array; on any structural change refresh every slab`s draw_depth and re-upload the small (~1 MB) const buffer, leaving the instance buffer untouched. - a tail-appended entity draws last, which only mis-orders wires whose overlap resolves by submission order rather than the z-bias: transparency, and 3D-solid fallback edges (Solid3D/Region/Body/Surface are excluded from draw_depth_map, so their opaque edges share depth 0). `append_unsafe` bails to a full rebuild when the set contains either. - an in-place edit also flushes its consts to the GPU (a colour change is not structural, so the whole-buffer refresh would otherwise skip it). Still behind OCS_WIRE_GPU_PATCH (default off); every uncertain case returns false and the caller does the identical full rebuild, so correctness never rides on the fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:37:27 +03:00
if structural {
// The entity count changed ⇒ draw_depth_map re-normalised every
// entity's z-bias. Refresh each live slab's draw_depth from the new
// depth map and re-upload the whole (small) const buffer; the instance
// buffer is untouched.
for (h, slab) in &self.slabs {
let dd = depth_map.get(&h.value()).copied().unwrap_or(0.0);
for k in 0..slab.const_len {
self.consts_cpu[(slab.const_off + k) as usize].draw_depth = dd;
}
}
queue.write_buffer(&self.const_buf, 0, bytemuck::cast_slice(&self.consts_cpu));
if self.tombstoned > self.inst_tail / 2 {
return false;
}
}
perf(render): in-place GPU wire arena for move/edit (phase 4, OCS_WIRE_GPU_PATCH) On a geometry edit the resident set gets a fresh content id, so the batched wire path (build_wire_buffers) re-emits and re-uploads EVERY wire instance — the whole buffer even when one line moved. Behind the OCS_WIRE_GPU_PATCH flag (default off, native only), add a persistent per-entity instance arena: one long-lived VERTEX|COPY_DST instance buffer + its WireConst storage, with a per-entity slab table. An in-place edit (move / rotate / scale / colour — same segment count) becomes a queue.write_buffer of just that entity`s slab. Scope is deliberately IN-PLACE ONLY. draw_depth_map normalises each entity`s draw-order z-bias by the block`s entity count, so an Add or Remove re-scales EVERY entity`s z-bias — which a per-slab patch can`t honour — and a segment-count change relocates the entity. patch() returns false for any of those (and for mesh-edge / scissored sets), and the caller falls back to the identical full batched rebuild, so correctness never rides on the fast path. An adversarial review of an earlier append/tombstone design surfaced exactly these draw-order hazards; restricting to in-place removes them while keeping the common interactive-edit win. The Scene hands the changed handles to the arena via model_wire_gpu_patch, recorded by try_resident_patch for the Model block and matched by content id in prepare(); scissored paper viewports and mixed 2D/3D sets keep the batched path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:02:25 +03:00
true
}
/// One draw batch wrapping the persistent instance buffer. `instance_count`
/// is the whole tail (tombstones included — they draw nothing).
pub fn wire_gpus(&self) -> Vec<WireGpu> {
if self.inst_tail == 0 {
return vec![];
}
vec![WireGpu {
instance_buffer: self.inst_buf.clone(),
instance_count: self.inst_tail,
vp_scissor: None,
is_3d_mesh_edge: false,
const_bind_group: Some(self.const_bind_group.clone()),
}]
}
}