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 { 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..3c646e33334 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>; @@ -38,6 +38,11 @@ pub type CanvasRc = Rc>>; 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 { // 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 +55,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 +70,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 +679,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( @@ -680,7 +691,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 @@ -768,7 +782,8 @@ 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, }; @@ -1039,105 +1054,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 +1153,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 } 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." + ) } } diff --git a/internal/renderers/skia/itemrenderer.rs b/internal/renderers/skia/itemrenderer.rs index bd9cc9233c9..edf61837975 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,20 +324,18 @@ 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( - &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.size_length() + }) }) { 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 +349,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 +371,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 +384,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..2628c21b727 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(), @@ -305,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(), @@ -338,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(), @@ -371,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(), @@ -404,6 +412,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 +462,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 +658,7 @@ impl SkiaRenderer { window, surface, &self.image_cache, + &self.layer_cache, &self.path_cache, &mut box_shadow_cache, );