gallery/vendor/i-slint-core/graphics.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

338 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 bitmapfont glversion
#![allow(unsafe_code)]
#![warn(missing_docs)]
/*!
Graphics Abstractions.
This module contains the abstractions and convenience types used for rendering.
*/
extern crate alloc;
use crate::Coord;
use crate::SharedString;
use crate::api::PlatformError;
use crate::lengths::LogicalLength;
use alloc::boxed::Box;
pub use euclid;
/// 2D Rectangle
pub type Rect = euclid::default::Rect<Coord>;
/// 2D Rectangle with integer coordinates
pub type IntRect = euclid::default::Rect<i32>;
/// 2D Point
pub type Point = euclid::default::Point2D<Coord>;
/// 2D Size
pub type Size = euclid::default::Size2D<Coord>;
/// 2D Size in integer coordinates
pub type IntSize = euclid::default::Size2D<u32>;
/// 2D Transform
pub type Transform = euclid::default::Transform2D<Coord>;
pub(crate) mod color;
pub use color::*;
#[cfg(feature = "shared-fontique")]
use i_slint_common::sharedfontique::{self, fontique};
#[cfg(feature = "path")]
mod path;
#[cfg(feature = "path")]
pub use path::*;
mod brush;
pub use brush::*;
pub(crate) mod image;
pub use self::image::*;
pub(crate) mod bitmapfont;
pub use self::bitmapfont::*;
pub mod rendering_metrics_collector;
#[cfg(feature = "box-shadow-cache")]
pub mod boxshadowcache;
pub mod border_radius;
pub use border_radius::*;
#[cfg(feature = "wgpu-29")]
pub mod wgpu_29;
#[cfg(feature = "wgpu-30")]
pub mod wgpu_30;
/// Adjusts a rectangle and a border width for drawing the border entirely inside the
/// rectangle's geometry: renderers stroke a border centered on the path, so the rectangle
/// is inset by half the border width. If the border width exceeds half of the rectangle's
/// width, it is clamped so that the border just fills the rectangle.
pub fn adjust_rect_and_border_for_inner_drawing<U>(
rect: &mut euclid::Rect<f32, U>,
border_width: &mut euclid::Length<f32, U>,
) {
use crate::lengths::RectLengths;
// If the border width exceeds the width, just fill the rectangle.
*border_width = border_width.min(rect.width_length() / 2.);
// adjust the size so that the border is drawn within the geometry
rect.origin += euclid::Size2D::from_lengths(*border_width / 2., *border_width / 2.);
rect.size -= euclid::Size2D::from_lengths(*border_width, *border_width);
}
/// CachedGraphicsData allows the graphics backend to store an arbitrary piece of data associated with
/// an item, which is typically computed by accessing properties. The dependency_tracker is used to allow
/// for a lazy computation. Typically, back ends store either compute intensive data or handles that refer to
/// data that's stored in GPU memory.
pub struct CachedGraphicsData<T> {
/// The backend specific data.
pub data: T,
/// The property tracker that should be used to evaluate whether the primitive needs to be re-created
/// or not.
pub dependency_tracker: Option<core::pin::Pin<Box<crate::properties::PropertyTracker>>>,
}
impl<T> CachedGraphicsData<T> {
/// Creates a new TrackingRenderingPrimitive by evaluating the provided update_fn once, storing the returned
/// rendering primitive and initializing the dependency tracker.
pub fn new(update_fn: impl FnOnce() -> T) -> Self {
let dependency_tracker = Box::pin(crate::properties::PropertyTracker::default());
let data = dependency_tracker.as_ref().evaluate(update_fn);
Self { data, dependency_tracker: Some(dependency_tracker) }
}
}
/// FontRequest collects all the developer-configurable properties for fonts, such as family, weight, etc.
/// It is submitted as a request to the platform font system (i.e. CoreText on macOS) and in exchange the
/// backend returns a `Box<dyn Font>`.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct FontRequest {
/// The name of the font family to be used, such as "Helvetica". An empty family name means the system
/// default font family should be used.
pub family: Option<SharedString>,
/// If the weight is None, the system default font weight should be used.
pub weight: Option<i32>,
/// If the pixel size is None, the system default font size should be used.
pub pixel_size: Option<LogicalLength>,
/// The additional spacing (or shrinking if negative) between glyphs. This is usually not submitted to
/// the font-subsystem but collected here for API convenience
pub letter_spacing: Option<LogicalLength>,
/// The line height as a factor applied to the font's natural line height.
/// `None` uses the natural line height unchanged (a factor of 1).
pub line_height_factor: Option<f32>,
/// Whether to select an italic face of the font family.
pub italic: bool,
}
impl FontRequest {
/// Returns the configured line height given the font's natural line height
/// (in any unit), or `None` when the natural line height applies unchanged.
pub fn line_height_for_natural_height(&self, natural_line_height: f32) -> Option<f32> {
self.line_height_factor.map(|factor| natural_line_height * factor)
}
}
#[cfg(feature = "shared-fontique")]
impl FontRequest {
/// Attempts to query the fontique font collection for a matching font.
pub fn query_fontique(
&self,
collection: &mut fontique::Collection,
source_cache: &mut fontique::SourceCache,
) -> Option<fontique::QueryFont> {
let mut query = collection.query(source_cache);
query.set_families(
self.family
.as_ref()
.map(|family| fontique::QueryFamily::from(family.as_str()))
.into_iter()
.chain(
sharedfontique::FALLBACK_FAMILIES
.into_iter()
.map(fontique::QueryFamily::Generic),
),
);
query.set_attributes(fontique::Attributes {
weight: self
.weight
.as_ref()
.map(|&weight| fontique::FontWeight::new(weight as f32))
.unwrap_or_default(),
style: if self.italic {
fontique::FontStyle::Italic
} else {
fontique::FontStyle::Normal
},
..Default::default()
});
let mut font = None;
query.matches_with(|queried_font| {
font = Some(queried_font.clone());
fontique::QueryStatus::Stop
});
font
}
}
/// Internal enum to specify which version of OpenGL to request
/// from the windowing system.
#[derive(Debug, Clone, PartialEq)]
pub enum RequestedOpenGLVersion {
/// OpenGL
OpenGL(Option<(u8, u8)>),
/// OpenGL ES
OpenGLES(Option<(u8, u8)>),
}
/// Internal enum specify which graphics API should be used, when
/// the backend selector requests that from a built-in backend.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum RequestedGraphicsAPI {
/// OpenGL (ES)
OpenGL(RequestedOpenGLVersion),
/// Metal
Metal,
/// Vulkan
Vulkan,
/// Direct 3D
Direct3D,
#[cfg(feature = "unstable-wgpu-29")]
/// WGPU 29.x
WGPU29(wgpu_29::api::WGPUConfiguration),
#[cfg(feature = "unstable-wgpu-30")]
/// WGPU 30.x
WGPU30(wgpu_30::api::WGPUConfiguration),
}
impl TryFrom<&RequestedGraphicsAPI> for RequestedOpenGLVersion {
type Error = PlatformError;
fn try_from(requested_graphics_api: &RequestedGraphicsAPI) -> Result<Self, Self::Error> {
match requested_graphics_api {
RequestedGraphicsAPI::OpenGL(requested_open_glversion) => {
Ok(requested_open_glversion.clone())
}
RequestedGraphicsAPI::Metal => {
Err("Metal rendering is not supported with an OpenGL renderer".into())
}
RequestedGraphicsAPI::Vulkan => {
Err("Vulkan rendering is not supported with an OpenGL renderer".into())
}
RequestedGraphicsAPI::Direct3D => {
Err("Direct3D rendering is not supported with an OpenGL renderer".into())
}
#[cfg(feature = "unstable-wgpu-29")]
RequestedGraphicsAPI::WGPU29(..) => {
Err("WGPU 29.x rendering is not supported with an OpenGL renderer".into())
}
#[cfg(feature = "unstable-wgpu-30")]
RequestedGraphicsAPI::WGPU30(..) => {
Err("WGPU 30.x rendering is not supported with an OpenGL renderer".into())
}
}
}
}
impl From<RequestedOpenGLVersion> for RequestedGraphicsAPI {
fn from(version: RequestedOpenGLVersion) -> Self {
Self::OpenGL(version)
}
}
/// Private API exposed to just the renderers to create GraphicsAPI instance with
/// non-exhaustive enum variant.
#[cfg(feature = "unstable-wgpu-29")]
pub fn create_graphics_api_wgpu_29(
instance: wgpu_29::wgpu::Instance,
device: wgpu_29::wgpu::Device,
queue: wgpu_29::wgpu::Queue,
) -> crate::api::GraphicsAPI<'static> {
crate::api::GraphicsAPI::WGPU29 { instance, device, queue }
}
/// Private API exposed to just the renderers to create GraphicsAPI instance with
/// non-exhaustive enum variant.
#[cfg(feature = "unstable-wgpu-30")]
pub fn create_graphics_api_wgpu_30(
instance: wgpu_30::wgpu::Instance,
device: wgpu_30::wgpu::Device,
queue: wgpu_30::wgpu::Queue,
) -> crate::api::GraphicsAPI<'static> {
crate::api::GraphicsAPI::WGPU30 { instance, device, queue }
}
/// Internal module for use by cbindgen and the C++ platform API layer.
#[cfg(feature = "ffi")]
pub mod ffi {
#![allow(unsafe_code)]
/// Expand Rect so that cbindgen can see it. ( is in fact euclid::default::Rect<f32>)
#[cfg(cbindgen)]
#[repr(C)]
struct Rect {
x: f32,
y: f32,
width: f32,
height: f32,
}
/// Expand IntRect so that cbindgen can see it. ( is in fact euclid::default::Rect<i32>)
#[cfg(cbindgen)]
#[repr(C)]
struct IntRect {
x: i32,
y: i32,
width: i32,
height: i32,
}
/// Expand Point so that cbindgen can see it. ( is in fact euclid::default::Point2D<f32>)
#[cfg(cbindgen)]
#[repr(C)]
struct Point {
x: f32,
y: f32,
}
/// Expand Box2D so that cbindgen can see it.
#[cfg(cbindgen)]
#[repr(C)]
struct Box2D<T, U> {
min: euclid::Point2D<T>,
max: euclid::Point2D<T>,
_unit: std::marker::PhantomData<U>,
}
#[cfg(feature = "std")]
pub use super::path::ffi::*;
/// Conversion function used by C++ platform API layer to
/// convert the PhysicalSize used in the Rust WindowAdapter API
/// to the ffi.
pub fn physical_size_from_api(
size: crate::api::PhysicalSize,
) -> crate::graphics::euclid::default::Size2D<u32> {
size.to_euclid()
}
/// Conversion function used by C++ platform API layer to
/// convert the PhysicalPosition used in the Rust WindowAdapter API
/// to the ffi.
pub fn physical_position_from_api(
position: crate::api::PhysicalPosition,
) -> crate::graphics::euclid::default::Point2D<i32> {
position.to_euclid()
}
/// Conversion function used by C++ platform API layer to
/// convert from the ffi to PhysicalPosition.
pub fn physical_position_to_api(
position: crate::graphics::euclid::default::Point2D<i32>,
) -> crate::api::PhysicalPosition {
crate::api::PhysicalPosition::from_euclid(position)
}
}