From 5ebdc50988b71912ac64991b0a925b3d5d5df4d5 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 18 Nov 2025 11:24:23 +0100 Subject: [PATCH 1/9] femtovg: Switch to Edition 2024 --- internal/renderers/femtovg/Cargo.toml | 2 +- internal/renderers/femtovg/images.rs | 4 ++-- internal/renderers/femtovg/itemrenderer.rs | 6 +++--- internal/renderers/femtovg/lib.rs | 8 +++++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/renderers/femtovg/Cargo.toml b/internal/renderers/femtovg/Cargo.toml index bb8c7508adb..c129b868a81 100644 --- a/internal/renderers/femtovg/Cargo.toml +++ b/internal/renderers/femtovg/Cargo.toml @@ -5,7 +5,7 @@ name = "i-slint-renderer-femtovg" description = "FemtoVG based renderer for Slint" authors.workspace = true -edition.workspace = true +edition = "2024" homepage.workspace = true license.workspace = true repository.workspace = true diff --git a/internal/renderers/femtovg/images.rs b/internal/renderers/femtovg/images.rs index 1f231138c63..cff151d37f2 100644 --- a/internal/renderers/femtovg/images.rs +++ b/internal/renderers/femtovg/images.rs @@ -4,13 +4,13 @@ use std::collections::HashMap; use std::rc::Rc; -use i_slint_core::graphics::euclid; #[cfg(not(target_arch = "wasm32"))] use i_slint_core::graphics::BorrowedOpenGLTexture; +use i_slint_core::graphics::euclid; use i_slint_core::graphics::{ImageCacheKey, IntSize, SharedImageBuffer}; use i_slint_core::items::ImageTiling; use i_slint_core::lengths::PhysicalPx; -use i_slint_core::{items::ImageRendering, ImageInner}; +use i_slint_core::{ImageInner, items::ImageRendering}; use super::itemrenderer::CanvasRc; diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 4a4ca9f912f..3d2bb741359 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -22,14 +22,14 @@ use i_slint_core::lengths::{ LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector, RectLengths, ScaleFactor, }; -use i_slint_core::textlayout::sharedparley::{self, parley, GlyphRenderer}; +use i_slint_core::textlayout::sharedparley::{self, GlyphRenderer, parley}; use i_slint_core::{Brush, Color, ImageInner, SharedString}; use crate::images::TextureImporter; -use super::images::{Texture, TextureCacheKey}; use super::PhysicalSize; -use super::{font_cache, PhysicalBorderRadius, PhysicalLength, PhysicalPoint, PhysicalRect}; +use super::images::{Texture, TextureCacheKey}; +use super::{PhysicalBorderRadius, PhysicalLength, PhysicalPoint, PhysicalRect, font_cache}; type FemtovgBoxShadowCache = BoxShadowCache>; diff --git a/internal/renderers/femtovg/lib.rs b/internal/renderers/femtovg/lib.rs index 295834ab5c2..396be943ede 100644 --- a/internal/renderers/femtovg/lib.rs +++ b/internal/renderers/femtovg/lib.rs @@ -10,10 +10,11 @@ use std::pin::Pin; use std::rc::{Rc, Weak}; use i_slint_common::sharedfontique; +use i_slint_core::Brush; use i_slint_core::api::{RenderingNotifier, RenderingState, SetRenderingNotifierError}; use i_slint_core::graphics::SharedPixelBuffer; -use i_slint_core::graphics::{euclid, rendering_metrics_collector::RenderingMetricsCollector}; use i_slint_core::graphics::{BorderRadius, Rgba8Pixel}; +use i_slint_core::graphics::{euclid, rendering_metrics_collector::RenderingMetricsCollector}; use i_slint_core::item_rendering::ItemRenderer; use i_slint_core::item_tree::ItemTreeWeak; use i_slint_core::items::{ItemRc, TextWrap}; @@ -22,7 +23,6 @@ use i_slint_core::platform::PlatformError; use i_slint_core::renderer::RendererSealed; use i_slint_core::textlayout::sharedparley; use i_slint_core::window::{WindowAdapter, WindowInner}; -use i_slint_core::Brush; use images::TextureImporter; type PhysicalLength = euclid::Length; @@ -491,7 +491,9 @@ impl FemtoVGRendererExt for FemtoVGRenderer { if let Some(canvas) = self.canvas.borrow_mut().take() { if Rc::strong_count(&canvas) != 1 { - i_slint_core::debug_log!("internal warning: there are canvas references left when destroying the window. OpenGL resources will be leaked.") + i_slint_core::debug_log!( + "internal warning: there are canvas references left when destroying the window. OpenGL resources will be leaked." + ) } } From f484fba6d9c4358324fb1965b5cc8b3011235b18 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 18 Nov 2025 11:23:40 +0100 Subject: [PATCH 2/9] femtovg: Fix rendering of children outside a layer's intrinsic bounding rect The layer clips (by being a texture), so we must make sure to size it beyond its intrinsic size and include the bounding rect of the children, in size **and** position. cc #9808 --- internal/renderers/femtovg/itemrenderer.rs | 161 +++++++++++---------- 1 file changed, 84 insertions(+), 77 deletions(-) diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 3d2bb741359..3bed894c013 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -38,6 +38,10 @@ pub type CanvasRc = Rc>>; pub enum ItemGraphicsCacheEntry { Texture(Rc>), + TextureWithOrigin { + texture: Rc>, + origin: PhysicalPoint, + }, ColorizedImage { // This original image Rc is kept here to keep the image in the shared image cache, so that // changes to the colorization brush will not require re-uploading the image. @@ -50,6 +54,9 @@ impl Clone for ItemGraphicsCacheEntry fn clone(&self) -> Self { match self { Self::Texture(arg0) => Self::Texture(arg0.clone()), + Self::TextureWithOrigin { texture, origin } => { + Self::TextureWithOrigin { texture: texture.clone(), origin: origin.clone() } + } Self::ColorizedImage { _original_image, colorized_image } => Self::ColorizedImage { _original_image: _original_image.clone(), colorized_image: colorized_image.clone(), @@ -62,6 +69,7 @@ impl ItemGraphicsCacheEntry { fn as_texture(&self) -> &Rc> { match self { ItemGraphicsCacheEntry::Texture(image) => image, + ItemGraphicsCacheEntry::TextureWithOrigin { texture, .. } => texture, ItemGraphicsCacheEntry::ColorizedImage { colorized_image, .. } => colorized_image, } } @@ -670,7 +678,9 @@ impl<'a, R: femtovg::Renderer + TextureImporter> ItemRenderer for GLItemRenderer let border_width = clip_item.border_width(); if !radius.is_zero() { - if let Some(layer_image) = self.render_layer(item_rc, &|| item_rc.geometry()) { + if let Some((layer_origin, layer_image)) = + self.render_layer(item_rc, &|| item_rc.geometry()) + { let layer_image_paint = layer_image.as_paint(); let layer_path = clip_path_for_rect_alike_item( @@ -769,6 +779,7 @@ impl<'a, R: femtovg::Renderer + TextureImporter> ItemRenderer for GLItemRenderer }); let image_id = match cache_entry { Some(ItemGraphicsCacheEntry::Texture(image)) => image.id, + Some(ItemGraphicsCacheEntry::TextureWithOrigin { texture, .. }) => texture.id, Some(ItemGraphicsCacheEntry::ColorizedImage { .. }) => unreachable!(), None => return, }; @@ -1039,105 +1050,98 @@ impl<'a, R: femtovg::Renderer + TextureImporter> GLItemRenderer<'a, R> { &mut self, item_rc: &ItemRc, layer_bounding_rect_fn: &dyn Fn() -> LogicalRect, - ) -> Option>> { + ) -> Option<(PhysicalPoint, Rc>)> { let existing_layer_texture = self.graphics_cache.with_entry(item_rc, |cache_entry| match cache_entry { - Some(ItemGraphicsCacheEntry::Texture(texture)) => Some(texture.clone()), + Some(ItemGraphicsCacheEntry::TextureWithOrigin { texture, .. }) => { + Some(texture.clone()) + } _ => None, }); let cache_entry = self.graphics_cache.get_or_update_cache_entry(item_rc, || { - ItemGraphicsCacheEntry::Texture({ - let bounding_rect = layer_bounding_rect_fn(); - let size = (bounding_rect.size * self.scale_factor).ceil().try_cast()?; - - let layer_image = existing_layer_texture - .and_then(|layer_texture| { - // If we have an existing layer texture, there must be only one reference from within - // the existing cache entry and one through the `existing_layer_texture` variable. - // Then it is safe to render new content into it in this callback and when we return - // into `get_or_update_cache_entry` the first ref is dropped. - debug_assert_eq!(Rc::strong_count(&layer_texture), 2); - if layer_texture.size() == Some(size.to_untyped()) { - Some(layer_texture) - } else { - None - } - }) - .or_else(|| { - *self.metrics.layers_created.as_mut().unwrap() += 1; - Texture::new_empty_on_gpu(&self.canvas, size.width, size.height) - })?; + let bounding_rect = layer_bounding_rect_fn(); + let origin = bounding_rect.origin * self.scale_factor; + let size = (bounding_rect.size * self.scale_factor).ceil().try_cast()?; + + let layer_image = existing_layer_texture + .and_then(|layer_texture| { + // If we have an existing layer texture, there must be only one reference from within + // the existing cache entry and one through the `existing_layer_texture` variable. + // Then it is safe to render new content into it in this callback and when we return + // into `get_or_update_cache_entry` the first ref is dropped. + debug_assert_eq!(Rc::strong_count(&layer_texture), 2); + if layer_texture.size() == Some(size.to_untyped()) { + Some(layer_texture) + } else { + None + } + }) + .or_else(|| { + *self.metrics.layers_created.as_mut().unwrap() += 1; + Texture::new_empty_on_gpu(&self.canvas, size.width, size.height) + })?; - let previous_render_target = self.current_render_target(); + let previous_render_target = self.current_render_target(); - { - let mut canvas = self.canvas.borrow_mut(); - canvas.save(); + { + let mut canvas = self.canvas.borrow_mut(); + canvas.save(); - canvas.set_render_target(layer_image.as_render_target()); + canvas.set_render_target(layer_image.as_render_target()); - canvas.reset(); + canvas.reset(); - canvas.clear_rect( - 0, - 0, - size.width, - size.height, - femtovg::Color::rgba(0, 0, 0, 0), - ); + canvas.clear_rect(0, 0, size.width, size.height, femtovg::Color::rgba(0, 0, 0, 0)); - let origin = bounding_rect.origin * self.scale_factor; - canvas.translate(-origin.x, -origin.y); - } + canvas.translate(-origin.x, -origin.y); + } - *self.state.last_mut().unwrap() = State { - scissor: LogicalRect::new(LogicalPoint::default(), bounding_rect.size), - global_alpha: 1., - current_render_target: layer_image.as_render_target(), - }; + *self.state.last_mut().unwrap() = State { + scissor: bounding_rect, + global_alpha: 1., + current_render_target: layer_image.as_render_target(), + }; - let window_adapter = self.window().window_adapter(); + let window_adapter = self.window().window_adapter(); - i_slint_core::item_rendering::render_item_children( - self, - item_rc.item_tree(), - item_rc.index() as isize, - &window_adapter, - ); + i_slint_core::item_rendering::render_item_children( + self, + item_rc.item_tree(), + item_rc.index() as isize, + &window_adapter, + ); - { - let mut canvas = self.canvas.borrow_mut(); - canvas.restore(); + { + let mut canvas = self.canvas.borrow_mut(); + canvas.restore(); - canvas.set_render_target(previous_render_target); - } + canvas.set_render_target(previous_render_target); + } - layer_image - }) - .into() + Some(ItemGraphicsCacheEntry::TextureWithOrigin { texture: layer_image, origin }) }); - cache_entry.map(|item_cache_entry| item_cache_entry.as_texture().clone()) + cache_entry.and_then(|item_cache_entry| match item_cache_entry { + ItemGraphicsCacheEntry::TextureWithOrigin { texture, origin } => { + Some((origin, texture.clone())) + } + _ => None, + }) } fn render_and_blend_layer(&mut self, alpha_tint: f32, item_rc: &ItemRc) -> RenderingResult { let current_clip = self.get_current_clip(); - if let Some((layer_image, layer_size)) = self - .render_layer(item_rc, &|| { - // We don't need to include the size of the opacity item itself, since it has no content. - let children_rect = i_slint_core::properties::evaluate_no_tracking(|| { - item_rc.geometry().union( - &i_slint_core::item_rendering::item_children_bounding_rect( - item_rc.item_tree(), - item_rc.index() as isize, - ¤t_clip, - ), - ) - }); - children_rect + if let Some((layer_origin, layer_image)) = self.render_layer(item_rc, &|| { + // We don't need to include the size of the opacity item itself, since it has no content. + i_slint_core::properties::evaluate_no_tracking(|| { + i_slint_core::item_rendering::item_children_bounding_rect( + item_rc.item_tree(), + item_rc.index() as isize, + ¤t_clip, + ) }) - .and_then(|image| image.size().map(|size| (image, size))) + }) && let Some(layer_size) = layer_image.size() { let mut layer_path = femtovg::Path::new(); // On the paint for the layer, we don't need anti-aliasing on the fringes, @@ -1145,8 +1149,11 @@ impl<'a, R: femtovg::Renderer + TextureImporter> GLItemRenderer<'a, R> { let layer_image_paint = layer_image.as_paint_with_alpha(alpha_tint).with_anti_alias(false); - layer_path.rect(0., 0., layer_size.width as _, layer_size.height as _); - self.canvas.borrow_mut().fill_path(&layer_path, &layer_image_paint); + self.canvas.borrow_mut().save_with(|canvas| { + canvas.translate(layer_origin.x, layer_origin.y); + layer_path.rect(0., 0., layer_size.width as _, layer_size.height as _); + canvas.fill_path(&layer_path, &layer_image_paint); + }); } RenderingResult::ContinueRenderingWithoutChildren } From 1b5f443bb7d1c25f89cd6862e9a35c9f4bc80e0f Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 8 Oct 2025 13:54:34 +0200 Subject: [PATCH 3/9] Better debug for the ItemRc --- internal/core/item_tree.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/core/item_tree.rs b/internal/core/item_tree.rs index afc5883eb16..48e481606f4 100644 --- a/internal/core/item_tree.rs +++ b/internal/core/item_tree.rs @@ -269,12 +269,22 @@ pub enum ParentItemTraversalMode { /// A ItemRc is holding a reference to a ItemTree containing the item, and the index of this item #[repr(C)] -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ItemRc { item_tree: vtable::VRc, index: u32, } +impl core::fmt::Debug for ItemRc { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let comp_ref_pin = vtable::VRc::borrow_pin(&self.item_tree); + let mut debug = SharedString::new(); + comp_ref_pin.as_ref().item_element_infos(self.index, &mut debug); + + write!(f, "ItemRc{{ {:p}, {:?} {debug}}}", comp_ref_pin.as_ptr(), self.index) + } +} + impl ItemRc { /// Create an ItemRc from a ItemTree and an index pub fn new(item_tree: vtable::VRc, index: u32) -> Self { From 95bc34eaff99b9403555879a05929fbdc28de01b Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 18 Nov 2025 13:09:40 +0100 Subject: [PATCH 4/9] femtovg: fix warning about unused variable The layer_origin should normally always be 0,0 for the Clip element since the Clip element always cover its parent. But the correct thing to do is anyway to translate by the origin. --- internal/renderers/femtovg/itemrenderer.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 3bed894c013..78e455c0c22 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -690,7 +690,10 @@ impl<'a, R: femtovg::Renderer + TextureImporter> ItemRenderer for GLItemRenderer self.scale_factor, ); - self.canvas.borrow_mut().fill_path(&layer_path, &layer_image_paint); + self.canvas.borrow_mut().save_with(|canvas| { + canvas.translate(layer_origin.x, layer_origin.y); + canvas.fill_path(&layer_path, &layer_image_paint); + }); } RenderingResult::ContinueRenderingWithoutChildren From 8fe4ff3dca6110b5d113487691b5b30ca95c5f26 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 18 Nov 2025 16:26:16 +0100 Subject: [PATCH 5/9] skia: Fix rendering of children outside a layer's intrinsic bounding rect The layer clips (by being a texture), so we must make sure to size it beyond its intrinsic size and include the bounding rect of the children, in size **and** position. cc #9808 --- internal/renderers/skia/itemrenderer.rs | 22 +++++++++++++++------- internal/renderers/skia/lib.rs | 8 ++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/internal/renderers/skia/itemrenderer.rs b/internal/renderers/skia/itemrenderer.rs index bd9cc9233c9..1ffe9c1ec3d 100644 --- a/internal/renderers/skia/itemrenderer.rs +++ b/internal/renderers/skia/itemrenderer.rs @@ -39,6 +39,7 @@ pub struct SkiaItemRenderer<'a> { state_stack: Vec, current_state: RenderState, image_cache: &'a ItemCache>, + layer_cache: &'a ItemCache, skia_safe::Image)>>, path_cache: &'a ItemCache, skia_safe::Path)>>, box_shadow_cache: &'a mut SkiaBoxShadowCache, } @@ -49,6 +50,7 @@ impl<'a> SkiaItemRenderer<'a> { window: &'a i_slint_core::api::Window, surface: Option<&'a dyn crate::Surface>, image_cache: &'a ItemCache>, + layer_cache: &'a ItemCache, skia_safe::Image)>>, path_cache: &'a ItemCache, skia_safe::Path)>>, box_shadow_cache: &'a mut SkiaBoxShadowCache, ) -> Self { @@ -60,6 +62,7 @@ impl<'a> SkiaItemRenderer<'a> { state_stack: vec![], current_state: RenderState { alpha: 1.0, translation: Default::default() }, image_cache, + layer_cache, path_cache, box_shadow_cache, } @@ -321,7 +324,7 @@ impl<'a> SkiaItemRenderer<'a> { fn render_and_blend_layer(&mut self, item_rc: &ItemRc) -> RenderingResult { let current_clip = self.get_current_clip(); - if let Some(layer_image) = self.render_layer(item_rc, &|| { + if let Some((layer_offset, layer_image)) = self.render_layer(item_rc, &|| { // We don't need to include the size of the "layer" item itself, since it has no content. let children_rect = i_slint_core::properties::evaluate_no_tracking(|| { item_rc.geometry().union( @@ -332,9 +335,10 @@ impl<'a> SkiaItemRenderer<'a> { ), ) }); - children_rect.size_length() + children_rect }) { let _saved_canvas = self.pixel_align_origin(); + self.canvas.translate(skia_safe::Vector::from((layer_offset.x, layer_offset.y))); self.canvas.draw_image_with_sampling_options( layer_image, skia_safe::Point::default(), @@ -348,10 +352,12 @@ impl<'a> SkiaItemRenderer<'a> { fn render_layer( &mut self, item_rc: &ItemRc, - layer_logical_size_fn: &dyn Fn() -> LogicalSize, - ) -> Option { - self.image_cache.get_or_update_cache_entry(item_rc, || { - let layer_size = layer_logical_size_fn() * self.scale_factor; + layer_bounding_rect_fn: &dyn Fn() -> LogicalRect, + ) -> Option<(Vector2D, skia_safe::Image)> { + self.layer_cache.get_or_update_cache_entry(item_rc, || { + let bounding_rect = layer_bounding_rect_fn(); + let physical_origin = bounding_rect.origin * self.scale_factor; + let layer_size = bounding_rect.size * self.scale_factor; let image_info = skia_safe::ImageInfo::new( to_skia_size(&layer_size).to_ceil(), @@ -368,9 +374,11 @@ impl<'a> SkiaItemRenderer<'a> { self.window, self.surface, self.image_cache, + self.layer_cache, self.path_cache, self.box_shadow_cache, ); + sub_renderer.translate(-bounding_rect.origin.to_vector()); i_slint_core::item_rendering::render_item_children( &mut sub_renderer, @@ -379,7 +387,7 @@ impl<'a> SkiaItemRenderer<'a> { &WindowInner::from_pub(self.window).window_adapter(), ); - Some(surface.image_snapshot()) + Some((physical_origin.to_vector(), surface.image_snapshot())) }) } diff --git a/internal/renderers/skia/lib.rs b/internal/renderers/skia/lib.rs index 42984c7fb4a..d3f0129e965 100644 --- a/internal/renderers/skia/lib.rs +++ b/internal/renderers/skia/lib.rs @@ -161,6 +161,7 @@ pub struct SkiaRenderer { maybe_window_adapter: RefCell>>, rendering_notifier: RefCell>>, image_cache: ItemCache>, + layer_cache: ItemCache, skia_safe::Image)>>, path_cache: ItemCache, skia_safe::Path)>>, rendering_metrics_collector: RefCell>>, rendering_first_time: Cell, @@ -186,6 +187,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -206,6 +208,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -239,6 +242,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -272,6 +276,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -404,6 +409,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -453,6 +459,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Cell::new(true), @@ -648,6 +655,7 @@ impl SkiaRenderer { window, surface, &self.image_cache, + &self.layer_cache, &self.path_cache, &mut box_shadow_cache, ); From 0cd11126d9ddde750d55886a18718a74fae699ba Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Tue, 18 Nov 2025 17:15:13 +0100 Subject: [PATCH 6/9] skia: Remove unnecessary union with the layer's own geometry (as advised by Olivier and also done in femtovg) --- internal/renderers/skia/itemrenderer.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/renderers/skia/itemrenderer.rs b/internal/renderers/skia/itemrenderer.rs index 1ffe9c1ec3d..edf61837975 100644 --- a/internal/renderers/skia/itemrenderer.rs +++ b/internal/renderers/skia/itemrenderer.rs @@ -326,16 +326,13 @@ impl<'a> SkiaItemRenderer<'a> { let current_clip = self.get_current_clip(); if let Some((layer_offset, layer_image)) = self.render_layer(item_rc, &|| { // We don't need to include the size of the "layer" item itself, since it has no content. - let children_rect = i_slint_core::properties::evaluate_no_tracking(|| { - item_rc.geometry().union( - &i_slint_core::item_rendering::item_children_bounding_rect( - item_rc.item_tree(), - item_rc.index() as isize, - ¤t_clip, - ), + i_slint_core::properties::evaluate_no_tracking(|| { + i_slint_core::item_rendering::item_children_bounding_rect( + item_rc.item_tree(), + item_rc.index() as isize, + ¤t_clip, ) - }); - children_rect + }) }) { let _saved_canvas = self.pixel_align_origin(); self.canvas.translate(skia_safe::Vector::from((layer_offset.x, layer_offset.y))); From 0a7dbffca9760408035888d63b17d6453c207bb1 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 19 Nov 2025 08:50:04 +0100 Subject: [PATCH 7/9] Fix skia build --- internal/renderers/skia/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/renderers/skia/lib.rs b/internal/renderers/skia/lib.rs index d3f0129e965..2628c21b727 100644 --- a/internal/renderers/skia/lib.rs +++ b/internal/renderers/skia/lib.rs @@ -310,6 +310,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -343,6 +344,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), @@ -376,6 +378,7 @@ impl SkiaRenderer { maybe_window_adapter: Default::default(), rendering_notifier: Default::default(), image_cache: Default::default(), + layer_cache: Default::default(), path_cache: Default::default(), rendering_metrics_collector: Default::default(), rendering_first_time: Default::default(), From 56d136e35cb54276f403cd6e48eadf0ea439f953 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 19 Nov 2025 08:51:31 +0100 Subject: [PATCH 8/9] femtovg: Add doc comment for origin field in TextureWithOrigin --- internal/renderers/femtovg/itemrenderer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 78e455c0c22..57d8d8f1088 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -40,6 +40,7 @@ pub enum ItemGraphicsCacheEntry { Texture(Rc>), TextureWithOrigin { texture: Rc>, + /// Designated point where to draw the texture, relative to the item this cache entry is associated with. origin: PhysicalPoint, }, ColorizedImage { From 5ff952caa633f6fce9b791be746c2b02c92da6d6 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 19 Nov 2025 08:52:19 +0100 Subject: [PATCH 9/9] femtovg: Rename tuple field for ItemGraphicsCacheEntry::Texture match to match TextureWithOrigin --- internal/renderers/femtovg/itemrenderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 57d8d8f1088..3c646e33334 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -782,7 +782,7 @@ impl<'a, R: femtovg::Renderer + TextureImporter> ItemRenderer for GLItemRenderer cached_image }); let image_id = match cache_entry { - Some(ItemGraphicsCacheEntry::Texture(image)) => image.id, + Some(ItemGraphicsCacheEntry::Texture(texture)) => texture.id, Some(ItemGraphicsCacheEntry::TextureWithOrigin { texture, .. }) => texture.id, Some(ItemGraphicsCacheEntry::ColorizedImage { .. }) => unreachable!(), None => return,