Skip to content

Commit c31ab8e

Browse files
authored
Merge pull request #1811 from iced-rs/incremental-rendering
Incremental rendering
2 parents e373010 + a755472 commit c31ab8e

23 files changed

Lines changed: 640 additions & 269 deletions

File tree

core/src/image.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::path::PathBuf;
66
use std::sync::Arc;
77

88
/// A handle of some image data.
9-
#[derive(Debug, Clone)]
9+
#[derive(Debug, Clone, PartialEq, Eq)]
1010
pub struct Handle {
1111
id: u64,
1212
data: Data,
@@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes {
110110
}
111111
}
112112

113+
impl PartialEq for Bytes {
114+
fn eq(&self, other: &Self) -> bool {
115+
self.as_ref() == other.as_ref()
116+
}
117+
}
118+
119+
impl Eq for Bytes {}
120+
113121
impl AsRef<[u8]> for Bytes {
114122
fn as_ref(&self) -> &[u8] {
115123
self.0.as_ref().as_ref()
@@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {
125133
}
126134

127135
/// The data of a raster image.
128-
#[derive(Clone, Hash)]
136+
#[derive(Clone, PartialEq, Eq, Hash)]
129137
pub enum Data {
130138
/// File data
131139
Path(PathBuf),

core/src/rectangle.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ impl Rectangle<f32> {
6666
Size::new(self.width, self.height)
6767
}
6868

69+
/// Returns the area of the [`Rectangle`].
70+
pub fn area(&self) -> f32 {
71+
self.width * self.height
72+
}
73+
6974
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
7075
pub fn contains(&self, point: Point) -> bool {
7176
self.x <= point.x
@@ -74,6 +79,15 @@ impl Rectangle<f32> {
7479
&& point.y <= self.y + self.height
7580
}
7681

82+
/// Returns true if the current [`Rectangle`] is completely within the given
83+
/// `container`.
84+
pub fn is_within(&self, container: &Rectangle) -> bool {
85+
container.contains(self.position())
86+
&& container.contains(
87+
self.position() + Vector::new(self.width, self.height),
88+
)
89+
}
90+
7791
/// Computes the intersection with the given [`Rectangle`].
7892
pub fn intersection(
7993
&self,
@@ -100,6 +114,30 @@ impl Rectangle<f32> {
100114
}
101115
}
102116

117+
/// Returns whether the [`Rectangle`] intersects with the given one.
118+
pub fn intersects(&self, other: &Self) -> bool {
119+
self.intersection(other).is_some()
120+
}
121+
122+
/// Computes the union with the given [`Rectangle`].
123+
pub fn union(&self, other: &Self) -> Self {
124+
let x = self.x.min(other.x);
125+
let y = self.y.min(other.y);
126+
127+
let lower_right_x = (self.x + self.width).max(other.x + other.width);
128+
let lower_right_y = (self.y + self.height).max(other.y + other.height);
129+
130+
let width = lower_right_x - x;
131+
let height = lower_right_y - y;
132+
133+
Rectangle {
134+
x,
135+
y,
136+
width,
137+
height,
138+
}
139+
}
140+
103141
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
104142
pub fn snap(self) -> Rectangle<u32> {
105143
Rectangle {
@@ -109,6 +147,16 @@ impl Rectangle<f32> {
109147
height: self.height as u32,
110148
}
111149
}
150+
151+
/// Expands the [`Rectangle`] a given amount.
152+
pub fn expand(self, amount: f32) -> Self {
153+
Self {
154+
x: self.x - amount,
155+
y: self.y - amount,
156+
width: self.width + amount * 2.0,
157+
height: self.height + amount * 2.0,
158+
}
159+
}
112160
}
113161

114162
impl std::ops::Mul<f32> for Rectangle<f32> {

core/src/svg.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::path::PathBuf;
77
use std::sync::Arc;
88

99
/// A handle of Svg data.
10-
#[derive(Debug, Clone)]
10+
#[derive(Debug, Clone, PartialEq, Eq)]
1111
pub struct Handle {
1212
id: u64,
1313
data: Arc<Data>,
@@ -57,7 +57,7 @@ impl Hash for Handle {
5757
}
5858

5959
/// The data of a vectorial image.
60-
#[derive(Clone, Hash)]
60+
#[derive(Clone, Hash, PartialEq, Eq)]
6161
pub enum Data {
6262
/// File data
6363
Path(PathBuf),

examples/integration/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
3636
web_sys::window()
3737
.and_then(|win| win.document())
3838
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
39-
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())?
39+
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
40+
.expect("Get canvas element")
4041
};
4142
#[cfg(not(target_arch = "wasm32"))]
4243
env_logger::init();

graphics/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ version = "0.9"
3131
path = "../core"
3232

3333
[dependencies.tiny-skia]
34-
version = "0.8"
34+
version = "0.9"
3535
optional = true
3636

3737
[dependencies.image]

graphics/src/damage.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use crate::core::{Rectangle, Size};
2+
use crate::Primitive;
3+
4+
use std::sync::Arc;
5+
6+
pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
7+
match (a, b) {
8+
(
9+
Primitive::Group {
10+
primitives: primitives_a,
11+
},
12+
Primitive::Group {
13+
primitives: primitives_b,
14+
},
15+
) => return list(primitives_a, primitives_b),
16+
(
17+
Primitive::Clip {
18+
bounds: bounds_a,
19+
content: content_a,
20+
..
21+
},
22+
Primitive::Clip {
23+
bounds: bounds_b,
24+
content: content_b,
25+
..
26+
},
27+
) => {
28+
if bounds_a == bounds_b {
29+
return regions(content_a, content_b)
30+
.into_iter()
31+
.filter_map(|r| r.intersection(&bounds_a.expand(1.0)))
32+
.collect();
33+
} else {
34+
return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
35+
}
36+
}
37+
(
38+
Primitive::Translate {
39+
translation: translation_a,
40+
content: content_a,
41+
},
42+
Primitive::Translate {
43+
translation: translation_b,
44+
content: content_b,
45+
},
46+
) => {
47+
if translation_a == translation_b {
48+
return regions(content_a, content_b)
49+
.into_iter()
50+
.map(|r| r + *translation_a)
51+
.collect();
52+
}
53+
}
54+
(
55+
Primitive::Cache { content: content_a },
56+
Primitive::Cache { content: content_b },
57+
) => {
58+
if Arc::ptr_eq(content_a, content_b) {
59+
return vec![];
60+
}
61+
}
62+
_ if a == b => return vec![],
63+
_ => {}
64+
}
65+
66+
let bounds_a = a.bounds();
67+
let bounds_b = b.bounds();
68+
69+
if bounds_a == bounds_b {
70+
vec![bounds_a]
71+
} else {
72+
vec![bounds_a, bounds_b]
73+
}
74+
}
75+
76+
pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> {
77+
let damage = previous
78+
.iter()
79+
.zip(current)
80+
.flat_map(|(a, b)| regions(a, b));
81+
82+
if previous.len() == current.len() {
83+
damage.collect()
84+
} else {
85+
let (smaller, bigger) = if previous.len() < current.len() {
86+
(previous, current)
87+
} else {
88+
(current, previous)
89+
};
90+
91+
// Extend damage by the added/removed primitives
92+
damage
93+
.chain(bigger[smaller.len()..].iter().map(Primitive::bounds))
94+
.collect()
95+
}
96+
}
97+
98+
pub fn group(
99+
mut damage: Vec<Rectangle>,
100+
scale_factor: f32,
101+
bounds: Size<u32>,
102+
) -> Vec<Rectangle> {
103+
use std::cmp::Ordering;
104+
105+
const AREA_THRESHOLD: f32 = 20_000.0;
106+
107+
let bounds = Rectangle {
108+
x: 0.0,
109+
y: 0.0,
110+
width: bounds.width as f32,
111+
height: bounds.height as f32,
112+
};
113+
114+
damage.sort_by(|a, b| {
115+
a.x.partial_cmp(&b.x)
116+
.unwrap_or(Ordering::Equal)
117+
.then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
118+
});
119+
120+
let mut output = Vec::new();
121+
let mut scaled = damage
122+
.into_iter()
123+
.filter_map(|region| (region * scale_factor).intersection(&bounds))
124+
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
125+
126+
if let Some(mut current) = scaled.next() {
127+
for region in scaled {
128+
let union = current.union(&region);
129+
130+
if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
131+
current = union;
132+
} else {
133+
output.push(current);
134+
current = region;
135+
}
136+
}
137+
138+
output.push(current);
139+
}
140+
141+
output
142+
}

graphics/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod viewport;
2828

2929
pub mod backend;
3030
pub mod compositor;
31+
pub mod damage;
3132
pub mod primitive;
3233
pub mod renderer;
3334

graphics/src/primitive.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bytemuck::{Pod, Zeroable};
77
use std::sync::Arc;
88

99
/// A rendering primitive.
10-
#[derive(Debug, Clone)]
10+
#[derive(Debug, Clone, PartialEq)]
1111
#[non_exhaustive]
1212
pub enum Primitive {
1313
/// A text primitive
@@ -147,10 +147,71 @@ impl Primitive {
147147
content: Box::new(self),
148148
}
149149
}
150+
151+
pub fn bounds(&self) -> Rectangle {
152+
match self {
153+
Self::Text {
154+
bounds,
155+
horizontal_alignment,
156+
vertical_alignment,
157+
..
158+
} => {
159+
let mut bounds = *bounds;
160+
161+
bounds.x = match horizontal_alignment {
162+
alignment::Horizontal::Left => bounds.x,
163+
alignment::Horizontal::Center => {
164+
bounds.x - bounds.width / 2.0
165+
}
166+
alignment::Horizontal::Right => bounds.x - bounds.width,
167+
};
168+
169+
bounds.y = match vertical_alignment {
170+
alignment::Vertical::Top => bounds.y,
171+
alignment::Vertical::Center => {
172+
bounds.y - bounds.height / 2.0
173+
}
174+
alignment::Vertical::Bottom => bounds.y - bounds.height,
175+
};
176+
177+
bounds.expand(1.5)
178+
}
179+
Self::Quad { bounds, .. }
180+
| Self::Image { bounds, .. }
181+
| Self::Svg { bounds, .. } => bounds.expand(1.0),
182+
Self::Clip { bounds, .. } => bounds.expand(1.0),
183+
Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => {
184+
Rectangle::with_size(*size)
185+
}
186+
#[cfg(feature = "tiny-skia")]
187+
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
188+
let bounds = path.bounds();
189+
190+
Rectangle {
191+
x: bounds.x(),
192+
y: bounds.y(),
193+
width: bounds.width(),
194+
height: bounds.height(),
195+
}
196+
.expand(1.0)
197+
}
198+
Self::Group { primitives } => primitives
199+
.iter()
200+
.map(Self::bounds)
201+
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
202+
Rectangle::union(&a, &b)
203+
}),
204+
Self::Translate {
205+
translation,
206+
content,
207+
} => content.bounds() + *translation,
208+
Self::Cache { content } => content.bounds(),
209+
}
210+
}
150211
}
151212

152213
/// A set of [`Vertex2D`] and indices representing a list of triangles.
153-
#[derive(Clone, Debug)]
214+
#[derive(Clone, Debug, PartialEq, Eq)]
154215
pub struct Mesh2D<T> {
155216
/// The vertices of the mesh
156217
pub vertices: Vec<T>,
@@ -162,15 +223,15 @@ pub struct Mesh2D<T> {
162223
}
163224

164225
/// A two-dimensional vertex.
165-
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
226+
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
166227
#[repr(C)]
167228
pub struct Vertex2D {
168229
/// The vertex position in 2D space.
169230
pub position: [f32; 2],
170231
}
171232

172233
/// A two-dimensional vertex with a color.
173-
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
234+
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
174235
#[repr(C)]
175236
pub struct ColoredVertex2D {
176237
/// The vertex position in 2D space.

0 commit comments

Comments
 (0)