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 <[email protected]>
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
|
||||
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
|
||||
|
||||
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]>>,
|
||||
/// 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) ────────
|
||||
|
||||
+24
-11
@@ -32,6 +32,10 @@ pub struct CameraState {
|
||||
#[derive(Debug)]
|
||||
pub struct ViewportData {
|
||||
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
|
||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||
pub(super) face3d_wires: Arc<Vec<WireModel>>,
|
||||
@@ -272,6 +276,10 @@ impl shader::Primitive for Primitive {
|
||||
);
|
||||
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;
|
||||
inner.compute_wire_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 {
|
||||
(self.model_wires_for_viewport_arc(inst.handle, full.height), None)
|
||||
};
|
||||
// Wire-buffer content id for the upload gate. Live preview / interim
|
||||
// wires are appended below and vary frame-to-frame, so any frame
|
||||
// carrying them forces a fresh upload, as do the non-tile paths.
|
||||
let has_overlay = self.interim_wire.is_some() || !self.preview_wires.is_empty();
|
||||
// Wire-buffer content id for the upload gate. Preview / interim wires
|
||||
// are NOT part of this buffer anymore (they go in a separate per-frame
|
||||
// overlay buffer below), so the base id is the stable tile content gen
|
||||
// — 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 {
|
||||
Some(g) if !has_overlay => g,
|
||||
_ => {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
let n = self.wire_force_nonce.get().wrapping_add(1);
|
||||
self.wire_force_nonce.set(n);
|
||||
n | (1u64 << 63)
|
||||
@@ -676,12 +685,15 @@ impl Scene {
|
||||
(Arc::new(f), Arc::new(o))
|
||||
}
|
||||
};
|
||||
// Reuse the cached `other` Arc directly when no live preview/interim
|
||||
// wires need appending; otherwise clone once to extend.
|
||||
let all_wires = if !has_overlay {
|
||||
other_arc
|
||||
// Base wire set — the cached `other` Arc directly, never cloned to
|
||||
// append overlays. Preview / interim wires ride in their own small
|
||||
// per-frame buffer so the (potentially huge) base buffer stays resident
|
||||
// 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 {
|
||||
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 {
|
||||
v.push(iw.clone());
|
||||
}
|
||||
@@ -749,6 +761,7 @@ impl Scene {
|
||||
|
||||
Some(ViewportData {
|
||||
wires: all_wires,
|
||||
preview_wires,
|
||||
face3d_wires,
|
||||
draw_depths: self.draw_depth_map(),
|
||||
hatches,
|
||||
|
||||
Reference in new issue
Block a user