perf(scene): draw preview/grip overlay from a separate GPU buffer

Command-preview / interim / grip-drag wires were appended into the main
wire buffer (all_wires), so every drag frame re-packed and re-uploaded the
entire batched base set — O(N) per move even though only the small overlay
changed.

Split them out: build_primitive keeps `wires` as the cached base only and
passes the overlay as a separate `preview_wires` Arc. The base keeps the
stable tile content id (no per-frame nonce), so its buffer stays resident
and is not re-uploaded during a drag. The overlay uploads to its own small
gpu_preview_wires buffer each frame and draws on top in the wire pass
(same pipeline, same order/depth). The dragged entity still renders
highlighted via its selected-path tessellation.

With the no-re-tess changes, a grip / command drag now costs a single
small overlay upload per move and nothing else.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-05 10:57:38 +03:00
commit e37dfa5890
3 changed files with 61 additions and 11 deletions

View file

@ -81,6 +81,10 @@ pub struct Pipeline {
wire_pixel_scissors: Vec<Option<[u32; 4]>>,
/// Ghost copies (25% alpha) of selected wires for the X-ray depth pass.
gpu_selected_wires: Vec<WireGpu>,
/// Command-preview / interim / grip-drag overlay wires. Re-uploaded every
/// frame they are present (small), drawn on top of the base wire pass — so
/// a live drag never re-uploads the resident base buffer.
gpu_preview_wires: Vec<WireGpu>,
/// Phase 4-B — single batched-hatch GPU resource. Drawn in one
/// indexed call with per-instance visibility masking the rest.
/// Legacy per-hatch `Vec<HatchGpu>` + scissor / skip-flag plumbing
@ -882,6 +886,7 @@ impl Pipeline {
gpu_wires: vec![],
wire_pixel_scissors: vec![],
gpu_selected_wires: vec![],
gpu_preview_wires: vec![],
gpu_hatch_batched: None,
gpu_wipeouts: vec![],
wipeout_skip_flags: vec![],
@ -974,6 +979,22 @@ impl Pipeline {
self.gpu_selected_wires = WireGpu::from_run(device, &selected, depth_map, None, false);
}
/// Upload the live overlay (command preview / interim / grip-drag) wires.
/// Small and refreshed each frame they're present; kept separate from the
/// base wire buffer so a drag never re-uploads the resident base set.
pub fn upload_preview_wires(
&mut self,
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &rustc_hash::FxHashMap<u64, f32>,
) {
self.gpu_preview_wires = if wires.is_empty() {
vec![]
} else {
WireGpu::from_run(device, wires, depth_map, None, false)
};
}
/// Recompute pixel scissor rects for viewport-clipped wires from the current view_proj.
/// Called every frame from prepare() because scissor pixels shift with pan/zoom.
pub fn compute_wire_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) {
@ -1559,6 +1580,14 @@ impl Pipeline {
if scissor_active {
pass.set_scissor_rect(0, 0, vp.width, vp.height);
}
// Live overlay wires (command preview / interim / grip drag) on top
// of the base pass, same pipeline + depth test. No scissor.
for pw in &self.gpu_preview_wires {
if pw.instance_count > 0 {
pass.set_vertex_buffer(0, pw.instance_buffer.slice(..));
pass.draw(0..6, 0..pw.instance_count);
}
}
}
// ── Pass 6: wipeout fills (drawn after wires to mask them) ────────