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
|
### 1. Scene & Entity Management
|
||||||
- `scene_entity_ingestion_10k`: Ingestion throughput for 10,000 mixed 2D entities (Lines, Circles, Arcs, Polylines).
|
- `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`: 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_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_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
|
### 6. State Management & Draworder
|
||||||
- `selection_state_deep_clone`: Deep cloning of `SelectionState` (with 64 polygon points).
|
- `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::snap::Snapper;
|
||||||
use OpenCADStudio::ui::icons::{self, CHECK};
|
use OpenCADStudio::ui::icons::{self, CHECK};
|
||||||
use OpenCADStudio::ui::overlay::{
|
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::properties::LinetypeItem;
|
||||||
use OpenCADStudio::ui::ribbon::{LayerInfo, Ribbon};
|
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 ───────────────────────────────
|
// ── Plot Style Layer-Usage Table Rebuild ─────────────────────────────────────
|
||||||
// Covers Mission #19 `build_layer_usage`: layer names bucketed by ACI into a
|
// Covers `build_layer_usage`: layer names bucketed by ACI into a 256-bucket
|
||||||
// 256-bucket table, rebuilt per Plot Style modal view.
|
// table, rebuilt per Plot Style modal view.
|
||||||
|
|
||||||
fn bench_ui_plotstyle_layer_usage(runner: &mut BenchmarkRunner) {
|
fn bench_ui_plotstyle_layer_usage(runner: &mut BenchmarkRunner) {
|
||||||
if !runner.should_run("ui_plotstyle_layer_usage") {
|
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 ─────────────────────────────
|
// ── 13. Wide & Tapered Arc + Donut Tessellation ─────────────────────────────
|
||||||
|
|
||||||
fn bench_wide_and_tapered_arc_tessellation(runner: &mut BenchmarkRunner) {
|
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_statusbar_derived_data(&mut runner);
|
||||||
bench_ui_grip_budget(&mut runner);
|
bench_ui_grip_budget(&mut runner);
|
||||||
bench_ui_constraint_glyphs(&mut runner);
|
bench_ui_constraint_glyphs(&mut runner);
|
||||||
|
bench_ui_selection_overlay(&mut runner);
|
||||||
bench_wide_and_tapered_arc_tessellation(&mut runner);
|
bench_wide_and_tapered_arc_tessellation(&mut runner);
|
||||||
bench_zoom_extents_calculation(&mut runner);
|
bench_zoom_extents_calculation(&mut runner);
|
||||||
bench_batch_entity_mutation(&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.
|
/// private / `pub(crate)`; only these two names are reachable externally.
|
||||||
pub use properties::apply_grip_budget;
|
pub use properties::apply_grip_budget;
|
||||||
pub use settings::MAX_SELECTED_GRIPS;
|
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;
|
use document::DocumentTab;
|
||||||
|
|
||||||
|
|||||||
@@ -2121,6 +2121,10 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
self.show_block_palette,
|
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 {
|
if self.show_file_tabs {
|
||||||
col = col.push(doc_tab_bar(
|
col = col.push(doc_tab_bar(
|
||||||
&self.tabs,
|
&self.tabs,
|
||||||
@@ -2262,6 +2266,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
})
|
})
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill);
|
.height(Fill);
|
||||||
|
mark("statusbar");
|
||||||
|
|
||||||
// History labels are built only when their dropdown is actually open;
|
// History labels are built only when their dropdown is actually open;
|
||||||
// otherwise an empty slice short-circuits the overlay gate
|
// otherwise an empty slice short-circuits the overlay gate
|
||||||
|
|||||||
Reference in new issue
Block a user