forked from 1b-agent/opencadstudio-upstream
feat(scene): PERF frame-budget HUD for wire re-tessellation cost
Phase 5.3 (CPU tessellation slice). Add a `PERF` command that toggles an overlay on the active viewport showing the cost of the most recent wire re-tessellation: ms, wire count, geometry epoch. Anchored top-left via position_canvas_overlay. Timing is captured at the wire-cache miss paths (model_tile_wires_arc, paper_sheet_wires_arc) and stored in Cells on Scene, read directly by the UI view (Scene is single-threaded / Rc-based, so build_primitive and view share a thread). Reads ~0 ms on a warm cache, so it isolates the exact work a miss costs — the slice 2.2 / 3.1 / 3.3 each move, making them PR-to-PR comparable. GPU upload/draw/GPU-wait spans (need wgpu timestamp queries) remain open. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
7111922852
commit
9b112ce12c
5 files changed
+83
-3
No files matched your search
+10
-3
@@ -261,10 +261,17 @@ Opened "x.dwg" — 84321 entities — parse 1.2s, purge 80ms, caches 340ms, xref
|
||||
|
||||
Regressions are visible immediately.
|
||||
|
||||
### 5.3 Frame-budget HUD
|
||||
### 5.3 Frame-budget HUD ✅ DONE (CPU tess slice)
|
||||
|
||||
Add a CLI `PERF` toggle (or F12) that shows per-frame breakdown: tess ms,
|
||||
upload ms, draw ms, GPU wait ms. Makes PR-to-PR comparison trivial.
|
||||
A CLI `PERF` toggle overlays the cost of the most recent wire
|
||||
re-tessellation (ms + wire count + geometry epoch) on the active viewport,
|
||||
anchored top-left. Reads ~0 ms while the wire cache is warm (idle
|
||||
pan/zoom), so it isolates exactly the work a cache miss costs — the slice
|
||||
every render-path change (2.2 / 3.1 / 3.3) moves. Timed at the miss paths
|
||||
in `model_tile_wires_arc` / `paper_sheet_wires_arc` and stored on `Scene`.
|
||||
|
||||
Still open: GPU-side `upload` / `draw` / `GPU-wait` spans need wgpu
|
||||
timestamp queries; the CPU tessellation slice covers the current hot path.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,6 +48,18 @@ impl OpenCADStudio {
|
||||
return iced::exit();
|
||||
}
|
||||
|
||||
// ── Frame-budget HUD (Phase 5.3) ───────────────────────────────
|
||||
// Toggle the per-rebuild wire-tessellation readout overlay.
|
||||
"PERF" => {
|
||||
self.perf_hud = !self.perf_hud;
|
||||
self.command_line.push_info(if self.perf_hud {
|
||||
"PERF HUD on — shows last wire re-tessellation cost"
|
||||
} else {
|
||||
"PERF HUD off"
|
||||
});
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// ── Background color ───────────────────────────────────────────
|
||||
// Usage: BACKGROUND <r> <g> <b> (0–255 each)
|
||||
// BACKGROUND RESET (restore default)
|
||||
|
||||
@@ -186,6 +186,9 @@ pub(super) struct OpenCADStudio {
|
||||
/// Selection cycling: clicking where objects overlap opens a list box
|
||||
/// to pick which one; the pick is added to the current selection.
|
||||
selection_cycling: bool,
|
||||
/// Frame-budget HUD (Phase 5.3): overlays the last wire re-tessellation
|
||||
/// cost on the active viewport. Toggled by the `PERF` command.
|
||||
perf_hud: bool,
|
||||
/// When set, the cycling list box is open: (canvas point, candidates).
|
||||
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
|
||||
/// Which status-bar pills the user has chosen to show (persisted).
|
||||
@@ -1254,6 +1257,7 @@ impl OpenCADStudio {
|
||||
clean_screen: false,
|
||||
quick_properties: false,
|
||||
selection_cycling: false,
|
||||
perf_hud: false,
|
||||
cycle_candidates: None,
|
||||
pre_cmd_tangent: None,
|
||||
ortho_mode: false,
|
||||
|
||||
@@ -1014,6 +1014,48 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// Frame-budget HUD (Phase 5.3): toggle with the PERF command. Shows
|
||||
// the cost of the most recent wire re-tessellation — the work avoided
|
||||
// by a warm wire cache — so render-path changes can be compared
|
||||
// PR-to-PR. Reads ~0 ms while panning/zooming on a hit cache.
|
||||
if self.perf_hud && !tab.is_start {
|
||||
let s = &tab.scene;
|
||||
let label = format!(
|
||||
"tess {:.1} ms · {} wires · epoch {}",
|
||||
s.last_tess_ms.get(),
|
||||
s.last_tess_wires.get(),
|
||||
s.geometry_epoch,
|
||||
);
|
||||
let panel = container(text(label).size(12).color(Color {
|
||||
r: 0.6,
|
||||
g: 1.0,
|
||||
b: 0.6,
|
||||
a: 1.0,
|
||||
}))
|
||||
.padding(6)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
r: 0.08,
|
||||
g: 0.08,
|
||||
b: 0.08,
|
||||
a: 0.85,
|
||||
})),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
viewport_stack = viewport_stack
|
||||
.push(position_canvas_overlay(iced::Point::new(12.0, 40.0), panel.into()));
|
||||
}
|
||||
|
||||
// Selection-cycling list box: pick among overlapping objects.
|
||||
if let Some((pt, cands)) = &self.cycle_candidates {
|
||||
if !tab.is_start {
|
||||
|
||||
@@ -712,6 +712,13 @@ pub struct Scene {
|
||||
/// without depending on the shader widget's internal `Program::State`
|
||||
/// (which can miss events under overlapping overlays).
|
||||
pub viewcube_hover: std::cell::Cell<Option<usize>>,
|
||||
/// Wall time (ms) of the most recent wire re-tessellation — the work done
|
||||
/// on a wire-cache miss in `model_tile_wires_arc` / `paper_sheet_wires_arc`.
|
||||
/// Stays at the last value while the cache is hit (idle pan/zoom on a warm
|
||||
/// cache reads ~0). Surfaced by the frame-budget HUD (Phase 5.3).
|
||||
pub(crate) last_tess_ms: std::cell::Cell<f32>,
|
||||
/// Wire count produced by that most recent re-tessellation.
|
||||
pub(crate) last_tess_wires: std::cell::Cell<usize>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
@@ -769,6 +776,8 @@ impl Scene {
|
||||
last_render_aspect: std::cell::Cell::new(16.0 / 9.0),
|
||||
last_world_per_pixel: std::cell::Cell::new(0.0),
|
||||
viewcube_hover: std::cell::Cell::new(None),
|
||||
last_tess_ms: std::cell::Cell::new(0.0),
|
||||
last_tess_wires: std::cell::Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1570,7 +1579,10 @@ impl Scene {
|
||||
None
|
||||
};
|
||||
let block = self.model_space_block_handle();
|
||||
let t_tess = std::time::Instant::now();
|
||||
let arc = Arc::new(self.wires_for_block_culled(block, view_aabb, wpp, None, None));
|
||||
self.last_tess_ms.set(t_tess.elapsed().as_secs_f32() * 1000.0);
|
||||
self.last_tess_wires.set(arc.len());
|
||||
self.model_tile_wire_cache
|
||||
.borrow_mut()
|
||||
.insert(tile_idx, (key, Arc::clone(&arc)));
|
||||
@@ -1590,7 +1602,10 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let t_tess = std::time::Instant::now();
|
||||
let mut wires = self.wires_for_block(layout_block);
|
||||
self.last_tess_ms.set(t_tess.elapsed().as_secs_f32() * 1000.0);
|
||||
self.last_tess_wires.set(wires.len());
|
||||
// The overall "sheet" viewport now IS the paper view itself, so its own
|
||||
// border rectangle must not be drawn as an entity on the sheet.
|
||||
let sheet = self.current_layout_sheet_viewport_handle();
|
||||
|
||||
Reference in new issue
Block a user