diff options
author | 2023-04-27 16:09:39 +0200 | |
---|---|---|
committer | 2023-04-27 16:09:39 +0200 | |
commit | c31ab8eee6a49a48b4e6ad92207b8ee0360a0eff (patch) | |
tree | 0b099fc1e3810c2709a40627e11da5dc4988822b /tiny_skia/src | |
parent | e3730106e9d4f75de199e1b83cf285b8ff031968 (diff) | |
parent | a755472ee35dfb7839f989becafc6028921a3b99 (diff) | |
download | iced-c31ab8eee6a49a48b4e6ad92207b8ee0360a0eff.tar.gz iced-c31ab8eee6a49a48b4e6ad92207b8ee0360a0eff.tar.bz2 iced-c31ab8eee6a49a48b4e6ad92207b8ee0360a0eff.zip |
Merge pull request #1811 from iced-rs/incremental-rendering
Incremental rendering
Diffstat (limited to 'tiny_skia/src')
-rw-r--r-- | tiny_skia/src/backend.rs | 278 | ||||
-rw-r--r-- | tiny_skia/src/primitive.rs | 82 | ||||
-rw-r--r-- | tiny_skia/src/raster.rs | 2 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 18 | ||||
-rw-r--r-- | tiny_skia/src/vector.rs | 8 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 56 |
6 files changed, 259 insertions, 185 deletions
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 58076b84..9c69e1d2 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,4 +1,3 @@ -use crate::core::alignment; use crate::core::text; use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; use crate::graphics::backend; @@ -37,51 +36,99 @@ impl Backend { pub fn draw<T: AsRef<str>>( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::ClipMask, + clip_mask: &mut tiny_skia::Mask, primitives: &[Primitive], viewport: &Viewport, + damage: &[Rectangle], background_color: Color, overlay: &[T], ) { - pixels.fill(into_color(background_color)); - + let physical_size = viewport.physical_size(); let scale_factor = viewport.scale_factor() as f32; - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, + if !overlay.is_empty() { + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + 0.0, + 0.0, + physical_size.width as f32, + physical_size.height as f32, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color(Color { + a: 0.1, + ..background_color + })), + anti_alias: false, + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), None, - scale_factor, - Vector::ZERO, ); } - for (i, text) in overlay.iter().enumerate() { - const OVERLAY_TEXT_SIZE: f32 = 20.0; - - self.draw_primitive( - &Primitive::Text { - content: text.as_ref().to_owned(), - size: OVERLAY_TEXT_SIZE, - bounds: Rectangle { - x: 10.0, - y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, - width: f32::INFINITY, - height: f32::INFINITY, - }, - color: Color::BLACK, - font: Font::MONOSPACE, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + for ®ion in damage { + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + region.x, + region.y, + region.width, + region.height, + ) + .expect("Create damage rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + background_color, + )), + anti_alias: false, + ..Default::default() }, - pixels, - clip_mask, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), None, - scale_factor, - Vector::ZERO, ); + + adjust_clip_mask(clip_mask, region); + + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + region, + scale_factor, + Vector::ZERO, + ); + } + + if !overlay.is_empty() { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + Color::from_rgb(1.0, 0.0, 0.0), + )), + anti_alias: false, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: 1.0, + ..tiny_skia::Stroke::default() + }, + tiny_skia::Transform::identity(), + None, + ); + } } self.text_pipeline.trim_cache(); @@ -97,8 +144,8 @@ impl Backend { &mut self, primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::ClipMask, - clip_bounds: Option<Rectangle>, + clip_mask: &mut tiny_skia::Mask, + clip_bounds: Rectangle, scale_factor: f32, translation: Vector, ) { @@ -110,6 +157,15 @@ impl Backend { border_width, border_color, } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, @@ -117,7 +173,6 @@ impl Backend { .post_scale(scale_factor, scale_factor); let path = rounded_rectangle(*bounds, *border_radius); - let clip_mask = clip_bounds.map(|_| clip_mask as &_); pixels.fill_path( &path, @@ -165,6 +220,16 @@ impl Backend { horizontal_alignment, vertical_alignment, } => { + let physical_bounds = + (primitive.bounds() + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + self.text_pipeline.draw( content, (*bounds + translation) * scale_factor, @@ -174,24 +239,28 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - clip_bounds.map(|_| clip_mask as &_), + clip_mask, ); } #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, ) .post_scale(scale_factor, scale_factor); - self.raster_pipeline.draw( - handle, - *bounds, - pixels, - transform, - clip_bounds.map(|_| clip_mask as &_), - ); + self.raster_pipeline + .draw(handle, *bounds, pixels, transform, clip_mask); } #[cfg(feature = "svg")] Primitive::Svg { @@ -199,12 +268,21 @@ impl Backend { bounds, color, } => { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + self.vector_pipeline.draw( handle, *color, (*bounds + translation) * scale_factor, pixels, - clip_bounds.map(|_| clip_mask as &_), + clip_mask, ); } Primitive::Fill { @@ -213,6 +291,23 @@ impl Backend { rule, transform, } => { + let bounds = path.bounds(); + + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + pixels.fill_path( path, paint, @@ -220,7 +315,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_bounds.map(|_| clip_mask as &_), + clip_mask, ); } Primitive::Stroke { @@ -229,6 +324,23 @@ impl Backend { stroke, transform, } => { + let bounds = path.bounds(); + + let physical_bounds = (Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { + return; + } + + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then_some(clip_mask as &_); + pixels.stroke_path( path, paint, @@ -236,7 +348,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_bounds.map(|_| clip_mask as &_), + clip_mask, ); } Primitive::Group { primitives } => { @@ -267,29 +379,38 @@ impl Backend { Primitive::Clip { bounds, content } => { let bounds = (*bounds + translation) * scale_factor; - if bounds.x + bounds.width <= 0.0 - || bounds.y + bounds.height <= 0.0 - || bounds.x as u32 >= pixels.width() - || bounds.y as u32 >= pixels.height() - { - return; - } - - adjust_clip_mask(clip_mask, pixels, bounds); + if bounds == clip_bounds { + self.draw_primitive( + content, + pixels, + clip_mask, + bounds, + scale_factor, + translation, + ); + } else if let Some(bounds) = clip_bounds.intersection(&bounds) { + if bounds.x + bounds.width <= 0.0 + || bounds.y + bounds.height <= 0.0 + || bounds.x as u32 >= pixels.width() + || bounds.y as u32 >= pixels.height() + || bounds.width <= 1.0 + || bounds.height <= 1.0 + { + return; + } + + adjust_clip_mask(clip_mask, bounds); - self.draw_primitive( - content, - pixels, - clip_mask, - Some(bounds), - scale_factor, - translation, - ); + self.draw_primitive( + content, + pixels, + clip_mask, + bounds, + scale_factor, + translation, + ); - if let Some(bounds) = clip_bounds { - adjust_clip_mask(clip_mask, pixels, bounds); - } else { - clip_mask.clear(); + adjust_clip_mask(clip_mask, clip_bounds); } } Primitive::Cache { content } => { @@ -462,11 +583,9 @@ fn arc_to( } } -fn adjust_clip_mask( - clip_mask: &mut tiny_skia::ClipMask, - pixels: &tiny_skia::PixmapMut<'_>, - bounds: Rectangle, -) { +fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { + clip_mask.clear(); + let path = { let mut builder = tiny_skia::PathBuilder::new(); builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); @@ -474,15 +593,12 @@ fn adjust_clip_mask( builder.finish().unwrap() }; - clip_mask - .set_path( - pixels.width(), - pixels.height(), - &path, - tiny_skia::FillRule::EvenOdd, - true, - ) - .expect("Set path of clipping area"); + clip_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + false, + tiny_skia::Transform::default(), + ); } impl iced_graphics::Backend for Backend { diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs deleted file mode 100644 index 22daaedc..00000000 --- a/tiny_skia/src/primitive.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::{Rectangle, Vector}; - -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub enum Primitive { - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec<Primitive>, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box<Primitive>, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box<Primitive>, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cached { - /// The cached primitive - cache: Arc<Primitive>, - }, - /// A basic primitive. - Basic(iced_graphics::Primitive), -} - -impl iced_graphics::backend::Primitive for Primitive { - fn translate(self, translation: Vector) -> Self { - Self::Translate { - translation, - content: Box::new(self), - } - } - - fn clip(self, bounds: Rectangle) -> Self { - Self::Clip { - bounds, - content: Box::new(self), - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct Recording(pub(crate) Vec<Primitive>); - -impl iced_graphics::backend::Recording for Recording { - type Primitive = Primitive; - - fn push(&mut self, primitive: Primitive) { - self.0.push(primitive); - } - - fn push_basic(&mut self, basic: iced_graphics::Primitive) { - self.0.push(Primitive::Basic(basic)); - } - - fn group(self) -> Self::Primitive { - Primitive::Group { primitives: self.0 } - } - - fn clear(&mut self) { - self.0.clear(); - } -} - -impl Recording { - pub fn primitives(&self) -> &[Primitive] { - &self.0 - } -} diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 2fd73f8c..3887ec8d 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -31,7 +31,7 @@ impl Pipeline { bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, transform: tiny_skia::Transform, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: Option<&tiny_skia::Mask>, ) { if let Some(image) = self.cache.borrow_mut().allocate(handle) { let width_scale = bounds.width / image.width() as f32; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index e0e893bd..1246bbd5 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -50,7 +50,7 @@ impl Pipeline { horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: Option<&tiny_skia::Mask>, ) { let font_system = self.font_system.get_mut(); let key = Key { @@ -336,6 +336,7 @@ struct Cache { entries: FxHashMap<KeyHash, cosmic_text::Buffer>, recently_used: FxHashSet<KeyHash>, hasher: HashBuilder, + trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; impl Cache { + const TRIM_INTERVAL: usize = 300; + fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), + trim_count: 0, } } @@ -397,10 +401,16 @@ impl Cache { } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); + if self.trim_count > Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); - self.recently_used.clear(); + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } } } diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 8509b761..fc411fdd 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -32,7 +32,7 @@ impl Pipeline { color: Option<Color>, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: Option<&tiny_skia::Mask>, ) { if let Some(image) = self.cache.borrow_mut().draw( handle, @@ -72,6 +72,8 @@ struct RasterKey { impl Cache { fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { + use usvg::TreeParsing; + let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { @@ -131,9 +133,9 @@ impl Cache { resvg::render( tree, if size.width > size.height { - usvg::FitTo::Width(size.width) + resvg::FitTo::Width(size.width) } else { - usvg::FitTo::Height(size.height) + resvg::FitTo::Height(size.height) }, tiny_skia::Transform::default(), image.as_mut(), diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index cea1cabf..9999a188 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,5 +1,6 @@ -use crate::core::Color; +use crate::core::{Color, Rectangle}; use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::graphics::damage; use crate::graphics::{Error, Primitive, Viewport}; use crate::{Backend, Renderer, Settings}; @@ -7,13 +8,15 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; pub struct Compositor<Theme> { - clip_mask: tiny_skia::ClipMask, _theme: PhantomData<Theme>, } pub struct Surface { window: softbuffer::GraphicsContext, buffer: Vec<u32>, + clip_mask: tiny_skia::Mask, + primitives: Option<Vec<Primitive>>, + background_color: Color, } impl<Theme> crate::graphics::Compositor for Compositor<Theme> { @@ -43,6 +46,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { Surface { window, buffer: vec![0; width as usize * height as usize], + clip_mask: tiny_skia::Mask::new(width, height) + .expect("Create clip mask"), + primitives: None, + background_color: Color::BLACK, } } @@ -53,6 +60,9 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { height: u32, ) { surface.buffer.resize((width * height) as usize, 0); + surface.clip_mask = + tiny_skia::Mask::new(width, height).expect("Create clip mask"); + surface.primitives = None; } fn fetch_information(&self) -> Information { @@ -72,7 +82,6 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { ) -> Result<(), SurfaceError> { renderer.with_primitives(|backend, primitives| { present( - self, backend, surface, primitives, @@ -85,18 +94,15 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> { } pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) { - // TOD ( Compositor { - clip_mask: tiny_skia::ClipMask::new(), _theme: PhantomData, }, Backend::new(settings), ) } -pub fn present<Theme, T: AsRef<str>>( - compositor: &mut Compositor<Theme>, +pub fn present<T: AsRef<str>>( backend: &mut Backend, surface: &mut Surface, primitives: &[Primitive], @@ -105,17 +111,39 @@ pub fn present<Theme, T: AsRef<str>>( overlay: &[T], ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + + let mut pixels = tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"); + + let damage = surface + .primitives + .as_deref() + .and_then(|last_primitives| { + (surface.background_color == background_color) + .then(|| damage::list(last_primitives, primitives)) + }) + .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); + + if damage.is_empty() { + return Ok(()); + } + + surface.primitives = Some(primitives.to_vec()); + surface.background_color = background_color; + + let damage = damage::group(damage, scale_factor, physical_size); backend.draw( - &mut tiny_skia::PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut surface.buffer), - physical_size.width, - physical_size.height, - ) - .expect("Create pixel map"), - &mut compositor.clip_mask, + &mut pixels, + &mut surface.clip_mask, primitives, viewport, + &damage, background_color, overlay, ); |