120Hz: early waker thread + render freshness guard (vsync-edge pacing)
The Choreographer vsync callback reaches the UI thread only 2.5-6 ms AFTER the vsync edge (measured on the phone). With a 2.5-5 ms Skia render plus present, that left no phase margin inside the 8.3 ms slot: 51% of frames landed one slot late (judder) and ~4% two slots late (hitches), on every build tested so far. Attempted fixes, all measured and retired (see commit history): - render-ahead hold (present previous frame at next render start): the held present lands after the NEXT wake's 3.2-6 ms Choreographer latency -> 52% one-slot late. - present-at-end + present GATE (sleep on the UI thread to quantize the present to the vsync grid): the gate sleep (2.5-4 ms) added to the slot budget (wake 5.3 + core 3 + gate 3 + present 1 = 12.3 ms > 8.3 ms) -> cadence collapsed to 60 Hz. Working fix (this commit): - vsync.rs: an 'early waker' thread learns the vsync grid from the callback's frame_time_nanos (vkprobe::note_vsync_edge) and wakes the main looper ~0.8 ms BEFORE each edge while animating. Render start moves from edge+2.5..6 to edge-0.8..+0.5, so the present lands at a natural ~edge+3..5 ms phase with no UI-thread sleep. - lib.rs: a render freshness guard (min gap = period - 1.5 ms) makes the FIRST wake of each slot (early or the late Choreographer fallback) render and rejects the second (double render = 60 Hz collapse). Threshold sits between late-after-early (5.8 ms, reject) and early-after-late (7.5 ms, accept). - lib.rs: locked renders no longer consume pending_redraw (it carried the motion invalidations; consuming it starved the follow-up unlocked iteration -> 60 Hz collapse). - stg.rs: IT lines now log the render decision (locked/saw_wake/ pr_before/do_render); vkprobe logs gate sleep (GATE_US, now 0). Measured (phone, 8580-file grid, slow 4 s drags): in-burst cadence: 8.29-8.44 ms median, p90 8.6-10.2 ms (was 8.36 median / 10.2 p90 with 3.7-20% two-slot gaps) in-burst >16.7 ms gaps: 0-2 per burst (was 3-13) present-to-present: 27% exact one slot, 33% one-slot-late steady state (imvisible), 3% f-jumps (Skia core 2.5-5 ms variance). Residual judder lever: Skia core cost variance (next step). Test hygiene reminders (HANDOFF 8.10): verify ResumedActivity, keep the task clean (sz=1), record the perf-log byte offset before each run (the file accumulates across process launches).
This commit is contained in:
parent
99d155e859
commit
7c2d0f354e
|
|
@ -268,22 +268,91 @@ impl i_slint_core::platform::Platform for AndroidPlatform {
|
|||
// to one slot of extra input latency.
|
||||
let do_render = if locked {
|
||||
// Present EVERY vsync while animating (not only on content
|
||||
// change): the regular 120 Hz present stream keeps the Mali
|
||||
// present path warm (PRE ≈ 0.4 ms) and locks the display
|
||||
// cadence. Re-rendering unchanged content is cheap with
|
||||
// Skia's incremental pipeline.
|
||||
self.window.pending_redraw.take();
|
||||
saw_wake
|
||||
// change): the regular 120 Hz present stream locks the
|
||||
// display cadence. Re-rendering unchanged content is cheap
|
||||
// with Skia's incremental pipeline.
|
||||
//
|
||||
// IMPORTANT: do NOT `pending_redraw.take()` here. The render
|
||||
// is driven by the vsync wake, but the pending flag also
|
||||
// carries the motion events' invalidations. Consuming it
|
||||
// here starves the follow-up unlocked iteration (which runs
|
||||
// 0.4 ms later, before the next motion event arrives) of
|
||||
// anything to render, collapsing the cadence to 60 Hz
|
||||
// (one render per two vsyncs). Letting the flag survive
|
||||
// means the next iteration renders too; if the content is
|
||||
// unchanged the extra render is invisible.
|
||||
//
|
||||
// FRESHNESS GUARD: with the early waker (vsync.rs), TWO
|
||||
// wakes can arrive per slot — the early one (~0.8 ms BEFORE
|
||||
// the vsync edge) and the Choreographer's late fallback
|
||||
// (~5 ms after it). The first one of each slot renders;
|
||||
// the second must NOT (double render = 2× the slot budget =
|
||||
// 60 Hz collapse). The intervals (measured):
|
||||
// late-after-early = ~5.8 ms → must be REJECTED
|
||||
// early-after-late = ~7.5 ms → must be ACCEPTED
|
||||
// same-kind = ~8.33 ms → ACCEPTED
|
||||
// Threshold `period − 1.5 ms` (≈6.8 ms) sits between 5.8
|
||||
// and 7.5 with ~0.7-1.0 ms of jitter margin on both sides.
|
||||
// (A 0.7-period threshold rejects early-after-late and a
|
||||
// 1.0-period threshold locks the cadence onto the late
|
||||
// wakes — both measured to fragment the burst.)
|
||||
let __now_ns = i_slint_renderer_skia::wgpu_30_surface::vkprobe::monotonic_ns();
|
||||
let __period_ns = i_slint_renderer_skia::wgpu_30_surface::vkprobe::VSYNC_PERIOD_NS
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let __last = i_slint_renderer_skia::wgpu_30_surface::vkprobe::LAST_RENDER_START_NS
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let __min_gap = __period_ns.saturating_sub(1_500_000);
|
||||
let __fresh = __period_ns == 0
|
||||
|| __now_ns.saturating_sub(__last) >= __min_gap;
|
||||
if __fresh {
|
||||
i_slint_renderer_skia::wgpu_30_surface::vkprobe::LAST_RENDER_START_NS
|
||||
.store(__now_ns, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
saw_wake && __fresh
|
||||
} else {
|
||||
self.window.pending_redraw.get()
|
||||
};
|
||||
if do_render {
|
||||
// Locked (vsync-wake) renders leave the flag set so the
|
||||
// follow-up unlocked iteration renders too (60 Hz collapse,
|
||||
// see the do_render note); unlocked renders consume it.
|
||||
if !locked {
|
||||
self.window.pending_redraw.take();
|
||||
// RENDER_AHEAD: activate the hold-without-present pipeline
|
||||
// only for locked (animating) renders; an unlocked render
|
||||
// presents immediately (see RENDER_AHEAD_ACTIVE).
|
||||
}
|
||||
// RENDER_AHEAD (hold-without-present) is DISABLED: the held
|
||||
// frame is presented at the start of the NEXT render, i.e.
|
||||
// after the next vsync wake's Choreographer latency
|
||||
// (measured 3.2–6 ms on this device) plus the present cost —
|
||||
// landing at vsync+12.4–15.2 ms against a 13.8 ms latch
|
||||
// deadline: a 1.4 ms margin that missed 52% of frames
|
||||
// (one slot late) during real slow scrolls. Presenting at
|
||||
// the END of the render instead still left 51% one slot
|
||||
// late (the wake-arrival latency 2.4–6 ms + render 3–9 ms
|
||||
// make the present phase random). The actual fix is the
|
||||
// PRESENT GATE: the render still runs on the vsync wake
|
||||
// (locked pacing kept), but `Queue::present` sleeps to a
|
||||
// fixed vsync-grid phase (next edge + 1.5 ms, from the
|
||||
// Choreographer frameTimeNanos), so cadence is exactly one
|
||||
// period. See `vkprobe::present_gate` and
|
||||
// docs/BUGS/scroll-jank-120hz.md §9.
|
||||
i_slint_renderer_skia::wgpu_30_surface::vkprobe::RENDER_AHEAD_ACTIVE
|
||||
.store(locked, std::sync::atomic::Ordering::Relaxed);
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
// PRESENT GATE: quantize the present to the vsync grid while
|
||||
// animating (drag/fling) — including the follow-up UNLOCKED
|
||||
// renders (see the do_render note above), so every present
|
||||
// in the burst lands on the same grid phase. One-off
|
||||
// redraws (taps, model updates) stay ungated for minimum
|
||||
// latency.
|
||||
// PRESENT GATE: DISABLED. Sleeping on the UI thread to
|
||||
// quantize the present proved counterproductive — every ms
|
||||
// of gate sleep adds to the slot budget (wake 5.3 + core 3
|
||||
// + gate 3 + present 1 = 12.3 ms > 8.3 ms slot → 60 Hz
|
||||
// collapse, measured). The early waker (vsync.rs) instead
|
||||
// moves the RENDER START to the vsync edge, so the present
|
||||
// lands at a constant natural phase (~edge+3 ms) with no
|
||||
// sleep at all. The gate code stays for reference.
|
||||
i_slint_renderer_skia::wgpu_30_surface::vkprobe::PRESENT_GATE_ON
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
stg::S_RENDER_START.store(t2, std::sync::atomic::Ordering::Relaxed);
|
||||
self.window.do_render()?;
|
||||
let t3 = stg::now_us();
|
||||
|
|
@ -320,6 +389,7 @@ impl i_slint_core::platform::Platform for AndroidPlatform {
|
|||
stg::input_snapshot(),
|
||||
stg::timeout_snapshot(),
|
||||
stg::wake_snapshot(),
|
||||
(locked, saw_wake, pr_before, do_render),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ pub fn log_iter(
|
|||
inp: (u32, u64, u64),
|
||||
tocls: (u64, u64, u64),
|
||||
wk: (u64, u64, u64),
|
||||
dec: (bool, bool, bool, bool),
|
||||
) {
|
||||
// Jank-hunt fix: emit through the log facade (nblog) instead of a private
|
||||
// BufWriter — two independent buffers appending to the same file made IT
|
||||
|
|
@ -249,9 +250,10 @@ pub fn log_iter(
|
|||
let live_rows = i_slint_core::model::repeater::LV_LIVE_ROWS
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
log::info!(
|
||||
"IT {} poll={}us nev={} fe={}us timers={}us ev={}us render={}us anim={} to={}ms lv1={}/{} e={}/{}/{} ent={}/{} lv2={}/{} flk={}/{} inb={}/{} toc={}/{}/{} wk={}/{}/{} live={}",
|
||||
"IT {} poll={}us nev={} fe={}us timers={}us ev={}us render={}us anim={} to={}ms lv1={}/{} e={}/{}/{} ent={}/{} lv2={}/{} flk={}/{} inb={}/{} toc={}/{}/{} wk={}/{}/{} live={} dec={}/{}/{}/{}",
|
||||
start_us, poll_us, nev, first_ev_us, timer_us, event_us, render_us, animating as u8, timeout_ms,
|
||||
lv1.0, lv1.1, lv1.2, lv1.3, lv1.4, lv1.5, lv1.6, lv2.0, lv2.1, flk.0, flk.1, inp.0, inp.2, tocls.0, tocls.1, tocls.2, wk.0, wk.1, wk.2, live_rows
|
||||
lv1.0, lv1.1, lv1.2, lv1.3, lv1.4, lv1.5, lv1.6, lv2.0, lv2.1, flk.0, flk.1, inp.0, inp.2, tocls.0, tocls.1, tocls.2, wk.0, wk.1, wk.2, live_rows,
|
||||
dec.0 as u8, dec.1 as u8, dec.2 as u8, dec.3 as u8
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ struct State {
|
|||
pub struct VsyncDriver {
|
||||
shared: Arc<Shared>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
waker_handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl VsyncDriver {
|
||||
|
|
@ -63,7 +64,7 @@ impl VsyncDriver {
|
|||
cond: Condvar::new(),
|
||||
driving: AtomicBool::new(false),
|
||||
looper: Mutex::new(None),
|
||||
main_waker,
|
||||
main_waker: main_waker.clone(),
|
||||
choreographer: std::ptr::null_mut(),
|
||||
});
|
||||
let handle = std::thread::Builder::new()
|
||||
|
|
@ -73,7 +74,28 @@ impl VsyncDriver {
|
|||
move || vsync_thread(shared)
|
||||
})
|
||||
.ok();
|
||||
Self { shared, handle }
|
||||
// EARLY WAKER (the real 120Hz fix, part 2): the Choreographer
|
||||
// callback only reaches the UI thread ~2.5-6 ms AFTER the vsync
|
||||
// edge — with a 2.5-5 ms Skia render plus present, that leaves
|
||||
// no phase margin inside the 8.3 ms slot (measured: 51% of frames
|
||||
// landed one slot late). This thread learns the vsync grid from
|
||||
// the callback's `frame_time_nanos` (vkprobe::note_vsync_edge) and
|
||||
// wakes the main loop slightly BEFORE each edge, so render start ≈
|
||||
// the edge and present lands at a constant ~edge+3 ms phase.
|
||||
// The Choreographer's late wake remains as fallback (the render
|
||||
// freshness guard in the event loop prevents double renders).
|
||||
let waker_handle = std::thread::Builder::new()
|
||||
.name("slint early-waker".into())
|
||||
.spawn({
|
||||
let shared = shared.clone();
|
||||
move || early_waker_thread(shared)
|
||||
})
|
||||
.ok();
|
||||
Self {
|
||||
shared,
|
||||
handle,
|
||||
waker_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_driving(&self) -> bool {
|
||||
|
|
@ -112,6 +134,62 @@ impl Drop for VsyncDriver {
|
|||
if let Some(handle) = self.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
if let Some(handle) = self.waker_handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wakes the main loop a few hundred microseconds before each vsync edge
|
||||
/// (see the `early_waker_handle` docs in `VsyncDriver::new`). Runs at 120 Hz
|
||||
/// only while animating; otherwise parks in short sleeps.
|
||||
fn early_waker_thread(shared: Arc<Shared>) {
|
||||
let waker = shared.main_waker.clone();
|
||||
// Wake slightly BEFORE the edge so poll-returns + render-start lands on
|
||||
// the edge; with a 2.5-5 ms Skia render the present then sits at a
|
||||
// constant ~edge+3 ms (middle of the slot, ~2.5 ms of deadline margin
|
||||
// both ways).
|
||||
const LEAD_NS: u64 = 800_000;
|
||||
loop {
|
||||
{
|
||||
let state = shared.state.lock().unwrap();
|
||||
if state.quit {
|
||||
return;
|
||||
}
|
||||
if !state.animating {
|
||||
drop(state);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let edge = i_slint_renderer_skia::wgpu_30_surface::vkprobe::VSYNC_EDGE_NS
|
||||
.load(Ordering::Relaxed);
|
||||
let period = i_slint_renderer_skia::wgpu_30_surface::vkprobe::VSYNC_PERIOD_NS
|
||||
.load(Ordering::Relaxed);
|
||||
let now = i_slint_renderer_skia::wgpu_30_surface::vkprobe::monotonic_ns();
|
||||
// Grid unknown or stale (no callbacks for ~100 ms, e.g. the app
|
||||
// went to the background): park briefly and re-check.
|
||||
if edge == 0 || period == 0 || now == 0 || now > edge + 100_000_000 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
// Next edge at/after (now + LEAD).
|
||||
let mut target = edge + period;
|
||||
while target < now.saturating_add(LEAD_NS) {
|
||||
target += period;
|
||||
}
|
||||
let delay = target.saturating_sub(now + LEAD_NS);
|
||||
if delay > 0 {
|
||||
std::thread::sleep(std::time::Duration::from_nanos(delay));
|
||||
}
|
||||
let landed = i_slint_renderer_skia::wgpu_30_surface::vkprobe::monotonic_ns()
|
||||
.saturating_sub(target);
|
||||
// Wake only if we landed within ~1 ms of the edge. If we overshot
|
||||
// (CPU starvation), the Choreographer's late wake (~5 ms after the
|
||||
// edge) covers the frame instead.
|
||||
if landed <= 1_000_000 {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,6 +228,12 @@ unsafe extern "C" fn frame_callback(
|
|||
// before the wake would push the main loop's render start (and thus the
|
||||
// present phase) later in the slot.
|
||||
shared.main_waker.wake();
|
||||
// PRESENT GATE: publish the exact vsync edge (CLOCK_MONOTONIC ns — the
|
||||
// same epoch as `mon_ns()` above) so the renderer can quantize its
|
||||
// `Queue::present` to the vsync grid (wgpu_30_surface::vkprobe).
|
||||
i_slint_renderer_skia::wgpu_30_surface::vkprobe::note_vsync_edge(
|
||||
frame_time_nanos as u64,
|
||||
);
|
||||
let mon = unsafe { mon_ns() };
|
||||
if mon > 0 {
|
||||
log::info!("VSYNC ftn={} mon={}", frame_time_nanos, mon);
|
||||
|
|
|
|||
94
vendor/i-slint-renderer-skia/wgpu_30_surface.rs
vendored
94
vendor/i-slint-renderer-skia/wgpu_30_surface.rs
vendored
|
|
@ -61,6 +61,88 @@ pub mod vkprobe {
|
|||
/// renders coming) and never reach the screen.
|
||||
pub static RENDER_AHEAD_ACTIVE: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
// ------------------------------------------------------------------
|
||||
// PRESENT GATE (vsync-grid quantization, the real 120Hz fix).
|
||||
//
|
||||
// Measured on the phone: the Choreographer vsync callback reaches the
|
||||
// UI thread 2.4–6 ms (median 3.3 ms) after the vsync edge, and the
|
||||
// render takes 3–9 ms — so each frame's present lands at a RANDOM
|
||||
// phase 8–14 ms after the edge against a 13.8 ms latch deadline.
|
||||
// 51% of frames land one slot late (judder) and ~4% two slots late
|
||||
// (visible hitch), independent of where in the render() call the
|
||||
// present happens.
|
||||
//
|
||||
// Fix: quantize the PRESENT to the vsync grid. The vsync driver hands
|
||||
// us the exact edge (Choreographer `frame_time_nanos`, CLOCK_MONOTONIC)
|
||||
// via `note_vsync_edge`; before `Queue::present` we sleep (≤ ~2 ms) so
|
||||
// the present lands at (next edge + 1.5 ms) — constant phase, ~4 ms of
|
||||
// deadline margin, frame-to-frame gaps of exactly one period. See
|
||||
// docs/BUGS/scroll-jank-120hz.md §9.
|
||||
/// Latest Choreographer `frame_time_nanos` (CLOCK_MONOTONIC ns).
|
||||
pub static VSYNC_EDGE_NS: AtomicU64 = AtomicU64::new(0);
|
||||
/// Detected vsync period in ns (from consecutive callback deltas).
|
||||
pub static VSYNC_PERIOD_NS: AtomicU64 = AtomicU64::new(0);
|
||||
/// Gate enabled for the current render (set per-frame by the event
|
||||
/// loop; true while animating/locked, false for one-off redraws so
|
||||
/// taps and model updates keep minimum latency).
|
||||
pub static PRESENT_GATE_ON: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
/// Microseconds the most recent `present_gate()` slept (0 = no sleep).
|
||||
pub static GATE_US: AtomicU64 = AtomicU64::new(0);
|
||||
/// CLOCK_MONOTONIC ns when the last vsync-wake (locked) render STARTED.
|
||||
/// The event loop uses it to skip a second wake (e.g. the Choreographer's
|
||||
/// late fallback wake arriving ~5 ms after the early-waker's edge wake
|
||||
/// already triggered a render) instead of double-rendering the slot.
|
||||
pub static LAST_RENDER_START_NS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Called from the vsync driver on every Choreographer callback.
|
||||
pub fn note_vsync_edge(ftn_ns: u64) {
|
||||
let prev = VSYNC_EDGE_NS.swap(ftn_ns, Ordering::Relaxed);
|
||||
if prev > 0 {
|
||||
let d = ftn_ns.saturating_sub(prev);
|
||||
if (4_000_000..20_000_000).contains(&d) {
|
||||
VSYNC_PERIOD_NS.store(d, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sleep so the `Queue::present` that follows lands at (latest vsync
|
||||
/// edge + one period + 1.5 ms). No-op when disabled, when the grid is
|
||||
/// unknown/stale (no recent callbacks), or when already past target.
|
||||
pub fn present_gate() {
|
||||
if !PRESENT_GATE_ON.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
let edge = VSYNC_EDGE_NS.load(Ordering::Relaxed);
|
||||
let period = VSYNC_PERIOD_NS.load(Ordering::Relaxed);
|
||||
if edge == 0 || period == 0 {
|
||||
return;
|
||||
}
|
||||
let now = monotonic_ns();
|
||||
if now == 0 || now > edge + 4 * period {
|
||||
return; // stale grid (e.g. after resume) — don't gate
|
||||
}
|
||||
// Earliest grid phase (a vsync edge + 1.5 ms) strictly after now:
|
||||
// T = edge + m*period + 1.5 ms with m = floor((now-edge-1.5ms)
|
||||
// / period) + 1.
|
||||
// NOTE: a Choreographer callback can fire MID-RENDER (the next
|
||||
// edge's callback arrives at edge+~11.6 ms while a slow render is
|
||||
// still running), which advances `edge` by one period — the
|
||||
// smallest-phase rule above then just yields the same absolute T, so
|
||||
// the mid-render callback can never add an extra slot of delay.
|
||||
const PHASE_NS: u64 = 1_500_000;
|
||||
let rel = now.saturating_sub(edge).saturating_sub(PHASE_NS) / period;
|
||||
let target = edge + (rel + 1) * period + PHASE_NS;
|
||||
let wait = target.saturating_sub(now);
|
||||
// Only sleep for close phases; if the next grid phase is >4 ms out
|
||||
// this frame is already running late for it — present immediately
|
||||
// (the next frame re-syncs to the grid).
|
||||
let slept = if wait > 200_000 && wait <= 4_000_000 { wait } else { 0 };
|
||||
GATE_US.store(slept / 1000, Ordering::Relaxed);
|
||||
if slept > 0 {
|
||||
std::thread::sleep(std::time::Duration::from_nanos(slept));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scn_reset() {
|
||||
crate::scn::reset();
|
||||
|
|
@ -638,6 +720,10 @@ impl crate::Surface for WGPUSurface {
|
|||
} else {
|
||||
// Unlocked render: present immediately. (Any frame left in the
|
||||
// held slot was already presented at the top of this call.)
|
||||
// PRESENT GATE: while animating, land this present at a fixed
|
||||
// vsync-grid phase (next edge + 1.5 ms) so frame-to-frame
|
||||
// cadence is exactly one period (see `present_gate`).
|
||||
vkprobe::present_gate();
|
||||
self.wgpu.queue.present(frame);
|
||||
vkprobe::now_us().saturating_sub(__t_pre)
|
||||
};
|
||||
|
|
@ -649,7 +735,13 @@ impl crate::Surface for WGPUSurface {
|
|||
// against VSYNC mon= stamps: phase = pres_mon - previous_vsync_mon.
|
||||
let __mon = vkprobe::monotonic_ns();
|
||||
if __mon > 0 {
|
||||
log::info!("PRES f={} mon={} pre_us={}", __f, __mon, __pre_us);
|
||||
log::info!(
|
||||
"PRES f={} mon={} pre_us={} gate_us={}",
|
||||
__f,
|
||||
__mon,
|
||||
__pre_us,
|
||||
vkprobe::GATE_US.load(std::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user