forked from 1b-agent/opencadstudio-upstream
chore(perf): profile frame pipeline CPU side
Adds ui_selection_overlay bench (benches/performance_benchmarks.rs + README metric 31): builds a representative selection overlay element (snap, crosshair, UCS tripod, 128 grips quick / 4096 capped full) and times widget-tree construction. Measured 0.4us typical, 2.4us at the cap (threshold 50us). Adds ribbon/statusbar section marks to the chrome bucket in view_main (src/app/view/mod.rs) so live PERF=1 view-detail traces attribute those two costs separately. Gated on the existing perf switch; no-op otherwise. Adds pub re-exports of CursorType/IsoPlane (src/app/mod.rs) for the bench fixture (same pattern as the existing grip re-exports). Ranked result over all ui_* benches: ribbon view construction ~556us (largest; follow-up already tracked), everything else under 50us or synthetic-fixture shapes. No new follow-up filed. GPU and canvas-draw costs are unmeasurable in headless runs (view() never executes there).
This commit is contained in:
1 parent
0f02a74eb1
commit
133bfddba9
4 files changed
+128
-5
No files matched your search
+2
-1
@@ -19,7 +19,7 @@ cargo bench --bench performance_benchmarks -- --quick
|
||||
|
||||
---
|
||||
|
||||
## Benchmark Suite Catalog (30 Metrics)
|
||||
## Benchmark Suite Catalog (31 Metrics)
|
||||
|
||||
### 1. Scene & Entity Management
|
||||
- `scene_entity_ingestion_10k`: Ingestion throughput for 10,000 mixed 2D entities (Lines, Circles, Arcs, Polylines).
|
||||
@@ -56,6 +56,7 @@ cargo bench --bench performance_benchmarks -- --quick
|
||||
- `ui_grip_budget`: Budgeted per-frame selection-grip projection (dense-polyline grips capped to `MAX_SELECTED_GRIPS`).
|
||||
- `ui_grip_budget_build`: Selection-change `apply_grip_budget` cost over the full grip set (sort + FxHashSet + filter).
|
||||
- `ui_constraint_glyphs`: Constraint-glyph cache hit (`cached_glyph_placements` key build + `Arc` clone over dozens of glyphs, no recompute).
|
||||
- `ui_selection_overlay`: Selection overlay Element construction (widget tree with capped grip count, no canvas draw).
|
||||
|
||||
### 6. State Management & Draworder
|
||||
- `selection_state_deep_clone`: Deep cloning of `SelectionState` (with 64 polygon points).
|
||||
|
||||
@@ -62,7 +62,9 @@ use OpenCADStudio::scene::{ChangeKind, Scene};
|
||||
use OpenCADStudio::snap::Snapper;
|
||||
use OpenCADStudio::ui::icons::{self, CHECK};
|
||||
use OpenCADStudio::ui::overlay::{
|
||||
grid_segments, should_reuse, GridCanvasState, GridKey, GridParams, GridStyle,
|
||||
grid_segments, selection_overlay, should_reuse, CrosshairOptions, GridCanvasState,
|
||||
GridKey, GridParams, GridStyle, GripMarker, OstTrackPoint, SelectionVisualOptions,
|
||||
UcsIconParams,
|
||||
};
|
||||
use OpenCADStudio::ui::properties::LinetypeItem;
|
||||
use OpenCADStudio::ui::ribbon::{LayerInfo, Ribbon};
|
||||
@@ -1362,9 +1364,9 @@ fn bench_ui_icon_caching(runner: &mut BenchmarkRunner) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 12b. Plot Style Layer-Usage Table Rebuild ───────────────────────────────
|
||||
// Covers Mission #19 `build_layer_usage`: layer names bucketed by ACI into a
|
||||
// 256-bucket table, rebuilt per Plot Style modal view.
|
||||
// ── Plot Style Layer-Usage Table Rebuild ─────────────────────────────────────
|
||||
// Covers `build_layer_usage`: layer names bucketed by ACI into a 256-bucket
|
||||
// table, rebuilt per Plot Style modal view.
|
||||
|
||||
fn bench_ui_plotstyle_layer_usage(runner: &mut BenchmarkRunner) {
|
||||
if !runner.should_run("ui_plotstyle_layer_usage") {
|
||||
@@ -1712,6 +1714,116 @@ fn bench_ui_constraint_glyphs(runner: &mut BenchmarkRunner) {
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_ui_selection_overlay(runner: &mut BenchmarkRunner) {
|
||||
if !runner.should_run("ui_selection_overlay") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fixture: the per-frame selection-overlay shape — an active snap,
|
||||
// crosshair, one UCS tripod, empty constraint glyphs. Grip count is the
|
||||
// scaling axis: typical selections carry dozens, the grip budget caps at
|
||||
// 4096, so full mode measures the cap. Element (widget-tree)
|
||||
// construction only; canvas draw/tessellation runs in the renderer.
|
||||
let n_grips = if runner.quick_mode { 128 } else { 4096 };
|
||||
// Warm-up for allocator settling.
|
||||
for _ in 0..10 {
|
||||
let _ = black_box(build_selection_overlay_element(n_grips));
|
||||
}
|
||||
|
||||
let n = if runner.quick_mode { 20 } else { 100 };
|
||||
let runs = 5;
|
||||
let mut samples = Vec::with_capacity(runs);
|
||||
|
||||
for _ in 0..runs {
|
||||
let t0 = Instant::now();
|
||||
for _ in 0..n {
|
||||
let elem = build_selection_overlay_element(black_box(n_grips));
|
||||
black_box(elem);
|
||||
}
|
||||
let per_us = (t0.elapsed().as_micros() as f64) / (n as f64);
|
||||
samples.push(per_us);
|
||||
}
|
||||
|
||||
let median_us = samples[samples.len() / 2];
|
||||
runner.record(
|
||||
"ui_selection_overlay",
|
||||
&format!(
|
||||
"Selection overlay Element construction with {} grips (widget tree, no draw)",
|
||||
n_grips
|
||||
),
|
||||
"µs",
|
||||
samples,
|
||||
Some((1_000_000.0 / median_us, "overlays/s")),
|
||||
Some(50.0), // Target < 50 µs (2.4 µs at the 4096-grip cap, ~20x headroom)
|
||||
);
|
||||
}
|
||||
|
||||
fn build_selection_overlay_element(
|
||||
n_grips: usize,
|
||||
) -> iced::Element<'static, OpenCADStudio::app::Message> {
|
||||
use OpenCADStudio::app::{CursorType, IsoPlane};
|
||||
use OpenCADStudio::scene::model::object::GripShape;
|
||||
use OpenCADStudio::scene::parametric_constraints::GlyphEntry;
|
||||
use OpenCADStudio::snap::SnapType;
|
||||
|
||||
let selection = Arc::new(std::cell::RefCell::new(SelectionState::default()));
|
||||
let grips: Vec<GripMarker> = (0..n_grips)
|
||||
.map(|i| GripMarker {
|
||||
pos: Point::new(100.0 + i as f32 * 5.0, 200.0),
|
||||
shape: GripShape::Square,
|
||||
is_hot: false,
|
||||
is_hovered: false,
|
||||
dir: None,
|
||||
})
|
||||
.collect();
|
||||
let ucs_icons = vec![UcsIconParams {
|
||||
view_proj: Mat4::IDENTITY,
|
||||
bounds: Rectangle { x: 0.0, y: 0.0, width: 1920.0, height: 1080.0 },
|
||||
axes: (Vec3::X, Vec3::Y, Vec3::Z),
|
||||
origin_screen: None,
|
||||
hover: false,
|
||||
selected: false,
|
||||
}];
|
||||
let empty_glyphs: Arc<[GlyphEntry]> = Arc::from([]);
|
||||
let empty_selected: Arc<[bool]> = Arc::from([]);
|
||||
selection_overlay(
|
||||
selection,
|
||||
Some((Point::new(400.0, 300.0), SnapType::Endpoint)),
|
||||
None,
|
||||
None,
|
||||
grips,
|
||||
None,
|
||||
None,
|
||||
ucs_icons,
|
||||
vec![OstTrackPoint { screen: Point::new(500.0, 500.0) }],
|
||||
vec![(Point::new(0.0, 0.0), Point::new(100.0, 100.0))],
|
||||
None,
|
||||
true,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[0.1, 0.1, 0.1, 1.0],
|
||||
CrosshairOptions {
|
||||
size_percent: 5,
|
||||
pick_box: 3,
|
||||
cursor_type: CursorType::Crosshair,
|
||||
color: None,
|
||||
isometric: false,
|
||||
iso_plane: IsoPlane::Top,
|
||||
snap_angle_deg: 0.0,
|
||||
point_mode: false,
|
||||
},
|
||||
SelectionVisualOptions::default(),
|
||||
empty_glyphs,
|
||||
empty_selected,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
// ── 13. Wide & Tapered Arc + Donut Tessellation ─────────────────────────────
|
||||
|
||||
fn bench_wide_and_tapered_arc_tessellation(runner: &mut BenchmarkRunner) {
|
||||
@@ -2216,6 +2328,7 @@ fn main() {
|
||||
bench_ui_statusbar_derived_data(&mut runner);
|
||||
bench_ui_grip_budget(&mut runner);
|
||||
bench_ui_constraint_glyphs(&mut runner);
|
||||
bench_ui_selection_overlay(&mut runner);
|
||||
bench_wide_and_tapered_arc_tessellation(&mut runner);
|
||||
bench_zoom_extents_calculation(&mut runner);
|
||||
bench_batch_entity_mutation(&mut runner);
|
||||
|
||||
@@ -50,6 +50,10 @@ pub use style_ops::StyleKind;
|
||||
/// private / `pub(crate)`; only these two names are reachable externally.
|
||||
pub use properties::apply_grip_budget;
|
||||
pub use settings::MAX_SELECTED_GRIPS;
|
||||
/// Re-exported for the `cargo bench` harness (`ui_selection_overlay`
|
||||
/// constructs `CrosshairOptions`, which names these types). Modules stay
|
||||
/// as they are; only these two names are reachable externally.
|
||||
pub use settings::{CursorType, IsoPlane};
|
||||
|
||||
use document::DocumentTab;
|
||||
|
||||
|
||||
@@ -2121,6 +2121,10 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
self.show_block_palette,
|
||||
));
|
||||
}
|
||||
// Split the `chrome` bucket so live `view-detail` traces show
|
||||
// ribbon vs status-bar construction separately (both gated on
|
||||
// PERF; zero cost otherwise).
|
||||
mark("ribbon");
|
||||
if self.show_file_tabs {
|
||||
col = col.push(doc_tab_bar(
|
||||
&self.tabs,
|
||||
@@ -2262,6 +2266,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
})
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
mark("statusbar");
|
||||
|
||||
// History labels are built only when their dropdown is actually open;
|
||||
// otherwise an empty slice short-circuits the overlay gate
|
||||
|
||||
Reference in new issue
Block a user