Skip to content

Commit

Permalink
Merge pull request #1811 from iced-rs/incremental-rendering
Browse files Browse the repository at this point in the history
Incremental rendering
  • Loading branch information
hecrj authored Apr 27, 2023
2 parents e373010 + a755472 commit c31ab8e
Show file tree
Hide file tree
Showing 23 changed files with 640 additions and 269 deletions.
12 changes: 10 additions & 2 deletions core/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand All @@ -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),
Expand Down
48 changes: 48 additions & 0 deletions core/src/rectangle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ impl Rectangle<f32> {
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
Expand All @@ -74,6 +79,15 @@ impl Rectangle<f32> {
&& 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,
Expand All @@ -100,6 +114,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 {
Expand All @@ -109,6 +147,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> {
Expand Down
4 changes: 2 additions & 2 deletions core/src/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion examples/integration/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())?
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
.expect("Get canvas element")
};
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
Expand Down
2 changes: 1 addition & 1 deletion graphics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ version = "0.9"
path = "../core"

[dependencies.tiny-skia]
version = "0.8"
version = "0.9"
optional = true

[dependencies.image]
Expand Down
142 changes: 142 additions & 0 deletions graphics/src/damage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use crate::core::{Rectangle, Size};
use crate::Primitive;

use std::sync::Arc;

pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
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.expand(1.0)))
.collect();
} else {
return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
}
}
(
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<Rectangle> {
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<Rectangle>,
scale_factor: f32,
bounds: Size<u32>,
) -> Vec<Rectangle> {
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(&region);

if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
current = union;
} else {
output.push(current);
current = region;
}
}

output.push(current);
}

output
}
1 change: 1 addition & 0 deletions graphics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ mod viewport;

pub mod backend;
pub mod compositor;
pub mod damage;
pub mod primitive;
pub mod renderer;

Expand Down
69 changes: 65 additions & 4 deletions graphics/src/primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -147,10 +147,71 @@ 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.5)
}
Self::Quad { bounds, .. }
| Self::Image { bounds, .. }
| Self::Svg { bounds, .. } => bounds.expand(1.0),
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(),
}
}
}

/// 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>,
Expand All @@ -162,15 +223,15 @@ 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.
pub position: [f32; 2],
}

/// 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.
Expand Down
Loading

0 comments on commit c31ab8e

Please sign in to comment.