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).
264 lines
8.5 KiB
Rust
264 lines
8.5 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
|
|
|
|
#![allow(unsafe_code)]
|
|
|
|
use alloc::rc::Rc;
|
|
use core::ffi::c_void;
|
|
|
|
use super::DataTransfer;
|
|
use crate::SharedString;
|
|
#[cfg(feature = "std")]
|
|
use crate::SharedVector;
|
|
use crate::api::Image;
|
|
#[cfg(feature = "std")]
|
|
use crate::slice::Slice;
|
|
|
|
// The unit of a path's native representation, matching C++'s
|
|
// `std::filesystem::path::value_type`: UTF-16 code units on Windows, bytes
|
|
// elsewhere. Paths cross the FFI as slices of this type so that any valid
|
|
// path round-trips losslessly.
|
|
#[cfg(all(feature = "std", windows))]
|
|
pub type PathValueType = u16;
|
|
#[cfg(all(feature = "std", not(windows)))]
|
|
pub type PathValueType = u8;
|
|
|
|
#[cfg(feature = "std")]
|
|
fn path_from_units(units: &[PathValueType]) -> std::path::PathBuf {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::ffi::OsStrExt;
|
|
std::ffi::OsStr::from_bytes(units).into()
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::os::windows::ffi::OsStringExt;
|
|
std::ffi::OsString::from_wide(units).into()
|
|
}
|
|
#[cfg(not(any(unix, windows)))]
|
|
{
|
|
alloc::string::String::from_utf8_lossy(units).into_owned().into()
|
|
}
|
|
}
|
|
#[cfg(feature = "std")]
|
|
fn path_to_units(path: &std::path::Path) -> SharedVector<PathValueType> {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::ffi::OsStrExt;
|
|
path.as_os_str().as_bytes().into()
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
use std::os::windows::ffi::OsStrExt;
|
|
path.as_os_str().encode_wide().collect()
|
|
}
|
|
#[cfg(not(any(unix, windows)))]
|
|
{
|
|
path.to_string_lossy().as_bytes().into()
|
|
}
|
|
}
|
|
|
|
/// Opaque placeholder used by C++ to reserve storage with the same size and
|
|
/// alignment as Rust's `DataTransfer`. The actual `DataTransfer` contains
|
|
/// `Option<Rc<...>>` fields whose layout cannot be expressed via cbindgen, so
|
|
/// C++ never inspects these fields directly: copy/destruction goes through
|
|
/// the `slint_data_transfer_*` FFI functions below, which operate on a real
|
|
/// `DataTransfer`.
|
|
///
|
|
/// The three pointer-sized fields correspond to:
|
|
/// - `_rc_inner`: thin `Rc` pointer for `Option<Rc<DataTransferInner>>` (null = `None`)
|
|
/// - `_rc_any_0`/`_rc_any_1`: data + vtable pointers of the
|
|
/// `Option<Rc<dyn Any>>` user data fat pointer (null data = `None`)
|
|
#[repr(C)]
|
|
pub struct DataTransferOpaque {
|
|
_rc_inner: *mut core::ffi::c_void,
|
|
_rc_any_0: *mut core::ffi::c_void,
|
|
_rc_any_1: *mut core::ffi::c_void,
|
|
}
|
|
|
|
static_assertions::assert_eq_align!(DataTransferOpaque, DataTransfer);
|
|
static_assertions::assert_eq_size!(DataTransferOpaque, DataTransfer);
|
|
|
|
/// Default-construct a `DataTransfer` in place at `out`.
|
|
///
|
|
/// # Safety
|
|
/// `out` must be valid for writes of `DataTransfer` and must not currently
|
|
/// hold an initialized `DataTransfer` (otherwise the previous value is leaked).
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn slint_data_transfer_init_default(out: *mut DataTransfer) {
|
|
unsafe { core::ptr::write(out, DataTransfer::default()) }
|
|
}
|
|
|
|
/// Drop a `DataTransfer` in place.
|
|
///
|
|
/// # Safety
|
|
/// `d` must point to an initialized `DataTransfer` and must not be used after
|
|
/// this call returns.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn slint_data_transfer_drop(d: *mut DataTransfer) {
|
|
unsafe { core::ptr::drop_in_place(d) }
|
|
}
|
|
|
|
/// Clone `src` into the uninitialized memory at `out`.
|
|
///
|
|
/// # Safety
|
|
/// `out` must be valid for writes of `DataTransfer` and must not currently
|
|
/// hold an initialized `DataTransfer` (otherwise the previous value is leaked).
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn slint_data_transfer_clone(out: *mut DataTransfer, src: &DataTransfer) {
|
|
unsafe { core::ptr::write(out, src.clone()) }
|
|
}
|
|
|
|
/// Compare two `DataTransfer` values for equality (same semantics as
|
|
/// `<DataTransfer as PartialEq>::eq`).
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_eq(a: &DataTransfer, b: &DataTransfer) -> bool {
|
|
a == b
|
|
}
|
|
|
|
/// Set the plain text representation of `d` to a clone of `text`.
|
|
///
|
|
/// An empty `text` clears the previously-set plain text instead of storing it.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_set_plain_text(d: &mut DataTransfer, text: &SharedString) {
|
|
d.set_plain_text(text.clone());
|
|
}
|
|
|
|
/// Set the image representation of `d` to a clone of `image`.
|
|
///
|
|
/// A default-constructed `image` clears the previously-set image instead of storing it.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_set_image(d: &mut DataTransfer, image: &Image) {
|
|
d.set_image(image.clone());
|
|
}
|
|
|
|
/// Set the file path list of `d`. Each path is a slice of its native
|
|
/// representation units (see `PathValueType`).
|
|
///
|
|
/// An empty `paths` clears the previously-set file paths instead of storing it.
|
|
#[cfg(feature = "std")]
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_set_file_paths(
|
|
d: &mut DataTransfer,
|
|
paths: Slice<Slice<PathValueType>>,
|
|
) {
|
|
d.set_file_paths_vec(paths.iter().map(|path| path_from_units(path.as_slice())).collect());
|
|
}
|
|
|
|
/// Returns `true` if `d` advertises a plain text representation.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_has_plain_text(d: &DataTransfer) -> bool {
|
|
d.has_plain_text()
|
|
}
|
|
|
|
/// Returns `true` if `d` advertises an image representation.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_has_image(d: &DataTransfer) -> bool {
|
|
d.has_image()
|
|
}
|
|
|
|
/// Returns `true` if `d` advertises a list of file paths.
|
|
#[cfg(feature = "std")]
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_has_file_paths(d: &DataTransfer) -> bool {
|
|
d.has_file_paths()
|
|
}
|
|
|
|
/// Returns `true` if `d` carries no data: no plain text, no image, no file paths,
|
|
/// and no user data.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_is_empty(d: &DataTransfer) -> bool {
|
|
d.is_empty()
|
|
}
|
|
|
|
/// If `d` has a plain text representation, write a clone of it into `out` and
|
|
/// return `true`. Otherwise leave `out` unchanged and return `false`.
|
|
///
|
|
/// `out` must point to an initialized `SharedString`.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_plain_text(d: &DataTransfer, out: &mut SharedString) -> bool {
|
|
match d.plain_text() {
|
|
Ok(s) => {
|
|
*out = s;
|
|
true
|
|
}
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// If `d` has an image representation, write a clone of it into `out` and
|
|
/// return `true`. Otherwise leave `out` unchanged and return `false`.
|
|
///
|
|
/// `out` must point to an initialized `Image`.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_image(d: &DataTransfer, out: &mut Image) -> bool {
|
|
match d.image() {
|
|
Ok(i) => {
|
|
*out = i;
|
|
true
|
|
}
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// If `d` has a list of file paths, write them into `out` and return `true`.
|
|
/// Otherwise leave `out` unchanged and return `false`. Each path is a vector
|
|
/// of its native representation units (see `PathValueType`).
|
|
///
|
|
/// `out` must point to an initialized `SharedVector<SharedVector<PathValueType>>`.
|
|
#[cfg(feature = "std")]
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_file_paths(
|
|
d: &DataTransfer,
|
|
out: &mut SharedVector<SharedVector<PathValueType>>,
|
|
) -> bool {
|
|
match d.file_paths() {
|
|
Ok(paths) => {
|
|
*out = paths.map(path_to_units).collect();
|
|
true
|
|
}
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// C++-owned user-data handle stored in a `DataTransfer`.
|
|
struct CppUserData {
|
|
handle: *mut c_void,
|
|
drop_fn: unsafe extern "C" fn(*mut c_void),
|
|
}
|
|
|
|
impl Drop for CppUserData {
|
|
fn drop(&mut self) {
|
|
unsafe { (self.drop_fn)(self.handle) }
|
|
}
|
|
}
|
|
|
|
/// Store `handle` as the user data of `d`. `drop_fn(handle)` runs when `d` is dropped.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn slint_data_transfer_set_user_data(
|
|
d: &mut DataTransfer,
|
|
handle: *mut c_void,
|
|
drop_fn: unsafe extern "C" fn(*mut c_void),
|
|
) {
|
|
d.set_user_data(Rc::new(CppUserData { handle, drop_fn }));
|
|
}
|
|
|
|
/// Write a borrowed pointer to `d`'s C++ user-data handle into `out_handle`.
|
|
/// Returns `false` if `d` has no C++ user data.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn slint_data_transfer_user_data(
|
|
d: &DataTransfer,
|
|
out_handle: *mut *const c_void,
|
|
) -> bool {
|
|
let Some(any) = d.user_data() else { return false };
|
|
let Some(cpp) = any.downcast_ref::<CppUserData>() else { return false };
|
|
unsafe { *out_handle = cpp.handle as *const c_void };
|
|
true
|
|
}
|
|
|
|
/// Clear the user data of `d`, if any.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_data_transfer_clear_user_data(d: &mut DataTransfer) {
|
|
d.user_data = None;
|
|
}
|