forked from 1b-agent/opencadstudio-upstream
bench(navigation): add viewport navigation and spatial candidate benchmarks
- Add Section 19 benchmarks covering camera pan viewport uniform updates, spatial hover candidate search, and viewport data assembly scaling - Add navigation telemetry probes in src/perf.rs (active only when PERF=1) to track frame interval, batched input moves, and layout-draw gap
This commit is contained in:
1 parent
af41825a33
commit
7d008c4914
6 files changed
+220
-4
No files matched your search
@@ -530,7 +530,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
let elapsed_us = started.elapsed().as_micros() as u64;
|
||||
crate::perf::note_update_time(elapsed_us);
|
||||
let elapsed_ms = elapsed_us as f64 / 1000.0;
|
||||
if elapsed_ms >= 5.0 {
|
||||
crate::perf_record!("[perf] update {:>7.1}ms message={perf_label}", elapsed_ms,);
|
||||
}
|
||||
|
||||
@@ -1201,6 +1201,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub(in crate::app) fn on_viewport_move(&mut self, p: Point) -> Task<Message> {
|
||||
crate::perf::note_pointer_move();
|
||||
// A ribbon dropdown is open over the viewport. Its backdrop
|
||||
// cannot swallow cursor motion — in iced 0.14 mouse_area/opaque
|
||||
// capture only button presses, never CursorMoved — so the move
|
||||
@@ -5683,6 +5684,7 @@ properties={:.1}ms picked={}",
|
||||
pub(super) fn on_viewport_scroll(&mut self, delta: mouse::ScrollDelta) -> Task<Message> {
|
||||
match scroll_intent(delta, self.touchpad_pan, self.ctrl_down) {
|
||||
ScrollIntent::Zoom { notches } => {
|
||||
crate::perf::note_scroll_zoom();
|
||||
let mut s =
|
||||
notches * crate::app::settings::zoom_notch_steps(self.zoom_factor);
|
||||
if self.zoom_wheel_reversed {
|
||||
|
||||
@@ -202,6 +202,7 @@ impl OpenCADStudio {
|
||||
crate::perf_record!("[perf] view {:>7.1}ms", elapsed_ms);
|
||||
}
|
||||
}
|
||||
crate::perf::note_view_finish();
|
||||
element
|
||||
}
|
||||
|
||||
|
||||
+64
@@ -10,6 +10,11 @@ const MAX_LINES: usize = 500;
|
||||
static UI_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
static ENV_ENABLED: OnceLock<bool> = OnceLock::new();
|
||||
static LINES: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
|
||||
static POINTER_MOVES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||
static SCROLL_ZOOMS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||
static UPDATE_TIME_US: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
static LAST_FRAME_INSTANT: Mutex<Option<iced::time::Instant>> = Mutex::new(None);
|
||||
static VIEW_FINISH_INSTANT: Mutex<Option<iced::time::Instant>> = Mutex::new(None);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
static STDERR_TX: OnceLock<std::sync::mpsc::SyncSender<String>> = OnceLock::new();
|
||||
|
||||
@@ -17,6 +22,65 @@ fn lines() -> &'static Mutex<VecDeque<String>> {
|
||||
LINES.get_or_init(|| Mutex::new(VecDeque::with_capacity(MAX_LINES)))
|
||||
}
|
||||
|
||||
/// Record a pointer move event received in update.
|
||||
pub fn note_pointer_move() {
|
||||
if enabled() {
|
||||
POINTER_MOVES.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a zoom event received in update.
|
||||
pub fn note_scroll_zoom() {
|
||||
if enabled() {
|
||||
SCROLL_ZOOMS.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulate time spent handling an update message in this batch.
|
||||
pub fn note_update_time(duration_us: u64) {
|
||||
if enabled() {
|
||||
UPDATE_TIME_US.fetch_add(duration_us, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark when view() finishes, to time the layout/draw gap until prepare().
|
||||
pub fn note_view_finish() {
|
||||
if enabled() {
|
||||
let mut guard = VIEW_FINISH_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
|
||||
*guard = Some(iced::time::Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure the gap between view() finish and prepare() start.
|
||||
pub fn take_view_to_prepare_ms() -> Option<f64> {
|
||||
if !enabled() {
|
||||
return None;
|
||||
}
|
||||
let mut guard = VIEW_FINISH_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
|
||||
guard.take().map(|t| t.elapsed().as_secs_f64() * 1000.0)
|
||||
}
|
||||
|
||||
/// Record a completed frame render and report frame-to-frame interval, batched moves, and update time.
|
||||
pub fn note_frame_render(encode_ms: f64) {
|
||||
if !enabled() {
|
||||
return;
|
||||
}
|
||||
let moves = POINTER_MOVES.swap(0, Ordering::Relaxed);
|
||||
let zooms = SCROLL_ZOOMS.swap(0, Ordering::Relaxed);
|
||||
let update_us = UPDATE_TIME_US.swap(0, Ordering::Relaxed);
|
||||
let mut guard = LAST_FRAME_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let now = iced::time::Instant::now();
|
||||
let prev = guard.replace(now);
|
||||
if let Some(prev) = prev {
|
||||
let interval_ms = (now - prev).as_secs_f64() * 1000.0;
|
||||
let update_ms = update_us as f64 / 1000.0;
|
||||
record(format_args!(
|
||||
"[perf] frame-interval {:>7.1}ms moves_batched={:<3} zooms={:<2} update_batch={:.2}ms encode={:.2}ms",
|
||||
interval_ms, moves, zooms, update_ms, encode_ms
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// True when tracing was requested with `PERF=1` or the in-app panel is open.
|
||||
pub fn enabled() -> bool {
|
||||
UI_ENABLED.load(Ordering::Relaxed)
|
||||
|
||||
@@ -379,6 +379,11 @@ impl shader::Primitive for Primitive {
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
let nav_prepare_started = iced::time::Instant::now();
|
||||
if let Some(gap_ms) = crate::perf::take_view_to_prepare_ms() {
|
||||
if gap_ms >= 1.0 {
|
||||
crate::perf_record!("[perf] layout-draw {:>7.1}ms", gap_ms);
|
||||
}
|
||||
}
|
||||
let errors_at_entry = crate::scene::pipeline::gpu_errors_seen();
|
||||
let recovering = pipeline.gpu_error_epoch != errors_at_entry;
|
||||
if recovering {
|
||||
@@ -1232,6 +1237,7 @@ retained_contributors={}",
|
||||
if inner.wire_arena_id == vp.wire_content_id
|
||||
&& inner.wire_cull_key != cull_key
|
||||
{
|
||||
let t_cull = iced::time::Instant::now();
|
||||
let mut visible = if inner.wire_arena_fallback_kind == Some(false) {
|
||||
inner.wire_arena_fallback.as_ref().clone()
|
||||
} else {
|
||||
@@ -1258,6 +1264,10 @@ retained_contributors={}",
|
||||
clip_size.height,
|
||||
));
|
||||
}
|
||||
let cull_ms = t_cull.elapsed().as_secs_f64() * 1000.0;
|
||||
if crate::perf::enabled() {
|
||||
crate::perf_record!("[perf] wire-cull {:>7.2}ms items={}", cull_ms, visible.len());
|
||||
}
|
||||
inner.gpu_wires = std::sync::Arc::new(visible);
|
||||
inner.wire_cull_key = cull_key;
|
||||
}
|
||||
@@ -1294,9 +1304,9 @@ retained_contributors={}",
|
||||
sample.started.elapsed().as_secs_f64() * 1000.0,
|
||||
self.viewports.len(),
|
||||
));
|
||||
} else if crate::perf::enabled() && prepare_ms >= 5.0 {
|
||||
} else if crate::perf::enabled() {
|
||||
crate::perf_record!(
|
||||
"[perf] frame-prepare {:>7.1}ms viewports={}",
|
||||
"[perf] frame-prepare {:>7.2}ms viewports={}",
|
||||
prepare_ms,
|
||||
self.viewports.len(),
|
||||
);
|
||||
@@ -1370,6 +1380,7 @@ retained_contributors={}",
|
||||
}
|
||||
}
|
||||
let render_ms = nav_render_started.elapsed().as_secs_f64() * 1000.0;
|
||||
crate::perf::note_frame_render(render_ms);
|
||||
if let Some(sample) = self.nav_perf {
|
||||
crate::perf::record(format_args!(
|
||||
"[perf] nav-render op={} space={} mode={} encode={:.2}ms elapsed={:.2}ms viewports={}",
|
||||
|
||||
Reference in new issue
Block a user