Residual scroll jank at 120 Hz (suspected Slint event-loop bug) needs per-frame + per-stage timing the logcat ring buffer can't hold. New cargo feature `perf` (default OFF — all sites are cfg-gated, zero overhead when off): - FR: per-frame app-side render cost + frame gap, from the rendering notifier (BeforeRendering/AfterRendering). - STG: event-loop stage costs (wake/timers/events/poll/lastrender) from timestamps instrumented into the pinned vendor copy of i-slint-backend-android-activity (stg.rs; Cargo.toml now depends on it directly + optional under `perf`). - DECODE: per-thumbnail-job cost on the workers. - All lines mirrored to /sdcard/Download/.gallery_perf.log (logcat rotates too fast at ~120 lines/s). Format + reading guide: docs/BUGS/scroll-jank-120hz.md.
49 lines
2.2 KiB
Rust
49 lines
2.2 KiB
Rust
//! STG (stage-timestamp) instrumentation for the jank hunt, 2026-10-28.
|
|
//!
|
|
//! The app's FR hook (rendering notifier) measures render cost and frame
|
|
//! gaps; this measures what happens AROUND rendering inside the event
|
|
//! loop: Choreographer wake latency, timer callbacks (the app's 8 ms
|
|
//! drain tick runs in `update_timers_and_animations`), and event
|
|
//! processing. Timestamps are plain atomics — the UI thread only does
|
|
//! atomic loads/stores here; the APP formats the log line from its own
|
|
//! tick (never log from the UI thread: logd backpressure).
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::Instant;
|
|
|
|
static T0: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
|
|
pub fn now_us() -> u64 {
|
|
T0.get_or_init(Instant::now).elapsed().as_micros() as u64
|
|
}
|
|
|
|
// Per-iteration stage marks (us since process start); reset at loop top.
|
|
pub static S_START: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_FIRST_EV: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_POLL_END: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_RENDER_START: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_RENDER_END: AtomicU64 = AtomicU64::new(0);
|
|
// Accumulated per-iteration costs.
|
|
pub static S_TIMER_US: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_EVENT_US: AtomicU64 = AtomicU64::new(0);
|
|
pub static S_N_EVENTS: AtomicU64 = AtomicU64::new(0);
|
|
// Full do_render duration of the PREVIOUS iteration (the AfterRendering
|
|
// hook fires mid-render, so the current iteration's render can't be read
|
|
// yet): covers renderer.draw PLUS the wgpu surface tail (flush_and_submit
|
|
// gr_context.submit + queue.present — the Mali driver calls).
|
|
pub static S_LAST_RENDER_US: AtomicU64 = AtomicU64::new(0);
|
|
|
|
/// One iteration of the event loop: start, first-event, poll-end,
|
|
/// render-start, render-end, timer-us, event-us, n-events.
|
|
pub fn snapshot() -> (u64, u64, u64, u64, u64, u64, u64, u64) {
|
|
(
|
|
S_START.load(Ordering::Relaxed),
|
|
S_FIRST_EV.load(Ordering::Relaxed),
|
|
S_POLL_END.load(Ordering::Relaxed),
|
|
S_RENDER_START.load(Ordering::Relaxed),
|
|
S_RENDER_END.load(Ordering::Relaxed),
|
|
S_TIMER_US.load(Ordering::Relaxed),
|
|
S_EVENT_US.load(Ordering::Relaxed),
|
|
S_N_EVENTS.load(Ordering::Relaxed),
|
|
)
|
|
}
|