forked from 1b-agent/opencadstudio-upstream
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:
1 parent
55022ae60e
commit
e37dfa5890
3 files changed
+61
-11
No files matched your search
@@ -266,6 +266,14 @@ the batched hatch pipeline (`hatch_batched_gpu.rs`) to wires:
|
|||||||
At 100 k wires that collapses thousands of draw calls into one. If iced
|
At 100 k wires that collapses thousands of draw calls into one. If iced
|
||||||
0.14's widget-pipeline limits allow, immediate win.
|
0.14's widget-pipeline limits allow, immediate win.
|
||||||
|
|
||||||
|
**Follow-up (landed): separate overlay buffer.** Command-preview / interim /
|
||||||
|
grip-drag wires were appended into the main wire buffer, so every drag frame
|
||||||
|
re-uploaded the entire (batched) base set. They now ride in their own small
|
||||||
|
per-frame `gpu_preview_wires` buffer drawn on top in the wire pass, so the
|
||||||
|
resident base buffer stays untouched during a drag — only the tiny overlay
|
||||||
|
re-uploads. Combined with the no-re-tess work, a grip/command drag now costs
|
||||||
|
one small overlay upload per move, nothing else.
|
||||||
|
|
||||||
### 3.4 Hardware instancing for repeated block inserts
|
### 3.4 Hardware instancing for repeated block inserts
|
||||||
|
|
||||||
When the same block defn is `INSERT`-ed N times (every door / window in
|
When the same block defn is `INSERT`-ed N times (every door / window in
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ pub struct Pipeline {
|
|||||||
wire_pixel_scissors: Vec<Option<[u32; 4]>>,
|
wire_pixel_scissors: Vec<Option<[u32; 4]>>,
|
||||||
/// Ghost copies (25% alpha) of selected wires for the X-ray depth pass.
|
/// Ghost copies (25% alpha) of selected wires for the X-ray depth pass.
|
||||||
gpu_selected_wires: Vec<WireGpu>,
|
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
|
/// Phase 4-B — single batched-hatch GPU resource. Drawn in one
|
||||||
/// indexed call with per-instance visibility masking the rest.
|
/// indexed call with per-instance visibility masking the rest.
|
||||||
/// Legacy per-hatch `Vec<HatchGpu>` + scissor / skip-flag plumbing
|
/// Legacy per-hatch `Vec<HatchGpu>` + scissor / skip-flag plumbing
|
||||||
@@ -882,6 +886,7 @@ impl Pipeline {
|
|||||||
gpu_wires: vec![],
|
gpu_wires: vec![],
|
||||||
wire_pixel_scissors: vec![],
|
wire_pixel_scissors: vec![],
|
||||||
gpu_selected_wires: vec![],
|
gpu_selected_wires: vec![],
|
||||||
|
gpu_preview_wires: vec![],
|
||||||
gpu_hatch_batched: None,
|
gpu_hatch_batched: None,
|
||||||
gpu_wipeouts: vec![],
|
gpu_wipeouts: vec![],
|
||||||
wipeout_skip_flags: vec![],
|
wipeout_skip_flags: vec![],
|
||||||
@@ -974,6 +979,22 @@ impl Pipeline {
|
|||||||
self.gpu_selected_wires = WireGpu::from_run(device, &selected, depth_map, None, false);
|
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.
|
/// 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.
|
/// 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) {
|
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 {
|
if scissor_active {
|
||||||
pass.set_scissor_rect(0, 0, vp.width, vp.height);
|
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) ────────
|
// ── Pass 6: wipeout fills (drawn after wires to mask them) ────────
|
||||||
|
|||||||
+24
-11
@@ -32,6 +32,10 @@ pub struct CameraState {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ViewportData {
|
pub struct ViewportData {
|
||||||
pub(super) wires: Arc<Vec<WireModel>>,
|
pub(super) wires: Arc<Vec<WireModel>>,
|
||||||
|
/// Live command-preview / interim / grip-drag overlay wires. Kept out of
|
||||||
|
/// the main `wires` buffer so a drag re-uploads only this small set each
|
||||||
|
/// frame, never the resident base buffer. Drawn on top in the wire pass.
|
||||||
|
pub(super) preview_wires: Arc<Vec<WireModel>>,
|
||||||
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
||||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||||
pub(super) face3d_wires: Arc<Vec<WireModel>>,
|
pub(super) face3d_wires: Arc<Vec<WireModel>>,
|
||||||
@@ -272,6 +276,10 @@ impl shader::Primitive for Primitive {
|
|||||||
);
|
);
|
||||||
inner.cached_selection = sel_key;
|
inner.cached_selection = sel_key;
|
||||||
}
|
}
|
||||||
|
// Live overlay (command preview / interim / grip drag) — small and
|
||||||
|
// refreshed every frame it's present, so a drag never re-uploads
|
||||||
|
// the resident base wire buffer.
|
||||||
|
inner.upload_preview_wires(device, &vp.preview_wires[..], &vp.draw_depths);
|
||||||
let vproj = vp.uniforms.view_proj;
|
let vproj = vp.uniforms.view_proj;
|
||||||
inner.compute_wire_scissors(vproj, clip_size.width, clip_size.height);
|
inner.compute_wire_scissors(vproj, clip_size.width, clip_size.height);
|
||||||
inner.compute_wipeout_scissors(vproj, clip_size.width, clip_size.height);
|
inner.compute_wipeout_scissors(vproj, clip_size.width, clip_size.height);
|
||||||
@@ -642,13 +650,14 @@ impl Scene {
|
|||||||
} else {
|
} else {
|
||||||
(self.model_wires_for_viewport_arc(inst.handle, full.height), None)
|
(self.model_wires_for_viewport_arc(inst.handle, full.height), None)
|
||||||
};
|
};
|
||||||
// Wire-buffer content id for the upload gate. Live preview / interim
|
// Wire-buffer content id for the upload gate. Preview / interim wires
|
||||||
// wires are appended below and vary frame-to-frame, so any frame
|
// are NOT part of this buffer anymore (they go in a separate per-frame
|
||||||
// carrying them forces a fresh upload, as do the non-tile paths.
|
// overlay buffer below), so the base id is the stable tile content gen
|
||||||
let has_overlay = self.interim_wire.is_some() || !self.preview_wires.is_empty();
|
// — a drag no longer re-uploads the whole base wire set every move.
|
||||||
|
// Non-tile paths have no stable id and force a re-upload.
|
||||||
let wire_content_id = match tile_wire_gen {
|
let wire_content_id = match tile_wire_gen {
|
||||||
Some(g) if !has_overlay => g,
|
Some(g) => g,
|
||||||
_ => {
|
None => {
|
||||||
let n = self.wire_force_nonce.get().wrapping_add(1);
|
let n = self.wire_force_nonce.get().wrapping_add(1);
|
||||||
self.wire_force_nonce.set(n);
|
self.wire_force_nonce.set(n);
|
||||||
n | (1u64 << 63)
|
n | (1u64 << 63)
|
||||||
@@ -676,12 +685,15 @@ impl Scene {
|
|||||||
(Arc::new(f), Arc::new(o))
|
(Arc::new(f), Arc::new(o))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Reuse the cached `other` Arc directly when no live preview/interim
|
// Base wire set — the cached `other` Arc directly, never cloned to
|
||||||
// wires need appending; otherwise clone once to extend.
|
// append overlays. Preview / interim wires ride in their own small
|
||||||
let all_wires = if !has_overlay {
|
// per-frame buffer so the (potentially huge) base buffer stays resident
|
||||||
other_arc
|
// and unchanged while a command preview or grip drag is live.
|
||||||
|
let all_wires = other_arc;
|
||||||
|
let preview_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
|
||||||
|
Arc::new(Vec::new())
|
||||||
} else {
|
} else {
|
||||||
let mut v = (*other_arc).clone();
|
let mut v: Vec<WireModel> = Vec::with_capacity(self.preview_wires.len() + 1);
|
||||||
if let Some(iw) = &self.interim_wire {
|
if let Some(iw) = &self.interim_wire {
|
||||||
v.push(iw.clone());
|
v.push(iw.clone());
|
||||||
}
|
}
|
||||||
@@ -749,6 +761,7 @@ impl Scene {
|
|||||||
|
|
||||||
Some(ViewportData {
|
Some(ViewportData {
|
||||||
wires: all_wires,
|
wires: all_wires,
|
||||||
|
preview_wires,
|
||||||
face3d_wires,
|
face3d_wires,
|
||||||
draw_depths: self.draw_depth_map(),
|
draw_depths: self.draw_depth_map(),
|
||||||
hatches,
|
hatches,
|
||||||
|
|||||||
Reference in new issue
Block a user