Merge pull request #1811 from iced-rs/incremental-rendering
Incremental rendering
This commit is contained in:
commit
c31ab8eee6
23 changed files with 641 additions and 270 deletions
|
|
@ -6,7 +6,7 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A handle of some image data.
|
/// A handle of some image data.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
id: u64,
|
id: u64,
|
||||||
data: Data,
|
data: Data,
|
||||||
|
|
@ -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 {
|
impl AsRef<[u8]> for Bytes {
|
||||||
fn as_ref(&self) -> &[u8] {
|
fn as_ref(&self) -> &[u8] {
|
||||||
self.0.as_ref().as_ref()
|
self.0.as_ref().as_ref()
|
||||||
|
|
@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data of a raster image.
|
/// The data of a raster image.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Data {
|
pub enum Data {
|
||||||
/// File data
|
/// File data
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,11 @@ impl Rectangle<f32> {
|
||||||
Size::new(self.width, self.height)
|
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`].
|
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
|
||||||
pub fn contains(&self, point: Point) -> bool {
|
pub fn contains(&self, point: Point) -> bool {
|
||||||
self.x <= point.x
|
self.x <= point.x
|
||||||
|
|
@ -74,6 +79,15 @@ impl Rectangle<f32> {
|
||||||
&& point.y <= self.y + self.height
|
&& 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`].
|
/// Computes the intersection with the given [`Rectangle`].
|
||||||
pub fn intersection(
|
pub fn intersection(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -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.
|
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
|
||||||
pub fn snap(self) -> Rectangle<u32> {
|
pub fn snap(self) -> Rectangle<u32> {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -109,6 +147,16 @@ impl Rectangle<f32> {
|
||||||
height: self.height as u32,
|
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> {
|
impl std::ops::Mul<f32> for Rectangle<f32> {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A handle of Svg data.
|
/// A handle of Svg data.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Handle {
|
pub struct Handle {
|
||||||
id: u64,
|
id: u64,
|
||||||
data: Arc<Data>,
|
data: Arc<Data>,
|
||||||
|
|
@ -57,7 +57,7 @@ impl Hash for Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data of a vectorial image.
|
/// The data of a vectorial image.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum Data {
|
pub enum Data {
|
||||||
/// File data
|
/// File data
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
web_sys::window()
|
web_sys::window()
|
||||||
.and_then(|win| win.document())
|
.and_then(|win| win.document())
|
||||||
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
|
.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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ version = "0.9"
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
|
||||||
[dependencies.tiny-skia]
|
[dependencies.tiny-skia]
|
||||||
version = "0.8"
|
version = "0.9"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.image]
|
[dependencies.image]
|
||||||
|
|
|
||||||
142
graphics/src/damage.rs
Normal file
142
graphics/src/damage.rs
Normal file
|
|
@ -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(®ion);
|
||||||
|
|
||||||
|
if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
|
||||||
|
current = union;
|
||||||
|
} else {
|
||||||
|
output.push(current);
|
||||||
|
current = region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
|
pub mod damage;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use bytemuck::{Pod, Zeroable};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A rendering primitive.
|
/// A rendering primitive.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Primitive {
|
pub enum Primitive {
|
||||||
/// A text primitive
|
/// A text primitive
|
||||||
|
|
@ -147,10 +147,71 @@ impl Primitive {
|
||||||
content: Box::new(self),
|
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.
|
/// A set of [`Vertex2D`] and indices representing a list of triangles.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Mesh2D<T> {
|
pub struct Mesh2D<T> {
|
||||||
/// The vertices of the mesh
|
/// The vertices of the mesh
|
||||||
pub vertices: Vec<T>,
|
pub vertices: Vec<T>,
|
||||||
|
|
@ -162,7 +223,7 @@ pub struct Mesh2D<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A two-dimensional vertex.
|
/// A two-dimensional vertex.
|
||||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Vertex2D {
|
pub struct Vertex2D {
|
||||||
/// The vertex position in 2D space.
|
/// The vertex position in 2D space.
|
||||||
|
|
@ -170,7 +231,7 @@ pub struct Vertex2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A two-dimensional vertex with a color.
|
/// A two-dimensional vertex with a color.
|
||||||
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
|
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ColoredVertex2D {
|
pub struct ColoredVertex2D {
|
||||||
/// The vertex position in 2D space.
|
/// The vertex position in 2D space.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn backend(&self) -> &B {
|
||||||
&self.backend
|
&self.backend
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::graphics::{Error, Viewport};
|
||||||
use crate::{Renderer, Settings};
|
use crate::{Renderer, Settings};
|
||||||
|
|
||||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
pub enum Compositor<Theme> {
|
pub enum Compositor<Theme> {
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
|
|
@ -28,53 +29,13 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
settings: Self::Settings,
|
settings: Self::Settings,
|
||||||
compatible_window: Option<&W>,
|
compatible_window: Option<&W>,
|
||||||
) -> Result<(Self, Self::Renderer), Error> {
|
) -> Result<(Self, Self::Renderer), Error> {
|
||||||
#[cfg(feature = "wgpu")]
|
let candidates =
|
||||||
let new_wgpu = |settings: Self::Settings, compatible_window| {
|
Candidate::list_from_env().unwrap_or(Candidate::default_list());
|
||||||
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 mut error = Error::GraphicsAdapterNotFound;
|
let mut error = Error::GraphicsAdapterNotFound;
|
||||||
|
|
||||||
for candidate in candidates {
|
for candidate in candidates {
|
||||||
match candidate(settings, compatible_window) {
|
match candidate.build(settings, compatible_window) {
|
||||||
Ok((compositor, renderer)) => {
|
Ok((compositor, renderer)) => {
|
||||||
return Ok((compositor, renderer))
|
return Ok((compositor, renderer))
|
||||||
}
|
}
|
||||||
|
|
@ -162,11 +123,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
),
|
),
|
||||||
#[cfg(feature = "tiny-skia")]
|
#[cfg(feature = "tiny-skia")]
|
||||||
(
|
(
|
||||||
Self::TinySkia(compositor),
|
Self::TinySkia(_compositor),
|
||||||
crate::Backend::TinySkia(backend),
|
crate::Backend::TinySkia(backend),
|
||||||
Surface::TinySkia(surface),
|
Surface::TinySkia(surface),
|
||||||
) => iced_tiny_skia::window::compositor::present(
|
) => iced_tiny_skia::window::compositor::present(
|
||||||
compositor,
|
|
||||||
backend,
|
backend,
|
||||||
surface,
|
surface,
|
||||||
primitives,
|
primitives,
|
||||||
|
|
@ -183,3 +143,86 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Candidate {
|
||||||
|
Wgpu,
|
||||||
|
TinySkia,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Candidate {
|
||||||
|
fn default_list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
Self::Wgpu,
|
||||||
|
#[cfg(feature = "tiny-skia")]
|
||||||
|
Self::TinySkia,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_from_env() -> Option<Vec<Self>> {
|
||||||
|
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<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
|
||||||
|
self,
|
||||||
|
settings: Settings,
|
||||||
|
_compatible_window: Option<&W>,
|
||||||
|
) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> {
|
||||||
|
match self {
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
Self::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,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Compositor::Wgpu(compositor),
|
||||||
|
Renderer::new(crate::Backend::Wgpu(backend)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tiny-skia")]
|
||||||
|
Self::TinySkia => {
|
||||||
|
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`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ geometry = ["iced_graphics/geometry"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
softbuffer = "0.2"
|
softbuffer = "0.2"
|
||||||
tiny-skia = "0.8"
|
tiny-skia = "0.9"
|
||||||
cosmic-text = "0.8"
|
cosmic-text = "0.8"
|
||||||
bytemuck = "1"
|
bytemuck = "1"
|
||||||
rustc-hash = "1.1"
|
rustc-hash = "1.1"
|
||||||
|
|
@ -32,5 +32,5 @@ version = "1.6.1"
|
||||||
features = ["std"]
|
features = ["std"]
|
||||||
|
|
||||||
[dependencies.resvg]
|
[dependencies.resvg]
|
||||||
version = "0.29"
|
version = "0.32"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::core::alignment;
|
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
|
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
|
||||||
use crate::graphics::backend;
|
use crate::graphics::backend;
|
||||||
|
|
@ -37,51 +36,99 @@ impl Backend {
|
||||||
pub fn draw<T: AsRef<str>>(
|
pub fn draw<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: &mut tiny_skia::ClipMask,
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
primitives: &[Primitive],
|
primitives: &[Primitive],
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
|
damage: &[Rectangle],
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) {
|
) {
|
||||||
pixels.fill(into_color(background_color));
|
let physical_size = viewport.physical_size();
|
||||||
|
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
for primitive in primitives {
|
if !overlay.is_empty() {
|
||||||
self.draw_primitive(
|
let path = tiny_skia::PathBuilder::from_rect(
|
||||||
primitive,
|
tiny_skia::Rect::from_xywh(
|
||||||
pixels,
|
0.0,
|
||||||
clip_mask,
|
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,
|
None,
|
||||||
scale_factor,
|
|
||||||
Vector::ZERO,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, text) in overlay.iter().enumerate() {
|
for ®ion in damage {
|
||||||
const OVERLAY_TEXT_SIZE: f32 = 20.0;
|
let path = tiny_skia::PathBuilder::from_rect(
|
||||||
|
tiny_skia::Rect::from_xywh(
|
||||||
self.draw_primitive(
|
region.x,
|
||||||
&Primitive::Text {
|
region.y,
|
||||||
content: text.as_ref().to_owned(),
|
region.width,
|
||||||
size: OVERLAY_TEXT_SIZE,
|
region.height,
|
||||||
bounds: Rectangle {
|
)
|
||||||
x: 10.0,
|
.expect("Create damage rectangle"),
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pixels.fill_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(into_color(
|
||||||
|
background_color,
|
||||||
|
)),
|
||||||
|
anti_alias: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tiny_skia::FillRule::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
adjust_clip_mask(clip_mask, region);
|
||||||
|
|
||||||
|
for primitive in primitives {
|
||||||
|
self.draw_primitive(
|
||||||
|
primitive,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
region,
|
||||||
|
scale_factor,
|
||||||
|
Vector::ZERO,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text_pipeline.trim_cache();
|
self.text_pipeline.trim_cache();
|
||||||
|
|
@ -97,8 +144,8 @@ impl Backend {
|
||||||
&mut self,
|
&mut self,
|
||||||
primitive: &Primitive,
|
primitive: &Primitive,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: &mut tiny_skia::ClipMask,
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
clip_bounds: Option<Rectangle>,
|
clip_bounds: Rectangle,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
translation: Vector,
|
translation: Vector,
|
||||||
) {
|
) {
|
||||||
|
|
@ -110,6 +157,15 @@ impl Backend {
|
||||||
border_width,
|
border_width,
|
||||||
border_color,
|
border_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 &_);
|
||||||
|
|
||||||
let transform = tiny_skia::Transform::from_translate(
|
let transform = tiny_skia::Transform::from_translate(
|
||||||
translation.x,
|
translation.x,
|
||||||
translation.y,
|
translation.y,
|
||||||
|
|
@ -117,7 +173,6 @@ impl Backend {
|
||||||
.post_scale(scale_factor, scale_factor);
|
.post_scale(scale_factor, scale_factor);
|
||||||
|
|
||||||
let path = rounded_rectangle(*bounds, *border_radius);
|
let path = rounded_rectangle(*bounds, *border_radius);
|
||||||
let clip_mask = clip_bounds.map(|_| clip_mask as &_);
|
|
||||||
|
|
||||||
pixels.fill_path(
|
pixels.fill_path(
|
||||||
&path,
|
&path,
|
||||||
|
|
@ -165,6 +220,16 @@ impl Backend {
|
||||||
horizontal_alignment,
|
horizontal_alignment,
|
||||||
vertical_alignment,
|
vertical_alignment,
|
||||||
} => {
|
} => {
|
||||||
|
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_some(clip_mask as &_);
|
||||||
|
|
||||||
self.text_pipeline.draw(
|
self.text_pipeline.draw(
|
||||||
content,
|
content,
|
||||||
(*bounds + translation) * scale_factor,
|
(*bounds + translation) * scale_factor,
|
||||||
|
|
@ -174,24 +239,28 @@ impl Backend {
|
||||||
*horizontal_alignment,
|
*horizontal_alignment,
|
||||||
*vertical_alignment,
|
*vertical_alignment,
|
||||||
pixels,
|
pixels,
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
clip_mask,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
Primitive::Image { handle, bounds } => {
|
Primitive::Image { handle, bounds } => {
|
||||||
|
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(
|
let transform = tiny_skia::Transform::from_translate(
|
||||||
translation.x,
|
translation.x,
|
||||||
translation.y,
|
translation.y,
|
||||||
)
|
)
|
||||||
.post_scale(scale_factor, scale_factor);
|
.post_scale(scale_factor, scale_factor);
|
||||||
|
|
||||||
self.raster_pipeline.draw(
|
self.raster_pipeline
|
||||||
handle,
|
.draw(handle, *bounds, pixels, transform, clip_mask);
|
||||||
*bounds,
|
|
||||||
pixels,
|
|
||||||
transform,
|
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
Primitive::Svg {
|
Primitive::Svg {
|
||||||
|
|
@ -199,12 +268,21 @@ impl Backend {
|
||||||
bounds,
|
bounds,
|
||||||
color,
|
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(
|
self.vector_pipeline.draw(
|
||||||
handle,
|
handle,
|
||||||
*color,
|
*color,
|
||||||
(*bounds + translation) * scale_factor,
|
(*bounds + translation) * scale_factor,
|
||||||
pixels,
|
pixels,
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
clip_mask,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Primitive::Fill {
|
Primitive::Fill {
|
||||||
|
|
@ -213,6 +291,23 @@ impl Backend {
|
||||||
rule,
|
rule,
|
||||||
transform,
|
transform,
|
||||||
} => {
|
} => {
|
||||||
|
let bounds = path.bounds();
|
||||||
|
|
||||||
|
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_some(clip_mask as &_);
|
||||||
|
|
||||||
pixels.fill_path(
|
pixels.fill_path(
|
||||||
path,
|
path,
|
||||||
paint,
|
paint,
|
||||||
|
|
@ -220,7 +315,7 @@ impl Backend {
|
||||||
transform
|
transform
|
||||||
.post_translate(translation.x, translation.y)
|
.post_translate(translation.x, translation.y)
|
||||||
.post_scale(scale_factor, scale_factor),
|
.post_scale(scale_factor, scale_factor),
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
clip_mask,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Primitive::Stroke {
|
Primitive::Stroke {
|
||||||
|
|
@ -229,6 +324,23 @@ impl Backend {
|
||||||
stroke,
|
stroke,
|
||||||
transform,
|
transform,
|
||||||
} => {
|
} => {
|
||||||
|
let bounds = path.bounds();
|
||||||
|
|
||||||
|
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_some(clip_mask as &_);
|
||||||
|
|
||||||
pixels.stroke_path(
|
pixels.stroke_path(
|
||||||
path,
|
path,
|
||||||
paint,
|
paint,
|
||||||
|
|
@ -236,7 +348,7 @@ impl Backend {
|
||||||
transform
|
transform
|
||||||
.post_translate(translation.x, translation.y)
|
.post_translate(translation.x, translation.y)
|
||||||
.post_scale(scale_factor, scale_factor),
|
.post_scale(scale_factor, scale_factor),
|
||||||
clip_bounds.map(|_| clip_mask as &_),
|
clip_mask,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Primitive::Group { primitives } => {
|
Primitive::Group { primitives } => {
|
||||||
|
|
@ -267,29 +379,38 @@ impl Backend {
|
||||||
Primitive::Clip { bounds, content } => {
|
Primitive::Clip { bounds, content } => {
|
||||||
let bounds = (*bounds + translation) * scale_factor;
|
let bounds = (*bounds + translation) * scale_factor;
|
||||||
|
|
||||||
if bounds.x + bounds.width <= 0.0
|
if bounds == clip_bounds {
|
||||||
|| bounds.y + bounds.height <= 0.0
|
self.draw_primitive(
|
||||||
|| bounds.x as u32 >= pixels.width()
|
content,
|
||||||
|| bounds.y as u32 >= pixels.height()
|
pixels,
|
||||||
{
|
clip_mask,
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
adjust_clip_mask(clip_mask, pixels, bounds);
|
adjust_clip_mask(clip_mask, bounds);
|
||||||
|
|
||||||
self.draw_primitive(
|
self.draw_primitive(
|
||||||
content,
|
content,
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
Some(bounds),
|
bounds,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
translation,
|
translation,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(bounds) = clip_bounds {
|
adjust_clip_mask(clip_mask, clip_bounds);
|
||||||
adjust_clip_mask(clip_mask, pixels, bounds);
|
|
||||||
} else {
|
|
||||||
clip_mask.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primitive::Cache { content } => {
|
Primitive::Cache { content } => {
|
||||||
|
|
@ -462,11 +583,9 @@ fn arc_to(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adjust_clip_mask(
|
fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
|
||||||
clip_mask: &mut tiny_skia::ClipMask,
|
clip_mask.clear();
|
||||||
pixels: &tiny_skia::PixmapMut<'_>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
let path = {
|
let path = {
|
||||||
let mut builder = tiny_skia::PathBuilder::new();
|
let mut builder = tiny_skia::PathBuilder::new();
|
||||||
builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||||
|
|
@ -474,15 +593,12 @@ fn adjust_clip_mask(
|
||||||
builder.finish().unwrap()
|
builder.finish().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
clip_mask
|
clip_mask.fill_path(
|
||||||
.set_path(
|
&path,
|
||||||
pixels.width(),
|
tiny_skia::FillRule::EvenOdd,
|
||||||
pixels.height(),
|
false,
|
||||||
&path,
|
tiny_skia::Transform::default(),
|
||||||
tiny_skia::FillRule::EvenOdd,
|
);
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("Set path of clipping area");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl iced_graphics::Backend for Backend {
|
impl iced_graphics::Backend for Backend {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl Pipeline {
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
transform: tiny_skia::Transform,
|
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) {
|
if let Some(image) = self.cache.borrow_mut().allocate(handle) {
|
||||||
let width_scale = bounds.width / image.width() as f32;
|
let width_scale = bounds.width / image.width() as f32;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ impl Pipeline {
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
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 font_system = self.font_system.get_mut();
|
||||||
let key = Key {
|
let key = Key {
|
||||||
|
|
@ -336,6 +336,7 @@ struct Cache {
|
||||||
entries: FxHashMap<KeyHash, cosmic_text::Buffer>,
|
entries: FxHashMap<KeyHash, cosmic_text::Buffer>,
|
||||||
recently_used: FxHashSet<KeyHash>,
|
recently_used: FxHashSet<KeyHash>,
|
||||||
hasher: HashBuilder,
|
hasher: HashBuilder,
|
||||||
|
trim_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
|
const TRIM_INTERVAL: usize = 300;
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: FxHashMap::default(),
|
entries: FxHashMap::default(),
|
||||||
recently_used: FxHashSet::default(),
|
recently_used: FxHashSet::default(),
|
||||||
hasher: HashBuilder::default(),
|
hasher: HashBuilder::default(),
|
||||||
|
trim_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -397,10 +401,16 @@ impl Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trim(&mut self) {
|
fn trim(&mut self) {
|
||||||
self.entries
|
if self.trim_count > Self::TRIM_INTERVAL {
|
||||||
.retain(|key, _| self.recently_used.contains(key));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ impl Pipeline {
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
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(
|
if let Some(image) = self.cache.borrow_mut().draw(
|
||||||
handle,
|
handle,
|
||||||
|
|
@ -72,6 +72,8 @@ struct RasterKey {
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
|
||||||
|
use usvg::TreeParsing;
|
||||||
|
|
||||||
let id = handle.id();
|
let id = handle.id();
|
||||||
|
|
||||||
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
|
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
|
||||||
|
|
@ -131,9 +133,9 @@ impl Cache {
|
||||||
resvg::render(
|
resvg::render(
|
||||||
tree,
|
tree,
|
||||||
if size.width > size.height {
|
if size.width > size.height {
|
||||||
usvg::FitTo::Width(size.width)
|
resvg::FitTo::Width(size.width)
|
||||||
} else {
|
} else {
|
||||||
usvg::FitTo::Height(size.height)
|
resvg::FitTo::Height(size.height)
|
||||||
},
|
},
|
||||||
tiny_skia::Transform::default(),
|
tiny_skia::Transform::default(),
|
||||||
image.as_mut(),
|
image.as_mut(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::core::Color;
|
use crate::core::{Color, Rectangle};
|
||||||
use crate::graphics::compositor::{self, Information, SurfaceError};
|
use crate::graphics::compositor::{self, Information, SurfaceError};
|
||||||
|
use crate::graphics::damage;
|
||||||
use crate::graphics::{Error, Primitive, Viewport};
|
use crate::graphics::{Error, Primitive, Viewport};
|
||||||
use crate::{Backend, Renderer, Settings};
|
use crate::{Backend, Renderer, Settings};
|
||||||
|
|
||||||
|
|
@ -7,13 +8,15 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct Compositor<Theme> {
|
pub struct Compositor<Theme> {
|
||||||
clip_mask: tiny_skia::ClipMask,
|
|
||||||
_theme: PhantomData<Theme>,
|
_theme: PhantomData<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Surface {
|
pub struct Surface {
|
||||||
window: softbuffer::GraphicsContext,
|
window: softbuffer::GraphicsContext,
|
||||||
buffer: Vec<u32>,
|
buffer: Vec<u32>,
|
||||||
|
clip_mask: tiny_skia::Mask,
|
||||||
|
primitives: Option<Vec<Primitive>>,
|
||||||
|
background_color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
|
|
@ -43,6 +46,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
Surface {
|
Surface {
|
||||||
window,
|
window,
|
||||||
buffer: vec![0; width as usize * height as usize],
|
buffer: vec![0; width as usize * height as usize],
|
||||||
|
clip_mask: tiny_skia::Mask::new(width, height)
|
||||||
|
.expect("Create clip mask"),
|
||||||
|
primitives: None,
|
||||||
|
background_color: Color::BLACK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +60,9 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
height: u32,
|
height: u32,
|
||||||
) {
|
) {
|
||||||
surface.buffer.resize((width * height) as usize, 0);
|
surface.buffer.resize((width * height) as usize, 0);
|
||||||
|
surface.clip_mask =
|
||||||
|
tiny_skia::Mask::new(width, height).expect("Create clip mask");
|
||||||
|
surface.primitives = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_information(&self) -> Information {
|
fn fetch_information(&self) -> Information {
|
||||||
|
|
@ -72,7 +82,6 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
) -> Result<(), SurfaceError> {
|
) -> Result<(), SurfaceError> {
|
||||||
renderer.with_primitives(|backend, primitives| {
|
renderer.with_primitives(|backend, primitives| {
|
||||||
present(
|
present(
|
||||||
self,
|
|
||||||
backend,
|
backend,
|
||||||
surface,
|
surface,
|
||||||
primitives,
|
primitives,
|
||||||
|
|
@ -85,18 +94,15 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
|
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
|
||||||
// TOD
|
|
||||||
(
|
(
|
||||||
Compositor {
|
Compositor {
|
||||||
clip_mask: tiny_skia::ClipMask::new(),
|
|
||||||
_theme: PhantomData,
|
_theme: PhantomData,
|
||||||
},
|
},
|
||||||
Backend::new(settings),
|
Backend::new(settings),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present<Theme, T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
compositor: &mut Compositor<Theme>,
|
|
||||||
backend: &mut Backend,
|
backend: &mut Backend,
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
primitives: &[Primitive],
|
primitives: &[Primitive],
|
||||||
|
|
@ -105,17 +111,39 @@ pub fn present<Theme, T: AsRef<str>>(
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
let physical_size = viewport.physical_size();
|
let physical_size = viewport.physical_size();
|
||||||
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
|
let mut pixels = tiny_skia::PixmapMut::from_bytes(
|
||||||
|
bytemuck::cast_slice_mut(&mut surface.buffer),
|
||||||
|
physical_size.width,
|
||||||
|
physical_size.height,
|
||||||
|
)
|
||||||
|
.expect("Create pixel map");
|
||||||
|
|
||||||
|
let damage = surface
|
||||||
|
.primitives
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|last_primitives| {
|
||||||
|
(surface.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.primitives = Some(primitives.to_vec());
|
||||||
|
surface.background_color = background_color;
|
||||||
|
|
||||||
|
let damage = damage::group(damage, scale_factor, physical_size);
|
||||||
|
|
||||||
backend.draw(
|
backend.draw(
|
||||||
&mut tiny_skia::PixmapMut::from_bytes(
|
&mut pixels,
|
||||||
bytemuck::cast_slice_mut(&mut surface.buffer),
|
&mut surface.clip_mask,
|
||||||
physical_size.width,
|
|
||||||
physical_size.height,
|
|
||||||
)
|
|
||||||
.expect("Create pixel map"),
|
|
||||||
&mut compositor.clip_mask,
|
|
||||||
primitives,
|
primitives,
|
||||||
viewport,
|
viewport,
|
||||||
|
&damage,
|
||||||
background_color,
|
background_color,
|
||||||
overlay,
|
overlay,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ once_cell = "1.0"
|
||||||
rustc-hash = "1.1"
|
rustc-hash = "1.1"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
wgpu = { version = "0.14", features = ["webgl"] }
|
wgpu = { version = "0.15", features = ["webgl"] }
|
||||||
|
|
||||||
[dependencies.twox-hash]
|
[dependencies.twox-hash]
|
||||||
version = "1.6"
|
version = "1.6"
|
||||||
|
|
@ -58,7 +58,7 @@ version = "1.0"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.resvg]
|
[dependencies.resvg]
|
||||||
version = "0.29"
|
version = "0.32"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.tracing]
|
[dependencies.tracing]
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ type ColorFilter = Option<[u8; 4]>;
|
||||||
impl Cache {
|
impl Cache {
|
||||||
/// Load svg
|
/// Load svg
|
||||||
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
||||||
|
use usvg::TreeParsing;
|
||||||
|
|
||||||
if self.svgs.contains_key(&handle.id()) {
|
if self.svgs.contains_key(&handle.id()) {
|
||||||
return self.svgs.get(&handle.id()).unwrap();
|
return self.svgs.get(&handle.id()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -116,9 +118,9 @@ impl Cache {
|
||||||
resvg::render(
|
resvg::render(
|
||||||
tree,
|
tree,
|
||||||
if width > height {
|
if width > height {
|
||||||
usvg::FitTo::Width(width)
|
resvg::FitTo::Width(width)
|
||||||
} else {
|
} else {
|
||||||
usvg::FitTo::Height(height)
|
resvg::FitTo::Height(height)
|
||||||
},
|
},
|
||||||
tiny_skia::Transform::default(),
|
tiny_skia::Transform::default(),
|
||||||
img.as_mut(),
|
img.as_mut(),
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ impl Settings {
|
||||||
/// - `primary`
|
/// - `primary`
|
||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
Settings {
|
Settings {
|
||||||
internal_backend: backend_from_env()
|
internal_backend: wgpu::util::backend_bits_from_env()
|
||||||
.unwrap_or(wgpu::Backends::all()),
|
.unwrap_or(wgpu::Backends::all()),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
|
|
@ -64,18 +64,3 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backend_from_env() -> Option<wgpu::Backends> {
|
|
||||||
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}"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
mod msaa;
|
mod msaa;
|
||||||
|
|
||||||
use crate::buffer::r#static::Buffer;
|
use crate::buffer::r#static::Buffer;
|
||||||
use crate::core::{Gradient, Size};
|
use crate::core::Size;
|
||||||
use crate::graphics::{Antialiasing, Transformation};
|
use crate::graphics::{Antialiasing, Transformation};
|
||||||
use crate::layer::mesh::{self, Mesh};
|
use crate::layer::mesh::{self, Mesh};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::core::Gradient;
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,12 @@ impl<Theme> Compositor<Theme> {
|
||||||
|
|
||||||
let adapter = instance
|
let adapter = instance
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
power_preference: if settings.antialiasing.is_none() {
|
power_preference: wgpu::util::power_preference_from_env()
|
||||||
wgpu::PowerPreference::LowPower
|
.unwrap_or(if settings.antialiasing.is_none() {
|
||||||
} else {
|
wgpu::PowerPreference::LowPower
|
||||||
wgpu::PowerPreference::HighPerformance
|
} else {
|
||||||
},
|
wgpu::PowerPreference::HighPerformance
|
||||||
|
}),
|
||||||
compatible_surface: compatible_surface.as_ref(),
|
compatible_surface: compatible_surface.as_ref(),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1084,6 +1084,8 @@ pub fn draw<Renderer>(
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut Renderer| {
|
||||||
if let Some((cursor, color)) = cursor {
|
if let Some((cursor, color)) = cursor {
|
||||||
renderer.fill_quad(cursor, color);
|
renderer.fill_quad(cursor, color);
|
||||||
|
} else {
|
||||||
|
renderer.with_translation(Vector::ZERO, |_| {});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.fill_text(Text {
|
renderer.fill_text(Text {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue