summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-03-22 00:36:57 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-04-04 02:08:02 +0200
commit0f7abffc0e94b4bb9f8117db633bfd07d900eb93 (patch)
tree89b6606db2619574369bf8c4a29d5ef0ecb7969c
parent6fae8bf6cbe7155bcee42eaeba68e31564df057c (diff)
downloadiced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.tar.gz
iced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.tar.bz2
iced-0f7abffc0e94b4bb9f8117db633bfd07d900eb93.zip
Draft (very) basic incremental rendering for `iced_tiny_skia`
-rw-r--r--Cargo.toml3
-rw-r--r--core/src/image.rs30
-rw-r--r--core/src/rectangle.rs34
-rw-r--r--core/src/svg.rs4
-rw-r--r--graphics/src/primitive.rs151
-rw-r--r--graphics/src/renderer.rs2
-rw-r--r--runtime/src/debug/basic.rs2
-rw-r--r--tiny_skia/src/backend.rs68
-rw-r--r--tiny_skia/src/primitive.rs82
-rw-r--r--widget/src/text_input.rs2
10 files changed, 286 insertions, 92 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 38c35f43..854dc8a0 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 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 {