perf(blocks): keep repeated block copies as one stub wire each

Every further reference to an already expanded block (a translated copy
of the same definition, at the top level or nested) was materialised as
a full copy of every wire, although the GPU already draws those copies
from one shared source with a per-copy offset. Such a copy is now a
single stub wire holding the shared prototype and its offset (stubs nest
inside prototypes, so nested repeats collapse too).

- The block renderer expands stubs into instance references while it
  groups by source, so drawing is unchanged.
- Interaction queries expand only the stubs near the query, so snapping,
  picking, window and crossing selection see each copy's geometry.
- Plot, boundary detection, viewport projection and entity_wires() get
  fully expanded wires; count and reference-edit recolouring expand the
  set while they are active.
- OCS_NO_BLOCK_STUBS turns it off.

A drawing with 480 references to a block of 100 nested 50-line blocks
(2.4M lines) now opens with 11k wires instead of 2.4M: peak memory
5.4 GB -> 0.37 GB, wire assembly 2.4 s -> 36 ms, interaction index
3.7 s -> 23 ms. The viewport, a plotted PDF, snaps and selections are
identical with and without stubs, and on five real drawings the view
renders the same.

Refs #1698, #1542

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-13 14:06:27 +03:00
1 parent 8d287decd8
commit 29c0d1df9a
14 files changed
+445 -52

No files matched your search

+17 -7
View File
@@ -71,35 +71,45 @@ fn block_edits_preserve_cache_coordinates_and_arena_partition() {
let original = pipeline.upload_block_wires(
&device,
&queue,
&[&first, &second],
&[(&first).into(), (&second).into()],
&depth,
&mut block_geometry,
);
let unchanged = pipeline.upload_block_wires(
&device,
&queue,
&[&first, &second],
&[(&first).into(), (&second).into()],
&depth,
&mut block_geometry,
);
assert_eq!(original[0].geometry_id(), unchanged[0].geometry_id());
assert_eq!(original[0].instance_count, 2);
let removed =
pipeline.upload_block_wires(&device, &queue, &[&second], &depth, &mut block_geometry);
let removed = pipeline.upload_block_wires(
&device,
&queue,
&[(&second).into()],
&depth,
&mut block_geometry,
);
assert_ne!(
original[0].geometry_id(),
removed[0].geometry_id(),
"removing the base instance must replace vertices baked at its old position"
);
let moved = block(2, 30.0);
let moved_gpu =
pipeline.upload_block_wires(&device, &queue, &[&moved], &depth, &mut block_geometry);
let moved_gpu = pipeline.upload_block_wires(
&device,
&queue,
&[(&moved).into()],
&depth,
&mut block_geometry,
);
assert_ne!(removed[0].geometry_id(), moved_gpu[0].geometry_id());
let restored = pipeline.upload_block_wires(
&device,
&queue,
&[&first, &second],
&[(&first).into(), (&second).into()],
&depth,
&mut block_geometry,
);
+7 -7
View File
@@ -2486,7 +2486,7 @@ impl Pipeline {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[&WireModel],
wires: &[wire_gpu::BlockRef<'_>],
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
block_geometry: &mut wire_gpu::BlockGeometryCache,
) -> Vec<BlockWireGpu> {
@@ -2620,7 +2620,7 @@ impl Pipeline {
// and alpha blending both depend on it. Scissor and mesh-edge stay
// grouping keys because the draw loop sets one scissor per batch and
let mut batches: Vec<WireGpu> = Vec::new();
let mut block_wires: Vec<&WireModel> = Vec::new();
let mut block_wires: Vec<wire_gpu::BlockRef<'_>> = Vec::new();
let mut i = 0;
while i < wires.len() {
let kind = wire_arena::classify_wire(&wires[i]);
@@ -2633,7 +2633,7 @@ impl Pipeline {
continue;
}
wire_arena::WireKind::Block => {
block_wires.push(&wires[i]);
wire_gpu::push_block_refs(&wires[i], &mut block_wires);
i += 1;
continue;
}
@@ -2721,7 +2721,7 @@ impl Pipeline {
Vec<CircleInstance>,
Vec<EllipseInstance>,
Vec<&'a WireModel>,
Vec<&'a WireModel>,
Vec<wire_gpu::BlockRef<'a>>,
) {
use crate::par::prelude::*;
// Pure per-wire work, so it fans out; `collect` on an indexed
@@ -2734,7 +2734,7 @@ impl Pipeline {
)> = wires
.par_iter()
.map(|&wire| {
if wire.render_instance.is_some() {
if wire.render_instance.is_some() || wire.stub.is_some() {
return (None, None, true);
}
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
@@ -2763,10 +2763,10 @@ impl Pipeline {
let mut circles: Vec<CircleInstance> = Vec::new();
let mut ellipses: Vec<EllipseInstance> = Vec::new();
let mut regular: Vec<&'a WireModel> = Vec::new();
let mut blocks: Vec<&'a WireModel> = Vec::new();
let mut blocks: Vec<wire_gpu::BlockRef<'a>> = Vec::new();
for (&wire, (wire_circles, wire_ellipses, is_block)) in wires.iter().zip(per) {
if is_block {
blocks.push(wire);
wire_gpu::push_block_refs(wire, &mut blocks);
continue;
}
let shaped = wire_circles.is_some() || wire_ellipses.is_some();
+4 -4
View File
@@ -146,7 +146,7 @@ pub fn classify_wire(wire: &WireModel) -> WireKind {
if !wire.display_visible {
return WireKind::Invisible;
}
if wire.render_instance.is_some() {
if wire.render_instance.is_some() || wire.stub.is_some() {
return WireKind::Block;
}
if !wire.tangent_geoms.is_empty()
@@ -176,7 +176,7 @@ pub fn classify_wire(wire: &WireModel) -> WireKind {
pub struct PartitionedWires<'a> {
pub regular: Vec<&'a WireModel>,
pub mesh: Vec<&'a WireModel>,
pub instanced: Vec<&'a WireModel>,
pub instanced: Vec<super::wire_gpu::BlockRef<'a>>,
pub circle_instances: Vec<super::circle_gpu::CircleInstance>,
pub ellipse_instances: Vec<super::ellipse_gpu::EllipseInstance>,
/// Handles that contributed to the retained analytical uploads.
@@ -213,11 +213,11 @@ pub fn partition_wires<'a>(
if !wire.display_visible {
continue;
}
if wire.render_instance.is_some() {
if wire.render_instance.is_some() || wire.stub.is_some() {
if let Some(handle) = handle_of(wire) {
contributors.insert(handle);
}
instanced.push(wire);
super::wire_gpu::push_block_refs(wire, &mut instanced);
continue;
}
// Resolve draw depth only for wires eligible for analytical extraction.
+96 -13
View File
@@ -1043,6 +1043,83 @@ pub fn fast_parse_u64(s: &str) -> Option<u64> {
/// wires that carry no document handle). A wire carrying a block-local
/// `depth_override` (a wide-polyline band inside a block) composes it into
/// the insert's own sub-range so the band orders against its block siblings.
/// One block-instanced wire to draw: a wire of the set, or a prototype wire
/// reached through a stub, moved by `delta` with its depth shifted by
/// `depth_delta` and owned (named, depth-ordered) by the stub `owner`.
#[derive(Clone, Copy)]
pub struct BlockRef<'a> {
pub wire: &'a WireModel,
pub delta: [f64; 3],
pub depth_delta: f32,
pub owner: Option<&'a WireModel>,
}
impl<'a> From<&'a WireModel> for BlockRef<'a> {
fn from(wire: &'a WireModel) -> Self {
Self {
wire,
delta: [0.0; 3],
depth_delta: 0.0,
owner: None,
}
}
}
impl<'a> BlockRef<'a> {
/// The wire whose name and visibility stand for this copy.
pub fn named(&self) -> &'a WireModel {
self.owner.unwrap_or(self.wire)
}
/// The copy's draw depth: its owner's slot, the prototype wire's
/// block-local depth shifted for this copy.
pub fn draw_depth(&self, depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>) -> f32 {
let base = fast_parse_u64(&self.named().name).and_then(|h| depth_map.get(&h).copied());
match (base, self.wire.depth_override) {
(Some([d, half]), Some(local)) => d + (local + self.depth_delta) * half,
(Some([d, _]), None) => d,
(None, _) => 0.0,
}
}
}
/// Push the block-instanced wires `wire` stands for: itself, or every copy a
/// stub holds (stubs inside stubs included).
pub fn push_block_refs<'a>(wire: &'a WireModel, out: &mut Vec<BlockRef<'a>>) {
fn walk<'a>(
wire: &'a WireModel,
delta: [f64; 3],
depth_delta: f32,
owner: &'a WireModel,
out: &mut Vec<BlockRef<'a>>,
) {
match &wire.stub {
Some(stub) => {
let delta = [
delta[0] + stub.delta[0],
delta[1] + stub.delta[1],
delta[2] + stub.delta[2],
];
for inner in stub.prototype.iter() {
walk(inner, delta, depth_delta + stub.depth_delta, owner, out);
}
}
None if wire.render_instance.is_some() => out.push(BlockRef {
wire,
delta,
depth_delta,
owner: Some(owner),
}),
None => {}
}
}
if wire.stub.is_some() {
walk(wire, [0.0; 3], 0.0, wire, out);
} else if wire.render_instance.is_some() {
out.push(BlockRef::from(wire));
}
}
pub(crate) fn wire_draw_depth(
wire: &WireModel,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
@@ -1121,7 +1198,7 @@ impl BlockWireGpu {
pub fn from_wires(
device: &wgpu::Device,
queue: &wgpu::Queue,
wires: &[&WireModel],
wires: &[BlockRef<'_>],
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
color_override: Option<[f32; 4]>,
target: BlockWireTarget<'_>,
@@ -1129,9 +1206,12 @@ impl BlockWireGpu {
) -> Vec<Self> {
let BlockWireTarget { const_bgl, mode } = target;
let mut slots: rustc_hash::FxHashMap<(u64, bool), usize> = rustc_hash::FxHashMap::default();
let mut groups: Vec<(bool, Vec<&WireModel>)> = Vec::new();
for &wire in wires {
if color_override.is_none() && !wire.display_visible {
let mut groups: Vec<(bool, Vec<BlockRef<'_>>)> = Vec::new();
for &block in wires {
let wire = block.wire;
if color_override.is_none()
&& !(wire.display_visible && block.named().display_visible)
{
continue;
}
let Some(instance) = wire.render_instance else {
@@ -1147,7 +1227,7 @@ impl BlockWireGpu {
groups.push((mesh_edge, Vec::new()));
slot
});
groups[slot].1.push(wire);
groups[slot].1.push(block);
}
let mut arena = BlockArenaBuilder::new(device, mode);
@@ -1157,7 +1237,10 @@ impl BlockWireGpu {
// visible rather than assumed.
let mut emitted_segments = 0usize;
for (mesh_edge, group) in groups {
let Some(&source) = group.first() else {
// A copy reached through a stub carries the prototype's
// geometry at the prototype's placement, which is what `base`
// means; its own `delta` only moves the instance.
let Some(source) = group.first().map(|block| block.wire) else {
continue;
};
let Some(base) = source.render_instance else {
@@ -1460,19 +1543,19 @@ impl BlockArenaBuilder {
/// Placements and draw depths relative to the group's first instance.
fn block_instances(
group: &[&WireModel],
group: &[BlockRef<'_>],
base: crate::scene::model::instance_model::RenderInstance,
mesh_edge: bool,
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
) -> Vec<BlockWireInstance> {
group
.iter()
.filter_map(|wire| {
let instance = wire.render_instance?;
.filter_map(|block| {
let instance = block.wire.render_instance?;
let delta = [
instance.translation[0] - base.translation[0],
instance.translation[1] - base.translation[1],
instance.translation[2] - base.translation[2],
instance.translation[0] + block.delta[0] - base.translation[0],
instance.translation[1] + block.delta[1] - base.translation[1],
instance.translation[2] + block.delta[2] - base.translation[2],
];
let high = delta.map(|value| value as f32);
Some(BlockWireInstance {
@@ -1486,7 +1569,7 @@ fn block_instances(
if mesh_edge {
0.0
} else {
wire_draw_depth(wire, depth_map)
block.draw_depth(depth_map)
},
0.0,
],