Skip to content
12 changes: 11 additions & 1 deletion internal/core/item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ItemTreeVTable>,
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<ItemTreeVTable>, index: u32) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion internal/renderers/femtovg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/renderers/femtovg/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
175 changes: 93 additions & 82 deletions internal/renderers/femtovg/itemrenderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<R> = BoxShadowCache<ItemGraphicsCacheEntry<R>>;

Expand All @@ -38,6 +38,11 @@ pub type CanvasRc<R> = Rc<RefCell<Canvas<R>>>;

pub enum ItemGraphicsCacheEntry<R: femtovg::Renderer + TextureImporter> {
Texture(Rc<Texture<R>>),
TextureWithOrigin {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do with a line of documentation here

texture: Rc<Texture<R>>,
/// 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.
Expand All @@ -50,6 +55,9 @@ impl<R: femtovg::Renderer + TextureImporter> Clone for ItemGraphicsCacheEntry<R>
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(),
Expand All @@ -62,6 +70,7 @@ impl<R: femtovg::Renderer + TextureImporter> ItemGraphicsCacheEntry<R> {
fn as_texture(&self) -> &Rc<Texture<R>> {
match self {
ItemGraphicsCacheEntry::Texture(image) => image,
ItemGraphicsCacheEntry::TextureWithOrigin { texture, .. } => texture,
ItemGraphicsCacheEntry::ColorizedImage { colorized_image, .. } => colorized_image,
}
}
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -1039,114 +1054,110 @@ impl<'a, R: femtovg::Renderer + TextureImporter> GLItemRenderer<'a, R> {
&mut self,
item_rc: &ItemRc,
layer_bounding_rect_fn: &dyn Fn() -> LogicalRect,
) -> Option<Rc<Texture<R>>> {
) -> Option<(PhysicalPoint, Rc<Texture<R>>)> {
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,
&current_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,
&current_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,
// since we are just blitting a texture. This saves a triangle strip for the stroke.
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
}
Expand Down
8 changes: 5 additions & 3 deletions internal/renderers/femtovg/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<f32, PhysicalPx>;
Expand Down Expand Up @@ -491,7 +491,9 @@ impl<B: GraphicsBackend> FemtoVGRendererExt for FemtoVGRenderer<B> {

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."
)
}
}

Expand Down
Loading
Loading