Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
97bbf3c
Add i-slint-renderer-anyrender crate skeleton
tronical Jul 21, 2026
4e68963
anyrender: add AnyrenderItemRenderer for shapes, brushes, and clipping
tronical Jul 21, 2026
9bf5db1
anyrender: render text through the shared parley layout
tronical Jul 21, 2026
7c256a2
anyrender: implement image rendering
tronical Jul 21, 2026
d1293f7
anyrender: implement path and box-shadow rendering
tronical Jul 21, 2026
a79a15c
anyrender: add AnyrenderSlintRenderer
tronical Jul 21, 2026
0eae18a
anyrender: add RecordingWindowRenderer
tronical Jul 21, 2026
6123499
tests: snapshot the anyrender command stream in the screenshot driver
tronical Jul 21, 2026
6b5f0ae
tests: add anyrender command-stream goldens
tronical Jul 21, 2026
57bbde1
tests: add replay-anyrender developer tool
tronical Jul 21, 2026
e507f9b
anyrender: record the base color in RecordingWindowRenderer
tronical Jul 21, 2026
2c5be78
tests: add vello_cpu pixel screenshot tests
tronical Jul 21, 2026
6a97873
tests: add vello_cpu pixel references
tronical Jul 21, 2026
8563bd2
anyrender: implement inset box shadows
tronical Jul 21, 2026
4103954
anyrender: honor explicit radial gradient center and radius
tronical Jul 22, 2026
8d3b2f1
anyrender: fix conic gradient rotation and honor explicit center
tronical Jul 22, 2026
449938b
anyrender: position border gradients relative to the border box
tronical Jul 21, 2026
2f09293
anyrender: tile only the clip rect when repeating images
tronical Jul 21, 2026
26fb04e
anyrender: snap tiled image fills to the device pixel grid
tronical Jul 21, 2026
b979632
anyrender: sanitize gradient color stops
tronical Jul 21, 2026
118f960
Add anyrender renderer vocabulary to the project dictionary
tronical Jul 22, 2026
8f5fe94
anyrender: cache converted images per item
tronical Jul 22, 2026
5d03af0
fixup! Add i-slint-renderer-anyrender crate skeleton
tronical Jul 28, 2026
d995752
fixup! anyrender: add AnyrenderItemRenderer for shapes, brushes, and …
tronical Jul 28, 2026
9b38805
fixup! anyrender: render text through the shared parley layout
tronical Jul 28, 2026
4d498e0
fixup! anyrender: implement image rendering
tronical Jul 28, 2026
5b50c29
fixup! anyrender: implement path and box-shadow rendering
tronical Jul 28, 2026
61e4aac
fixup! anyrender: add AnyrenderSlintRenderer
tronical Jul 28, 2026
260efdc
fixup! anyrender: add RecordingWindowRenderer
tronical Jul 28, 2026
31f454d
fixup! tests: snapshot the anyrender command stream in the screenshot…
tronical Jul 28, 2026
72651fc
fixup! tests: add anyrender command-stream goldens
tronical Jul 28, 2026
3884262
fixup! anyrender: record the base color in RecordingWindowRenderer
tronical Jul 28, 2026
6041223
fixup! anyrender: implement inset box shadows
tronical Jul 28, 2026
9f72f48
fixup! anyrender: sanitize gradient color stops
tronical Jul 28, 2026
a71133d
fixup! anyrender: cache converted images per item
tronical Jul 28, 2026
0055a21
fixup! anyrender: add AnyrenderItemRenderer for shapes, brushes, and …
tronical Jul 28, 2026
14c96d9
fixup! anyrender: render text through the shared parley layout
tronical Jul 28, 2026
bd766c1
fixup! anyrender: implement path and box-shadow rendering
tronical Jul 28, 2026
e049499
fixup! anyrender: implement inset box shadows
tronical Jul 28, 2026
eabb445
fixup! tests: add anyrender command-stream goldens
tronical Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .cspell/slint-project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ amdgpu
amet
Amoled
androideabi
anyrender
Appbar
appicon
appiconset
Expand Down Expand Up @@ -42,6 +43,7 @@ Baselining
baudrate
bdata
Belappetit
bezpath
BGRA
BGRX
bindgen
Expand Down Expand Up @@ -87,6 +89,7 @@ codepoint
codepoints
codesign
codingame
coeffs
coldict
colorspace
colour
Expand Down Expand Up @@ -246,6 +249,7 @@ gltf
glutin
glyphclusters
gnueabihf
goldens
grcov
Grimme
Grotesk
Expand Down Expand Up @@ -308,6 +312,7 @@ kitware
Klarälvdalens
kqueue
ksni
kurbo
labore
laboris
langtype
Expand Down Expand Up @@ -353,6 +358,7 @@ libxrandr
lightimg
limax
linearprogressindicator
linebender
linebreaker
linuxkms
lipo
Expand Down Expand Up @@ -452,6 +458,7 @@ pathlib
pbjson
PCLK
pdoc
peniko
picotool
pixabay
pixelcolor
Expand Down Expand Up @@ -499,6 +506,7 @@ Raleway
Randriana
rasterizer
rasterizes
rasterizing
RBUTTONUP
recompiles
rects
Expand Down Expand Up @@ -610,6 +618,7 @@ targetstruct
Tdefmt
tempor
termcolor
texels
texteditor
textinput
textinputinterface
Expand Down Expand Up @@ -642,6 +651,7 @@ udisksctl
udivdi
uintptr
ullamco
unclipped
unfocusable
unhinted
unicodes
Expand All @@ -653,14 +663,16 @@ unpark
unparseable
unpic
Unpremul
unpremultiply
unsendable
usbc
userconfig
usecases
userconfig
usize
usvg
vbus
vecmodel
vello
veniam
venv
verticalbox
Expand Down
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
tests/driver/rust/tests/*.rs linguist-generated
# These test files must keep their CRLF line terminators; the syntax test driver checks them.
internal/compiler/tests/syntax/**/crlf-* -text
# The anyrender command-stream goldens are compared byte-for-byte; keep LF
# on every platform so Windows checkouts do not introduce CRLF.
tests/screenshots/references/anyrender/**/*.json text eol=lf
45 changes: 44 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ members = [
'internal/renderers/software',
'internal/renderers/skia',
'internal/renderers/femtovg',
'internal/renderers/anyrender',
'internal/common',
'internal/compiler',
'internal/compiler/parser-test-macro',
Expand Down Expand Up @@ -92,6 +93,7 @@ i-slint-compiler = { version = "=1.18.0", path = "internal/compiler", default-fe
i-slint-core = { version = "=1.18.0", path = "internal/core", default-features = false }
i-slint-core-macros = { version = "=1.18.0", path = "internal/core-macros", default-features = false }
i-slint-live-preview = { version = "=1.18.0", path = "internal/live-preview", default-features = false }
i-slint-renderer-anyrender = { version = "=1.18.0", path = "internal/renderers/anyrender", default-features = false }
i-slint-renderer-femtovg = { version = "=1.18.0", path = "internal/renderers/femtovg", default-features = false }
i-slint-renderer-skia = { version = "=1.18.0", path = "internal/renderers/skia", default-features = false }
i-slint-renderer-software = { version = "=1.18.0", path = "internal/renderers/software", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ path = [
"editors/vscode/tests/grammar/**.slint",

"tests/screenshots/**/**.png",
"tests/screenshots/references/anyrender/**.json",

"tools/**.md",
"tools/slintpad/**.html",
Expand Down
32 changes: 32 additions & 0 deletions internal/renderers/anyrender/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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

[package]
name = "i-slint-renderer-anyrender"
description = "Slint renderer scaffolding generic over the anyrender backend"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
# Internal for now: no public API commitment yet, and no crates.io release.
publish = false

[lib]
path = "lib.rs"

[dependencies]
i-slint-core = { workspace = true, features = ["default", "shared-fontique", "shared-parley", "image-decoders", "svg"] }

vtable = { workspace = true }
lyon_path = { workspace = true }
rgb = "0.8.27"

anyrender = { version = "0.11" }
peniko = { version = "0.6" }
kurbo = { version = "0.13" }

[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
1 change: 1 addition & 0 deletions internal/renderers/anyrender/LICENSES/GPL-3.0-only.txt
94 changes: 94 additions & 0 deletions internal/renderers/anyrender/imagecache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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

//! Shared cache of Slint images converted to [`peniko::ImageData`].
//!
//! Converting a Slint image for anyrender means copying pixels (RGB
//! expansion, SVG rasterization, tile cropping) and wrapping them in a
//! [`peniko::Blob`]. Doing that per frame is wasteful in itself, but the
//! bigger cost is downstream: anyrender backends key their own image
//! resource caches on [`peniko::Blob::id`], so handing out a fresh `Blob`
//! every frame forces them to re-convert (and, for vello_cpu,
//! re-premultiply) the image on every fill. This cache returns the *same*
//! `ImageData` for the same source image across frames, making those
//! downstream caches effective.
//!
//! The caching model mirrors the femtovg renderer's texture cache: this
//! shared map deduplicates conversions across items, while the strong
//! references live in the per-item
//! [`ItemCache`](i_slint_core::item_rendering::ItemCache) held by
//! `AnyrenderSlintRenderer` (invalidated automatically when item
//! properties change). [`ImageConversionCache::drain`], called once per
//! frame, drops entries no item holds on to anymore.

use std::collections::HashMap;
use std::rc::Rc;

use i_slint_core::graphics::ImageCacheKey;

/// A cached, converted image. Cloning is cheap and shares the underlying
/// [`peniko::Blob`], keeping its id stable.
pub type SharedImageData = Rc<peniko::ImageData>;

/// Identifies which derived form of a source image an entry holds.
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) enum ImageVariant {
/// The image data as-is.
Full,
/// Rasterized at a specific pixel size (SVG).
Sized { width: u32, height: u32 },
/// A cropped sub-rectangle used as repeating tile. The source image
/// dimensions are part of the key because the crop coordinates are in
/// image-data space, whose content depends on the rasterized size for
/// scalable sources (SVG).
Tile { source_width: u32, source_height: u32, x: u32, y: u32, width: u32, height: u32 },
}

/// See the module documentation.
#[derive(Default)]
pub struct ImageConversionCache(HashMap<(ImageCacheKey, ImageVariant), SharedImageData>);

impl ImageConversionCache {
/// Look up (or convert and store) the `variant` of the image identified
/// by `key`. A `None` (or [`ImageCacheKey::Invalid`]) key means the
/// source has no stable identity; the conversion then runs uncached.
pub(crate) fn get_or_insert(
&mut self,
key: Option<ImageCacheKey>,
variant: ImageVariant,
convert: impl FnOnce() -> Option<peniko::ImageData>,
) -> Option<SharedImageData> {
let Some(key) = key.filter(|key| *key != ImageCacheKey::Invalid) else {
return convert().map(Rc::new);
};
match self.0.entry((key, variant)) {
std::collections::hash_map::Entry::Occupied(entry) => Some(entry.get().clone()),
std::collections::hash_map::Entry::Vacant(slot) => {
let image_data = Rc::new(convert()?);
slot.insert(image_data.clone());
Some(image_data)
}
}
}

/// Drop entries that are no longer referenced outside the cache. Call
/// once per rendered frame, like the femtovg renderer's texture cache
/// drain. Tiles are kept as long as their source image survives: they
/// are only referenced during the frame (no item holds them), but
/// re-cropping them every frame would defeat the cache for tiled
/// images.
pub(crate) fn drain(&mut self) {
self.0.retain(|(_, variant), image_data| {
matches!(variant, ImageVariant::Tile { .. }) || Rc::strong_count(image_data) > 1
});
let live_sources: std::collections::HashSet<ImageCacheKey> = self
.0
.keys()
.filter(|(_, variant)| !matches!(variant, ImageVariant::Tile { .. }))
.map(|(key, _)| key.clone())
.collect();
self.0.retain(|(key, variant), _| {
!matches!(variant, ImageVariant::Tile { .. }) || live_sources.contains(key)
});
}
}
Loading