diff options
author | 2023-03-22 00:36:57 +0100 | |
---|---|---|
committer | 2023-04-04 02:08:02 +0200 | |
commit | 0f7abffc0e94b4bb9f8117db633bfd07d900eb93 (patch) | |
tree | 89b6606db2619574369bf8c4a29d5ef0ecb7969c | |
parent | 6fae8bf6cbe7155bcee42eaeba68e31564df057c (diff) | |
download | iced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.tar.gz iced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.tar.bz2 iced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.zip |
Draft (very) basic incremental rendering for `iced_tiny_skia`
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | core/src/image.rs | 30 | ||||
-rw-r--r-- | core/src/rectangle.rs | 34 | ||||
-rw-r--r-- | core/src/svg.rs | 4 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 151 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 2 | ||||
-rw-r--r-- | runtime/src/debug/basic.rs | 2 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 68 | ||||
-rw-r--r-- | tiny_skia/src/primitive.rs | 82 | ||||
-rw-r--r-- | widget/src/text_input.rs | 2 |
10 files changed, 286 insertions, 92 deletions
@@ -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 70fbade0..618235ef 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 4fe91519..5cdcbe78 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -100,6 +100,30 @@ impl Rectangle<f32> { } } + /// 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<u32> { Rectangle { @@ -109,6 +133,16 @@ impl Rectangle<f32> { 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<f32> for Rectangle<f32> { diff --git a/core/src/svg.rs b/core/src/svg.rs index 9b98877a..54e9434e 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<Data>, @@ -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 195b62da..bbf300b0 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<Rectangle> { + 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<Rectangle> { + 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<T> { /// The vertices of the mesh pub vertices: Vec<T>, @@ -162,7 +305,7 @@ pub struct Mesh2D<T> { } /// 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 7bc462ef..23e594be 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -32,7 +32,7 @@ impl<B: Backend, T> Renderer<B, T> { } } - /// 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 32f725a1..e9be4c84 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<Message: std::fmt::Debug>(&mut self, message: &Message) { diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 58076b84..fe84f83b 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<Primitive>, + 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<Rectangle>, 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 22daaedc..00000000 --- a/tiny_skia/src/primitive.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::{Rectangle, Vector}; - -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub enum Primitive { - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec<Primitive>, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box<Primitive>, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box<Primitive>, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cached { - /// The cached primitive - cache: Arc<Primitive>, - }, - /// A basic primitive. - Basic(iced_graphics::Primitive), -} - -impl iced_graphics::backend::Primitive for Primitive { - fn translate(self, translation: Vector) -> Self { - Self::Translate { - translation, - content: Box::new(self), - } - } - - fn clip(self, bounds: Rectangle) -> Self { - Self::Clip { - bounds, - content: Box::new(self), - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct Recording(pub(crate) Vec<Primitive>); - -impl iced_graphics::backend::Recording for Recording { - type Primitive = Primitive; - - fn push(&mut self, primitive: Primitive) { - self.0.push(primitive); - } - - fn push_basic(&mut self, basic: iced_graphics::Primitive) { - self.0.push(Primitive::Basic(basic)); - } - - fn group(self) -> Self::Primitive { - Primitive::Group { primitives: self.0 } - } - - fn clear(&mut self) { - self.0.clear(); - } -} - -impl Recording { - pub fn primitives(&self) -> &[Primitive] { - &self.0 - } -} diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d066109a..4f018284 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -953,6 +953,8 @@ pub fn draw<Renderer>( 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 { |