From 0f7abffc0e94b4bb9f8117db633bfd07d900eb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 22 Mar 2023 00:36:57 +0100 Subject: [PATCH 01/28] Draft (very) basic incremental rendering for `iced_tiny_skia` --- Cargo.toml | 3 + core/src/image.rs | 30 +++++++- core/src/rectangle.rs | 34 +++++++++ core/src/svg.rs | 4 +- graphics/src/primitive.rs | 151 ++++++++++++++++++++++++++++++++++++- graphics/src/renderer.rs | 2 +- runtime/src/debug/basic.rs | 2 +- tiny_skia/src/backend.rs | 68 ++++++++++++++++- tiny_skia/src/primitive.rs | 82 -------------------- widget/src/text_input.rs | 2 + 10 files changed, 286 insertions(+), 92 deletions(-) delete mode 100644 tiny_skia/src/primitive.rs diff --git a/Cargo.toml b/Cargo.toml index 38c35f43bd..854dc8a065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,3 +86,6 @@ incremental = false opt-level = 3 overflow-checks = false strip = "debuginfo" + +[patch.crates-io] +tiny-skia = { version = "0.8", git = "https://github.com/hecrj/tiny-skia.git", rev = "213890dcbb3754d51533f5b558d9f5ffa3bf6da1" } diff --git a/core/src/image.rs b/core/src/image.rs index 70fbade052..618235efc0 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::sync::Arc; /// A handle of some image data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Data, @@ -156,6 +156,34 @@ impl std::fmt::Debug for Data { } } +impl PartialEq for Data { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Path(a), Self::Path(b)) => a == b, + (Self::Bytes(a), Self::Bytes(b)) => a.as_ref() == b.as_ref(), + ( + Self::Rgba { + width: width_a, + height: height_a, + pixels: pixels_a, + }, + Self::Rgba { + width: width_b, + height: height_b, + pixels: pixels_b, + }, + ) => { + width_a == width_b + && height_a == height_b + && pixels_a.as_ref() == pixels_b.as_ref() + } + _ => false, + } + } +} + +impl Eq for Data {} + /// A [`Renderer`] that can render raster graphics. /// /// [renderer]: crate::renderer diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 4fe9151957..5cdcbe785b 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -100,6 +100,30 @@ impl Rectangle { } } + /// Returns whether the [`Rectangle`] intersects with the given one. + pub fn intersects(&self, other: &Self) -> bool { + self.intersection(other).is_some() + } + + /// Computes the union with the given [`Rectangle`]. + pub fn union(&self, other: &Self) -> Self { + let x = self.x.min(other.x); + let y = self.y.min(other.y); + + let lower_right_x = (self.x + self.width).max(other.x + other.width); + let lower_right_y = (self.y + self.height).max(other.y + other.height); + + let width = lower_right_x - x; + let height = lower_right_y - y; + + Rectangle { + x, + y, + width, + height, + } + } + /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. pub fn snap(self) -> Rectangle { Rectangle { @@ -109,6 +133,16 @@ impl Rectangle { height: self.height as u32, } } + + /// Expands the [`Rectangle`] a given amount. + pub fn expand(self, amount: f32) -> Self { + Self { + x: self.x - amount, + y: self.y - amount, + width: self.width + amount * 2.0, + height: self.height + amount * 2.0, + } + } } impl std::ops::Mul for Rectangle { diff --git a/core/src/svg.rs b/core/src/svg.rs index 9b98877a2e..54e9434e05 100644 --- a/core/src/svg.rs +++ b/core/src/svg.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::sync::Arc; /// A handle of Svg data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Handle { id: u64, data: Arc, @@ -57,7 +57,7 @@ impl Hash for Handle { } /// The data of a vectorial image. -#[derive(Clone, Hash)] +#[derive(Clone, Hash, PartialEq, Eq)] pub enum Data { /// File data Path(PathBuf), diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 195b62daa5..bbf300b0be 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -7,7 +7,7 @@ use bytemuck::{Pod, Zeroable}; use std::sync::Arc; /// A rendering primitive. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum Primitive { /// A text primitive @@ -147,10 +147,153 @@ impl Primitive { content: Box::new(self), } } + + pub fn bounds(&self) -> Rectangle { + match self { + Self::Text { + bounds, + horizontal_alignment, + vertical_alignment, + .. + } => { + let mut bounds = *bounds; + + bounds.x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => { + bounds.x - bounds.width / 2.0 + } + alignment::Horizontal::Right => bounds.x - bounds.width, + }; + + bounds.y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => { + bounds.y - bounds.height / 2.0 + } + alignment::Vertical::Bottom => bounds.y - bounds.height, + }; + + bounds.expand(1.0) + } + Self::Quad { bounds, .. } + | Self::Image { bounds, .. } + | Self::Svg { bounds, .. } + | Self::Clip { bounds, .. } => bounds.expand(1.0), + Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { + Rectangle::with_size(*size) + } + #[cfg(feature = "tiny-skia")] + Self::Fill { path, .. } | Self::Stroke { path, .. } => { + let bounds = path.bounds(); + + Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + .expand(1.0) + } + Self::Group { primitives } => primitives + .iter() + .map(Self::bounds) + .fold(Rectangle::with_size(Size::ZERO), |a, b| { + Rectangle::union(&a, &b) + }), + Self::Translate { + translation, + content, + } => content.bounds() + *translation, + Self::Cache { content } => content.bounds(), + } + } + + pub fn damage(&self, other: &Self) -> Vec { + match (self, other) { + ( + Primitive::Group { + primitives: primitives_a, + }, + Primitive::Group { + primitives: primitives_b, + }, + ) => return Self::damage_list(primitives_a, primitives_b), + ( + Primitive::Clip { + bounds: bounds_a, + content: content_a, + }, + Primitive::Clip { + bounds: bounds_b, + content: content_b, + }, + ) => { + if bounds_a == bounds_b { + return content_a.damage(content_b); + } else { + return vec![*bounds_a, *bounds_b]; + } + } + ( + Primitive::Translate { + translation: translation_a, + content: content_a, + }, + Primitive::Translate { + translation: translation_b, + content: content_b, + }, + ) => { + if translation_a == translation_b { + return content_a.damage(content_b); + } + } + ( + Primitive::Cache { content: content_a }, + Primitive::Cache { content: content_b }, + ) => { + if Arc::ptr_eq(content_a, content_b) { + return vec![]; + } + } + _ if self == other => return vec![], + _ => {} + } + + let bounds_a = self.bounds(); + let bounds_b = other.bounds(); + + if bounds_a == bounds_b { + vec![bounds_a] + } else { + vec![bounds_a, bounds_b] + } + } + + pub fn damage_list(previous: &[Self], current: &[Self]) -> Vec { + let damage = + previous.iter().zip(current).flat_map(|(a, b)| a.damage(b)); + + if previous.len() == current.len() { + damage.collect() + } else { + let (smaller, bigger) = if previous.len() < current.len() { + (previous, current) + } else { + (current, previous) + }; + + // Extend damage by the added/removed primitives + damage + .chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) + .collect() + } + } } /// A set of [`Vertex2D`] and indices representing a list of triangles. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Mesh2D { /// The vertices of the mesh pub vertices: Vec, @@ -162,7 +305,7 @@ pub struct Mesh2D { } /// A two-dimensional vertex. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] #[repr(C)] pub struct Vertex2D { /// The vertex position in 2D space. @@ -170,7 +313,7 @@ pub struct Vertex2D { } /// A two-dimensional vertex with a color. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] #[repr(C)] pub struct ColoredVertex2D { /// The vertex position in 2D space. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 7bc462ef45..23e594be72 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -32,7 +32,7 @@ impl Renderer { } } - /// Returns the [`Backend`] of the [`Renderer`]. + /// Returns a reference to the [`Backend`] of the [`Renderer`]. pub fn backend(&self) -> &B { &self.backend } diff --git a/runtime/src/debug/basic.rs b/runtime/src/debug/basic.rs index 32f725a1d6..e9be4c84d7 100644 --- a/runtime/src/debug/basic.rs +++ b/runtime/src/debug/basic.rs @@ -129,7 +129,7 @@ impl Debug { pub fn render_finished(&mut self) { self.render_durations - .push(time::Instant::now() - self.render_start); + .push(dbg!(time::Instant::now() - self.render_start)); } pub fn log_message(&mut self, message: &Message) { diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 58076b840f..fe84f83b0f 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -17,6 +17,9 @@ pub struct Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline, + + last_primitives: Vec, + last_background_color: Color, } impl Backend { @@ -31,6 +34,9 @@ impl Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline::new(), + + last_primitives: Vec::new(), + last_background_color: Color::BLACK, } } @@ -43,10 +49,48 @@ impl Backend { background_color: Color, overlay: &[T], ) { - pixels.fill(into_color(background_color)); + let damage = if self.last_background_color == background_color { + Primitive::damage_list(&self.last_primitives, primitives) + } else { + vec![Rectangle::with_size(viewport.logical_size())] + }; + + if damage.is_empty() { + return; + } + + self.last_primitives = primitives.to_vec(); + self.last_background_color = background_color; let scale_factor = viewport.scale_factor() as f32; + dbg!(&damage); + + for region in &damage { + let region = *region * scale_factor; + + pixels.fill_path( + &tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + region.x, + region.y, + region.width.min(viewport.physical_width() as f32), + region.height.min(viewport.physical_height() as f32), + ) + .expect("Create damage rectangle"), + ), + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + background_color, + )), + ..Default::default() + }, + tiny_skia::FillRule::default(), + tiny_skia::Transform::identity(), + None, + ); + } + for primitive in primitives { self.draw_primitive( primitive, @@ -55,6 +99,7 @@ impl Backend { None, scale_factor, Vector::ZERO, + &damage, ); } @@ -81,6 +126,7 @@ impl Backend { None, scale_factor, Vector::ZERO, + &[], ); } @@ -101,6 +147,7 @@ impl Backend { clip_bounds: Option, scale_factor: f32, translation: Vector, + damage: &[Rectangle], ) { match primitive { Primitive::Quad { @@ -110,6 +157,10 @@ impl Backend { border_width, border_color, } => { + if !damage.iter().any(|damage| damage.intersects(bounds)) { + return; + } + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, @@ -165,6 +216,13 @@ impl Backend { horizontal_alignment, vertical_alignment, } => { + if !damage + .iter() + .any(|damage| damage.intersects(&primitive.bounds())) + { + return; + } + self.text_pipeline.draw( content, (*bounds + translation) * scale_factor, @@ -179,6 +237,10 @@ impl Backend { } #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { + if !damage.iter().any(|damage| damage.intersects(bounds)) { + return; + } + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, @@ -248,6 +310,7 @@ impl Backend { clip_bounds, scale_factor, translation, + damage, ); } } @@ -262,6 +325,7 @@ impl Backend { clip_bounds, scale_factor, translation + *offset, + damage, ); } Primitive::Clip { bounds, content } => { @@ -284,6 +348,7 @@ impl Backend { Some(bounds), scale_factor, translation, + damage, ); if let Some(bounds) = clip_bounds { @@ -300,6 +365,7 @@ impl Backend { clip_bounds, scale_factor, translation, + damage, ); } Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => { diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs deleted file mode 100644 index 22daaedc57..0000000000 --- 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, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cached { - /// The cached primitive - cache: Arc, - }, - /// 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); - -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/widget/src/text_input.rs b/widget/src/text_input.rs index d066109aab..4f0182846e 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -953,6 +953,8 @@ pub fn draw( let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { renderer.fill_quad(cursor, color); + } else { + renderer.with_translation(Vector::ZERO, |_| {}); } renderer.fill_text(Text { From 6270c33ed9823c67f6b6e6dac8fd32521e4ac5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 4 Apr 2023 20:47:53 +0200 Subject: [PATCH 02/28] Keep playing with incremental rendering (still very slow) --- graphics/src/primitive.rs | 12 +- tiny_skia/src/backend.rs | 232 ++++++++++++++++++++++++-------------- 2 files changed, 160 insertions(+), 84 deletions(-) diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index bbf300b0be..f079ff6fae 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -230,7 +230,11 @@ impl Primitive { }, ) => { if bounds_a == bounds_b { - return content_a.damage(content_b); + return content_a + .damage(content_b) + .into_iter() + .filter_map(|r| r.intersection(bounds_a)) + .collect(); } else { return vec![*bounds_a, *bounds_b]; } @@ -246,7 +250,11 @@ impl Primitive { }, ) => { if translation_a == translation_b { - return content_a.damage(content_b); + return content_a + .damage(content_b) + .into_iter() + .map(|r| r + *translation_a) + .collect(); } } ( diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index fe84f83b0f..8101082a41 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -20,6 +20,7 @@ pub struct Backend { last_primitives: Vec, last_background_color: Color, + last_size: Size, } impl Backend { @@ -37,6 +38,7 @@ impl Backend { last_primitives: Vec::new(), last_background_color: Color::BLACK, + last_size: Size::new(0, 0), } } @@ -47,9 +49,13 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, background_color: Color, - overlay: &[T], + _overlay: &[T], ) { - let damage = if self.last_background_color == background_color { + let physical_size = viewport.physical_size(); + + let damage = if self.last_background_color == background_color + && self.last_size == physical_size + { Primitive::damage_list(&self.last_primitives, primitives) } else { vec![Rectangle::with_size(viewport.logical_size())] @@ -61,24 +67,46 @@ impl Backend { self.last_primitives = primitives.to_vec(); self.last_background_color = background_color; + self.last_size = physical_size; let scale_factor = viewport.scale_factor() as f32; + let physical_bounds = Rectangle { + x: 0.0, + y: 0.0, + width: physical_size.width as f32, + height: physical_size.height as f32, + }; - dbg!(&damage); + dbg!(damage.len()); + + 'draw_regions: for (i, region) in damage.iter().enumerate() { + for previous in damage.iter().take(i) { + if previous.contains(region.position()) + && previous.contains( + region.position() + + Vector::new(region.width, region.height), + ) + { + continue 'draw_regions; + } + } - for region in &damage { let region = *region * scale_factor; + let Some(region) = physical_bounds.intersection(®ion) else { continue }; + + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + region.x, + region.y, + region.width.min(viewport.physical_width() as f32), + region.height.min(viewport.physical_height() as f32), + ) + .expect("Create damage rectangle"), + ); + pixels.fill_path( - &tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - region.x, - region.y, - region.width.min(viewport.physical_width() as f32), - region.height.min(viewport.physical_height() as f32), - ) - .expect("Create damage rectangle"), - ), + &path, &tiny_skia::Paint { shader: tiny_skia::Shader::SolidColor(into_color( background_color, @@ -89,47 +117,64 @@ impl Backend { tiny_skia::Transform::identity(), None, ); - } - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, - None, - scale_factor, - Vector::ZERO, - &damage, - ); - } + adjust_clip_mask(clip_mask, pixels, region); - 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, - }, - pixels, - clip_mask, - None, - scale_factor, - Vector::ZERO, - &[], - ); + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + region, + scale_factor, + Vector::ZERO, + ); + } + + //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: true, + // ..tiny_skia::Paint::default() + // }, + // &tiny_skia::Stroke { + // width: 1.0, + // ..tiny_skia::Stroke::default() + // }, + // tiny_skia::Transform::identity(), + // None, + //); } + //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, + // }, + // pixels, + // clip_mask, + // Rectangle::EMPTY, + // scale_factor, + // Vector::ZERO, + // ); + //} + self.text_pipeline.trim_cache(); #[cfg(feature = "image")] @@ -144,10 +189,9 @@ impl Backend { primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: &mut tiny_skia::ClipMask, - clip_bounds: Option, + clip_bounds: Rectangle, scale_factor: f32, translation: Vector, - damage: &[Rectangle], ) { match primitive { Primitive::Quad { @@ -157,7 +201,9 @@ impl Backend { border_width, border_color, } => { - if !damage.iter().any(|damage| damage.intersects(bounds)) { + if !clip_bounds + .intersects(&((*bounds + translation) * scale_factor)) + { return; } @@ -168,7 +214,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, @@ -185,7 +230,7 @@ impl Backend { }, tiny_skia::FillRule::EvenOdd, transform, - clip_mask, + Some(clip_mask), ); if *border_width > 0.0 { @@ -203,7 +248,7 @@ impl Backend { ..tiny_skia::Stroke::default() }, transform, - clip_mask, + Some(clip_mask), ); } } @@ -216,10 +261,9 @@ impl Backend { horizontal_alignment, vertical_alignment, } => { - if !damage - .iter() - .any(|damage| damage.intersects(&primitive.bounds())) - { + if !clip_bounds.intersects( + &((primitive.bounds() + translation) * scale_factor), + ) { return; } @@ -232,12 +276,14 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - clip_bounds.map(|_| clip_mask as &_), + Some(clip_mask), ); } #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { - if !damage.iter().any(|damage| damage.intersects(bounds)) { + if !clip_bounds + .intersects(&((*bounds + translation) * scale_factor)) + { return; } @@ -252,7 +298,7 @@ impl Backend { *bounds, pixels, transform, - clip_bounds.map(|_| clip_mask as &_), + Some(clip_mask), ); } #[cfg(feature = "svg")] @@ -275,6 +321,20 @@ impl Backend { rule, transform, } => { + let bounds = path.bounds(); + + if !clip_bounds.intersects( + &((Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor), + ) { + return; + } + pixels.fill_path( path, paint, @@ -282,7 +342,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_bounds.map(|_| clip_mask as &_), + Some(clip_mask), ); } Primitive::Stroke { @@ -291,6 +351,20 @@ impl Backend { stroke, transform, } => { + let bounds = path.bounds(); + + if !clip_bounds.intersects( + &((Rectangle { + x: bounds.x(), + y: bounds.y(), + width: bounds.width(), + height: bounds.height(), + } + translation) + * scale_factor), + ) { + return; + } + pixels.stroke_path( path, paint, @@ -298,7 +372,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_bounds.map(|_| clip_mask as &_), + Some(clip_mask), ); } Primitive::Group { primitives } => { @@ -310,7 +384,6 @@ impl Backend { clip_bounds, scale_factor, translation, - damage, ); } } @@ -325,7 +398,6 @@ impl Backend { clip_bounds, scale_factor, translation + *offset, - damage, ); } Primitive::Clip { bounds, content } => { @@ -339,22 +411,19 @@ impl Backend { return; } - adjust_clip_mask(clip_mask, pixels, bounds); + if let Some(bounds) = clip_bounds.intersection(&bounds) { + adjust_clip_mask(clip_mask, pixels, bounds); - self.draw_primitive( - content, - pixels, - clip_mask, - Some(bounds), - scale_factor, - translation, - damage, - ); + 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, pixels, clip_bounds); } } Primitive::Cache { content } => { @@ -365,7 +434,6 @@ impl Backend { clip_bounds, scale_factor, translation, - damage, ); } Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => { From f8cd1faa286daaf34cc532bf6d34b932b32eb35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 04:10:00 +0200 Subject: [PATCH 03/28] Group damage regions by area increase --- core/src/rectangle.rs | 14 ++ graphics/src/primitive.rs | 4 +- tiny_skia/src/backend.rs | 275 ++++++++++++++++++++++++-------------- tiny_skia/src/text.rs | 16 ++- 4 files changed, 200 insertions(+), 109 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 5cdcbe785b..7ff324cb89 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -66,6 +66,11 @@ impl Rectangle { Size::new(self.width, self.height) } + /// Returns the area of the [`Rectangle`]. + pub fn area(&self) -> f32 { + self.width * self.height + } + /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. pub fn contains(&self, point: Point) -> bool { self.x <= point.x @@ -74,6 +79,15 @@ impl Rectangle { && point.y <= self.y + self.height } + /// Returns true if the current [`Rectangle`] is completely within the given + /// `container`. + pub fn is_within(&self, container: &Rectangle) -> bool { + container.contains(self.position()) + && container.contains( + self.position() + Vector::new(self.width, self.height), + ) + } + /// Computes the intersection with the given [`Rectangle`]. pub fn intersection( &self, diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index f079ff6fae..01546dcb78 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -178,8 +178,8 @@ impl Primitive { } Self::Quad { bounds, .. } | Self::Image { bounds, .. } - | Self::Svg { bounds, .. } - | Self::Clip { bounds, .. } => bounds.expand(1.0), + | Self::Svg { bounds, .. } => bounds.expand(1.0), + Self::Clip { bounds, .. } => *bounds, Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { Rectangle::with_size(*size) } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 8101082a41..b141740971 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -49,7 +49,7 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, background_color: Color, - _overlay: &[T], + overlay: &[T], ) { let physical_size = viewport.physical_size(); @@ -70,37 +70,20 @@ impl Backend { self.last_size = physical_size; let scale_factor = viewport.scale_factor() as f32; - let physical_bounds = Rectangle { - x: 0.0, - y: 0.0, - width: physical_size.width as f32, - height: physical_size.height as f32, - }; - - dbg!(damage.len()); - 'draw_regions: for (i, region) in damage.iter().enumerate() { - for previous in damage.iter().take(i) { - if previous.contains(region.position()) - && previous.contains( - region.position() - + Vector::new(region.width, region.height), - ) - { - continue 'draw_regions; - } - } + let damage = group_damage(damage, scale_factor, physical_size); - let region = *region * scale_factor; - - let Some(region) = physical_bounds.intersection(®ion) else { continue }; + if !overlay.is_empty() { + pixels.fill(into_color(background_color)); + } + for region in damage { let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( region.x, region.y, - region.width.min(viewport.physical_width() as f32), - region.height.min(viewport.physical_height() as f32), + region.width, + region.height, ) .expect("Create damage rectangle"), ); @@ -111,6 +94,7 @@ impl Backend { shader: tiny_skia::Shader::SolidColor(into_color( background_color, )), + anti_alias: false, ..Default::default() }, tiny_skia::FillRule::default(), @@ -131,49 +115,62 @@ impl Backend { ); } - //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: true, - // ..tiny_skia::Paint::default() - // }, - // &tiny_skia::Stroke { - // width: 1.0, - // ..tiny_skia::Stroke::default() - // }, - // tiny_skia::Transform::identity(), - // None, - //); + 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, + ); + } } - //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, - // }, - // pixels, - // clip_mask, - // Rectangle::EMPTY, - // scale_factor, - // Vector::ZERO, - // ); - //} + if !overlay.is_empty() { + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: viewport.physical_width() as f32, + height: viewport.physical_height() as f32, + }; + + adjust_clip_mask(clip_mask, pixels, bounds); + + 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: bounds.width - 1.0, + height: bounds.height - 1.0, + }, + color: Color::BLACK, + font: Font::MONOSPACE, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }, + pixels, + clip_mask, + bounds, + scale_factor, + Vector::ZERO, + ); + } + } self.text_pipeline.trim_cache(); @@ -201,12 +198,15 @@ impl Backend { border_width, border_color, } => { - if !clip_bounds - .intersects(&((*bounds + translation) * scale_factor)) - { + let physical_bounds = (*bounds + translation) * scale_factor; + + if !clip_bounds.intersects(&physical_bounds) { return; } + let clip_mask = (!physical_bounds.is_within(&clip_bounds)) + .then(|| clip_mask as &_); + let transform = tiny_skia::Transform::from_translate( translation.x, translation.y, @@ -230,7 +230,7 @@ impl Backend { }, tiny_skia::FillRule::EvenOdd, transform, - Some(clip_mask), + clip_mask, ); if *border_width > 0.0 { @@ -248,7 +248,7 @@ impl Backend { ..tiny_skia::Stroke::default() }, transform, - Some(clip_mask), + clip_mask, ); } } @@ -261,12 +261,16 @@ impl Backend { horizontal_alignment, vertical_alignment, } => { - if !clip_bounds.intersects( - &((primitive.bounds() + translation) * scale_factor), - ) { + 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(|| clip_mask as &_); + self.text_pipeline.draw( content, (*bounds + translation) * scale_factor, @@ -276,7 +280,7 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - Some(clip_mask), + clip_mask, ); } #[cfg(feature = "image")] @@ -323,18 +327,21 @@ impl Backend { } => { let bounds = path.bounds(); - if !clip_bounds.intersects( - &((Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } + translation) - * scale_factor), - ) { + 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(|| clip_mask as &_); + pixels.fill_path( path, paint, @@ -342,7 +349,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - Some(clip_mask), + clip_mask, ); } Primitive::Stroke { @@ -353,18 +360,21 @@ impl Backend { } => { let bounds = path.bounds(); - if !clip_bounds.intersects( - &((Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } + translation) - * scale_factor), - ) { + 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(|| clip_mask as &_); + pixels.stroke_path( path, paint, @@ -372,7 +382,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - Some(clip_mask), + clip_mask, ); } Primitive::Group { primitives } => { @@ -403,15 +413,26 @@ 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; - } + 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; + } - if let Some(bounds) = clip_bounds.intersection(&bounds) { adjust_clip_mask(clip_mask, pixels, bounds); self.draw_primitive( @@ -614,11 +635,57 @@ fn adjust_clip_mask( pixels.height(), &path, tiny_skia::FillRule::EvenOdd, - true, + false, ) .expect("Set path of clipping area"); } +fn group_damage( + mut damage: Vec, + scale_factor: f32, + bounds: Size, +) -> Vec { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: bounds.width as f32, + height: bounds.height as f32, + }; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| (region * scale_factor).intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 865132b462..512503e0cb 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -336,6 +336,7 @@ struct Cache { entries: FxHashMap, recently_used: FxHashSet, hasher: HashBuilder, + trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault; impl Cache { + const TRIM_INTERVAL: usize = 300; + fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), + trim_count: 0, } } @@ -404,10 +408,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; + } } } From 4cae262d22cde458cbb5d0667e4bcf6adce9242a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 05:47:29 +0200 Subject: [PATCH 04/28] Implement `PartialEq` and `Eq` for `image::Bytes` --- core/src/image.rs | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/core/src/image.rs b/core/src/image.rs index 618235efc0..85d9d4758c 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes { } } +impl PartialEq for Bytes { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl Eq for Bytes {} + impl AsRef<[u8]> for Bytes { fn as_ref(&self) -> &[u8] { self.0.as_ref().as_ref() @@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes { } /// The data of a raster image. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum Data { /// File data Path(PathBuf), @@ -156,34 +164,6 @@ impl std::fmt::Debug for Data { } } -impl PartialEq for Data { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Path(a), Self::Path(b)) => a == b, - (Self::Bytes(a), Self::Bytes(b)) => a.as_ref() == b.as_ref(), - ( - Self::Rgba { - width: width_a, - height: height_a, - pixels: pixels_a, - }, - Self::Rgba { - width: width_b, - height: height_b, - pixels: pixels_b, - }, - ) => { - width_a == width_b - && height_a == height_b - && pixels_a.as_ref() == pixels_b.as_ref() - } - _ => false, - } - } -} - -impl Eq for Data {} - /// A [`Renderer`] that can render raster graphics. /// /// [renderer]: crate::renderer From 1bba9a080f796276d5da87bf8decd2ed89ef218a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 05:49:30 +0200 Subject: [PATCH 05/28] Fix `Svg` and `Image` primitives in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b141740971..8bc2321ce9 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -205,7 +205,7 @@ impl Backend { } let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then(|| clip_mask as &_); + .then_some(clip_mask as &_); let transform = tiny_skia::Transform::from_translate( translation.x, @@ -269,7 +269,7 @@ impl Backend { } let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then(|| clip_mask as &_); + .then_some(clip_mask as &_); self.text_pipeline.draw( content, @@ -285,25 +285,23 @@ impl Backend { } #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { - if !clip_bounds - .intersects(&((*bounds + translation) * scale_factor)) - { + 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, - Some(clip_mask), - ); + self.raster_pipeline + .draw(handle, *bounds, pixels, transform, clip_mask); } #[cfg(feature = "svg")] Primitive::Svg { @@ -311,12 +309,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 { @@ -340,7 +347,7 @@ impl Backend { } let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then(|| clip_mask as &_); + .then_some(clip_mask as &_); pixels.fill_path( path, @@ -373,7 +380,7 @@ impl Backend { } let clip_mask = (!physical_bounds.is_within(&clip_bounds)) - .then(|| clip_mask as &_); + .then_some(clip_mask as &_); pixels.stroke_path( path, From 4ede482ab5ff6364237f5f4626784075045d5dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 18:41:40 +0200 Subject: [PATCH 06/28] Present new frame only when damaged in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 6 ++++-- tiny_skia/src/window/compositor.rs | 14 ++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 8bc2321ce9..0b534bba2e 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -50,7 +50,7 @@ impl Backend { viewport: &Viewport, background_color: Color, overlay: &[T], - ) { + ) -> bool { let physical_size = viewport.physical_size(); let damage = if self.last_background_color == background_color @@ -62,7 +62,7 @@ impl Backend { }; if damage.is_empty() { - return; + return false; } self.last_primitives = primitives.to_vec(); @@ -179,6 +179,8 @@ impl Backend { #[cfg(feature = "svg")] self.vector_pipeline.trim_cache(); + + true } fn draw_primitive( diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index cea1cabfb4..6e4bb6efa8 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -106,7 +106,7 @@ pub fn present>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); - backend.draw( + let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut surface.buffer), physical_size.width, @@ -120,11 +120,13 @@ pub fn present>( overlay, ); - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); + if drawn { + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); + } Ok(()) } From e134a82f4ca67346fc9095a0d488122737cfb0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 18:42:27 +0200 Subject: [PATCH 07/28] Switch debug mode to fade old primitives and display damage in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 97 +++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 0b534bba2e..974faa7480 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -74,7 +74,30 @@ impl Backend { let damage = group_damage(damage, scale_factor, physical_size); if !overlay.is_empty() { - pixels.fill(into_color(background_color)); + 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, + ); } for region in damage { @@ -135,42 +158,42 @@ impl Backend { } } - if !overlay.is_empty() { - let bounds = Rectangle { - x: 0.0, - y: 0.0, - width: viewport.physical_width() as f32, - height: viewport.physical_height() as f32, - }; - - adjust_clip_mask(clip_mask, pixels, bounds); - - 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: bounds.width - 1.0, - height: bounds.height - 1.0, - }, - color: Color::BLACK, - font: Font::MONOSPACE, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - }, - pixels, - clip_mask, - bounds, - scale_factor, - Vector::ZERO, - ); - } - } + //if !overlay.is_empty() { + // let bounds = Rectangle { + // x: 0.0, + // y: 0.0, + // width: viewport.physical_width() as f32, + // height: viewport.physical_height() as f32, + // }; + + // adjust_clip_mask(clip_mask, pixels, bounds); + + // 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: bounds.width - 1.0, + // height: bounds.height - 1.0, + // }, + // color: Color::BLACK, + // font: Font::MONOSPACE, + // horizontal_alignment: alignment::Horizontal::Left, + // vertical_alignment: alignment::Vertical::Top, + // }, + // pixels, + // clip_mask, + // bounds, + // scale_factor, + // Vector::ZERO, + // ); + // } + //} self.text_pipeline.trim_cache(); From 92d61e5c592818745abbab245a82cb14b262ae67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 19:23:48 +0200 Subject: [PATCH 08/28] Use `softbuffer` fork with owned pixel buffer --- tiny_skia/Cargo.toml | 5 +++- tiny_skia/src/window/compositor.rs | 42 ++++++++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 349f690335..db590fc248 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -10,7 +10,6 @@ geometry = ["iced_graphics/geometry"] [dependencies] raw-window-handle = "0.5" -softbuffer = "0.2" tiny-skia = "0.8" bytemuck = "1" rustc-hash = "1.1" @@ -22,6 +21,10 @@ version = "0.7" path = "../graphics" features = ["tiny-skia"] +[dependencies.softbuffer] +git = "https://github.com/rust-windowing/softbuffer" +rev = "872c66a4c05fd7cb6cb133154f75fdce45a175a6" + [dependencies.cosmic-text] features = ["std", "swash"] git = "https://github.com/pop-os/cosmic-text" diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 6e4bb6efa8..5b1e56a4c5 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -5,6 +5,7 @@ use crate::{Backend, Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; +use std::num::NonZeroU32; pub struct Compositor { clip_mask: tiny_skia::ClipMask, @@ -12,8 +13,7 @@ pub struct Compositor { } pub struct Surface { - window: softbuffer::GraphicsContext, - buffer: Vec, + window: softbuffer::Surface, } impl crate::graphics::Compositor for Compositor { @@ -36,14 +36,20 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) -> Surface { - let window = - unsafe { softbuffer::GraphicsContext::new(window, window) } - .expect("Create softbuffer for window"); + let platform = unsafe { softbuffer::Context::new(window) } + .expect("Create softbuffer context"); - Surface { - window, - buffer: vec![0; width as usize * height as usize], - } + let mut window = unsafe { softbuffer::Surface::new(&platform, window) } + .expect("Create softbuffer surface"); + + window + .resize( + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ) + .expect("Resize surface"); + + Surface { window } } fn configure_surface( @@ -52,7 +58,13 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) { - surface.buffer.resize((width * height) as usize, 0); + surface + .window + .resize( + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ) + .expect("Resize surface"); } fn fetch_information(&self) -> Information { @@ -106,9 +118,11 @@ pub fn present>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); + let mut buffer = surface.window.buffer_mut().expect("Get window buffer"); + let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut surface.buffer), + bytemuck::cast_slice_mut(&mut buffer), physical_size.width, physical_size.height, ) @@ -121,11 +135,7 @@ pub fn present>( ); if drawn { - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); + let _ = buffer.present(); } Ok(()) From 940a47eafd098dce3567a95c38dc8697b0fc7115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 5 Apr 2023 19:30:07 +0200 Subject: [PATCH 09/28] Revert "Use `softbuffer` fork with owned pixel buffer" This reverts commit 92d61e5c592818745abbab245a82cb14b262ae67. The owned pixel buffer zeroes the data in some platforms. `softbuffer` will need some first-class support for damage regions. --- tiny_skia/Cargo.toml | 5 +--- tiny_skia/src/window/compositor.rs | 42 ++++++++++++------------------ 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index db590fc248..349f690335 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -10,6 +10,7 @@ geometry = ["iced_graphics/geometry"] [dependencies] raw-window-handle = "0.5" +softbuffer = "0.2" tiny-skia = "0.8" bytemuck = "1" rustc-hash = "1.1" @@ -21,10 +22,6 @@ version = "0.7" path = "../graphics" features = ["tiny-skia"] -[dependencies.softbuffer] -git = "https://github.com/rust-windowing/softbuffer" -rev = "872c66a4c05fd7cb6cb133154f75fdce45a175a6" - [dependencies.cosmic-text] features = ["std", "swash"] git = "https://github.com/pop-os/cosmic-text" diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 5b1e56a4c5..6e4bb6efa8 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -5,7 +5,6 @@ use crate::{Backend, Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; -use std::num::NonZeroU32; pub struct Compositor { clip_mask: tiny_skia::ClipMask, @@ -13,7 +12,8 @@ pub struct Compositor { } pub struct Surface { - window: softbuffer::Surface, + window: softbuffer::GraphicsContext, + buffer: Vec, } impl crate::graphics::Compositor for Compositor { @@ -36,20 +36,14 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) -> Surface { - let platform = unsafe { softbuffer::Context::new(window) } - .expect("Create softbuffer context"); + let window = + unsafe { softbuffer::GraphicsContext::new(window, window) } + .expect("Create softbuffer for window"); - let mut window = unsafe { softbuffer::Surface::new(&platform, window) } - .expect("Create softbuffer surface"); - - window - .resize( - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ) - .expect("Resize surface"); - - Surface { window } + Surface { + window, + buffer: vec![0; width as usize * height as usize], + } } fn configure_surface( @@ -58,13 +52,7 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) { - surface - .window - .resize( - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ) - .expect("Resize surface"); + surface.buffer.resize((width * height) as usize, 0); } fn fetch_information(&self) -> Information { @@ -118,11 +106,9 @@ pub fn present>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); - let mut buffer = surface.window.buffer_mut().expect("Get window buffer"); - let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut buffer), + bytemuck::cast_slice_mut(&mut surface.buffer), physical_size.width, physical_size.height, ) @@ -135,7 +121,11 @@ pub fn present>( ); if drawn { - let _ = buffer.present(); + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); } Ok(()) From 16e6efe020e75d51958875fa198196534679af8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 05:58:27 +0200 Subject: [PATCH 10/28] Use `pixels` for presentation in `iced_tiny_skia` when possible --- tiny_skia/Cargo.toml | 6 +++ tiny_skia/src/window/compositor.rs | 76 +++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 7a5f5b1741..731e06dea5 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [features] +default = ["gpu"] +gpu = ["pixels"] image = ["iced_graphics/image"] svg = ["resvg"] geometry = ["iced_graphics/geometry"] @@ -31,6 +33,10 @@ default-features = false version = "1.6.1" features = ["std"] +[dependencies.pixels] +version = "0.12" +optional = true + [dependencies.resvg] version = "0.29" optional = true diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 6e4bb6efa8..5a0097dff5 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -11,9 +11,13 @@ pub struct Compositor { _theme: PhantomData, } -pub struct Surface { - window: softbuffer::GraphicsContext, - buffer: Vec, +pub enum Surface { + Cpu { + window: softbuffer::GraphicsContext, + buffer: Vec, + }, + #[cfg(feature = "gpu")] + Gpu { pixels: pixels::Pixels }, } impl crate::graphics::Compositor for Compositor { @@ -36,11 +40,29 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) -> Surface { + #[cfg(feature = "gpu")] + { + let surface_texture = + pixels::SurfaceTexture::new(width, height, window); + + if let Ok(pixels) = + pixels::PixelsBuilder::new(width, height, surface_texture) + .texture_format(pixels::wgpu::TextureFormat::Bgra8UnormSrgb) + .build() + { + log::info!("GPU surface created"); + + return Surface::Gpu { pixels }; + } + } + let window = unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); - Surface { + log::info!("CPU surface created"); + + Surface::Cpu { window, buffer: vec![0; width as usize * height as usize], } @@ -52,7 +74,19 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) { - surface.buffer.resize((width * height) as usize, 0); + match surface { + Surface::Cpu { buffer, .. } => { + buffer.resize((width * height) as usize, 0); + } + #[cfg(feature = "gpu")] + Surface::Gpu { pixels } => { + pixels + .resize_surface(width, height) + .expect("Resize surface"); + + pixels.resize_buffer(width, height).expect("Resize buffer"); + } + } } fn fetch_information(&self) -> Information { @@ -106,9 +140,15 @@ pub fn present>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); + let buffer = match surface { + Surface::Cpu { buffer, .. } => bytemuck::cast_slice_mut(buffer), + #[cfg(feature = "gpu")] + Surface::Gpu { pixels } => pixels.frame_mut(), + }; + let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut surface.buffer), + buffer, physical_size.width, physical_size.height, ) @@ -121,12 +161,22 @@ pub fn present>( ); if drawn { - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); + match surface { + Surface::Cpu { window, buffer } => { + window.set_buffer( + buffer, + physical_size.width as u16, + physical_size.height as u16, + ); + + Ok(()) + } + #[cfg(feature = "gpu")] + Surface::Gpu { pixels } => { + pixels.render().map_err(|_| compositor::SurfaceError::Lost) + } + } + } else { + Ok(()) } - - Ok(()) } From 1872f7fa6d7b9ca9fa0db8d14bf44dcd3513ffca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 06:14:25 +0200 Subject: [PATCH 11/28] Use `*_from_env` helpers from `wgpu` in `iced_wgpu` --- wgpu/src/settings.rs | 17 +---------------- wgpu/src/window/compositor.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index ff041bdff8..266a2c87fc 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -46,7 +46,7 @@ impl Settings { /// - `primary` pub fn from_env() -> Self { Settings { - internal_backend: backend_from_env() + internal_backend: wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::all()), ..Self::default() } @@ -64,18 +64,3 @@ impl Default for Settings { } } } - -fn backend_from_env() -> Option { - std::env::var("WGPU_BACKEND").ok().map(|backend| { - match backend.to_lowercase().as_str() { - "vulkan" => wgpu::Backends::VULKAN, - "metal" => wgpu::Backends::METAL, - "dx12" => wgpu::Backends::DX12, - "dx11" => wgpu::Backends::DX11, - "gl" => wgpu::Backends::GL, - "webgpu" => wgpu::Backends::BROWSER_WEBGPU, - "primary" => wgpu::Backends::PRIMARY, - other => panic!("Unknown backend: {other}"), - } - }) -} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 025cd43a3d..15bef60c45 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -53,11 +53,12 @@ impl Compositor { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }, + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }) From 42b2e9b007d49506c98d0242b8b061df3aa4228b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 06:44:46 +0200 Subject: [PATCH 12/28] Support `ICED_BACKEND` environment variable --- renderer/src/compositor.rs | 127 ++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 43 deletions(-) diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 218e7e332e..1ce13c7d0f 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -4,6 +4,7 @@ use crate::graphics::{Error, Viewport}; use crate::{Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::env; pub enum Compositor { #[cfg(feature = "wgpu")] @@ -28,53 +29,13 @@ impl crate::graphics::Compositor for Compositor { settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - #[cfg(feature = "wgpu")] - let new_wgpu = |settings: Self::Settings, compatible_window| { - let (compositor, backend) = iced_wgpu::window::compositor::new( - iced_wgpu::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..iced_wgpu::Settings::from_env() - }, - compatible_window, - )?; - - Ok(( - Self::Wgpu(compositor), - Renderer::new(crate::Backend::Wgpu(backend)), - )) - }; - - #[cfg(feature = "tiny-skia")] - let new_tiny_skia = |settings: Self::Settings, _compatible_window| { - let (compositor, backend) = iced_tiny_skia::window::compositor::new( - iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }, - ); - - Ok(( - Self::TinySkia(compositor), - Renderer::new(crate::Backend::TinySkia(backend)), - )) - }; - - let fail = |_, _| Err(Error::GraphicsAdapterNotFound); - - let candidates = &[ - #[cfg(feature = "wgpu")] - new_wgpu, - #[cfg(feature = "tiny-skia")] - new_tiny_skia, - fail, - ]; + let candidates = + Candidate::list_from_env().unwrap_or(Candidate::default_list()); let mut error = Error::GraphicsAdapterNotFound; for candidate in candidates { - match candidate(settings, compatible_window) { + match candidate.build(settings, compatible_window) { Ok((compositor, renderer)) => { return Ok((compositor, renderer)) } @@ -183,3 +144,83 @@ impl crate::graphics::Compositor for Compositor { }) } } + +enum Candidate { + Wgpu, + TinySkia, +} + +impl Candidate { + fn default_list() -> Vec { + vec![ + #[cfg(feature = "wgpu")] + Self::Wgpu, + #[cfg(feature = "tiny-skia")] + Self::TinySkia, + ] + } + + fn list_from_env() -> Option> { + let backends = env::var("ICED_BACKEND").ok()?; + + Some( + backends + .split(',') + .map(str::trim) + .map(|backend| match backend { + "wgpu" => Self::Wgpu, + "tiny-skia" => Self::TinySkia, + _ => panic!("unknown backend value: \"{backend}\""), + }) + .collect(), + ) + } + + fn build( + self, + settings: Settings, + compatible_window: Option<&W>, + ) -> Result<(Compositor, Renderer), Error> { + match self { + Self::Wgpu => { + if cfg!(feature = "wgpu") { + let (compositor, backend) = + iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + )?; + + return Ok(( + Compositor::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )); + } else { + panic!("`wgpu` feature was not enabled in `iced_renderer`"); + } + } + Self::TinySkia => { + if cfg!(feature = "tiny-skia") { + let (compositor, backend) = + iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Compositor::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + } else { + panic!("`wgpu` feature was not enabled in `iced_renderer`"); + } + } + } + } +} From c2249d2506585abac0f056c619a6dca1b05e8c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 06:45:44 +0200 Subject: [PATCH 13/28] Fix copy-pasted panic description in `iced_renderer` --- renderer/src/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 1ce13c7d0f..484d91eb12 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -218,7 +218,7 @@ impl Candidate { Renderer::new(crate::Backend::TinySkia(backend)), )) } else { - panic!("`wgpu` feature was not enabled in `iced_renderer`"); + panic!("`tiny-skia` feature was not enabled in `iced_renderer`"); } } } From ba07abe5c8d3f6fb175e13f5cd10f74ee8ab460c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 07:08:03 +0200 Subject: [PATCH 14/28] Expand bounds of `Text` primitives a bit further --- graphics/src/primitive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 01546dcb78..7f2c8ae2b1 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -174,7 +174,7 @@ impl Primitive { alignment::Vertical::Bottom => bounds.y - bounds.height, }; - bounds.expand(1.0) + bounds.expand(1.5) } Self::Quad { bounds, .. } | Self::Image { bounds, .. } From d206b82ebb7617337cd378bd19542a7abf8a4529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 8 Apr 2023 07:35:16 +0200 Subject: [PATCH 15/28] Update `glyphon` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 36c891bb9e..c1f23246e3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "6601deec1c7595f8fd5f83f929b2497104905400" +rev = "1d26d92b19407c5dabe4625944d4a6babbbf0715" [dependencies.encase] version = "0.3.0" From 435b54e57ecdaf08b1f36626f0000438895c909d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:09:36 +0200 Subject: [PATCH 16/28] Revert "Use `pixels` for presentation in `iced_tiny_skia` when possible" This reverts commit 16e6efe020e75d51958875fa198196534679af8d. --- tiny_skia/Cargo.toml | 6 --- tiny_skia/src/window/compositor.rs | 76 +++++------------------------- 2 files changed, 13 insertions(+), 69 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index a8342e5fd0..f629dab934 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition = "2021" [features] -default = ["gpu"] -gpu = ["pixels"] image = ["iced_graphics/image"] svg = ["resvg"] geometry = ["iced_graphics/geometry"] @@ -33,10 +31,6 @@ default-features = false version = "1.6.1" features = ["std"] -[dependencies.pixels] -version = "0.12" -optional = true - [dependencies.resvg] version = "0.29" optional = true diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 5a0097dff5..6e4bb6efa8 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -11,13 +11,9 @@ pub struct Compositor { _theme: PhantomData, } -pub enum Surface { - Cpu { - window: softbuffer::GraphicsContext, - buffer: Vec, - }, - #[cfg(feature = "gpu")] - Gpu { pixels: pixels::Pixels }, +pub struct Surface { + window: softbuffer::GraphicsContext, + buffer: Vec, } impl crate::graphics::Compositor for Compositor { @@ -40,29 +36,11 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) -> Surface { - #[cfg(feature = "gpu")] - { - let surface_texture = - pixels::SurfaceTexture::new(width, height, window); - - if let Ok(pixels) = - pixels::PixelsBuilder::new(width, height, surface_texture) - .texture_format(pixels::wgpu::TextureFormat::Bgra8UnormSrgb) - .build() - { - log::info!("GPU surface created"); - - return Surface::Gpu { pixels }; - } - } - let window = unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); - log::info!("CPU surface created"); - - Surface::Cpu { + Surface { window, buffer: vec![0; width as usize * height as usize], } @@ -74,19 +52,7 @@ impl crate::graphics::Compositor for Compositor { width: u32, height: u32, ) { - match surface { - Surface::Cpu { buffer, .. } => { - buffer.resize((width * height) as usize, 0); - } - #[cfg(feature = "gpu")] - Surface::Gpu { pixels } => { - pixels - .resize_surface(width, height) - .expect("Resize surface"); - - pixels.resize_buffer(width, height).expect("Resize buffer"); - } - } + surface.buffer.resize((width * height) as usize, 0); } fn fetch_information(&self) -> Information { @@ -140,15 +106,9 @@ pub fn present>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); - let buffer = match surface { - Surface::Cpu { buffer, .. } => bytemuck::cast_slice_mut(buffer), - #[cfg(feature = "gpu")] - Surface::Gpu { pixels } => pixels.frame_mut(), - }; - let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( - buffer, + bytemuck::cast_slice_mut(&mut surface.buffer), physical_size.width, physical_size.height, ) @@ -161,22 +121,12 @@ pub fn present>( ); if drawn { - match surface { - Surface::Cpu { window, buffer } => { - window.set_buffer( - buffer, - physical_size.width as u16, - physical_size.height as u16, - ); - - Ok(()) - } - #[cfg(feature = "gpu")] - Surface::Gpu { pixels } => { - pixels.render().map_err(|_| compositor::SurfaceError::Lost) - } - } - } else { - Ok(()) + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); } + + Ok(()) } From d6345ff122e98fe716980301b2b0ea7342ad85d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:11:35 +0200 Subject: [PATCH 17/28] Remove `dbg!` statement leftover --- runtime/src/debug/basic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/debug/basic.rs b/runtime/src/debug/basic.rs index e9be4c84d7..32f725a1d6 100644 --- a/runtime/src/debug/basic.rs +++ b/runtime/src/debug/basic.rs @@ -129,7 +129,7 @@ impl Debug { pub fn render_finished(&mut self) { self.render_durations - .push(dbg!(time::Instant::now() - self.render_start)); + .push(time::Instant::now() - self.render_start); } pub fn log_message(&mut self, message: &Message) { From f0fa5f76cb0b0e451b3fc86b1678403c103867e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:15:53 +0200 Subject: [PATCH 18/28] Remove `debug` overlay from `iced_tiny_skia` for now --- tiny_skia/src/backend.rs | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 974faa7480..756e46b390 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; @@ -158,43 +157,6 @@ impl Backend { } } - //if !overlay.is_empty() { - // let bounds = Rectangle { - // x: 0.0, - // y: 0.0, - // width: viewport.physical_width() as f32, - // height: viewport.physical_height() as f32, - // }; - - // adjust_clip_mask(clip_mask, pixels, bounds); - - // 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: bounds.width - 1.0, - // height: bounds.height - 1.0, - // }, - // color: Color::BLACK, - // font: Font::MONOSPACE, - // horizontal_alignment: alignment::Horizontal::Left, - // vertical_alignment: alignment::Vertical::Top, - // }, - // pixels, - // clip_mask, - // bounds, - // scale_factor, - // Vector::ZERO, - // ); - // } - //} - self.text_pipeline.trim_cache(); #[cfg(feature = "image")] From e63cc181bddbdc0a5b9b091cfeee5e4343b3d906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:28:47 +0200 Subject: [PATCH 19/28] Fix `Candidate::build` in `compositor` of `iced_renderer` --- renderer/src/compositor.rs | 75 ++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 484d91eb12..e31c2bedba 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -179,47 +179,50 @@ impl Candidate { fn build( self, settings: Settings, - compatible_window: Option<&W>, + _compatible_window: Option<&W>, ) -> Result<(Compositor, Renderer), Error> { match self { + #[cfg(feature = "wgpu")] Self::Wgpu => { - if cfg!(feature = "wgpu") { - let (compositor, backend) = - iced_wgpu::window::compositor::new( - iced_wgpu::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..iced_wgpu::Settings::from_env() - }, - compatible_window, - )?; - - return Ok(( - Compositor::Wgpu(compositor), - Renderer::new(crate::Backend::Wgpu(backend)), - )); - } else { - panic!("`wgpu` feature was not enabled in `iced_renderer`"); - } + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + _compatible_window, + )?; + + Ok(( + Compositor::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) } + #[cfg(feature = "tiny-skia")] Self::TinySkia => { - if cfg!(feature = "tiny-skia") { - let (compositor, backend) = - iced_tiny_skia::window::compositor::new( - iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }, - ); - - Ok(( - Compositor::TinySkia(compositor), - Renderer::new(crate::Backend::TinySkia(backend)), - )) - } else { - panic!("`tiny-skia` feature was not enabled in `iced_renderer`"); - } + let (compositor, backend) = + iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Compositor::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + } + #[cfg(not(feature = "wgpu"))] + Self::Wgpu => { + panic!("`wgpu` feature was not enabled in `iced_renderer`") + } + #[cfg(not(feature = "tiny-skia"))] + Self::TinySkia => { + panic!( + "`tiny-skia` feature was not enabled in `iced_renderer`" + ); } } } From 9c63eb7df559e58b14188b4096e9bd206444bbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:46:27 +0200 Subject: [PATCH 20/28] Update `tiny-skia` and `resvg` --- Cargo.toml | 3 --- graphics/Cargo.toml | 2 +- renderer/src/compositor.rs | 3 +-- tiny_skia/Cargo.toml | 4 ++-- tiny_skia/src/backend.rs | 33 +++++++++++++----------------- tiny_skia/src/raster.rs | 2 +- tiny_skia/src/text.rs | 2 +- tiny_skia/src/vector.rs | 8 +++++--- tiny_skia/src/window/compositor.rs | 14 ++++++------- wgpu/Cargo.toml | 2 +- wgpu/src/image/vector.rs | 6 ++++-- 11 files changed, 37 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3f09fa607..2f92f9db0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,3 @@ incremental = false opt-level = 3 overflow-checks = false strip = "debuginfo" - -[patch.crates-io] -tiny-skia = { version = "0.8", git = "https://github.com/hecrj/tiny-skia.git", rev = "213890dcbb3754d51533f5b558d9f5ffa3bf6da1" } diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index f1ce6b3aa3..125ea17d22 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -31,7 +31,7 @@ version = "0.9" path = "../core" [dependencies.tiny-skia] -version = "0.8" +version = "0.9" optional = true [dependencies.image] diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index e31c2bedba..48ed4b1f1c 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -123,11 +123,10 @@ impl crate::graphics::Compositor for Compositor { ), #[cfg(feature = "tiny-skia")] ( - Self::TinySkia(compositor), + Self::TinySkia(_compositor), crate::Backend::TinySkia(backend), Surface::TinySkia(surface), ) => iced_tiny_skia::window::compositor::present( - compositor, backend, surface, primitives, diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index f629dab934..a3bddc931c 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -11,7 +11,7 @@ geometry = ["iced_graphics/geometry"] [dependencies] raw-window-handle = "0.5" softbuffer = "0.2" -tiny-skia = "0.8" +tiny-skia = "0.9" cosmic-text = "0.8" bytemuck = "1" rustc-hash = "1.1" @@ -32,5 +32,5 @@ version = "1.6.1" features = ["std"] [dependencies.resvg] -version = "0.29" +version = "0.32" optional = true diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 756e46b390..16a7f9b801 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -44,7 +44,7 @@ impl Backend { pub fn draw>( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::ClipMask, + clip_mask: &mut tiny_skia::Mask, primitives: &[Primitive], viewport: &Viewport, background_color: Color, @@ -124,7 +124,7 @@ impl Backend { None, ); - adjust_clip_mask(clip_mask, pixels, region); + adjust_clip_mask(clip_mask, region); for primitive in primitives { self.draw_primitive( @@ -172,7 +172,7 @@ impl Backend { &mut self, primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::ClipMask, + clip_mask: &mut tiny_skia::Mask, clip_bounds: Rectangle, scale_factor: f32, translation: Vector, @@ -427,7 +427,7 @@ impl Backend { return; } - adjust_clip_mask(clip_mask, pixels, bounds); + adjust_clip_mask(clip_mask, bounds); self.draw_primitive( content, @@ -438,7 +438,7 @@ impl Backend { translation, ); - adjust_clip_mask(clip_mask, pixels, clip_bounds); + adjust_clip_mask(clip_mask, clip_bounds); } } Primitive::Cache { content } => { @@ -611,11 +611,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); @@ -623,15 +621,12 @@ fn adjust_clip_mask( builder.finish().unwrap() }; - clip_mask - .set_path( - pixels.width(), - pixels.height(), - &path, - tiny_skia::FillRule::EvenOdd, - false, - ) - .expect("Set path of clipping area"); + clip_mask.fill_path( + &path, + tiny_skia::FillRule::EvenOdd, + false, + tiny_skia::Transform::default(), + ); } fn group_damage( diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 2fd73f8c7d..3887ec8dc0 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 f5994d0928..1246bbd566 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 { diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 8509b761e6..fc411fdd8b 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -32,7 +32,7 @@ impl Pipeline { color: Option, 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 6e4bb6efa8..7523e06f93 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -7,13 +7,13 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; pub struct Compositor { - clip_mask: tiny_skia::ClipMask, _theme: PhantomData, } pub struct Surface { window: softbuffer::GraphicsContext, buffer: Vec, + clip_mask: tiny_skia::Mask, } impl crate::graphics::Compositor for Compositor { @@ -43,6 +43,8 @@ impl crate::graphics::Compositor for Compositor { Surface { window, buffer: vec![0; width as usize * height as usize], + clip_mask: tiny_skia::Mask::new(width, height) + .expect("Create clip mask"), } } @@ -53,6 +55,8 @@ impl crate::graphics::Compositor for Compositor { height: u32, ) { surface.buffer.resize((width * height) as usize, 0); + surface.clip_mask = + tiny_skia::Mask::new(width, height).expect("Create clip mask"); } fn fetch_information(&self) -> Information { @@ -72,7 +76,6 @@ impl crate::graphics::Compositor for Compositor { ) -> Result<(), SurfaceError> { renderer.with_primitives(|backend, primitives| { present( - self, backend, surface, primitives, @@ -85,18 +88,15 @@ impl crate::graphics::Compositor for Compositor { } pub fn new(settings: Settings) -> (Compositor, Backend) { - // TOD ( Compositor { - clip_mask: tiny_skia::ClipMask::new(), _theme: PhantomData, }, Backend::new(settings), ) } -pub fn present>( - compositor: &mut Compositor, +pub fn present>( backend: &mut Backend, surface: &mut Surface, primitives: &[Primitive], @@ -113,7 +113,7 @@ pub fn present>( physical_size.height, ) .expect("Create pixel map"), - &mut compositor.clip_mask, + &mut surface.clip_mask, primitives, viewport, background_color, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 14dcd55074..ffae6e4ac0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -58,7 +58,7 @@ version = "1.0" optional = true [dependencies.resvg] -version = "0.29" +version = "0.32" optional = true [dependencies.tracing] diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 3624e46be6..58bdf64a68 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -43,6 +43,8 @@ type ColorFilter = Option<[u8; 4]>; impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + use usvg::TreeParsing; + if self.svgs.contains_key(&handle.id()) { return self.svgs.get(&handle.id()).unwrap(); } @@ -116,9 +118,9 @@ impl Cache { resvg::render( tree, if width > height { - usvg::FitTo::Width(width) + resvg::FitTo::Width(width) } else { - usvg::FitTo::Height(height) + resvg::FitTo::Height(height) }, tiny_skia::Transform::default(), img.as_mut(), From d953d12c38ad368110f4f9f40fb0f185c11dec54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 26 Apr 2023 16:48:41 +0200 Subject: [PATCH 21/28] Fix incorrect `wgpu` version for Wasm builds in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index ffae6e4ac0..9f9bd06651 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,7 +23,7 @@ once_cell = "1.0" rustc-hash = "1.1" [target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { version = "0.14", features = ["webgl"] } +wgpu = { version = "0.15", features = ["webgl"] } [dependencies.twox-hash] version = "1.6" From af0303f95e27737d9de8915f939b60a2bc3282ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:10:41 +0200 Subject: [PATCH 22/28] Move damage tracking logic to `compositor` in `iced_tiny_skia` --- graphics/src/damage.rs | 140 +++++++++++++++++++++++++++++ graphics/src/lib.rs | 1 + graphics/src/primitive.rs | 90 ------------------- tiny_skia/src/backend.rs | 80 +---------------- tiny_skia/src/window/compositor.rs | 54 +++++++---- 5 files changed, 183 insertions(+), 182 deletions(-) create mode 100644 graphics/src/damage.rs diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs new file mode 100644 index 0000000000..63604150be --- /dev/null +++ b/graphics/src/damage.rs @@ -0,0 +1,140 @@ +use crate::core::{Rectangle, Size}; +use crate::Primitive; + +use std::sync::Arc; + +pub fn regions(a: &Primitive, b: &Primitive) -> Vec { + match (a, b) { + ( + Primitive::Group { + primitives: primitives_a, + }, + Primitive::Group { + primitives: primitives_b, + }, + ) => return list(primitives_a, primitives_b), + ( + Primitive::Clip { + bounds: bounds_a, + content: content_a, + }, + Primitive::Clip { + bounds: bounds_b, + content: content_b, + }, + ) => { + if bounds_a == bounds_b { + return regions(content_a, content_b) + .into_iter() + .filter_map(|r| r.intersection(bounds_a)) + .collect(); + } else { + return vec![*bounds_a, *bounds_b]; + } + } + ( + Primitive::Translate { + translation: translation_a, + content: content_a, + }, + Primitive::Translate { + translation: translation_b, + content: content_b, + }, + ) => { + if translation_a == translation_b { + return regions(content_a, content_b) + .into_iter() + .map(|r| r + *translation_a) + .collect(); + } + } + ( + Primitive::Cache { content: content_a }, + Primitive::Cache { content: content_b }, + ) => { + if Arc::ptr_eq(content_a, content_b) { + return vec![]; + } + } + _ if a == b => return vec![], + _ => {} + } + + let bounds_a = a.bounds(); + let bounds_b = b.bounds(); + + if bounds_a == bounds_b { + vec![bounds_a] + } else { + vec![bounds_a, bounds_b] + } +} + +pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec { + let damage = previous + .iter() + .zip(current) + .flat_map(|(a, b)| regions(a, b)); + + if previous.len() == current.len() { + damage.collect() + } else { + let (smaller, bigger) = if previous.len() < current.len() { + (previous, current) + } else { + (current, previous) + }; + + // Extend damage by the added/removed primitives + damage + .chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) + .collect() + } +} + +pub fn group( + mut damage: Vec, + scale_factor: f32, + bounds: Size, +) -> Vec { + use std::cmp::Ordering; + + const AREA_THRESHOLD: f32 = 20_000.0; + + let bounds = Rectangle { + x: 0.0, + y: 0.0, + width: bounds.width as f32, + height: bounds.height as f32, + }; + + damage.sort_by(|a, b| { + a.x.partial_cmp(&b.x) + .unwrap_or(Ordering::Equal) + .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) + }); + + let mut output = Vec::new(); + let mut scaled = damage + .into_iter() + .filter_map(|region| (region * scale_factor).intersection(&bounds)) + .filter(|region| region.width >= 1.0 && region.height >= 1.0); + + if let Some(mut current) = scaled.next() { + for region in scaled { + let union = current.union(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 0c50db52d5..e3de40254c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -28,6 +28,7 @@ mod viewport; pub mod backend; pub mod compositor; +pub mod damage; pub mod primitive; pub mod renderer; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 7f2c8ae2b1..1751d03ae0 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -208,96 +208,6 @@ impl Primitive { Self::Cache { content } => content.bounds(), } } - - pub fn damage(&self, other: &Self) -> Vec { - match (self, other) { - ( - Primitive::Group { - primitives: primitives_a, - }, - Primitive::Group { - primitives: primitives_b, - }, - ) => return Self::damage_list(primitives_a, primitives_b), - ( - Primitive::Clip { - bounds: bounds_a, - content: content_a, - }, - Primitive::Clip { - bounds: bounds_b, - content: content_b, - }, - ) => { - if bounds_a == bounds_b { - return content_a - .damage(content_b) - .into_iter() - .filter_map(|r| r.intersection(bounds_a)) - .collect(); - } else { - return vec![*bounds_a, *bounds_b]; - } - } - ( - Primitive::Translate { - translation: translation_a, - content: content_a, - }, - Primitive::Translate { - translation: translation_b, - content: content_b, - }, - ) => { - if translation_a == translation_b { - return content_a - .damage(content_b) - .into_iter() - .map(|r| r + *translation_a) - .collect(); - } - } - ( - Primitive::Cache { content: content_a }, - Primitive::Cache { content: content_b }, - ) => { - if Arc::ptr_eq(content_a, content_b) { - return vec![]; - } - } - _ if self == other => return vec![], - _ => {} - } - - let bounds_a = self.bounds(); - let bounds_b = other.bounds(); - - if bounds_a == bounds_b { - vec![bounds_a] - } else { - vec![bounds_a, bounds_b] - } - } - - pub fn damage_list(previous: &[Self], current: &[Self]) -> Vec { - let damage = - previous.iter().zip(current).flat_map(|(a, b)| a.damage(b)); - - if previous.len() == current.len() { - damage.collect() - } else { - let (smaller, bigger) = if previous.len() < current.len() { - (previous, current) - } else { - (current, previous) - }; - - // Extend damage by the added/removed primitives - damage - .chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) - .collect() - } - } } /// A set of [`Vertex2D`] and indices representing a list of triangles. diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 16a7f9b801..9c69e1d21b 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -16,10 +16,6 @@ pub struct Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline, - - last_primitives: Vec, - last_background_color: Color, - last_size: Size, } impl Backend { @@ -34,10 +30,6 @@ impl Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline::new(), - - last_primitives: Vec::new(), - last_background_color: Color::BLACK, - last_size: Size::new(0, 0), } } @@ -47,31 +39,13 @@ impl Backend { clip_mask: &mut tiny_skia::Mask, primitives: &[Primitive], viewport: &Viewport, + damage: &[Rectangle], background_color: Color, overlay: &[T], - ) -> bool { + ) { let physical_size = viewport.physical_size(); - - let damage = if self.last_background_color == background_color - && self.last_size == physical_size - { - Primitive::damage_list(&self.last_primitives, primitives) - } else { - vec![Rectangle::with_size(viewport.logical_size())] - }; - - if damage.is_empty() { - return false; - } - - self.last_primitives = primitives.to_vec(); - self.last_background_color = background_color; - self.last_size = physical_size; - let scale_factor = viewport.scale_factor() as f32; - let damage = group_damage(damage, scale_factor, physical_size); - if !overlay.is_empty() { let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( @@ -99,7 +73,7 @@ impl Backend { ); } - for region in damage { + for ®ion in damage { let path = tiny_skia::PathBuilder::from_rect( tiny_skia::Rect::from_xywh( region.x, @@ -164,8 +138,6 @@ impl Backend { #[cfg(feature = "svg")] self.vector_pipeline.trim_cache(); - - true } fn draw_primitive( @@ -629,52 +601,6 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { ); } -fn group_damage( - mut damage: Vec, - scale_factor: f32, - bounds: Size, -) -> Vec { - use std::cmp::Ordering; - - const AREA_THRESHOLD: f32 = 20_000.0; - - let bounds = Rectangle { - x: 0.0, - y: 0.0, - width: bounds.width as f32, - height: bounds.height as f32, - }; - - damage.sort_by(|a, b| { - a.x.partial_cmp(&b.x) - .unwrap_or(Ordering::Equal) - .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal)) - }); - - let mut output = Vec::new(); - let mut scaled = damage - .into_iter() - .filter_map(|region| (region * scale_factor).intersection(&bounds)) - .filter(|region| region.width >= 1.0 && region.height >= 1.0); - - if let Some(mut current) = scaled.next() { - for region in scaled { - let union = current.union(®ion); - - if union.area() - current.area() - region.area() <= AREA_THRESHOLD { - current = union; - } else { - output.push(current); - current = region; - } - } - - output.push(current); - } - - output -} - impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 7523e06f93..d3dffb18aa 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}; @@ -14,6 +15,8 @@ pub struct Surface { window: softbuffer::GraphicsContext, buffer: Vec, clip_mask: tiny_skia::Mask, + last_primitives: Vec, + last_background_color: Color, } impl crate::graphics::Compositor for Compositor { @@ -45,6 +48,8 @@ impl crate::graphics::Compositor for Compositor { buffer: vec![0; width as usize * height as usize], clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), + last_primitives: Vec::new(), + last_background_color: Color::BLACK, } } @@ -57,6 +62,8 @@ impl crate::graphics::Compositor for Compositor { surface.buffer.resize((width * height) as usize, 0); surface.clip_mask = tiny_skia::Mask::new(width, height).expect("Create clip mask"); + + surface.last_primitives.clear(); } fn fetch_information(&self) -> Information { @@ -105,28 +112,45 @@ pub fn present>( overlay: &[T], ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + + let mut pixels = &mut tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"); + + let damage = if surface.last_background_color == background_color { + damage::list(&surface.last_primitives, primitives) + } else { + vec![Rectangle::with_size(viewport.logical_size())] + }; + + if damage.is_empty() { + return Ok(()); + } + + surface.last_primitives = primitives.to_vec(); + surface.last_background_color = background_color; + + let damage = damage::group(damage, scale_factor, physical_size); - let drawn = 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"), + backend.draw( + &mut pixels, &mut surface.clip_mask, primitives, viewport, + &damage, background_color, overlay, ); - if drawn { - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); - } + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); Ok(()) } From 92d808ee73b261b639a4a8d1d4f930cd61a380c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:16:05 +0200 Subject: [PATCH 23/28] Fix double reference in `compositor` in `iced_tiny_skia` --- tiny_skia/src/window/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index d3dffb18aa..1bcce6fe3d 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -114,7 +114,7 @@ pub fn present>( let physical_size = viewport.physical_size(); let scale_factor = viewport.scale_factor() as f32; - let mut pixels = &mut tiny_skia::PixmapMut::from_bytes( + let mut pixels = tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut surface.buffer), physical_size.width, physical_size.height, From 88d3247717b6deb38d500c8c1b1b1f32d6df9991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:17:06 +0200 Subject: [PATCH 24/28] Fix build of `integration` example for Wasm target --- examples/integration/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 98d2bc5940..d9aae7b9ab 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -36,7 +36,8 @@ pub fn main() -> Result<(), Box> { web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.get_element_by_id("iced_canvas")) - .and_then(|element| element.dyn_into::().ok())? + .and_then(|element| element.dyn_into::().ok()) + .expect("Get canvas element") }; #[cfg(not(target_arch = "wasm32"))] env_logger::init(); From 200a29c069ae0c6461b6d44e075aacc9ddad9974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:17:45 +0200 Subject: [PATCH 25/28] Fix unused import in `triangle` pipeline for Wasm target in `iced_wgpu` --- wgpu/src/triangle.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 0df8dd023b..eb15a45801 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,10 +2,13 @@ mod msaa; use crate::buffer::r#static::Buffer; -use crate::core::{Gradient, Size}; +use crate::core::Size; use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; +#[cfg(not(target_arch = "wasm32"))] +use crate::core::Gradient; + #[cfg(feature = "tracing")] use tracing::info_span; From 38f82ab35fd24a1aaf96728148b2826211956a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:25:59 +0200 Subject: [PATCH 26/28] Expand damage regions of `Clip` primitives a bit --- graphics/src/damage.rs | 6 ++++-- graphics/src/primitive.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 63604150be..5aab06b17b 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -17,19 +17,21 @@ pub fn regions(a: &Primitive, b: &Primitive) -> Vec { Primitive::Clip { bounds: bounds_a, content: content_a, + .. }, Primitive::Clip { bounds: bounds_b, content: content_b, + .. }, ) => { if bounds_a == bounds_b { return regions(content_a, content_b) .into_iter() - .filter_map(|r| r.intersection(bounds_a)) + .filter_map(|r| r.intersection(&bounds_a.expand(1.0))) .collect(); } else { - return vec![*bounds_a, *bounds_b]; + return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)]; } } ( diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 1751d03ae0..d6a2c4c4cc 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -179,7 +179,7 @@ impl Primitive { Self::Quad { bounds, .. } | Self::Image { bounds, .. } | Self::Svg { bounds, .. } => bounds.expand(1.0), - Self::Clip { bounds, .. } => *bounds, + Self::Clip { bounds, .. } => bounds.expand(1.0), Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => { Rectangle::with_size(*size) } From eb1b2bf24174759bba8c63db0ec89c7c6d15d191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:45:02 +0200 Subject: [PATCH 27/28] Invalidate `last_primitives` on resize in `iced_tiny_skia` --- tiny_skia/src/window/compositor.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 1bcce6fe3d..3bc0697001 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -15,7 +15,7 @@ pub struct Surface { window: softbuffer::GraphicsContext, buffer: Vec, clip_mask: tiny_skia::Mask, - last_primitives: Vec, + last_primitives: Option>, last_background_color: Color, } @@ -48,7 +48,7 @@ impl crate::graphics::Compositor for Compositor { buffer: vec![0; width as usize * height as usize], clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), - last_primitives: Vec::new(), + last_primitives: None, last_background_color: Color::BLACK, } } @@ -62,8 +62,7 @@ impl crate::graphics::Compositor for Compositor { surface.buffer.resize((width * height) as usize, 0); surface.clip_mask = tiny_skia::Mask::new(width, height).expect("Create clip mask"); - - surface.last_primitives.clear(); + surface.last_primitives = None; } fn fetch_information(&self) -> Information { @@ -121,17 +120,20 @@ pub fn present>( ) .expect("Create pixel map"); - let damage = if surface.last_background_color == background_color { - damage::list(&surface.last_primitives, primitives) - } else { - vec![Rectangle::with_size(viewport.logical_size())] - }; + let damage = surface + .last_primitives + .as_deref() + .and_then(|last_primitives| { + (surface.last_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.last_primitives = primitives.to_vec(); + surface.last_primitives = Some(primitives.to_vec()); surface.last_background_color = background_color; let damage = damage::group(damage, scale_factor, physical_size); From a755472ee35dfb7839f989becafc6028921a3b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 27 Apr 2023 15:51:51 +0200 Subject: [PATCH 28/28] Remove unnecessary `last_` prefix in `Surface` of `iced_tiny_skia` --- tiny_skia/src/window/compositor.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 3bc0697001..9999a18888 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -15,8 +15,8 @@ pub struct Surface { window: softbuffer::GraphicsContext, buffer: Vec, clip_mask: tiny_skia::Mask, - last_primitives: Option>, - last_background_color: Color, + primitives: Option>, + background_color: Color, } impl crate::graphics::Compositor for Compositor { @@ -48,8 +48,8 @@ impl crate::graphics::Compositor for Compositor { buffer: vec![0; width as usize * height as usize], clip_mask: tiny_skia::Mask::new(width, height) .expect("Create clip mask"), - last_primitives: None, - last_background_color: Color::BLACK, + primitives: None, + background_color: Color::BLACK, } } @@ -62,7 +62,7 @@ impl crate::graphics::Compositor for Compositor { surface.buffer.resize((width * height) as usize, 0); surface.clip_mask = tiny_skia::Mask::new(width, height).expect("Create clip mask"); - surface.last_primitives = None; + surface.primitives = None; } fn fetch_information(&self) -> Information { @@ -121,10 +121,10 @@ pub fn present>( .expect("Create pixel map"); let damage = surface - .last_primitives + .primitives .as_deref() .and_then(|last_primitives| { - (surface.last_background_color == background_color) + (surface.background_color == background_color) .then(|| damage::list(last_primitives, primitives)) }) .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]); @@ -133,8 +133,8 @@ pub fn present>( return Ok(()); } - surface.last_primitives = Some(primitives.to_vec()); - surface.last_background_color = background_color; + surface.primitives = Some(primitives.to_vec()); + surface.background_color = background_color; let damage = damage::group(damage, scale_factor, physical_size);