gallery/vendor/i-slint-core/window/popup.rs
Greg Pomerantz 2f4768f354 Fix 120Hz scroll jank end-to-end: render-ahead present + full-cache eviction off the UI thread
Three root causes, all fixed and verified on the Pixel 9 Pro:

1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the
   render to the vsync wake; RENDER_AHEAD presents the previous frame at
   the start of render() (phase = wake + ~1.1 ms) and holds the new
   frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never
   merely dropped, before the next acquire (AlreadyAcquired panic at
   launch otherwise); discard_pending_frame presents too.

2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled
   through a ~20-cell viewport forever (137 decodes/20 s) and each
   eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes,
   ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone
   cooldown on evicted paths (idle prefetch skips, urgent tap bypasses)
   + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us).

3. (earlier) Skia final flush + Mailbox present mode (vendor, M3).

Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK
lines for cross-thread closure timing and push-site tagging.

Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact
120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero
>33 ms); PRES phase median ~994 us, zero late frames. Docs updated
(docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference).

Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch),
vkshim profiling tooling, backend made a non-optional dep, debuggable
flag for simpleperf (REMOVE before shipping).
2026-10-30 02:49:23 -04:00

296 lines
11 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
// cSpell: ignore unclipped
//! Popup window handling helpers
use crate::lengths::{LogicalPoint, LogicalRect, LogicalSize};
/// A collection of data that might influence the placement of a `Popup`.
pub enum Placement {
/// Request a fixed position
Fixed(LogicalRect),
}
/// Find a placement for the `Popup`, using the provided `Placement`.
/// When a `clip_region` is provided, then the `Popup` will stay within those bounds.
/// The `clip_region` typically is the window or the screen the window is on.
pub fn place_popup(placement: Placement, clip_region: &Option<LogicalRect>) -> LogicalRect {
match placement {
Placement::Fixed(rect) => {
let clip = clip_region.unwrap_or(rect);
if clip.contains_rect(&rect) {
rect
} else {
let size = LogicalSize::new(
crate::Coord::min(rect.size.width, clip.size.width),
crate::Coord::min(rect.size.height, clip.size.height),
);
let origin = LogicalPoint::new(
rect.origin
.x
.clamp(clip.origin.x, clip.origin.x + clip.size.width - size.width),
rect.origin
.y
.clamp(clip.origin.y, clip.origin.y + clip.size.height - size.height),
);
LogicalRect::new(origin, size)
}
}
}
}
#[cfg(test)]
fn r(x: i32, y: i32, w: i32, h: i32) -> LogicalRect {
LogicalRect::new(LogicalPoint::new(x as f32, y as f32), LogicalSize::new(w as f32, h as f32))
}
#[cfg(test)]
#[track_caller]
fn fixed_placement(input: LogicalRect, expected: LogicalRect, clip: Option<LogicalRect>) {
std::eprintln!("fixed: {input:?}, clip({clip:?}) => {expected:?}");
let result = place_popup(Placement::Fixed(input), &clip);
if let Some(clip) = clip {
clip.contains_rect(&result);
}
assert_eq!(result, expected);
}
#[test]
fn test_place_popup_fixed_unclipped() {
let data = r(5, 5, 100, 100);
fixed_placement(data, data, None);
let data = r(5, -20, 100, 100);
fixed_placement(data, data, None);
let data = r(2000, -20, 100, 100);
fixed_placement(data, data, None);
let data = r(2000, 5, 100, 100);
fixed_placement(data, data, None);
let data = r(2000, 2000, 100, 100);
fixed_placement(data, data, None);
let data = r(5, 2000, 100, 100);
fixed_placement(data, data, None);
let data = r(-20, 2000, 100, 100);
fixed_placement(data, data, None);
let data = r(-20, 5, 100, 100);
fixed_placement(data, data, None);
let data = r(-20, -20, 100, 100);
fixed_placement(data, data, None);
let data = r(-20, -20, 2000, 2000);
fixed_placement(data, data, None);
}
#[test]
fn test_place_popup_fixed_clipped() {
for (clip_offset_x, clip_offset_y) in [
(-200, -200),
(-200, 0),
(-200, 200),
(0, -200),
(0, 0),
(0, 200),
(200, -200),
(200, 0),
(200, 200),
] {
for (clip_width, clip_height) in [(110, 110), (800, 600)] {
let clip = r(clip_offset_x, clip_offset_y, clip_width, clip_height);
let x_w = clip_offset_x - 10;
let x_c = clip_offset_x + 5;
let x_e = clip_offset_x + clip_width - 80;
let y_n = clip_offset_y - 10;
let y_c = clip_offset_y + 5;
let y_s = clip_offset_y + clip_height - 80;
let x_min = clip_offset_x;
let x_max = clip_offset_x + clip_width;
let y_min = clip_offset_y;
let y_max = clip_offset_y + clip_height;
assert!(clip_width > 105 && clip_width < 1000);
assert!(clip_height > 105 && clip_height < 1000);
// smaller, inside
fixed_placement(r(x_c, y_c, 100, 100), r(x_c, y_c, 100, 100), Some(clip));
// smaller, partial outside
fixed_placement(r(x_c, y_n, 100, 100), r(x_c, y_min, 100, 100), Some(clip));
fixed_placement(r(x_e, y_n, 100, 100), r(x_max - 100, y_min, 100, 100), Some(clip));
fixed_placement(r(x_e, y_c, 100, 100), r(x_max - 100, y_c, 100, 100), Some(clip));
fixed_placement(
r(x_e, y_s, 100, 100),
r(x_max - 100, y_max - 100, 100, 100),
Some(clip),
);
fixed_placement(r(x_c, y_s, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
fixed_placement(r(x_c, y_s, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
fixed_placement(r(x_w, y_s, 100, 100), r(x_min, y_max - 100, 100, 100), Some(clip));
fixed_placement(r(x_w, y_c, 100, 100), r(x_min, y_c, 100, 100), Some(clip));
fixed_placement(r(x_w, y_n, 100, 100), r(x_min, y_min, 100, 100), Some(clip));
// smaller, totally outside
fixed_placement(r(x_c, -2000, 100, 100), r(x_c, y_min, 100, 100), Some(clip));
fixed_placement(r(2000, -2000, 100, 100), r(x_max - 100, y_min, 100, 100), Some(clip));
fixed_placement(r(2000, y_c, 100, 100), r(x_max - 100, y_c, 100, 100), Some(clip));
fixed_placement(
r(2000, 2000, 100, 100),
r(x_max - 100, y_max - 100, 100, 100),
Some(clip),
);
fixed_placement(r(x_c, 2000, 100, 100), r(x_c, y_max - 100, 100, 100), Some(clip));
fixed_placement(r(-2000, 2000, 100, 100), r(x_min, y_max - 100, 100, 100), Some(clip));
fixed_placement(r(-2000, y_c, 100, 100), r(x_min, y_c, 100, 100), Some(clip));
fixed_placement(r(-2000, -2000, 100, 100), r(x_min, y_min, 100, 100), Some(clip));
// matching size, covering
fixed_placement(
r(x_min, y_min, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
// matching size, overlapping
fixed_placement(
r(x_c, y_c, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_c, y_n, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_n, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_c, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_s, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_c, y_s, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_s, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_c, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_n, clip_width, clip_height),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
// too big, overlapping
fixed_placement(
r(x_c, y_c, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_c, y_n, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_n, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_c, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_e, y_s, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_c, y_s, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_s, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_c, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_w, y_n, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
// too big, outside
fixed_placement(
r(x_c, -3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(3000, -3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(3000, y_c, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(3000, 3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(x_c, 3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(-3000, 3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(-3000, y_c, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
fixed_placement(
r(-3000, -3000, clip_width + 5, clip_height + 5),
r(x_min, y_min, clip_width, clip_height),
Some(clip),
);
}
}
}