summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-03 21:07:54 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-04-03 21:07:54 +0200
commitb05e61f5c8ae61c9f3c7cc08cded53901ebbccfd (patch)
tree3d35a011d94d4936f09b5a9be4031358a09c60da
parent99a904112ca111f2ab0e60e30b6c369741b1653b (diff)
downloadiced-b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd.tar.gz
iced-b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd.tar.bz2
iced-b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd.zip
Redesign `iced_wgpu` layering architecture
-rw-r--r--core/src/rectangle.rs27
-rw-r--r--core/src/renderer.rs8
-rw-r--r--core/src/renderer/null.rs4
-rw-r--r--core/src/size.rs14
-rw-r--r--core/src/transformation.rs6
-rw-r--r--examples/geometry/src/main.rs6
-rw-r--r--examples/integration/src/main.rs35
-rw-r--r--graphics/src/cached.rs8
-rw-r--r--graphics/src/geometry/cache.rs10
-rw-r--r--graphics/src/image.rs154
-rw-r--r--graphics/src/layer.rs47
-rw-r--r--graphics/src/lib.rs6
-rw-r--r--graphics/src/mesh.rs90
-rw-r--r--graphics/src/renderer.rs4
-rw-r--r--graphics/src/text.rs57
-rw-r--r--renderer/Cargo.toml1
-rw-r--r--renderer/src/fallback.rs32
-rw-r--r--wgpu/Cargo.toml3
-rw-r--r--wgpu/src/backend.rs432
-rw-r--r--wgpu/src/engine.rs79
-rw-r--r--wgpu/src/geometry.rs175
-rw-r--r--wgpu/src/image/cache.rs107
-rw-r--r--wgpu/src/image/mod.rs (renamed from wgpu/src/image.rs)431
-rw-r--r--wgpu/src/image/null.rs10
-rw-r--r--wgpu/src/layer.rs617
-rw-r--r--wgpu/src/layer/image.rs30
-rw-r--r--wgpu/src/layer/mesh.rs97
-rw-r--r--wgpu/src/layer/pipeline.rs17
-rw-r--r--wgpu/src/layer/text.rs70
-rw-r--r--wgpu/src/lib.rs578
-rw-r--r--wgpu/src/primitive.rs20
-rw-r--r--wgpu/src/quad.rs294
-rw-r--r--wgpu/src/text.rs628
-rw-r--r--wgpu/src/triangle.rs396
-rw-r--r--wgpu/src/window/compositor.rs96
-rw-r--r--widget/src/scrollable.rs6
36 files changed, 2664 insertions, 1931 deletions
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index c1c2eeac..45acd5ac 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -16,24 +16,29 @@ pub struct Rectangle<T = f32> {
pub height: T,
}
-impl Rectangle<f32> {
- /// Creates a new [`Rectangle`] with its top-left corner in the given
- /// [`Point`] and with the provided [`Size`].
- pub fn new(top_left: Point, size: Size) -> Self {
+impl<T> Rectangle<T>
+where
+ T: Default,
+{
+ /// Creates a new [`Rectangle`] with its top-left corner at the origin
+ /// and with the provided [`Size`].
+ pub fn with_size(size: Size<T>) -> Self {
Self {
- x: top_left.x,
- y: top_left.y,
+ x: T::default(),
+ y: T::default(),
width: size.width,
height: size.height,
}
}
+}
- /// Creates a new [`Rectangle`] with its top-left corner at the origin
- /// and with the provided [`Size`].
- pub fn with_size(size: Size) -> Self {
+impl Rectangle<f32> {
+ /// Creates a new [`Rectangle`] with its top-left corner in the given
+ /// [`Point`] and with the provided [`Size`].
+ pub fn new(top_left: Point, size: Size) -> Self {
Self {
- x: 0.0,
- y: 0.0,
+ x: top_left.x,
+ y: top_left.y,
width: size.width,
height: size.height,
}
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 6712314e..f5ef8f68 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -9,7 +9,7 @@ use crate::{
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer {
/// Starts recording a new layer.
- fn start_layer(&mut self);
+ fn start_layer(&mut self, bounds: Rectangle);
/// Ends recording a new layer.
///
@@ -20,13 +20,13 @@ pub trait Renderer {
///
/// The layer will clip its contents to the provided `bounds`.
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
- self.start_layer();
+ self.start_layer(bounds);
f(self);
self.end_layer(bounds);
}
/// Starts recording with a new [`Transformation`].
- fn start_transformation(&mut self);
+ fn start_transformation(&mut self, transformation: Transformation);
/// Ends recording a new layer.
///
@@ -39,7 +39,7 @@ pub trait Renderer {
transformation: Transformation,
f: impl FnOnce(&mut Self),
) {
- self.start_transformation();
+ self.start_transformation(transformation);
f(self);
self.end_transformation(transformation);
}
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 1caf71b3..f36d19aa 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -10,11 +10,11 @@ use crate::{
use std::borrow::Cow;
impl Renderer for () {
- fn start_layer(&mut self) {}
+ fn start_layer(&mut self, _bounds: Rectangle) {}
fn end_layer(&mut self, _bounds: Rectangle) {}
- fn start_transformation(&mut self) {}
+ fn start_transformation(&mut self, _transformation: Transformation) {}
fn end_transformation(&mut self, _transformation: Transformation) {}
diff --git a/core/src/size.rs b/core/src/size.rs
index 55db759d..c2b5671a 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -99,3 +99,17 @@ where
}
}
}
+
+impl<T> std::ops::Mul<T> for Size<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Size<T>;
+
+ fn mul(self, rhs: T) -> Self::Output {
+ Size {
+ width: self.width * rhs,
+ height: self.height * rhs,
+ }
+ }
+}
diff --git a/core/src/transformation.rs b/core/src/transformation.rs
index b2c488b0..74183147 100644
--- a/core/src/transformation.rs
+++ b/core/src/transformation.rs
@@ -42,6 +42,12 @@ impl Transformation {
}
}
+impl Default for Transformation {
+ fn default() -> Self {
+ Transformation::IDENTITY
+ }
+}
+
impl Mul for Transformation {
type Output = Self;
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 16cdb86f..9532a24a 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -6,7 +6,10 @@ mod rainbow {
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
- use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
+ use iced::{
+ Element, Length, Rectangle, Renderer, Size, Theme, Transformation,
+ Vector,
+ };
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@@ -130,6 +133,7 @@ mod rainbow {
0, 8, 1, // L
],
},
+ transformation: Transformation::IDENTITY,
};
renderer.with_translation(
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 9cd801b2..c292709f 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -5,12 +5,12 @@ use controls::Controls;
use scene::Scene;
use iced_wgpu::graphics::Viewport;
-use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_wgpu::{wgpu, Engine, Renderer, Settings};
use iced_winit::conversion;
use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::window;
-use iced_winit::core::{Color, Font, Pixels, Size, Theme};
+use iced_winit::core::{Color, Size, Theme};
use iced_winit::futures;
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
@@ -155,11 +155,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize iced
let mut debug = Debug::new();
- let mut renderer = Renderer::new(
- Backend::new(&adapter, &device, &queue, Settings::default(), format),
- Font::default(),
- Pixels(16.0),
- );
+ let mut engine = Engine::new(&adapter, &device, &queue, format, None);
+ let mut renderer = Renderer::new(Settings::default(), &engine);
let mut state = program::State::new(
controls,
@@ -228,19 +225,17 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
}
// And then iced on top
- renderer.with_primitives(|backend, primitive| {
- backend.present(
- &device,
- &queue,
- &mut encoder,
- None,
- frame.texture.format(),
- &view,
- primitive,
- &viewport,
- &debug.overlay(),
- );
- });
+ renderer.present(
+ &mut engine,
+ &device,
+ &queue,
+ &mut encoder,
+ None,
+ frame.texture.format(),
+ &view,
+ &viewport,
+ &debug.overlay(),
+ );
// Then we submit the work
queue.submit(Some(encoder.finish()));
diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs
index b52f9d9d..1ba82f9f 100644
--- a/graphics/src/cached.rs
+++ b/graphics/src/cached.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
/// A piece of data that can be cached.
pub trait Cached: Sized {
/// The type of cache produced.
- type Cache;
+ type Cache: Clone;
/// Loads the [`Cache`] into a proper instance.
///
@@ -15,7 +15,7 @@ pub trait Cached: Sized {
/// Caches this value, producing its corresponding [`Cache`].
///
/// [`Cache`]: Self::Cache
- fn cache(self) -> Self::Cache;
+ fn cache(self, previous: Option<Self::Cache>) -> Self::Cache;
}
impl<T> Cached for Primitive<T> {
@@ -27,7 +27,7 @@ impl<T> Cached for Primitive<T> {
}
}
- fn cache(self) -> Arc<Self> {
+ fn cache(self, _previous: Option<Arc<Self>>) -> Arc<Self> {
Arc::new(self)
}
}
@@ -38,5 +38,5 @@ impl Cached for () {
fn load(_cache: &Self::Cache) -> Self {}
- fn cache(self) -> Self::Cache {}
+ fn cache(self, _previous: Option<Self::Cache>) -> Self::Cache {}
}
diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs
index 37d433c2..ebbafd14 100644
--- a/graphics/src/geometry/cache.rs
+++ b/graphics/src/geometry/cache.rs
@@ -49,7 +49,7 @@ where
) -> Renderer::Geometry {
use std::ops::Deref;
- if let State::Filled {
+ let previous = if let State::Filled {
bounds: cached_bounds,
geometry,
} = self.state.borrow().deref()
@@ -57,12 +57,16 @@ where
if *cached_bounds == bounds {
return Cached::load(geometry);
}
- }
+
+ Some(geometry.clone())
+ } else {
+ None
+ };
let mut frame = Frame::new(renderer, bounds);
draw_fn(&mut frame);
- let geometry = frame.into_geometry().cache();
+ let geometry = frame.into_geometry().cache(previous);
let result = Cached::load(&geometry);
*self.state.borrow_mut() = State::Filled { bounds, geometry };
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
index d89caace..e8626717 100644
--- a/graphics/src/image.rs
+++ b/graphics/src/image.rs
@@ -1,14 +1,94 @@
//! Load and operate on images.
-use crate::core::image::{Data, Handle};
+#[cfg(feature = "image")]
+pub use ::image as image_rs;
-use bitflags::bitflags;
+use crate::core::image;
+use crate::core::svg;
+use crate::core::{Color, Rectangle};
-pub use ::image as image_rs;
+/// A raster or vector image.
+#[derive(Debug, Clone)]
+pub enum Image {
+ /// A raster image.
+ Raster {
+ /// The handle of a raster image.
+ handle: image::Handle,
+
+ /// The filter method of a raster image.
+ filter_method: image::FilterMethod,
+
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+ /// A vector image.
+ Vector {
+ /// The handle of a vector image.
+ handle: svg::Handle,
+
+ /// The [`Color`] filter
+ color: Option<Color>,
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+}
+
+#[cfg(feature = "image")]
/// Tries to load an image by its [`Handle`].
-pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
+pub fn load(
+ handle: &image::Handle,
+) -> ::image::ImageResult<::image::DynamicImage> {
+ use bitflags::bitflags;
+
+ bitflags! {
+ struct Operation: u8 {
+ const FLIP_HORIZONTALLY = 0b001;
+ const ROTATE_180 = 0b010;
+ const FLIP_DIAGONALLY = 0b100;
+ }
+ }
+
+ impl Operation {
+ // Meaning of the returned value is described e.g. at:
+ // https://magnushoff.com/articles/jpeg-orientation/
+ fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
+ where
+ R: std::io::BufRead + std::io::Seek,
+ {
+ let exif = exif::Reader::new().read_from_container(reader)?;
+
+ Ok(exif
+ .get_field(exif::Tag::Orientation, exif::In::PRIMARY)
+ .and_then(|field| field.value.get_uint(0))
+ .and_then(|value| u8::try_from(value).ok())
+ .and_then(|value| Self::from_bits(value.saturating_sub(1)))
+ .unwrap_or_else(Self::empty))
+ }
+
+ fn perform(
+ self,
+ mut image: ::image::DynamicImage,
+ ) -> ::image::DynamicImage {
+ use ::image::imageops;
+
+ if self.contains(Self::FLIP_DIAGONALLY) {
+ imageops::flip_vertical_in_place(&mut image);
+ }
+
+ if self.contains(Self::ROTATE_180) {
+ imageops::rotate180_in_place(&mut image);
+ }
+
+ if self.contains(Self::FLIP_HORIZONTALLY) {
+ imageops::flip_horizontal_in_place(&mut image);
+ }
+
+ image
+ }
+ }
+
match handle.data() {
- Data::Path(path) => {
+ image::Data::Path(path) => {
let image = ::image::open(path)?;
let operation = std::fs::File::open(path)
@@ -19,7 +99,7 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
- Data::Bytes(bytes) => {
+ image::Data::Bytes(bytes) => {
let image = ::image::load_from_memory(bytes)?;
let operation =
Operation::from_exif(&mut std::io::Cursor::new(bytes))
@@ -28,68 +108,22 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
- Data::Rgba {
+ image::Data::Rgba {
width,
height,
pixels,
} => {
- if let Some(image) = image_rs::ImageBuffer::from_vec(
- *width,
- *height,
- pixels.to_vec(),
- ) {
- Ok(image_rs::DynamicImage::ImageRgba8(image))
+ if let Some(image) =
+ ::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec())
+ {
+ Ok(::image::DynamicImage::ImageRgba8(image))
} else {
- Err(image_rs::error::ImageError::Limits(
- image_rs::error::LimitError::from_kind(
- image_rs::error::LimitErrorKind::DimensionError,
+ Err(::image::error::ImageError::Limits(
+ ::image::error::LimitError::from_kind(
+ ::image::error::LimitErrorKind::DimensionError,
),
))
}
}
}
}
-
-bitflags! {
- struct Operation: u8 {
- const FLIP_HORIZONTALLY = 0b001;
- const ROTATE_180 = 0b010;
- const FLIP_DIAGONALLY = 0b100;
- }
-}
-
-impl Operation {
- // Meaning of the returned value is described e.g. at:
- // https://magnushoff.com/articles/jpeg-orientation/
- fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
- where
- R: std::io::BufRead + std::io::Seek,
- {
- let exif = exif::Reader::new().read_from_container(reader)?;
-
- Ok(exif
- .get_field(exif::Tag::Orientation, exif::In::PRIMARY)
- .and_then(|field| field.value.get_uint(0))
- .and_then(|value| u8::try_from(value).ok())
- .and_then(|value| Self::from_bits(value.saturating_sub(1)))
- .unwrap_or_else(Self::empty))
- }
-
- fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage {
- use image::imageops;
-
- if self.contains(Self::FLIP_DIAGONALLY) {
- imageops::flip_vertical_in_place(&mut image);
- }
-
- if self.contains(Self::ROTATE_180) {
- imageops::rotate180_in_place(&mut image);
- }
-
- if self.contains(Self::FLIP_HORIZONTALLY) {
- imageops::flip_horizontal_in_place(&mut image);
- }
-
- image
- }
-}
diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs
new file mode 100644
index 00000000..5b8aacab
--- /dev/null
+++ b/graphics/src/layer.rs
@@ -0,0 +1,47 @@
+pub trait Layer {
+ type Cache;
+
+ fn new() -> Self;
+
+ fn clear(&mut self);
+}
+
+pub struct Recorder<T: Layer> {
+ layers: Vec<T>,
+ caches: Vec<T::Cache>,
+ stack: Vec<Kind>,
+ current: usize,
+}
+
+enum Kind {
+ Fresh(usize),
+ Cache(usize),
+}
+
+impl<T: Layer> Recorder<T> {
+ pub fn new() -> Self {
+ Self {
+ layers: vec![Layer::new()],
+ caches: Vec::new(),
+ stack: Vec::new(),
+ current: 0,
+ }
+ }
+
+ pub fn fill_quad(&mut self) {}
+
+ pub fn push_cache(&mut self, cache: T::Cache) {
+ self.caches.push(cache);
+ }
+
+ pub fn clear(&mut self) {
+ self.caches.clear();
+ self.stack.clear();
+
+ for mut layer in self.layers {
+ layer.clear();
+ }
+
+ self.current = 0;
+ }
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index d7f2f439..5857aea5 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -28,6 +28,7 @@ pub mod compositor;
pub mod damage;
pub mod error;
pub mod gradient;
+pub mod image;
pub mod mesh;
pub mod renderer;
pub mod text;
@@ -35,9 +36,6 @@ pub mod text;
#[cfg(feature = "geometry")]
pub mod geometry;
-#[cfg(feature = "image")]
-pub mod image;
-
pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use cached::Cached;
@@ -45,10 +43,12 @@ pub use compositor::Compositor;
pub use damage::Damage;
pub use error::Error;
pub use gradient::Gradient;
+pub use image::Image;
pub use mesh::Mesh;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use settings::Settings;
+pub use text::Text;
pub use viewport::Viewport;
pub use iced_core as core;
diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs
index 20692b07..d3e7ffaf 100644
--- a/graphics/src/mesh.rs
+++ b/graphics/src/mesh.rs
@@ -1,8 +1,7 @@
//! Draw triangles!
use crate::color;
-use crate::core::{Rectangle, Size};
+use crate::core::{Rectangle, Size, Transformation};
use crate::gradient;
-use crate::Damage;
use bytemuck::{Pod, Zeroable};
@@ -14,9 +13,10 @@ pub enum Mesh {
/// The vertices and indices of the mesh.
buffers: Indexed<SolidVertex2D>,
- /// The size of the drawable region of the mesh.
- ///
- /// Any geometry that falls out of this region will be clipped.
+ /// The [`Transformation`] for the vertices of the [`Mesh`].
+ transformation: Transformation,
+
+ /// The [`Size`] of the [`Mesh`].
size: Size,
},
/// A mesh with a gradient.
@@ -24,19 +24,44 @@ pub enum Mesh {
/// The vertices and indices of the mesh.
buffers: Indexed<GradientVertex2D>,
- /// The size of the drawable region of the mesh.
- ///
- /// Any geometry that falls out of this region will be clipped.
+ /// The [`Transformation`] for the vertices of the [`Mesh`].
+ transformation: Transformation,
+
+ /// The [`Size`] of the [`Mesh`].
size: Size,
},
}
-impl Damage for Mesh {
- fn bounds(&self) -> Rectangle {
+impl Mesh {
+ /// Returns the indices of the [`Mesh`].
+ pub fn indices(&self) -> &[u32] {
+ match self {
+ Self::Solid { buffers, .. } => &buffers.indices,
+ Self::Gradient { buffers, .. } => &buffers.indices,
+ }
+ }
+
+ /// Returns the [`Transformation`] of the [`Mesh`].
+ pub fn transformation(&self) -> Transformation {
+ match self {
+ Self::Solid { transformation, .. }
+ | Self::Gradient { transformation, .. } => *transformation,
+ }
+ }
+
+ /// Returns the clip bounds of the [`Mesh`].
+ pub fn clip_bounds(&self) -> Rectangle {
match self {
- Self::Solid { size, .. } | Self::Gradient { size, .. } => {
- Rectangle::with_size(*size)
+ Self::Solid {
+ size,
+ transformation,
+ ..
}
+ | Self::Gradient {
+ size,
+ transformation,
+ ..
+ } => Rectangle::with_size(*size) * *transformation,
}
}
}
@@ -75,6 +100,47 @@ pub struct GradientVertex2D {
pub gradient: gradient::Packed,
}
+/// The result of counting the attributes of a set of meshes.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct AttributeCount {
+ /// The total amount of solid vertices.
+ pub solid_vertices: usize,
+
+ /// The total amount of solid meshes.
+ pub solids: usize,
+
+ /// The total amount of gradient vertices.
+ pub gradient_vertices: usize,
+
+ /// The total amount of gradient meshes.
+ pub gradients: usize,
+
+ /// The total amount of indices.
+ pub indices: usize,
+}
+
+/// Returns the number of total vertices & total indices of all [`Mesh`]es.
+pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount {
+ meshes
+ .iter()
+ .fold(AttributeCount::default(), |mut count, mesh| {
+ match mesh {
+ Mesh::Solid { buffers, .. } => {
+ count.solids += 1;
+ count.solid_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ Mesh::Gradient { buffers, .. } => {
+ count.gradients += 1;
+ count.gradient_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ }
+
+ count
+ })
+}
+
/// A renderer capable of drawing a [`Mesh`].
pub trait Renderer {
/// Draws the given [`Mesh`].
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index fb1a0d73..d4f91dab 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -62,7 +62,7 @@ impl<B: Backend> Renderer<B> {
}
impl<B: Backend> iced_core::Renderer for Renderer<B> {
- fn start_layer(&mut self) {
+ fn start_layer(&mut self, _bounds: Rectangle) {
self.stack.push(std::mem::take(&mut self.primitives));
}
@@ -75,7 +75,7 @@ impl<B: Backend> iced_core::Renderer for Renderer<B> {
self.primitives.push(Primitive::group(layer).clip(bounds));
}
- fn start_transformation(&mut self) {
+ fn start_transformation(&mut self, _transformation: Transformation) {
self.stack.push(std::mem::take(&mut self.primitives));
}
diff --git a/graphics/src/text.rs b/graphics/src/text.rs
index 0310ead7..c9c821c0 100644
--- a/graphics/src/text.rs
+++ b/graphics/src/text.rs
@@ -9,14 +9,67 @@ pub use paragraph::Paragraph;
pub use cosmic_text;
+use crate::core::alignment;
use crate::core::font::{self, Font};
-use crate::core::text::Shaping;
-use crate::core::{Color, Point, Rectangle, Size};
+use crate::core::text::{LineHeight, Shaping};
+use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
use once_cell::sync::OnceCell;
use std::borrow::Cow;
use std::sync::{Arc, RwLock, Weak};
+/// A text primitive.
+#[derive(Debug, Clone)]
+pub enum Text {
+ /// A paragraph.
+ #[allow(missing_docs)]
+ Paragraph {
+ paragraph: paragraph::Weak,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ },
+ /// An editor.
+ #[allow(missing_docs)]
+ Editor {
+ editor: editor::Weak,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ transformation: Transformation,
+ },
+ /// Some cached text.
+ Cached {
+ /// The contents of the text.
+ content: String,
+ /// The bounds of the text.
+ bounds: Rectangle,
+ /// The color of the text.
+ color: Color,
+ /// The size of the text in logical pixels.
+ size: Pixels,
+ /// The line height of the text.
+ line_height: LineHeight,
+ /// The font of the text.
+ font: Font,
+ /// The horizontal alignment of the text.
+ horizontal_alignment: alignment::Horizontal,
+ /// The vertical alignment of the text.
+ vertical_alignment: alignment::Vertical,
+ /// The shaping strategy of the text.
+ shaping: Shaping,
+ /// The clip bounds of the text.
+ clip_bounds: Rectangle,
+ },
+ /// Some raw text.
+ #[allow(missing_docs)]
+ Raw {
+ raw: Raw,
+ transformation: Transformation,
+ },
+}
+
/// The regular variant of the [Fira Sans] font.
///
/// It is loaded as part of the default fonts in Wasm builds.
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 39c19fa3..946a8ebb 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -16,7 +16,6 @@ tiny-skia = ["iced_tiny_skia"]
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
-tracing = ["iced_wgpu?/tracing"]
web-colors = ["iced_wgpu?/web-colors"]
webgl = ["iced_wgpu?/webgl"]
fira-sans = ["iced_graphics/fira-sans"]
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index b9ceb4b2..b6459243 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -39,16 +39,20 @@ where
delegate!(self, renderer, renderer.clear());
}
- fn start_layer(&mut self) {
- delegate!(self, renderer, renderer.start_layer());
+ fn start_layer(&mut self, bounds: Rectangle) {
+ delegate!(self, renderer, renderer.start_layer(bounds));
}
fn end_layer(&mut self, bounds: Rectangle) {
delegate!(self, renderer, renderer.end_layer(bounds));
}
- fn start_transformation(&mut self) {
- delegate!(self, renderer, renderer.start_transformation());
+ fn start_transformation(&mut self, transformation: Transformation) {
+ delegate!(
+ self,
+ renderer,
+ renderer.start_transformation(transformation)
+ );
}
fn end_transformation(&mut self, transformation: Transformation) {
@@ -433,6 +437,7 @@ mod geometry {
}
}
+ #[derive(Clone)]
pub enum Geometry<L, R> {
Left(L),
Right(R),
@@ -452,10 +457,21 @@ mod geometry {
}
}
- fn cache(self) -> Self::Cache {
- match self {
- Self::Left(geometry) => Geometry::Left(geometry.cache()),
- Self::Right(geometry) => Geometry::Right(geometry.cache()),
+ fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
+ match (self, previous) {
+ (Self::Left(geometry), Some(Geometry::Left(previous))) => {
+ Geometry::Left(geometry.cache(Some(previous)))
+ }
+ (Self::Left(geometry), None) => {
+ Geometry::Left(geometry.cache(None))
+ }
+ (Self::Right(geometry), Some(Geometry::Right(previous))) => {
+ Geometry::Right(geometry.cache(Some(previous)))
+ }
+ (Self::Right(geometry), None) => {
+ Geometry::Right(geometry.cache(None))
+ }
+ _ => unreachable!(),
}
}
}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 0b713784..75be53b5 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -41,6 +41,3 @@ lyon.optional = true
resvg.workspace = true
resvg.optional = true
-
-tracing.workspace = true
-tracing.optional = true
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
deleted file mode 100644
index 6ccf4111..00000000
--- a/wgpu/src/backend.rs
+++ /dev/null
@@ -1,432 +0,0 @@
-use crate::buffer;
-use crate::core::{Color, Size, Transformation};
-use crate::graphics::backend;
-use crate::graphics::color;
-use crate::graphics::Viewport;
-use crate::primitive::pipeline;
-use crate::primitive::{self, Primitive};
-use crate::quad;
-use crate::text;
-use crate::triangle;
-use crate::window;
-use crate::{Layer, Settings};
-
-#[cfg(feature = "tracing")]
-use tracing::info_span;
-
-#[cfg(any(feature = "image", feature = "svg"))]
-use crate::image;
-
-use std::borrow::Cow;
-
-/// A [`wgpu`] graphics backend for [`iced`].
-///
-/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
-/// [`iced`]: https://github.com/iced-rs/iced
-#[allow(missing_debug_implementations)]
-pub struct Backend {
- quad_pipeline: quad::Pipeline,
- text_pipeline: text::Pipeline,
- triangle_pipeline: triangle::Pipeline,
- pipeline_storage: pipeline::Storage,
- #[cfg(any(feature = "image", feature = "svg"))]
- image_pipeline: image::Pipeline,
- staging_belt: wgpu::util::StagingBelt,
-}
-
-impl Backend {
- /// Creates a new [`Backend`].
- pub fn new(
- _adapter: &wgpu::Adapter,
- device: &wgpu::Device,
- queue: &wgpu::Queue,
- settings: Settings,
- format: wgpu::TextureFormat,
- ) -> Self {
- let text_pipeline = text::Pipeline::new(device, queue, format);
- let quad_pipeline = quad::Pipeline::new(device, format);
- let triangle_pipeline =
- triangle::Pipeline::new(device, format, settings.antialiasing);
-
- #[cfg(any(feature = "image", feature = "svg"))]
- let image_pipeline = {
- let backend = _adapter.get_info().backend;
-
- image::Pipeline::new(device, format, backend)
- };
-
- Self {
- quad_pipeline,
- text_pipeline,
- triangle_pipeline,
- pipeline_storage: pipeline::Storage::default(),
-
- #[cfg(any(feature = "image", feature = "svg"))]
- image_pipeline,
-
- // TODO: Resize belt smartly (?)
- // It would be great if the `StagingBelt` API exposed methods
- // for introspection to detect when a resize may be worth it.
- staging_belt: wgpu::util::StagingBelt::new(
- buffer::MAX_WRITE_SIZE as u64,
- ),
- }
- }
-
- /// Draws the provided primitives in the given `TextureView`.
- ///
- /// The text provided as overlay will be rendered on top of the primitives.
- /// This is useful for rendering debug information.
- pub fn present<T: AsRef<str>>(
- &mut self,
- device: &wgpu::Device,
- queue: &wgpu::Queue,
- encoder: &mut wgpu::CommandEncoder,
- clear_color: Option<Color>,
- format: wgpu::TextureFormat,
- frame: &wgpu::TextureView,
- primitives: &[Primitive],
- viewport: &Viewport,
- overlay_text: &[T],
- ) {
- log::debug!("Drawing");
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Backend", "PRESENT").entered();
-
- let target_size = viewport.physical_size();
- let scale_factor = viewport.scale_factor() as f32;
- let transformation = viewport.projection();
-
- let mut layers = Layer::generate(primitives, viewport);
-
- if !overlay_text.is_empty() {
- layers.push(Layer::overlay(overlay_text, viewport));
- }
-
- self.prepare(
- device,
- queue,
- format,
- encoder,
- scale_factor,
- target_size,
- transformation,
- &layers,
- );
-
- self.staging_belt.finish();
-
- self.render(
- device,
- encoder,
- frame,
- clear_color,
- scale_factor,
- target_size,
- &layers,
- );
-
- self.quad_pipeline.end_frame();
- self.text_pipeline.end_frame();
- self.triangle_pipeline.end_frame();
-
- #[cfg(any(feature = "image", feature = "svg"))]
- self.image_pipeline.end_frame();
- }
-
- /// Recalls staging memory for future uploads.
- ///
- /// This method should be called after the command encoder
- /// has been submitted.
- pub fn recall(&mut self) {
- self.staging_belt.recall();
- }
-
- fn prepare(
- &mut self,
- device: &wgpu::Device,
- queue: &wgpu::Queue,
- format: wgpu::TextureFormat,
- encoder: &mut wgpu::CommandEncoder,
- scale_factor: f32,
- target_size: Size<u32>,
- transformation: Transformation,
- layers: &[Layer<'_>],
- ) {
- for layer in layers {
- let bounds = (layer.bounds * scale_factor).snap();
-
- if bounds.width < 1 || bounds.height < 1 {
- continue;
- }
-
- if !layer.quads.is_empty() {
- self.quad_pipeline.prepare(
- device,
- encoder,
- &mut self.staging_belt,
- &layer.quads,
- transformation,
- scale_factor,
- );
- }
-
- if !layer.meshes.is_empty() {
- let scaled =
- transformation * Transformation::scale(scale_factor);
-
- self.triangle_pipeline.prepare(
- device,
- encoder,
- &mut self.staging_belt,
- &layer.meshes,
- scaled,
- );
- }
-
- #[cfg(any(feature = "image", feature = "svg"))]
- {
- if !layer.images.is_empty() {
- let scaled =
- transformation * Transformation::scale(scale_factor);
-
- self.image_pipeline.prepare(
- device,
- encoder,
- &mut self.staging_belt,
- &layer.images,
- scaled,
- scale_factor,
- );
- }
- }
-
- if !layer.text.is_empty() {
- self.text_pipeline.prepare(
- device,
- queue,
- encoder,
- &layer.text,
- layer.bounds,
- scale_factor,
- target_size,
- );
- }
-
- if !layer.pipelines.is_empty() {
- for pipeline in &layer.pipelines {
- pipeline.primitive.prepare(
- format,
- device,
- queue,
- pipeline.bounds,
- target_size,
- scale_factor,
- &mut self.pipeline_storage,
- );
- }
- }
- }
- }
-
- fn render(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- target: &wgpu::TextureView,
- clear_color: Option<Color>,
- scale_factor: f32,
- target_size: Size<u32>,
- layers: &[Layer<'_>],
- ) {
- use std::mem::ManuallyDrop;
-
- let mut quad_layer = 0;
- let mut triangle_layer = 0;
- #[cfg(any(feature = "image", feature = "svg"))]
- let mut image_layer = 0;
- let mut text_layer = 0;
-
- let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
- &wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu render pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: match clear_color {
- Some(background_color) => wgpu::LoadOp::Clear({
- let [r, g, b, a] =
- color::pack(background_color).components();
-
- wgpu::Color {
- r: f64::from(r),
- g: f64::from(g),
- b: f64::from(b),
- a: f64::from(a),
- }
- }),
- None => wgpu::LoadOp::Load,
- },
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- },
- ));
-
- for layer in layers {
- let bounds = (layer.bounds * scale_factor).snap();
-
- if bounds.width < 1 || bounds.height < 1 {
- continue;
- }
-
- if !layer.quads.is_empty() {
- self.quad_pipeline.render(
- quad_layer,
- bounds,
- &layer.quads,
- &mut render_pass,
- );
-
- quad_layer += 1;
- }
-
- if !layer.meshes.is_empty() {
- let _ = ManuallyDrop::into_inner(render_pass);
-
- self.triangle_pipeline.render(
- device,
- encoder,
- target,
- triangle_layer,
- target_size,
- &layer.meshes,
- scale_factor,
- );
-
- triangle_layer += 1;
-
- render_pass = ManuallyDrop::new(encoder.begin_render_pass(
- &wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu render pass"),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: wgpu::StoreOp::Store,
- },
- },
- )],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- },
- ));
- }
-
- #[cfg(any(feature = "image", feature = "svg"))]
- {
- if !layer.images.is_empty() {
- self.image_pipeline.render(
- image_layer,
- bounds,
- &mut render_pass,
- );
-
- image_layer += 1;
- }
- }
-
- if !layer.text.is_empty() {
- self.text_pipeline
- .render(text_layer, bounds, &mut render_pass);
-
- text_layer += 1;
- }
-
- if !layer.pipelines.is_empty() {
- let _ = ManuallyDrop::into_inner(render_pass);
-
- for pipeline in &layer.pipelines {
- let viewport = (pipeline.viewport * scale_factor).snap();
-
- if viewport.width < 1 || viewport.height < 1 {
- continue;
- }
-
- pipeline.primitive.render(
- &self.pipeline_storage,
- target,
- target_size,
- viewport,
- encoder,
- );
- }
-
- render_pass = ManuallyDrop::new(encoder.begin_render_pass(
- &wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu render pass"),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: wgpu::StoreOp::Store,
- },
- },
- )],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- },
- ));
- }
- }
-
- let _ = ManuallyDrop::into_inner(render_pass);
- }
-}
-
-impl backend::Backend for Backend {
- type Primitive = primitive::Custom;
- type Compositor = window::Compositor;
-}
-
-impl backend::Text for Backend {
- fn load_font(&mut self, font: Cow<'static, [u8]>) {
- self.text_pipeline.load_font(font);
- }
-}
-
-#[cfg(feature = "image")]
-impl backend::Image for Backend {
- fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
- self.image_pipeline.dimensions(handle)
- }
-}
-
-#[cfg(feature = "svg")]
-impl backend::Svg for Backend {
- fn viewport_dimensions(
- &self,
- handle: &crate::core::svg::Handle,
- ) -> Size<u32> {
- self.image_pipeline.viewport_dimensions(handle)
- }
-}
-
-#[cfg(feature = "geometry")]
-impl crate::graphics::geometry::Backend for Backend {
- type Frame = crate::geometry::Frame;
-
- fn new_frame(&self, size: Size) -> Self::Frame {
- crate::geometry::Frame::new(size)
- }
-}
diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs
new file mode 100644
index 00000000..e45b62b2
--- /dev/null
+++ b/wgpu/src/engine.rs
@@ -0,0 +1,79 @@
+use crate::buffer;
+use crate::graphics::Antialiasing;
+use crate::primitive::pipeline;
+use crate::quad;
+use crate::text;
+use crate::triangle;
+
+#[allow(missing_debug_implementations)]
+pub struct Engine {
+ pub(crate) quad_pipeline: quad::Pipeline,
+ pub(crate) text_pipeline: text::Pipeline,
+ pub(crate) triangle_pipeline: triangle::Pipeline,
+ pub(crate) _pipeline_storage: pipeline::Storage,
+ #[cfg(any(feature = "image", feature = "svg"))]
+ pub(crate) image_pipeline: crate::image::Pipeline,
+ pub(crate) staging_belt: wgpu::util::StagingBelt,
+}
+
+impl Engine {
+ pub fn new(
+ _adapter: &wgpu::Adapter,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ format: wgpu::TextureFormat,
+ antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
+ ) -> Self {
+ let text_pipeline = text::Pipeline::new(device, queue, format);
+ let quad_pipeline = quad::Pipeline::new(device, format);
+ let triangle_pipeline =
+ triangle::Pipeline::new(device, format, antialiasing);
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ let image_pipeline = {
+ let backend = _adapter.get_info().backend;
+
+ crate::image::Pipeline::new(device, format, backend)
+ };
+
+ Self {
+ // TODO: Resize belt smartly (?)
+ // It would be great if the `StagingBelt` API exposed methods
+ // for introspection to detect when a resize may be worth it.
+ staging_belt: wgpu::util::StagingBelt::new(
+ buffer::MAX_WRITE_SIZE as u64,
+ ),
+ quad_pipeline,
+ text_pipeline,
+ triangle_pipeline,
+ _pipeline_storage: pipeline::Storage::default(),
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ image_pipeline,
+ }
+ }
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ pub fn image_cache(&self) -> &crate::image::cache::Shared {
+ self.image_pipeline.cache()
+ }
+
+ pub fn submit(
+ &mut self,
+ queue: &wgpu::Queue,
+ encoder: wgpu::CommandEncoder,
+ ) -> wgpu::SubmissionIndex {
+ self.staging_belt.finish();
+ let index = queue.submit(Some(encoder.finish()));
+ self.staging_belt.recall();
+
+ self.quad_pipeline.end_frame();
+ self.text_pipeline.end_frame();
+ self.triangle_pipeline.end_frame();
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ self.image_pipeline.end_frame();
+
+ index
+ }
+}
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index ba56c59d..7c8c0a35 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -10,31 +10,82 @@ use crate::graphics::geometry::{
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
-use crate::primitive::{self, Primitive};
+use crate::graphics::{self, Cached};
+use crate::layer;
+use crate::text;
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
+use std::cell::RefCell;
+use std::rc::Rc;
/// A frame for drawing some geometry.
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
buffers: BufferStack,
- primitives: Vec<Primitive>,
+ layers: Vec<layer::Live>,
+ text: text::Batch,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator,
}
+pub enum Geometry {
+ Live(Vec<layer::Live>),
+ Cached(Rc<[Rc<RefCell<layer::Cached>>]>),
+}
+
+impl Cached for Geometry {
+ type Cache = Rc<[Rc<RefCell<layer::Cached>>]>;
+
+ fn load(cache: &Self::Cache) -> Self {
+ Geometry::Cached(cache.clone())
+ }
+
+ fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
+ match self {
+ Self::Live(live) => {
+ let mut layers = live.into_iter();
+
+ let mut new: Vec<_> = previous
+ .map(|previous| {
+ previous
+ .iter()
+ .cloned()
+ .zip(layers.by_ref())
+ .map(|(cached, live)| {
+ cached.borrow_mut().update(live);
+ cached
+ })
+ .collect()
+ })
+ .unwrap_or_default();
+
+ new.extend(
+ layers
+ .map(layer::Live::into_cached)
+ .map(RefCell::new)
+ .map(Rc::new),
+ );
+
+ Rc::from(new)
+ }
+ Self::Cached(cache) => cache,
+ }
+ }
+}
+
impl Frame {
/// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: BufferStack::new(),
- primitives: Vec::new(),
+ layers: Vec::new(),
+ text: text::Batch::new(),
transforms: Transforms {
previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()),
@@ -44,49 +95,54 @@ impl Frame {
}
}
- fn into_primitives(mut self) -> Vec<Primitive> {
- for buffer in self.buffers.stack {
- match buffer {
- Buffer::Solid(buffer) => {
- if !buffer.indices.is_empty() {
- self.primitives.push(Primitive::Custom(
- primitive::Custom::Mesh(Mesh::Solid {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- size: self.size,
- }),
- ));
- }
- }
- Buffer::Gradient(buffer) => {
- if !buffer.indices.is_empty() {
- self.primitives.push(Primitive::Custom(
- primitive::Custom::Mesh(Mesh::Gradient {
- buffers: mesh::Indexed {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- size: self.size,
- }),
- ));
- }
- }
- }
+ fn into_layers(mut self) -> Vec<layer::Live> {
+ if !self.text.is_empty() || !self.buffers.stack.is_empty() {
+ let clip_bounds = Rectangle::with_size(self.size);
+ let transformation = Transformation::IDENTITY;
+
+ // TODO: Generate different meshes for different transformations (?)
+ // Instead of transforming each path
+ let meshes = self
+ .buffers
+ .stack
+ .into_iter()
+ .map(|buffer| match buffer {
+ Buffer::Solid(buffer) => Mesh::Solid {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ transformation: Transformation::IDENTITY,
+ size: self.size,
+ },
+ Buffer::Gradient(buffer) => Mesh::Gradient {
+ buffers: mesh::Indexed {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ transformation: Transformation::IDENTITY,
+ size: self.size,
+ },
+ })
+ .collect();
+
+ let layer = layer::Live {
+ bounds: Some(clip_bounds),
+ transformation,
+ meshes,
+ text: self.text,
+ ..layer::Live::default()
+ };
+
+ self.layers.push(layer);
}
- self.primitives
+ self.layers
}
}
impl geometry::frame::Backend for Frame {
- type Geometry = Primitive;
-
- /// Creates a new empty [`Frame`] with the given dimensions.
- ///
- /// The default coordinate system of a [`Frame`] has its origin at the
- /// top-left corner of its bounds.
+ type Geometry = Geometry;
#[inline]
fn width(&self) -> f32 {
@@ -246,8 +302,7 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
- // TODO: Honor layering!
- self.primitives.push(Primitive::Text {
+ self.text.push(graphics::Text::Cached {
content: text.content,
bounds,
color: text.color,
@@ -313,37 +368,17 @@ impl geometry::frame::Backend for Frame {
}
fn paste(&mut self, frame: Frame, at: Point) {
- let size = frame.size();
- let primitives = frame.into_primitives();
- let transformation = Transformation::translate(at.x, at.y);
-
- let (text, meshes) = primitives
- .into_iter()
- .partition(|primitive| matches!(primitive, Primitive::Text { .. }));
-
- self.primitives.push(Primitive::Group {
- primitives: vec![
- Primitive::Transform {
- transformation,
- content: Box::new(Primitive::Group { primitives: meshes }),
- },
- Primitive::Transform {
- transformation,
- content: Box::new(Primitive::Clip {
- bounds: Rectangle::with_size(size),
- content: Box::new(Primitive::Group {
- primitives: text,
- }),
- }),
- },
- ],
- });
+ let translation = Transformation::translate(at.x, at.y);
+
+ self.layers
+ .extend(frame.into_layers().into_iter().map(|mut layer| {
+ layer.transformation = layer.transformation * translation;
+ layer
+ }));
}
fn into_geometry(self) -> Self::Geometry {
- Primitive::Group {
- primitives: self.into_primitives(),
- }
+ Geometry::Live(self.into_layers())
}
}
diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs
new file mode 100644
index 00000000..32118ed6
--- /dev/null
+++ b/wgpu/src/image/cache.rs
@@ -0,0 +1,107 @@
+use crate::core::{self, Size};
+use crate::image::atlas::{self, Atlas};
+
+use std::cell::{RefCell, RefMut};
+use std::rc::Rc;
+
+#[derive(Debug)]
+pub struct Cache {
+ atlas: Atlas,
+ #[cfg(feature = "image")]
+ raster: crate::image::raster::Cache,
+ #[cfg(feature = "svg")]
+ vector: crate::image::vector::Cache,
+}
+
+impl Cache {
+ pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
+ Self {
+ atlas: Atlas::new(device, backend),
+ #[cfg(feature = "image")]
+ raster: crate::image::raster::Cache::default(),
+ #[cfg(feature = "svg")]
+ vector: crate::image::vector::Cache::default(),
+ }
+ }
+
+ pub fn layer_count(&self) -> usize {
+ self.atlas.layer_count()
+ }
+
+ #[cfg(feature = "image")]
+ pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
+ self.raster.load(handle).dimensions()
+ }
+
+ #[cfg(feature = "svg")]
+ pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
+ self.vector.load(handle).viewport_dimensions()
+ }
+
+ #[cfg(feature = "image")]
+ pub fn upload_raster(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &core::image::Handle,
+ ) -> Option<&atlas::Entry> {
+ self.raster.upload(device, encoder, handle, &mut self.atlas)
+ }
+
+ #[cfg(feature = "svg")]
+ pub fn upload_vector(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &core::svg::Handle,
+ color: Option<core::Color>,
+ size: [f32; 2],
+ scale: f32,
+ ) -> Option<&atlas::Entry> {
+ self.vector.upload(
+ device,
+ encoder,
+ handle,
+ color,
+ size,
+ scale,
+ &mut self.atlas,
+ )
+ }
+
+ pub fn create_bind_group(
+ &self,
+ device: &wgpu::Device,
+ layout: &wgpu::BindGroupLayout,
+ ) -> wgpu::BindGroup {
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image texture atlas bind group"),
+ layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(self.atlas.view()),
+ }],
+ })
+ }
+
+ pub fn trim(&mut self) {
+ #[cfg(feature = "image")]
+ self.raster.trim(&mut self.atlas);
+
+ #[cfg(feature = "svg")]
+ self.vector.trim(&mut self.atlas);
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Shared(Rc<RefCell<Cache>>);
+
+impl Shared {
+ pub fn new(cache: Cache) -> Self {
+ Self(Rc::new(RefCell::new(cache)))
+ }
+
+ pub fn lock(&self) -> RefMut<'_, Cache> {
+ self.0.borrow_mut()
+ }
+}
diff --git a/wgpu/src/image.rs b/wgpu/src/image/mod.rs
index d0bf1182..88e6bdb9 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image/mod.rs
@@ -1,3 +1,6 @@
+pub(crate) mod cache;
+pub(crate) use cache::Cache;
+
mod atlas;
#[cfg(feature = "image")]
@@ -6,194 +9,31 @@ mod raster;
#[cfg(feature = "svg")]
mod vector;
-use atlas::Atlas;
-
+use crate::core::image;
use crate::core::{Rectangle, Size, Transformation};
-use crate::layer;
use crate::Buffer;
-use std::cell::RefCell;
-use std::mem;
-
use bytemuck::{Pod, Zeroable};
+use std::mem;
-#[cfg(feature = "image")]
-use crate::core::image;
-
-#[cfg(feature = "svg")]
-use crate::core::svg;
+pub use crate::graphics::Image;
-#[cfg(feature = "tracing")]
-use tracing::info_span;
+pub type Batch = Vec<Image>;
#[derive(Debug)]
pub struct Pipeline {
- #[cfg(feature = "image")]
- raster_cache: RefCell<raster::Cache>,
- #[cfg(feature = "svg")]
- vector_cache: RefCell<vector::Cache>,
-
pipeline: wgpu::RenderPipeline,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
- texture_atlas: Atlas,
texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout,
-
+ cache: cache::Shared,
layers: Vec<Layer>,
prepare_layer: usize,
}
-#[derive(Debug)]
-struct Layer {
- uniforms: wgpu::Buffer,
- nearest: Data,
- linear: Data,
-}
-
-impl Layer {
- fn new(
- device: &wgpu::Device,
- constant_layout: &wgpu::BindGroupLayout,
- nearest_sampler: &wgpu::Sampler,
- linear_sampler: &wgpu::Sampler,
- ) -> Self {
- let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::image uniforms buffer"),
- size: mem::size_of::<Uniforms>() as u64,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let nearest =
- Data::new(device, constant_layout, nearest_sampler, &uniforms);
-
- let linear =
- Data::new(device, constant_layout, linear_sampler, &uniforms);
-
- Self {
- uniforms,
- nearest,
- linear,
- }
- }
-
- fn prepare(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- belt: &mut wgpu::util::StagingBelt,
- nearest_instances: &[Instance],
- linear_instances: &[Instance],
- transformation: Transformation,
- ) {
- let uniforms = Uniforms {
- transform: transformation.into(),
- };
-
- let bytes = bytemuck::bytes_of(&uniforms);
-
- belt.write_buffer(
- encoder,
- &self.uniforms,
- 0,
- (bytes.len() as u64).try_into().expect("Sized uniforms"),
- device,
- )
- .copy_from_slice(bytes);
-
- self.nearest
- .upload(device, encoder, belt, nearest_instances);
-
- self.linear.upload(device, encoder, belt, linear_instances);
- }
-
- fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
- self.nearest.render(render_pass);
- self.linear.render(render_pass);
- }
-}
-
-#[derive(Debug)]
-struct Data {
- constants: wgpu::BindGroup,
- instances: Buffer<Instance>,
- instance_count: usize,
-}
-
-impl Data {
- pub fn new(
- device: &wgpu::Device,
- constant_layout: &wgpu::BindGroupLayout,
- sampler: &wgpu::Sampler,
- uniforms: &wgpu::Buffer,
- ) -> Self {
- let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::image constants bind group"),
- layout: constant_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: uniforms,
- offset: 0,
- size: None,
- },
- ),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(sampler),
- },
- ],
- });
-
- let instances = Buffer::new(
- device,
- "iced_wgpu::image instance buffer",
- Instance::INITIAL,
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- );
-
- Self {
- constants,
- instances,
- instance_count: 0,
- }
- }
-
- fn upload(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- belt: &mut wgpu::util::StagingBelt,
- instances: &[Instance],
- ) {
- self.instance_count = instances.len();
-
- if self.instance_count == 0 {
- return;
- }
-
- let _ = self.instances.resize(device, instances.len());
- let _ = self.instances.write(device, encoder, belt, 0, instances);
- }
-
- fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
- if self.instance_count == 0 {
- return;
- }
-
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_vertex_buffer(0, self.instances.slice(..));
-
- render_pass.draw(0..6, 0..self.instance_count as u32);
- }
-}
-
impl Pipeline {
pub fn new(
device: &wgpu::Device,
@@ -276,9 +116,9 @@ impl Pipeline {
label: Some("iced_wgpu image shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
concat!(
- include_str!("shader/vertex.wgsl"),
+ include_str!("../shader/vertex.wgsl"),
"\n",
- include_str!("shader/image.wgsl"),
+ include_str!("../shader/image.wgsl"),
),
)),
});
@@ -341,54 +181,25 @@ impl Pipeline {
multiview: None,
});
- let texture_atlas = Atlas::new(device, backend);
-
- let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::image texture atlas bind group"),
- layout: &texture_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(
- texture_atlas.view(),
- ),
- }],
- });
+ let cache = Cache::new(device, backend);
+ let texture = cache.create_bind_group(device, &texture_layout);
Pipeline {
- #[cfg(feature = "image")]
- raster_cache: RefCell::new(raster::Cache::default()),
-
- #[cfg(feature = "svg")]
- vector_cache: RefCell::new(vector::Cache::default()),
-
pipeline,
nearest_sampler,
linear_sampler,
texture,
- texture_version: texture_atlas.layer_count(),
- texture_atlas,
+ texture_version: cache.layer_count(),
texture_layout,
constant_layout,
-
+ cache: cache::Shared::new(cache),
layers: Vec::new(),
prepare_layer: 0,
}
}
- #[cfg(feature = "image")]
- pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
- let mut cache = self.raster_cache.borrow_mut();
- let memory = cache.load(handle);
-
- memory.dimensions()
- }
-
- #[cfg(feature = "svg")]
- pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
- let mut cache = self.vector_cache.borrow_mut();
- let svg = cache.load(handle);
-
- svg.viewport_dimensions()
+ pub fn cache(&self) -> &cache::Shared {
+ &self.cache
}
pub fn prepare(
@@ -396,39 +207,28 @@ impl Pipeline {
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
- images: &[layer::Image],
+ images: &Batch,
transformation: Transformation,
- _scale: f32,
+ scale: f32,
) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Image", "PREPARE").entered();
-
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Image", "DRAW").entered();
+ let transformation = transformation * Transformation::scale(scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
- #[cfg(feature = "image")]
- let mut raster_cache = self.raster_cache.borrow_mut();
-
- #[cfg(feature = "svg")]
- let mut vector_cache = self.vector_cache.borrow_mut();
+ let mut cache = self.cache.lock();
for image in images {
match &image {
#[cfg(feature = "image")]
- layer::Image::Raster {
+ Image::Raster {
handle,
filter_method,
bounds,
} => {
- if let Some(atlas_entry) = raster_cache.upload(
- device,
- encoder,
- handle,
- &mut self.texture_atlas,
- ) {
+ if let Some(atlas_entry) =
+ cache.upload_raster(device, encoder, handle)
+ {
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
@@ -443,24 +243,18 @@ impl Pipeline {
}
}
#[cfg(not(feature = "image"))]
- layer::Image::Raster { .. } => {}
+ Image::Raster { .. } => {}
#[cfg(feature = "svg")]
- layer::Image::Vector {
+ Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
- if let Some(atlas_entry) = vector_cache.upload(
- device,
- encoder,
- handle,
- *color,
- size,
- _scale,
- &mut self.texture_atlas,
+ if let Some(atlas_entry) = cache.upload_vector(
+ device, encoder, handle, *color, size, scale,
) {
add_instances(
[bounds.x, bounds.y],
@@ -471,7 +265,7 @@ impl Pipeline {
}
}
#[cfg(not(feature = "svg"))]
- layer::Image::Vector { .. } => {}
+ Image::Vector { .. } => {}
}
}
@@ -479,23 +273,13 @@ impl Pipeline {
return;
}
- let texture_version = self.texture_atlas.layer_count();
+ let texture_version = cache.layer_count();
if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group...");
self.texture =
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::image texture atlas bind group"),
- layout: &self.texture_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(
- self.texture_atlas.view(),
- ),
- }],
- });
-
+ cache.create_bind_group(device, &self.texture_layout);
self.texture_version = texture_version;
}
@@ -545,13 +329,156 @@ impl Pipeline {
}
pub fn end_frame(&mut self) {
- #[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
+ self.cache.lock().trim();
+ self.prepare_layer = 0;
+ }
+}
- #[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
+#[derive(Debug)]
+struct Layer {
+ uniforms: wgpu::Buffer,
+ nearest: Data,
+ linear: Data,
+}
- self.prepare_layer = 0;
+impl Layer {
+ fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ nearest_sampler: &wgpu::Sampler,
+ linear_sampler: &wgpu::Sampler,
+ ) -> Self {
+ let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("iced_wgpu::image uniforms buffer"),
+ size: mem::size_of::<Uniforms>() as u64,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let nearest =
+ Data::new(device, constant_layout, nearest_sampler, &uniforms);
+
+ let linear =
+ Data::new(device, constant_layout, linear_sampler, &uniforms);
+
+ Self {
+ uniforms,
+ nearest,
+ linear,
+ }
+ }
+
+ fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ nearest_instances: &[Instance],
+ linear_instances: &[Instance],
+ transformation: Transformation,
+ ) {
+ let uniforms = Uniforms {
+ transform: transformation.into(),
+ };
+
+ let bytes = bytemuck::bytes_of(&uniforms);
+
+ belt.write_buffer(
+ encoder,
+ &self.uniforms,
+ 0,
+ (bytes.len() as u64).try_into().expect("Sized uniforms"),
+ device,
+ )
+ .copy_from_slice(bytes);
+
+ self.nearest
+ .upload(device, encoder, belt, nearest_instances);
+
+ self.linear.upload(device, encoder, belt, linear_instances);
+ }
+
+ fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ self.nearest.render(render_pass);
+ self.linear.render(render_pass);
+ }
+}
+
+#[derive(Debug)]
+struct Data {
+ constants: wgpu::BindGroup,
+ instances: Buffer<Instance>,
+ instance_count: usize,
+}
+
+impl Data {
+ pub fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ sampler: &wgpu::Sampler,
+ uniforms: &wgpu::Buffer,
+ ) -> Self {
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image constants bind group"),
+ layout: constant_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: uniforms,
+ offset: 0,
+ size: None,
+ },
+ ),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(sampler),
+ },
+ ],
+ });
+
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu::image instance buffer",
+ Instance::INITIAL,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ constants,
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ instances: &[Instance],
+ ) {
+ self.instance_count = instances.len();
+
+ if self.instance_count == 0 {
+ return;
+ }
+
+ let _ = self.instances.resize(device, instances.len());
+ let _ = self.instances.write(device, encoder, belt, 0, instances);
+ }
+
+ fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ if self.instance_count == 0 {
+ return;
+ }
+
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_vertex_buffer(0, self.instances.slice(..));
+
+ render_pass.draw(0..6, 0..self.instance_count as u32);
}
}
diff --git a/wgpu/src/image/null.rs b/wgpu/src/image/null.rs
new file mode 100644
index 00000000..900841a6
--- /dev/null
+++ b/wgpu/src/image/null.rs
@@ -0,0 +1,10 @@
+pub use crate::graphics::Image;
+
+#[derive(Default)]
+pub struct Batch;
+
+impl Batch {
+ pub fn push(&mut self, _image: Image) {}
+
+ pub fn clear(&mut self) {}
+}
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index cc767c25..5c23669a 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,343 +1,326 @@
-//! Organize rendering primitives into a flattened list of layers.
-mod image;
-mod pipeline;
-mod text;
-
-pub mod mesh;
-
-pub use image::Image;
-pub use mesh::Mesh;
-pub use pipeline::Pipeline;
-pub use text::Text;
-
-use crate::core;
-use crate::core::alignment;
-use crate::core::{
- Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector,
-};
-use crate::graphics;
+use crate::core::renderer;
+use crate::core::{Background, Color, Point, Rectangle, Transformation};
use crate::graphics::color;
-use crate::graphics::Viewport;
-use crate::primitive::{self, Primitive};
+use crate::graphics::text::{Editor, Paragraph};
+use crate::graphics::Mesh;
+use crate::image::{self, Image};
use crate::quad::{self, Quad};
+use crate::text::{self, Text};
+use crate::triangle;
-/// A group of primitives that should be clipped together.
-#[derive(Debug)]
-pub struct Layer<'a> {
- /// The clipping bounds of the [`Layer`].
- pub bounds: Rectangle,
+use std::cell::{self, RefCell};
+use std::rc::Rc;
- /// The quads of the [`Layer`].
- pub quads: quad::Batch,
-
- /// The triangle meshes of the [`Layer`].
- pub meshes: Vec<Mesh<'a>>,
-
- /// The text of the [`Layer`].
- pub text: Vec<Text<'a>>,
+pub enum Layer<'a> {
+ Live(&'a Live),
+ Cached(cell::Ref<'a, Cached>),
+}
- /// The images of the [`Layer`].
- pub images: Vec<Image>,
+pub enum LayerMut<'a> {
+ Live(&'a mut Live),
+ Cached(cell::RefMut<'a, Cached>),
+}
- /// The custom pipelines of this [`Layer`].
- pub pipelines: Vec<Pipeline>,
+pub struct Stack {
+ live: Vec<Live>,
+ cached: Vec<Rc<RefCell<Cached>>>,
+ order: Vec<Kind>,
+ transformations: Vec<Transformation>,
+ previous: Vec<usize>,
+ current: usize,
+ live_count: usize,
}
-impl<'a> Layer<'a> {
- /// Creates a new [`Layer`] with the given clipping bounds.
- pub fn new(bounds: Rectangle) -> Self {
+impl Stack {
+ pub fn new() -> Self {
Self {
- bounds,
- quads: quad::Batch::default(),
- meshes: Vec::new(),
- text: Vec::new(),
- images: Vec::new(),
- pipelines: Vec::new(),
+ live: vec![Live::default()],
+ cached: Vec::new(),
+ order: vec![Kind::Live],
+ transformations: vec![Transformation::IDENTITY],
+ previous: Vec::new(),
+ current: 0,
+ live_count: 1,
}
}
- /// Creates a new [`Layer`] for the provided overlay text.
- ///
- /// This can be useful for displaying debug information.
- pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
- let mut overlay =
- Layer::new(Rectangle::with_size(viewport.logical_size()));
-
- for (i, line) in lines.iter().enumerate() {
- let text = text::Cached {
- content: line.as_ref(),
- bounds: Rectangle::new(
- Point::new(11.0, 11.0 + 25.0 * i as f32),
- Size::INFINITY,
- ),
- color: Color::new(0.9, 0.9, 0.9, 1.0),
- size: Pixels(20.0),
- line_height: core::text::LineHeight::default(),
- font: Font::MONOSPACE,
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Top,
- shaping: core::text::Shaping::Basic,
- clip_bounds: Rectangle::with_size(Size::INFINITY),
- };
-
- overlay.text.push(Text::Cached(text.clone()));
-
- overlay.text.push(Text::Cached(text::Cached {
- bounds: text.bounds + Vector::new(-1.0, -1.0),
- color: Color::BLACK,
- ..text
- }));
- }
+ pub fn draw_quad(&mut self, quad: renderer::Quad, background: Background) {
+ let transformation = self.transformations.last().unwrap();
+ let bounds = quad.bounds * *transformation;
+
+ let quad = Quad {
+ position: [bounds.x, bounds.y],
+ size: [bounds.width, bounds.height],
+ border_color: color::pack(quad.border.color),
+ border_radius: quad.border.radius.into(),
+ border_width: quad.border.width,
+ shadow_color: color::pack(quad.shadow.color),
+ shadow_offset: quad.shadow.offset.into(),
+ shadow_blur_radius: quad.shadow.blur_radius,
+ };
+
+ self.live[self.current].quads.add(quad, &background);
+ }
- overlay
+ pub fn draw_paragraph(
+ &mut self,
+ paragraph: &Paragraph,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let paragraph = Text::Paragraph {
+ paragraph: paragraph.downgrade(),
+ position,
+ color,
+ clip_bounds,
+ transformation: self.transformations.last().copied().unwrap(),
+ };
+
+ self.live[self.current].text.push(paragraph);
}
- /// Distributes the given [`Primitive`] and generates a list of layers based
- /// on its contents.
- pub fn generate(
- primitives: &'a [Primitive],
- viewport: &Viewport,
- ) -> Vec<Self> {
- let first_layer =
- Layer::new(Rectangle::with_size(viewport.logical_size()));
-
- let mut layers = vec![first_layer];
-
- for primitive in primitives {
- Self::process_primitive(
- &mut layers,
- Transformation::IDENTITY,
- primitive,
- 0,
- );
- }
+ pub fn draw_editor(
+ &mut self,
+ editor: &Editor,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let paragraph = Text::Editor {
+ editor: editor.downgrade(),
+ position,
+ color,
+ clip_bounds,
+ transformation: self.transformations.last().copied().unwrap(),
+ };
+
+ self.live[self.current].text.push(paragraph);
+ }
- layers
+ pub fn draw_text(
+ &mut self,
+ text: crate::core::Text,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ let transformation = self.transformation();
+
+ let paragraph = Text::Cached {
+ content: text.content,
+ bounds: Rectangle::new(position, text.bounds) * transformation,
+ color,
+ size: text.size * transformation.scale_factor(),
+ line_height: text.line_height,
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ shaping: text.shaping,
+ clip_bounds: clip_bounds * transformation,
+ };
+
+ self.live[self.current].text.push(paragraph);
}
- fn process_primitive(
- layers: &mut Vec<Self>,
- transformation: Transformation,
- primitive: &'a Primitive,
- current_layer: usize,
+ pub fn draw_image(
+ &mut self,
+ handle: crate::core::image::Handle,
+ filter_method: crate::core::image::FilterMethod,
+ bounds: Rectangle,
) {
- match primitive {
- Primitive::Paragraph {
- paragraph,
- position,
- color,
- clip_bounds,
- } => {
- let layer = &mut layers[current_layer];
-
- layer.text.push(Text::Paragraph {
- paragraph: paragraph.clone(),
- position: *position,
- color: *color,
- clip_bounds: *clip_bounds,
- transformation,
- });
- }
- Primitive::Editor {
- editor,
- position,
- color,
- clip_bounds,
- } => {
- let layer = &mut layers[current_layer];
-
- layer.text.push(Text::Editor {
- editor: editor.clone(),
- position: *position,
- color: *color,
- clip_bounds: *clip_bounds,
- transformation,
- });
- }
- Primitive::Text {
- content,
- bounds,
- size,
- line_height,
- color,
- font,
- horizontal_alignment,
- vertical_alignment,
- shaping,
- clip_bounds,
- } => {
- let layer = &mut layers[current_layer];
-
- layer.text.push(Text::Cached(text::Cached {
- content,
- bounds: *bounds + transformation.translation(),
- size: *size * transformation.scale_factor(),
- line_height: *line_height,
- color: *color,
- font: *font,
- horizontal_alignment: *horizontal_alignment,
- vertical_alignment: *vertical_alignment,
- shaping: *shaping,
- clip_bounds: *clip_bounds * transformation,
- }));
- }
- graphics::Primitive::RawText(raw) => {
- let layer = &mut layers[current_layer];
+ let image = Image::Raster {
+ handle,
+ filter_method,
+ bounds: bounds * self.transformation(),
+ };
- layer.text.push(Text::Raw {
- raw: raw.clone(),
- transformation,
- });
- }
- Primitive::Quad {
- bounds,
- background,
- border,
- shadow,
- } => {
- let layer = &mut layers[current_layer];
- let bounds = *bounds * transformation;
-
- let quad = Quad {
- position: [bounds.x, bounds.y],
- size: [bounds.width, bounds.height],
- border_color: color::pack(border.color),
- border_radius: border.radius.into(),
- border_width: border.width,
- shadow_color: shadow.color.into_linear(),
- shadow_offset: shadow.offset.into(),
- shadow_blur_radius: shadow.blur_radius,
- };
-
- layer.quads.add(quad, background);
- }
- Primitive::Image {
- handle,
- filter_method,
- bounds,
- } => {
- let layer = &mut layers[current_layer];
-
- layer.images.push(Image::Raster {
- handle: handle.clone(),
- filter_method: *filter_method,
- bounds: *bounds * transformation,
- });
+ self.live[self.current].images.push(image);
+ }
+
+ pub fn draw_svg(
+ &mut self,
+ handle: crate::core::svg::Handle,
+ color: Option<Color>,
+ bounds: Rectangle,
+ ) {
+ let svg = Image::Vector {
+ handle,
+ color,
+ bounds: bounds * self.transformation(),
+ };
+
+ self.live[self.current].images.push(svg);
+ }
+
+ pub fn draw_mesh(&mut self, mut mesh: Mesh) {
+ match &mut mesh {
+ Mesh::Solid { transformation, .. }
+ | Mesh::Gradient { transformation, .. } => {
+ *transformation = *transformation * self.transformation();
}
- Primitive::Svg {
- handle,
- color,
+ }
+
+ self.live[self.current].meshes.push(mesh);
+ }
+
+ pub fn draw_layer(&mut self, mut layer: Live) {
+ layer.transformation = layer.transformation * self.transformation();
+
+ if self.live_count == self.live.len() {
+ self.live.push(layer);
+ } else {
+ self.live[self.live_count] = layer;
+ }
+
+ self.live_count += 1;
+ self.order.push(Kind::Live);
+ }
+
+ pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) {
+ {
+ let mut layer = layer.borrow_mut();
+ layer.transformation = self.transformation() * layer.transformation;
+ }
+
+ self.cached.push(layer.clone());
+ self.order.push(Kind::Cache);
+ }
+
+ pub fn push_clip(&mut self, bounds: Option<Rectangle>) {
+ self.previous.push(self.current);
+ self.order.push(Kind::Live);
+
+ self.current = self.live_count;
+ self.live_count += 1;
+
+ let bounds = bounds.map(|bounds| bounds * self.transformation());
+
+ if self.current == self.live.len() {
+ self.live.push(Live {
bounds,
- } => {
- let layer = &mut layers[current_layer];
-
- layer.images.push(Image::Vector {
- handle: handle.clone(),
- color: *color,
- bounds: *bounds * transformation,
- });
- }
- Primitive::Group { primitives } => {
- // TODO: Inspect a bit and regroup (?)
- for primitive in primitives {
- Self::process_primitive(
- layers,
- transformation,
- primitive,
- current_layer,
- );
- }
- }
- Primitive::Clip { bounds, content } => {
- let layer = &mut layers[current_layer];
- let translated_bounds = *bounds * transformation;
-
- // Only draw visible content
- if let Some(clip_bounds) =
- layer.bounds.intersection(&translated_bounds)
- {
- let clip_layer = Layer::new(clip_bounds);
- layers.push(clip_layer);
-
- Self::process_primitive(
- layers,
- transformation,
- content,
- layers.len() - 1,
- );
- }
- }
- Primitive::Transform {
- transformation: new_transformation,
- content,
- } => {
- Self::process_primitive(
- layers,
- transformation * *new_transformation,
- content,
- current_layer,
- );
- }
- Primitive::Cache { content } => {
- Self::process_primitive(
- layers,
- transformation,
- content,
- current_layer,
- );
+ ..Live::default()
+ });
+ } else {
+ self.live[self.current].bounds = bounds;
+ }
+ }
+
+ pub fn pop_clip(&mut self) {
+ self.current = self.previous.pop().unwrap();
+ }
+
+ pub fn push_transformation(&mut self, transformation: Transformation) {
+ self.transformations
+ .push(self.transformation() * transformation);
+ }
+
+ pub fn pop_transformation(&mut self) {
+ let _ = self.transformations.pop();
+ }
+
+ fn transformation(&self) -> Transformation {
+ self.transformations.last().copied().unwrap()
+ }
+
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = LayerMut<'_>> {
+ let mut live = self.live.iter_mut();
+ let mut cached = self.cached.iter_mut();
+
+ self.order.iter().map(move |kind| match kind {
+ Kind::Live => LayerMut::Live(live.next().unwrap()),
+ Kind::Cache => {
+ LayerMut::Cached(cached.next().unwrap().borrow_mut())
}
- Primitive::Custom(custom) => match custom {
- primitive::Custom::Mesh(mesh) => match mesh {
- graphics::Mesh::Solid { buffers, size } => {
- let layer = &mut layers[current_layer];
-
- let bounds =
- Rectangle::with_size(*size) * transformation;
-
- // Only draw visible content
- if let Some(clip_bounds) =
- layer.bounds.intersection(&bounds)
- {
- layer.meshes.push(Mesh::Solid {
- transformation,
- buffers,
- clip_bounds,
- });
- }
- }
- graphics::Mesh::Gradient { buffers, size } => {
- let layer = &mut layers[current_layer];
-
- let bounds =
- Rectangle::with_size(*size) * transformation;
-
- // Only draw visible content
- if let Some(clip_bounds) =
- layer.bounds.intersection(&bounds)
- {
- layer.meshes.push(Mesh::Gradient {
- transformation,
- buffers,
- clip_bounds,
- });
- }
- }
- },
- primitive::Custom::Pipeline(pipeline) => {
- let layer = &mut layers[current_layer];
- let bounds = pipeline.bounds * transformation;
-
- if let Some(clip_bounds) =
- layer.bounds.intersection(&bounds)
- {
- layer.pipelines.push(Pipeline {
- bounds,
- viewport: clip_bounds,
- primitive: pipeline.primitive.clone(),
- });
- }
- }
- },
+ })
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = Layer<'_>> {
+ let mut live = self.live.iter();
+ let mut cached = self.cached.iter();
+
+ self.order.iter().map(move |kind| match kind {
+ Kind::Live => Layer::Live(live.next().unwrap()),
+ Kind::Cache => Layer::Cached(cached.next().unwrap().borrow()),
+ })
+ }
+
+ pub fn clear(&mut self) {
+ for live in &mut self.live[..self.live_count] {
+ live.bounds = None;
+ live.transformation = Transformation::IDENTITY;
+
+ live.quads.clear();
+ live.meshes.clear();
+ live.text.clear();
+ live.images.clear();
}
+
+ self.current = 0;
+ self.live_count = 1;
+
+ self.order.clear();
+ self.order.push(Kind::Live);
+
+ self.cached.clear();
+ self.previous.clear();
+ }
+}
+
+impl Default for Stack {
+ fn default() -> Self {
+ Self::new()
}
}
+
+#[derive(Default)]
+pub struct Live {
+ pub bounds: Option<Rectangle>,
+ pub transformation: Transformation,
+ pub quads: quad::Batch,
+ pub meshes: triangle::Batch,
+ pub text: text::Batch,
+ pub images: image::Batch,
+}
+
+impl Live {
+ pub fn into_cached(self) -> Cached {
+ Cached {
+ bounds: self.bounds,
+ transformation: self.transformation,
+ last_transformation: None,
+ quads: quad::Cache::Staged(self.quads),
+ meshes: triangle::Cache::Staged(self.meshes),
+ text: text::Cache::Staged(self.text),
+ images: self.images,
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct Cached {
+ pub bounds: Option<Rectangle>,
+ pub transformation: Transformation,
+ pub last_transformation: Option<Transformation>,
+ pub quads: quad::Cache,
+ pub meshes: triangle::Cache,
+ pub text: text::Cache,
+ pub images: image::Batch,
+}
+
+impl Cached {
+ pub fn update(&mut self, live: Live) {
+ self.bounds = live.bounds;
+ self.transformation = live.transformation;
+
+ self.quads.update(live.quads);
+ self.meshes.update(live.meshes);
+ self.text.update(live.text);
+ self.images = live.images;
+ }
+}
+
+enum Kind {
+ Live,
+ Cache,
+}
diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs
deleted file mode 100644
index facbe192..00000000
--- a/wgpu/src/layer/image.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use crate::core::image;
-use crate::core::svg;
-use crate::core::{Color, Rectangle};
-
-/// A raster or vector image.
-#[derive(Debug, Clone)]
-pub enum Image {
- /// A raster image.
- Raster {
- /// The handle of a raster image.
- handle: image::Handle,
-
- /// The filter method of a raster image.
- filter_method: image::FilterMethod,
-
- /// The bounds of the image.
- bounds: Rectangle,
- },
- /// A vector image.
- Vector {
- /// The handle of a vector image.
- handle: svg::Handle,
-
- /// The [`Color`] filter
- color: Option<Color>,
-
- /// The bounds of the image.
- bounds: Rectangle,
- },
-}
diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs
deleted file mode 100644
index 5ed7c654..00000000
--- a/wgpu/src/layer/mesh.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-//! A collection of triangle primitives.
-use crate::core::{Rectangle, Transformation};
-use crate::graphics::mesh;
-
-/// A mesh of triangles.
-#[derive(Debug, Clone, Copy)]
-pub enum Mesh<'a> {
- /// A mesh of triangles with a solid color.
- Solid {
- /// The [`Transformation`] for the vertices of the [`Mesh`].
- transformation: Transformation,
-
- /// The vertex and index buffers of the [`Mesh`].
- buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
-
- /// The clipping bounds of the [`Mesh`].
- clip_bounds: Rectangle<f32>,
- },
- /// A mesh of triangles with a gradient color.
- Gradient {
- /// The [`Transformation`] for the vertices of the [`Mesh`].
- transformation: Transformation,
-
- /// The vertex and index buffers of the [`Mesh`].
- buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
-
- /// The clipping bounds of the [`Mesh`].
- clip_bounds: Rectangle<f32>,
- },
-}
-
-impl Mesh<'_> {
- /// Returns the origin of the [`Mesh`].
- pub fn transformation(&self) -> Transformation {
- match self {
- Self::Solid { transformation, .. }
- | Self::Gradient { transformation, .. } => *transformation,
- }
- }
-
- /// Returns the indices of the [`Mesh`].
- pub fn indices(&self) -> &[u32] {
- match self {
- Self::Solid { buffers, .. } => &buffers.indices,
- Self::Gradient { buffers, .. } => &buffers.indices,
- }
- }
-
- /// Returns the clip bounds of the [`Mesh`].
- pub fn clip_bounds(&self) -> Rectangle<f32> {
- match self {
- Self::Solid { clip_bounds, .. }
- | Self::Gradient { clip_bounds, .. } => *clip_bounds,
- }
- }
-}
-
-/// The result of counting the attributes of a set of meshes.
-#[derive(Debug, Clone, Copy, Default)]
-pub struct AttributeCount {
- /// The total amount of solid vertices.
- pub solid_vertices: usize,
-
- /// The total amount of solid meshes.
- pub solids: usize,
-
- /// The total amount of gradient vertices.
- pub gradient_vertices: usize,
-
- /// The total amount of gradient meshes.
- pub gradients: usize,
-
- /// The total amount of indices.
- pub indices: usize,
-}
-
-/// Returns the number of total vertices & total indices of all [`Mesh`]es.
-pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
- meshes
- .iter()
- .fold(AttributeCount::default(), |mut count, mesh| {
- match mesh {
- Mesh::Solid { buffers, .. } => {
- count.solids += 1;
- count.solid_vertices += buffers.vertices.len();
- count.indices += buffers.indices.len();
- }
- Mesh::Gradient { buffers, .. } => {
- count.gradients += 1;
- count.gradient_vertices += buffers.vertices.len();
- count.indices += buffers.indices.len();
- }
- }
-
- count
- })
-}
diff --git a/wgpu/src/layer/pipeline.rs b/wgpu/src/layer/pipeline.rs
deleted file mode 100644
index 6dfe6750..00000000
--- a/wgpu/src/layer/pipeline.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-use crate::core::Rectangle;
-use crate::primitive::pipeline::Primitive;
-
-use std::sync::Arc;
-
-#[derive(Clone, Debug)]
-/// A custom primitive which can be used to render primitives associated with a custom pipeline.
-pub struct Pipeline {
- /// The bounds of the [`Pipeline`].
- pub bounds: Rectangle,
-
- /// The viewport of the [`Pipeline`].
- pub viewport: Rectangle,
-
- /// The [`Primitive`] to render.
- pub primitive: Arc<dyn Primitive>,
-}
diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs
deleted file mode 100644
index b3a00130..00000000
--- a/wgpu/src/layer/text.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use crate::core::alignment;
-use crate::core::text;
-use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
-use crate::graphics;
-use crate::graphics::text::editor;
-use crate::graphics::text::paragraph;
-
-/// A text primitive.
-#[derive(Debug, Clone)]
-pub enum Text<'a> {
- /// A paragraph.
- #[allow(missing_docs)]
- Paragraph {
- paragraph: paragraph::Weak,
- position: Point,
- color: Color,
- clip_bounds: Rectangle,
- transformation: Transformation,
- },
- /// An editor.
- #[allow(missing_docs)]
- Editor {
- editor: editor::Weak,
- position: Point,
- color: Color,
- clip_bounds: Rectangle,
- transformation: Transformation,
- },
- /// Some cached text.
- Cached(Cached<'a>),
- /// Some raw text.
- #[allow(missing_docs)]
- Raw {
- raw: graphics::text::Raw,
- transformation: Transformation,
- },
-}
-
-#[derive(Debug, Clone)]
-pub struct Cached<'a> {
- /// The content of the [`Text`].
- pub content: &'a str,
-
- /// The layout bounds of the [`Text`].
- pub bounds: Rectangle,
-
- /// The color of the [`Text`], in __linear RGB_.
- pub color: Color,
-
- /// The size of the [`Text`] in logical pixels.
- pub size: Pixels,
-
- /// The line height of the [`Text`].
- pub line_height: text::LineHeight,
-
- /// The font of the [`Text`].
- pub font: Font,
-
- /// The horizontal alignment of the [`Text`].
- pub horizontal_alignment: alignment::Horizontal,
-
- /// The vertical alignment of the [`Text`].
- pub vertical_alignment: alignment::Vertical,
-
- /// The shaping strategy of the text.
- pub shaping: text::Shaping,
-
- /// The clip bounds of the text.
- pub clip_bounds: Rectangle,
-}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index b00e5c3c..0e173e0a 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -22,8 +22,8 @@
)]
#![forbid(rust_2018_idioms)]
#![deny(
- missing_debug_implementations,
- missing_docs,
+ // missing_debug_implementations,
+ //missing_docs,
unsafe_code,
unused_results,
rustdoc::broken_intra_doc_links
@@ -37,13 +37,21 @@ pub mod window;
#[cfg(feature = "geometry")]
pub mod geometry;
-mod backend;
mod buffer;
mod color;
+mod engine;
mod quad;
mod text;
mod triangle;
+#[cfg(any(feature = "image", feature = "svg"))]
+#[path = "image/mod.rs"]
+mod image;
+
+#[cfg(not(any(feature = "image", feature = "svg")))]
+#[path = "image/null.rs"]
+mod image;
+
use buffer::Buffer;
pub use iced_graphics as graphics;
@@ -51,16 +59,570 @@ pub use iced_graphics::core;
pub use wgpu;
-pub use backend::Backend;
-pub use layer::Layer;
+pub use engine::Engine;
+pub use layer::{Layer, LayerMut};
pub use primitive::Primitive;
pub use settings::Settings;
-#[cfg(any(feature = "image", feature = "svg"))]
-mod image;
+#[cfg(feature = "geometry")]
+pub use geometry::Geometry;
+
+use crate::core::{
+ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+};
+use crate::graphics::text::{Editor, Paragraph};
+use crate::graphics::Viewport;
+
+use std::borrow::Cow;
/// A [`wgpu`] graphics renderer for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
-pub type Renderer = iced_graphics::Renderer<Backend>;
+#[allow(missing_debug_implementations)]
+pub struct Renderer {
+ default_font: Font,
+ default_text_size: Pixels,
+ layers: layer::Stack,
+
+ // TODO: Centralize all the image feature handling
+ #[cfg(any(feature = "svg", feature = "image"))]
+ image_cache: image::cache::Shared,
+}
+
+impl Renderer {
+ pub fn new(settings: Settings, _engine: &Engine) -> Self {
+ Self {
+ default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
+ layers: layer::Stack::new(),
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ image_cache: _engine.image_cache().clone(),
+ }
+ }
+
+ pub fn draw_primitive(&mut self, _primitive: Primitive) {}
+
+ pub fn present<T: AsRef<str>>(
+ &mut self,
+ engine: &mut Engine,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ clear_color: Option<Color>,
+ format: wgpu::TextureFormat,
+ frame: &wgpu::TextureView,
+ viewport: &Viewport,
+ overlay: &[T],
+ ) {
+ let target_size = viewport.physical_size();
+ let scale_factor = viewport.scale_factor() as f32;
+ let transformation = viewport.projection();
+
+ for line in overlay {
+ println!("{}", line.as_ref());
+ }
+
+ self.prepare(
+ engine,
+ device,
+ queue,
+ format,
+ encoder,
+ scale_factor,
+ target_size,
+ transformation,
+ );
+
+ self.render(
+ engine,
+ device,
+ encoder,
+ frame,
+ clear_color,
+ scale_factor,
+ target_size,
+ );
+ }
+
+ fn prepare(
+ &mut self,
+ engine: &mut Engine,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ _format: wgpu::TextureFormat,
+ encoder: &mut wgpu::CommandEncoder,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ transformation: Transformation,
+ ) {
+ for layer in self.layers.iter_mut() {
+ match layer {
+ LayerMut::Live(live) => {
+ if !live.quads.is_empty() {
+ engine.quad_pipeline.prepare_batch(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &live.quads,
+ transformation,
+ scale_factor,
+ );
+ }
+
+ if !live.meshes.is_empty() {
+ engine.triangle_pipeline.prepare_batch(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &live.meshes,
+ transformation
+ * Transformation::scale(scale_factor),
+ );
+ }
+
+ if !live.text.is_empty() {
+ engine.text_pipeline.prepare_batch(
+ device,
+ queue,
+ encoder,
+ &live.text,
+ live.bounds.unwrap_or(Rectangle::with_size(
+ Size::INFINITY,
+ )),
+ scale_factor,
+ target_size,
+ );
+ }
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ if !live.images.is_empty() {
+ engine.image_pipeline.prepare(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &live.images,
+ transformation,
+ scale_factor,
+ );
+ }
+ }
+ LayerMut::Cached(mut cached) => {
+ if !cached.quads.is_empty() {
+ engine.quad_pipeline.prepare_cache(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &mut cached.quads,
+ transformation,
+ scale_factor,
+ );
+ }
+
+ if !cached.meshes.is_empty() {
+ engine.triangle_pipeline.prepare_cache(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &mut cached.meshes,
+ transformation
+ * Transformation::scale(scale_factor),
+ );
+ }
+
+ if !cached.text.is_empty() {
+ let bounds = cached
+ .bounds
+ .unwrap_or(Rectangle::with_size(Size::INFINITY));
+
+ engine.text_pipeline.prepare_cache(
+ device,
+ queue,
+ encoder,
+ &mut cached.text,
+ bounds,
+ scale_factor,
+ target_size,
+ );
+ }
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ if !cached.images.is_empty() {
+ engine.image_pipeline.prepare(
+ device,
+ encoder,
+ &mut engine.staging_belt,
+ &cached.images,
+ transformation,
+ scale_factor,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ fn render(
+ &mut self,
+ engine: &mut Engine,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ frame: &wgpu::TextureView,
+ clear_color: Option<Color>,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ ) {
+ use std::mem::ManuallyDrop;
+
+ let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu render pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: frame,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: match clear_color {
+ Some(background_color) => wgpu::LoadOp::Clear({
+ let [r, g, b, a] =
+ graphics::color::pack(background_color)
+ .components();
+
+ wgpu::Color {
+ r: f64::from(r),
+ g: f64::from(g),
+ b: f64::from(b),
+ a: f64::from(a),
+ }
+ }),
+ None => wgpu::LoadOp::Load,
+ },
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ },
+ ));
+
+ let mut quad_layer = 0;
+ let mut mesh_layer = 0;
+ let mut text_layer = 0;
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ let mut image_layer = 0;
+
+ // TODO: Can we avoid collecting here?
+ let layers: Vec<_> = self.layers.iter().collect();
+
+ for layer in &layers {
+ match layer {
+ Layer::Live(live) => {
+ let bounds = live
+ .bounds
+ .map(|bounds| bounds * scale_factor)
+ .map(Rectangle::snap)
+ .unwrap_or(Rectangle::with_size(target_size));
+
+ if !live.quads.is_empty() {
+ engine.quad_pipeline.render_batch(
+ quad_layer,
+ bounds,
+ &live.quads,
+ &mut render_pass,
+ );
+
+ quad_layer += 1;
+ }
+
+ if !live.meshes.is_empty() {
+ let _ = ManuallyDrop::into_inner(render_pass);
+
+ engine.triangle_pipeline.render_batch(
+ device,
+ encoder,
+ frame,
+ mesh_layer,
+ target_size,
+ &live.meshes,
+ bounds,
+ scale_factor,
+ );
+
+ mesh_layer += 1;
+
+ render_pass =
+ ManuallyDrop::new(encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu render pass"),
+ color_attachments: &[Some(
+ wgpu::RenderPassColorAttachment {
+ view: frame,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ },
+ )],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ },
+ ));
+ }
+
+ if !live.text.is_empty() {
+ engine.text_pipeline.render_batch(
+ text_layer,
+ bounds,
+ &mut render_pass,
+ );
+
+ text_layer += 1;
+ }
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ if !live.images.is_empty() {
+ engine.image_pipeline.render(
+ image_layer,
+ bounds,
+ &mut render_pass,
+ );
+
+ image_layer += 1;
+ }
+ }
+ Layer::Cached(cached) => {
+ let bounds = cached
+ .bounds
+ .map(|bounds| bounds * scale_factor)
+ .map(Rectangle::snap)
+ .unwrap_or(Rectangle::with_size(target_size));
+
+ if !cached.quads.is_empty() {
+ engine.quad_pipeline.render_cache(
+ &cached.quads,
+ bounds,
+ &mut render_pass,
+ );
+ }
+
+ if !cached.meshes.is_empty() {
+ let _ = ManuallyDrop::into_inner(render_pass);
+
+ engine.triangle_pipeline.render_cache(
+ device,
+ encoder,
+ frame,
+ target_size,
+ &cached.meshes,
+ bounds,
+ scale_factor,
+ );
+
+ render_pass =
+ ManuallyDrop::new(encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu render pass"),
+ color_attachments: &[Some(
+ wgpu::RenderPassColorAttachment {
+ view: frame,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ },
+ )],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ },
+ ));
+ }
+
+ if !cached.text.is_empty() {
+ engine.text_pipeline.render_cache(
+ &cached.text,
+ bounds,
+ &mut render_pass,
+ );
+ }
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ if !cached.images.is_empty() {
+ engine.image_pipeline.render(
+ image_layer,
+ bounds,
+ &mut render_pass,
+ );
+
+ image_layer += 1;
+ }
+ }
+ }
+ }
+
+ let _ = ManuallyDrop::into_inner(render_pass);
+ }
+}
+
+impl core::Renderer for Renderer {
+ fn start_layer(&mut self, bounds: Rectangle) {
+ self.layers.push_clip(Some(bounds));
+ }
+
+ fn end_layer(&mut self, _bounds: Rectangle) {
+ self.layers.pop_clip();
+ }
+
+ fn start_transformation(&mut self, transformation: Transformation) {
+ self.layers.push_transformation(transformation);
+ }
+
+ fn end_transformation(&mut self, _transformation: Transformation) {
+ self.layers.pop_transformation();
+ }
+
+ fn fill_quad(
+ &mut self,
+ quad: core::renderer::Quad,
+ background: impl Into<Background>,
+ ) {
+ self.layers.draw_quad(quad, background.into());
+ }
+
+ fn clear(&mut self) {
+ self.layers.clear();
+ }
+}
+
+impl core::text::Renderer for Renderer {
+ type Font = Font;
+ type Paragraph = Paragraph;
+ type Editor = Editor;
+
+ const ICON_FONT: Font = Font::with_name("Iced-Icons");
+ const CHECKMARK_ICON: char = '\u{f00c}';
+ const ARROW_DOWN_ICON: char = '\u{e800}';
+
+ fn default_font(&self) -> Self::Font {
+ self.default_font
+ }
+
+ fn default_size(&self) -> Pixels {
+ self.default_text_size
+ }
+
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ graphics::text::font_system()
+ .write()
+ .expect("Write font system")
+ .load_font(font);
+
+ // TODO: Invalidate buffer cache
+ }
+
+ fn fill_paragraph(
+ &mut self,
+ text: &Self::Paragraph,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ self.layers
+ .draw_paragraph(text, position, color, clip_bounds);
+ }
+
+ fn fill_editor(
+ &mut self,
+ editor: &Self::Editor,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ self.layers
+ .draw_editor(editor, position, color, clip_bounds);
+ }
+
+ fn fill_text(
+ &mut self,
+ text: core::Text,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ ) {
+ self.layers.draw_text(text, position, color, clip_bounds);
+ }
+}
+
+#[cfg(feature = "image")]
+impl core::image::Renderer for Renderer {
+ type Handle = core::image::Handle;
+
+ fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
+ self.image_cache.lock().measure_image(handle)
+ }
+
+ fn draw_image(
+ &mut self,
+ handle: Self::Handle,
+ filter_method: core::image::FilterMethod,
+ bounds: Rectangle,
+ ) {
+ self.layers.draw_image(handle, filter_method, bounds);
+ }
+}
+
+#[cfg(feature = "svg")]
+impl core::svg::Renderer for Renderer {
+ fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
+ self.image_cache.lock().measure_svg(handle)
+ }
+
+ fn draw_svg(
+ &mut self,
+ handle: core::svg::Handle,
+ color_filter: Option<Color>,
+ bounds: Rectangle,
+ ) {
+ self.layers.draw_svg(handle, color_filter, bounds);
+ }
+}
+
+impl graphics::mesh::Renderer for Renderer {
+ fn draw_mesh(&mut self, mesh: graphics::Mesh) {
+ self.layers.draw_mesh(mesh);
+ }
+}
+
+#[cfg(feature = "geometry")]
+impl graphics::geometry::Renderer for Renderer {
+ type Geometry = Geometry;
+ type Frame = geometry::Frame;
+
+ fn new_frame(&self, size: Size) -> Self::Frame {
+ geometry::Frame::new(size)
+ }
+
+ fn draw_geometry(&mut self, geometry: Self::Geometry) {
+ match geometry {
+ Geometry::Live(layers) => {
+ for layer in layers {
+ self.layers.draw_layer(layer);
+ }
+ }
+ Geometry::Cached(layers) => {
+ for layer in layers.as_ref() {
+ self.layers.draw_cached_layer(layer);
+ }
+ }
+ }
+ }
+}
+
+impl graphics::compositor::Default for crate::Renderer {
+ type Compositor = window::Compositor;
+}
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index ee9af93c..8e311d2b 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -3,8 +3,7 @@ pub mod pipeline;
pub use pipeline::Pipeline;
-use crate::core::Rectangle;
-use crate::graphics::{Damage, Mesh};
+use crate::graphics::Mesh;
use std::fmt::Debug;
@@ -19,20 +18,3 @@ pub enum Custom {
/// A custom pipeline primitive.
Pipeline(Pipeline),
}
-
-impl Damage for Custom {
- fn bounds(&self) -> Rectangle {
- match self {
- Self::Mesh(mesh) => mesh.bounds(),
- Self::Pipeline(pipeline) => pipeline.bounds,
- }
- }
-}
-
-impl TryFrom<Mesh> for Custom {
- type Error = &'static str;
-
- fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
- Ok(Custom::Mesh(mesh))
- }
-}
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index 0717a031..16d50b04 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};
use std::mem;
-#[cfg(feature = "tracing")]
-use tracing::info_span;
-
const INITIAL_INSTANCES: usize = 2_000;
+/// The properties of a quad.
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+#[repr(C)]
+pub struct Quad {
+ /// The position of the [`Quad`].
+ pub position: [f32; 2],
+
+ /// The size of the [`Quad`].
+ pub size: [f32; 2],
+
+ /// The border color of the [`Quad`], in __linear RGB__.
+ pub border_color: color::Packed,
+
+ /// The border radii of the [`Quad`].
+ pub border_radius: [f32; 4],
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+
+ /// The shadow color of the [`Quad`].
+ pub shadow_color: color::Packed,
+
+ /// The shadow offset of the [`Quad`].
+ pub shadow_offset: [f32; 2],
+
+ /// The shadow blur radius of the [`Quad`].
+ pub shadow_blur_radius: f32,
+}
+
#[derive(Debug)]
pub struct Pipeline {
solid: solid::Pipeline,
@@ -54,7 +80,7 @@ impl Pipeline {
}
}
- pub fn prepare(
+ pub fn prepare_batch(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
@@ -73,7 +99,64 @@ impl Pipeline {
self.prepare_layer += 1;
}
- pub fn render<'a>(
+ pub fn prepare_cache(
+ &self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ cache: &mut Cache,
+ transformation: Transformation,
+ scale: f32,
+ ) {
+ match cache {
+ Cache::Staged(_) => {
+ let Cache::Staged(batch) =
+ std::mem::replace(cache, Cache::Staged(Batch::default()))
+ else {
+ unreachable!()
+ };
+
+ let mut layer = Layer::new(device, &self.constant_layout);
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ &batch,
+ transformation,
+ scale,
+ );
+
+ *cache = Cache::Uploaded {
+ layer,
+ batch,
+ needs_reupload: false,
+ }
+ }
+
+ Cache::Uploaded {
+ batch,
+ layer,
+ needs_reupload,
+ } => {
+ if *needs_reupload {
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ batch,
+ transformation,
+ scale,
+ );
+
+ *needs_reupload = false;
+ } else {
+ layer.update(device, encoder, belt, transformation, scale);
+ }
+ }
+ }
+ }
+
+ pub fn render_batch<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
@@ -81,38 +164,59 @@ impl Pipeline {
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
- render_pass.set_scissor_rect(
- bounds.x,
- bounds.y,
- bounds.width,
- bounds.height,
- );
-
- let mut solid_offset = 0;
- let mut gradient_offset = 0;
-
- for (kind, count) in &quads.order {
- match kind {
- Kind::Solid => {
- self.solid.render(
- render_pass,
- &layer.constants,
- &layer.solid,
- solid_offset..(solid_offset + count),
- );
-
- solid_offset += count;
- }
- Kind::Gradient => {
- self.gradient.render(
- render_pass,
- &layer.constants,
- &layer.gradient,
- gradient_offset..(gradient_offset + count),
- );
-
- gradient_offset += count;
- }
+ self.render(bounds, layer, &quads.order, render_pass);
+ }
+ }
+
+ pub fn render_cache<'a>(
+ &'a self,
+ cache: &'a Cache,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ if let Cache::Uploaded { layer, batch, .. } = cache {
+ self.render(bounds, layer, &batch.order, render_pass);
+ }
+ }
+
+ fn render<'a>(
+ &'a self,
+ bounds: Rectangle<u32>,
+ layer: &'a Layer,
+ order: &Order,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
+
+ let mut solid_offset = 0;
+ let mut gradient_offset = 0;
+
+ for (kind, count) in order {
+ match kind {
+ Kind::Solid => {
+ self.solid.render(
+ render_pass,
+ &layer.constants,
+ &layer.solid,
+ solid_offset..(solid_offset + count),
+ );
+
+ solid_offset += count;
+ }
+ Kind::Gradient => {
+ self.gradient.render(
+ render_pass,
+ &layer.constants,
+ &layer.gradient,
+ gradient_offset..(gradient_offset + count),
+ );
+
+ gradient_offset += count;
}
}
}
@@ -124,7 +228,49 @@ impl Pipeline {
}
#[derive(Debug)]
-struct Layer {
+pub enum Cache {
+ Staged(Batch),
+ Uploaded {
+ batch: Batch,
+ layer: Layer,
+ needs_reupload: bool,
+ },
+}
+
+impl Cache {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
+ batch.is_empty()
+ }
+ }
+ }
+
+ pub fn update(&mut self, new_batch: Batch) {
+ match self {
+ Self::Staged(batch) => {
+ *batch = new_batch;
+ }
+ Self::Uploaded {
+ batch,
+ needs_reupload,
+ ..
+ } => {
+ *batch = new_batch;
+ *needs_reupload = true;
+ }
+ }
+ }
+}
+
+impl Default for Cache {
+ fn default() -> Self {
+ Self::Staged(Batch::default())
+ }
+}
+
+#[derive(Debug)]
+pub struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
solid: solid::Layer,
@@ -169,9 +315,26 @@ impl Layer {
transformation: Transformation,
scale: f32,
) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
+ self.update(device, encoder, belt, transformation, scale);
+
+ if !quads.solids.is_empty() {
+ self.solid.prepare(device, encoder, belt, &quads.solids);
+ }
+
+ if !quads.gradients.is_empty() {
+ self.gradient
+ .prepare(device, encoder, belt, &quads.gradients);
+ }
+ }
+ pub fn update(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ transformation: Transformation,
+ scale: f32,
+ ) {
let uniforms = Uniforms::new(transformation, scale);
let bytes = bytemuck::bytes_of(&uniforms);
@@ -183,47 +346,9 @@ impl Layer {
device,
)
.copy_from_slice(bytes);
-
- if !quads.solids.is_empty() {
- self.solid.prepare(device, encoder, belt, &quads.solids);
- }
-
- if !quads.gradients.is_empty() {
- self.gradient
- .prepare(device, encoder, belt, &quads.gradients);
- }
}
}
-/// The properties of a quad.
-#[derive(Clone, Copy, Debug, Pod, Zeroable)]
-#[repr(C)]
-pub struct Quad {
- /// The position of the [`Quad`].
- pub position: [f32; 2],
-
- /// The size of the [`Quad`].
- pub size: [f32; 2],
-
- /// The border color of the [`Quad`], in __linear RGB__.
- pub border_color: color::Packed,
-
- /// The border radii of the [`Quad`].
- pub border_radius: [f32; 4],
-
- /// The border width of the [`Quad`].
- pub border_width: f32,
-
- /// The shadow color of the [`Quad`].
- pub shadow_color: [f32; 4],
-
- /// The shadow offset of the [`Quad`].
- pub shadow_offset: [f32; 2],
-
- /// The shadow blur radius of the [`Quad`].
- pub shadow_blur_radius: f32,
-}
-
/// A group of [`Quad`]s rendered together.
#[derive(Default, Debug)]
pub struct Batch {
@@ -233,10 +358,13 @@ pub struct Batch {
/// The gradient quads of the [`Layer`].
gradients: Vec<Gradient>,
- /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
- order: Vec<(Kind, usize)>,
+ /// The quad order of the [`Layer`].
+ order: Order,
}
+/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
+type Order = Vec<(Kind, usize)>;
+
impl Batch {
/// Returns true if there are no quads of any type in [`Quads`].
pub fn is_empty(&self) -> bool {
@@ -276,6 +404,12 @@ impl Batch {
}
}
}
+
+ pub fn clear(&mut self) {
+ self.solids.clear();
+ self.gradients.clear();
+ self.order.clear();
+ }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 97ff77f5..016ac92a 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -1,20 +1,67 @@
use crate::core::alignment;
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::color;
-use crate::graphics::text::cache::{self, Cache};
+use crate::graphics::text::cache::{self, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
-use crate::layer::Text;
-use std::borrow::Cow;
-use std::cell::RefCell;
use std::sync::Arc;
+pub use crate::graphics::Text;
+
+pub type Batch = Vec<Text>;
+
#[allow(missing_debug_implementations)]
pub struct Pipeline {
- renderers: Vec<glyphon::TextRenderer>,
+ format: wgpu::TextureFormat,
atlas: glyphon::TextAtlas,
+ renderers: Vec<glyphon::TextRenderer>,
prepare_layer: usize,
- cache: RefCell<Cache>,
+ cache: BufferCache,
+}
+
+pub enum Cache {
+ Staged(Batch),
+ Uploaded {
+ batch: Batch,
+ renderer: glyphon::TextRenderer,
+ atlas: Option<glyphon::TextAtlas>,
+ buffer_cache: Option<BufferCache>,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ needs_reupload: bool,
+ },
+}
+
+impl Cache {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
+ batch.is_empty()
+ }
+ }
+ }
+
+ pub fn update(&mut self, new_batch: Batch) {
+ match self {
+ Self::Staged(batch) => {
+ *batch = new_batch;
+ }
+ Self::Uploaded {
+ batch,
+ needs_reupload,
+ ..
+ } => {
+ *batch = new_batch;
+ *needs_reupload = true;
+ }
+ }
+ }
+}
+
+impl Default for Cache {
+ fn default() -> Self {
+ Self::Staged(Batch::default())
+ }
}
impl Pipeline {
@@ -24,6 +71,7 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
+ format,
renderers: Vec::new(),
atlas: glyphon::TextAtlas::with_color_mode(
device,
@@ -36,25 +84,16 @@ impl Pipeline {
},
),
prepare_layer: 0,
- cache: RefCell::new(Cache::new()),
+ cache: BufferCache::new(),
}
}
- pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
- font_system()
- .write()
- .expect("Write font system")
- .load_font(bytes);
-
- self.cache = RefCell::new(Cache::new());
- }
-
- pub fn prepare(
+ pub fn prepare_batch(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
- sections: &[Text<'_>],
+ sections: &Batch,
layer_bounds: Rectangle,
scale_factor: f32,
target_size: Size<u32>,
@@ -68,210 +107,18 @@ impl Pipeline {
));
}
- let mut font_system = font_system().write().expect("Write font system");
- let font_system = font_system.raw();
-
let renderer = &mut self.renderers[self.prepare_layer];
- let cache = self.cache.get_mut();
-
- enum Allocation {
- Paragraph(Paragraph),
- Editor(Editor),
- Cache(cache::KeyHash),
- Raw(Arc<glyphon::Buffer>),
- }
-
- let allocations: Vec<_> = sections
- .iter()
- .map(|section| match section {
- Text::Paragraph { paragraph, .. } => {
- paragraph.upgrade().map(Allocation::Paragraph)
- }
- Text::Editor { editor, .. } => {
- editor.upgrade().map(Allocation::Editor)
- }
- Text::Cached(text) => {
- let (key, _) = cache.allocate(
- font_system,
- cache::Key {
- content: text.content,
- size: text.size.into(),
- line_height: f32::from(
- text.line_height.to_absolute(text.size),
- ),
- font: text.font,
- bounds: Size {
- width: text.bounds.width,
- height: text.bounds.height,
- },
- shaping: text.shaping,
- },
- );
-
- Some(Allocation::Cache(key))
- }
- Text::Raw { raw, .. } => {
- raw.buffer.upgrade().map(Allocation::Raw)
- }
- })
- .collect();
-
- let layer_bounds = layer_bounds * scale_factor;
-
- let text_areas = sections.iter().zip(allocations.iter()).filter_map(
- |(section, allocation)| {
- let (
- buffer,
- bounds,
- horizontal_alignment,
- vertical_alignment,
- color,
- clip_bounds,
- transformation,
- ) = match section {
- Text::Paragraph {
- position,
- color,
- clip_bounds,
- transformation,
- ..
- } => {
- use crate::core::text::Paragraph as _;
-
- let Some(Allocation::Paragraph(paragraph)) = allocation
- else {
- return None;
- };
-
- (
- paragraph.buffer(),
- Rectangle::new(*position, paragraph.min_bounds()),
- paragraph.horizontal_alignment(),
- paragraph.vertical_alignment(),
- *color,
- *clip_bounds,
- *transformation,
- )
- }
- Text::Editor {
- position,
- color,
- clip_bounds,
- transformation,
- ..
- } => {
- use crate::core::text::Editor as _;
-
- let Some(Allocation::Editor(editor)) = allocation
- else {
- return None;
- };
-
- (
- editor.buffer(),
- Rectangle::new(*position, editor.bounds()),
- alignment::Horizontal::Left,
- alignment::Vertical::Top,
- *color,
- *clip_bounds,
- *transformation,
- )
- }
- Text::Cached(text) => {
- let Some(Allocation::Cache(key)) = allocation else {
- return None;
- };
-
- let entry = cache.get(key).expect("Get cached buffer");
-
- (
- &entry.buffer,
- Rectangle::new(
- text.bounds.position(),
- entry.min_bounds,
- ),
- text.horizontal_alignment,
- text.vertical_alignment,
- text.color,
- text.clip_bounds,
- Transformation::IDENTITY,
- )
- }
- Text::Raw {
- raw,
- transformation,
- } => {
- let Some(Allocation::Raw(buffer)) = allocation else {
- return None;
- };
-
- let (width, height) = buffer.size();
-
- (
- buffer.as_ref(),
- Rectangle::new(
- raw.position,
- Size::new(width, height),
- ),
- alignment::Horizontal::Left,
- alignment::Vertical::Top,
- raw.color,
- raw.clip_bounds,
- *transformation,
- )
- }
- };
-
- let bounds = bounds * transformation * scale_factor;
-
- let left = match horizontal_alignment {
- alignment::Horizontal::Left => bounds.x,
- alignment::Horizontal::Center => {
- bounds.x - bounds.width / 2.0
- }
- alignment::Horizontal::Right => bounds.x - bounds.width,
- };
-
- let top = match vertical_alignment {
- alignment::Vertical::Top => bounds.y,
- alignment::Vertical::Center => {
- bounds.y - bounds.height / 2.0
- }
- alignment::Vertical::Bottom => bounds.y - bounds.height,
- };
-
- let clip_bounds = layer_bounds.intersection(
- &(clip_bounds * transformation * scale_factor),
- )?;
-
- Some(glyphon::TextArea {
- buffer,
- left,
- top,
- scale: scale_factor * transformation.scale_factor(),
- bounds: glyphon::TextBounds {
- left: clip_bounds.x as i32,
- top: clip_bounds.y as i32,
- right: (clip_bounds.x + clip_bounds.width) as i32,
- bottom: (clip_bounds.y + clip_bounds.height) as i32,
- },
- default_color: to_color(color),
- })
- },
- );
-
- let result = renderer.prepare(
+ let result = prepare(
device,
queue,
encoder,
- font_system,
+ renderer,
&mut self.atlas,
- glyphon::Resolution {
- width: target_size.width,
- height: target_size.height,
- },
- text_areas,
- &mut glyphon::SwashCache::new(),
+ &mut self.cache,
+ sections,
+ layer_bounds,
+ scale_factor,
+ target_size,
);
match result {
@@ -286,7 +133,109 @@ impl Pipeline {
}
}
- pub fn render<'a>(
+ pub fn prepare_cache(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ cache: &mut Cache,
+ layer_bounds: Rectangle,
+ new_scale_factor: f32,
+ new_target_size: Size<u32>,
+ ) {
+ match cache {
+ Cache::Staged(_) => {
+ let Cache::Staged(batch) =
+ std::mem::replace(cache, Cache::Staged(Batch::default()))
+ else {
+ unreachable!()
+ };
+
+ // TODO: Find a better heuristic (?)
+ let (mut atlas, mut buffer_cache) = if batch.len() > 10 {
+ (
+ Some(glyphon::TextAtlas::with_color_mode(
+ device,
+ queue,
+ self.format,
+ if color::GAMMA_CORRECTION {
+ glyphon::ColorMode::Accurate
+ } else {
+ glyphon::ColorMode::Web
+ },
+ )),
+ Some(BufferCache::new()),
+ )
+ } else {
+ (None, None)
+ };
+
+ let mut renderer = glyphon::TextRenderer::new(
+ atlas.as_mut().unwrap_or(&mut self.atlas),
+ device,
+ wgpu::MultisampleState::default(),
+ None,
+ );
+
+ let _ = prepare(
+ device,
+ queue,
+ encoder,
+ &mut renderer,
+ atlas.as_mut().unwrap_or(&mut self.atlas),
+ buffer_cache.as_mut().unwrap_or(&mut self.cache),
+ &batch,
+ layer_bounds,
+ new_scale_factor,
+ new_target_size,
+ );
+
+ *cache = Cache::Uploaded {
+ batch,
+ needs_reupload: false,
+ renderer,
+ atlas,
+ buffer_cache,
+ scale_factor: new_scale_factor,
+ target_size: new_target_size,
+ }
+ }
+ Cache::Uploaded {
+ batch,
+ needs_reupload,
+ renderer,
+ atlas,
+ buffer_cache,
+ scale_factor,
+ target_size,
+ } => {
+ if *needs_reupload
+ || atlas.is_none()
+ || buffer_cache.is_none()
+ || new_scale_factor != *scale_factor
+ || new_target_size != *target_size
+ {
+ let _ = prepare(
+ device,
+ queue,
+ encoder,
+ renderer,
+ atlas.as_mut().unwrap_or(&mut self.atlas),
+ buffer_cache.as_mut().unwrap_or(&mut self.cache),
+ batch,
+ layer_bounds,
+ *scale_factor,
+ *target_size,
+ );
+
+ *scale_factor = new_scale_factor;
+ *target_size = new_target_size;
+ }
+ }
+ }
+ }
+
+ pub fn render_batch<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
@@ -306,10 +255,251 @@ impl Pipeline {
.expect("Render text");
}
+ pub fn render_cache<'a>(
+ &'a self,
+ cache: &'a Cache,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ let Cache::Uploaded {
+ renderer, atlas, ..
+ } = cache
+ else {
+ return;
+ };
+
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
+
+ renderer
+ .render(atlas.as_ref().unwrap_or(&self.atlas), render_pass)
+ .expect("Render text");
+ }
+
pub fn end_frame(&mut self) {
self.atlas.trim();
- self.cache.get_mut().trim();
+ self.cache.trim();
self.prepare_layer = 0;
}
}
+
+fn prepare(
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ renderer: &mut glyphon::TextRenderer,
+ atlas: &mut glyphon::TextAtlas,
+ buffer_cache: &mut BufferCache,
+ sections: &Batch,
+ layer_bounds: Rectangle,
+ scale_factor: f32,
+ target_size: Size<u32>,
+) -> Result<(), glyphon::PrepareError> {
+ let mut font_system = font_system().write().expect("Write font system");
+ let font_system = font_system.raw();
+
+ enum Allocation {
+ Paragraph(Paragraph),
+ Editor(Editor),
+ Cache(cache::KeyHash),
+ Raw(Arc<glyphon::Buffer>),
+ }
+
+ let allocations: Vec<_> = sections
+ .iter()
+ .map(|section| match section {
+ Text::Paragraph { paragraph, .. } => {
+ paragraph.upgrade().map(Allocation::Paragraph)
+ }
+ Text::Editor { editor, .. } => {
+ editor.upgrade().map(Allocation::Editor)
+ }
+ Text::Cached {
+ content,
+ bounds,
+ size,
+ line_height,
+ font,
+ shaping,
+ ..
+ } => {
+ let (key, _) = buffer_cache.allocate(
+ font_system,
+ cache::Key {
+ content,
+ size: (*size).into(),
+ line_height: f32::from(line_height.to_absolute(*size)),
+ font: *font,
+ bounds: Size {
+ width: bounds.width,
+ height: bounds.height,
+ },
+ shaping: *shaping,
+ },
+ );
+
+ Some(Allocation::Cache(key))
+ }
+ Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
+ })
+ .collect();
+
+ let layer_bounds = layer_bounds * scale_factor;
+
+ let text_areas = sections.iter().zip(allocations.iter()).filter_map(
+ |(section, allocation)| {
+ let (
+ buffer,
+ bounds,
+ horizontal_alignment,
+ vertical_alignment,
+ color,
+ clip_bounds,
+ transformation,
+ ) = match section {
+ Text::Paragraph {
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ ..
+ } => {
+ use crate::core::text::Paragraph as _;
+
+ let Some(Allocation::Paragraph(paragraph)) = allocation
+ else {
+ return None;
+ };
+
+ (
+ paragraph.buffer(),
+ Rectangle::new(*position, paragraph.min_bounds()),
+ paragraph.horizontal_alignment(),
+ paragraph.vertical_alignment(),
+ *color,
+ *clip_bounds,
+ *transformation,
+ )
+ }
+ Text::Editor {
+ position,
+ color,
+ clip_bounds,
+ transformation,
+ ..
+ } => {
+ use crate::core::text::Editor as _;
+
+ let Some(Allocation::Editor(editor)) = allocation else {
+ return None;
+ };
+
+ (
+ editor.buffer(),
+ Rectangle::new(*position, editor.bounds()),
+ alignment::Horizontal::Left,
+ alignment::Vertical::Top,
+ *color,
+ *clip_bounds,
+ *transformation,
+ )
+ }
+ Text::Cached {
+ bounds,
+ horizontal_alignment,
+ vertical_alignment,
+ color,
+ clip_bounds,
+ ..
+ } => {
+ let Some(Allocation::Cache(key)) = allocation else {
+ return None;
+ };
+
+ let entry =
+ buffer_cache.get(key).expect("Get cached buffer");
+
+ (
+ &entry.buffer,
+ Rectangle::new(bounds.position(), entry.min_bounds),
+ *horizontal_alignment,
+ *vertical_alignment,
+ *color,
+ *clip_bounds,
+ Transformation::IDENTITY,
+ )
+ }
+ Text::Raw {
+ raw,
+ transformation,
+ } => {
+ let Some(Allocation::Raw(buffer)) = allocation else {
+ return None;
+ };
+
+ let (width, height) = buffer.size();
+
+ (
+ buffer.as_ref(),
+ Rectangle::new(raw.position, Size::new(width, height)),
+ alignment::Horizontal::Left,
+ alignment::Vertical::Top,
+ raw.color,
+ raw.clip_bounds,
+ *transformation,
+ )
+ }
+ };
+
+ let bounds = bounds * transformation * scale_factor;
+
+ let left = match horizontal_alignment {
+ alignment::Horizontal::Left => bounds.x,
+ alignment::Horizontal::Center => bounds.x - bounds.width / 2.0,
+ alignment::Horizontal::Right => bounds.x - bounds.width,
+ };
+
+ let top = match vertical_alignment {
+ alignment::Vertical::Top => bounds.y,
+ alignment::Vertical::Center => bounds.y - bounds.height / 2.0,
+ alignment::Vertical::Bottom => bounds.y - bounds.height,
+ };
+
+ let clip_bounds = layer_bounds
+ .intersection(&(clip_bounds * transformation * scale_factor))?;
+
+ Some(glyphon::TextArea {
+ buffer,
+ left,
+ top,
+ scale: scale_factor * transformation.scale_factor(),
+ bounds: glyphon::TextBounds {
+ left: clip_bounds.x as i32,
+ top: clip_bounds.y as i32,
+ right: (clip_bounds.x + clip_bounds.width) as i32,
+ bottom: (clip_bounds.y + clip_bounds.height) as i32,
+ },
+ default_color: to_color(color),
+ })
+ },
+ );
+
+ renderer.prepare(
+ device,
+ queue,
+ encoder,
+ font_system,
+ atlas,
+ glyphon::Resolution {
+ width: target_size.width,
+ height: target_size.height,
+ },
+ text_areas,
+ &mut glyphon::SwashCache::new(),
+ )
+}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index b6be54d4..6df97a7b 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,14 +1,16 @@
//! Draw meshes of triangles.
mod msaa;
-use crate::core::{Size, Transformation};
+use crate::core::{Rectangle, Size, Transformation};
+use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing;
-use crate::layer::mesh::{self, Mesh};
use crate::Buffer;
const INITIAL_INDEX_COUNT: usize = 1_000;
const INITIAL_VERTEX_COUNT: usize = 1_000;
+pub type Batch = Vec<Mesh>;
+
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
@@ -18,8 +20,270 @@ pub struct Pipeline {
prepare_layer: usize,
}
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ antialiasing: Option<Antialiasing>,
+ ) -> Pipeline {
+ Pipeline {
+ blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
+ solid: solid::Pipeline::new(device, format, antialiasing),
+ gradient: gradient::Pipeline::new(device, format, antialiasing),
+ layers: Vec::new(),
+ prepare_layer: 0,
+ }
+ }
+
+ pub fn prepare_batch(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ meshes: &Batch,
+ transformation: Transformation,
+ ) {
+ if self.layers.len() <= self.prepare_layer {
+ self.layers
+ .push(Layer::new(device, &self.solid, &self.gradient));
+ }
+
+ let layer = &mut self.layers[self.prepare_layer];
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ &self.solid,
+ &self.gradient,
+ meshes,
+ transformation,
+ );
+
+ self.prepare_layer += 1;
+ }
+
+ pub fn prepare_cache(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ cache: &mut Cache,
+ transformation: Transformation,
+ ) {
+ match cache {
+ Cache::Staged(_) => {
+ let Cache::Staged(batch) =
+ std::mem::replace(cache, Cache::Staged(Batch::default()))
+ else {
+ unreachable!()
+ };
+
+ let mut layer = Layer::new(device, &self.solid, &self.gradient);
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ &self.solid,
+ &self.gradient,
+ &batch,
+ transformation,
+ );
+
+ *cache = Cache::Uploaded {
+ layer,
+ batch,
+ needs_reupload: false,
+ }
+ }
+
+ Cache::Uploaded {
+ batch,
+ layer,
+ needs_reupload,
+ } => {
+ if *needs_reupload {
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ &self.solid,
+ &self.gradient,
+ batch,
+ transformation,
+ );
+
+ *needs_reupload = false;
+ }
+ }
+ }
+ }
+
+ pub fn render_batch(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ target: &wgpu::TextureView,
+ layer: usize,
+ target_size: Size<u32>,
+ meshes: &Batch,
+ bounds: Rectangle<u32>,
+ scale_factor: f32,
+ ) {
+ Self::render(
+ device,
+ encoder,
+ target,
+ self.blit.as_mut(),
+ &self.solid,
+ &self.gradient,
+ &self.layers[layer],
+ target_size,
+ meshes,
+ bounds,
+ scale_factor,
+ );
+ }
+
+ pub fn render_cache(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ target: &wgpu::TextureView,
+ target_size: Size<u32>,
+ cache: &Cache,
+ bounds: Rectangle<u32>,
+ scale_factor: f32,
+ ) {
+ let Cache::Uploaded { batch, layer, .. } = cache else {
+ return;
+ };
+
+ Self::render(
+ device,
+ encoder,
+ target,
+ self.blit.as_mut(),
+ &self.solid,
+ &self.gradient,
+ layer,
+ target_size,
+ batch,
+ bounds,
+ scale_factor,
+ );
+ }
+
+ fn render(
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ target: &wgpu::TextureView,
+ mut blit: Option<&mut msaa::Blit>,
+ solid: &solid::Pipeline,
+ gradient: &gradient::Pipeline,
+ layer: &Layer,
+ target_size: Size<u32>,
+ meshes: &Batch,
+ bounds: Rectangle<u32>,
+ scale_factor: f32,
+ ) {
+ {
+ let (attachment, resolve_target, load) = if let Some(blit) =
+ &mut blit
+ {
+ let (attachment, resolve_target) =
+ blit.targets(device, target_size.width, target_size.height);
+
+ (
+ attachment,
+ Some(resolve_target),
+ wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+ )
+ } else {
+ (target, None, wgpu::LoadOp::Load)
+ };
+
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu.triangle.render_pass"),
+ color_attachments: &[Some(
+ wgpu::RenderPassColorAttachment {
+ view: attachment,
+ resolve_target,
+ ops: wgpu::Operations {
+ load,
+ store: wgpu::StoreOp::Store,
+ },
+ },
+ )],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ });
+
+ layer.render(
+ solid,
+ gradient,
+ meshes,
+ bounds,
+ scale_factor,
+ &mut render_pass,
+ );
+ }
+
+ if let Some(blit) = blit {
+ blit.draw(encoder, target);
+ }
+ }
+
+ pub fn end_frame(&mut self) {
+ self.prepare_layer = 0;
+ }
+}
+
+#[derive(Debug)]
+pub enum Cache {
+ Staged(Batch),
+ Uploaded {
+ batch: Batch,
+ layer: Layer,
+ needs_reupload: bool,
+ },
+}
+
+impl Cache {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
+ batch.is_empty()
+ }
+ }
+ }
+
+ pub fn update(&mut self, new_batch: Batch) {
+ match self {
+ Self::Staged(batch) => {
+ *batch = new_batch;
+ }
+ Self::Uploaded {
+ batch,
+ needs_reupload,
+ ..
+ } => {
+ *batch = new_batch;
+ *needs_reupload = true;
+ }
+ }
+ }
+}
+
+impl Default for Cache {
+ fn default() -> Self {
+ Self::Staged(Batch::default())
+ }
+}
+
#[derive(Debug)]
-struct Layer {
+pub struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
@@ -52,7 +316,7 @@ impl Layer {
belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
- meshes: &[Mesh<'_>],
+ meshes: &Batch,
transformation: Transformation,
) {
// Count the total amount of vertices & indices we need to handle
@@ -100,7 +364,6 @@ impl Layer {
for mesh in meshes {
let indices = mesh.indices();
-
let uniforms =
Uniforms::new(transformation * mesh.transformation());
@@ -157,7 +420,8 @@ impl Layer {
&'a self,
solid: &'a solid::Pipeline,
gradient: &'a gradient::Pipeline,
- meshes: &[Mesh<'_>],
+ meshes: &Batch,
+ layer_bounds: Rectangle<u32>,
scale_factor: f32,
render_pass: &mut wgpu::RenderPass<'a>,
) {
@@ -166,7 +430,12 @@ impl Layer {
let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() {
- let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
+ let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds)
+ .intersection(&(mesh.clip_bounds() * scale_factor))
+ .map(Rectangle::snap)
+ else {
+ continue;
+ };
if clip_bounds.width < 1 || clip_bounds.height < 1 {
continue;
@@ -234,119 +503,6 @@ impl Layer {
}
}
-impl Pipeline {
- pub fn new(
- device: &wgpu::Device,
- format: wgpu::TextureFormat,
- antialiasing: Option<Antialiasing>,
- ) -> Pipeline {
- Pipeline {
- blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
- solid: solid::Pipeline::new(device, format, antialiasing),
- gradient: gradient::Pipeline::new(device, format, antialiasing),
- layers: Vec::new(),
- prepare_layer: 0,
- }
- }
-
- pub fn prepare(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- belt: &mut wgpu::util::StagingBelt,
- meshes: &[Mesh<'_>],
- transformation: Transformation,
- ) {
- #[cfg(feature = "tracing")]
- let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
-
- if self.layers.len() <= self.prepare_layer {
- self.layers
- .push(Layer::new(device, &self.solid, &self.gradient));
- }
-
- let layer = &mut self.layers[self.prepare_layer];
- layer.prepare(
- device,
- encoder,
- belt,
- &self.solid,
- &self.gradient,
- meshes,
- transformation,
- );
-
- self.prepare_layer += 1;
- }
-
- pub fn render(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- target: &wgpu::TextureView,
- layer: usize,
- target_size: Size<u32>,
- meshes: &[Mesh<'_>],
- scale_factor: f32,
- ) {
- #[cfg(feature = "tracing")]
- let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
-
- {
- let (attachment, resolve_target, load) = if let Some(blit) =
- &mut self.blit
- {
- let (attachment, resolve_target) =
- blit.targets(device, target_size.width, target_size.height);
-
- (
- attachment,
- Some(resolve_target),
- wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
- )
- } else {
- (target, None, wgpu::LoadOp::Load)
- };
-
- let mut render_pass =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu.triangle.render_pass"),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view: attachment,
- resolve_target,
- ops: wgpu::Operations {
- load,
- store: wgpu::StoreOp::Store,
- },
- },
- )],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
-
- let layer = &mut self.layers[layer];
-
- layer.render(
- &self.solid,
- &self.gradient,
- meshes,
- scale_factor,
- &mut render_pass,
- );
- }
-
- if let Some(blit) = &mut self.blit {
- blit.draw(encoder, target);
- }
- }
-
- pub fn end_frame(&mut self) {
- self.prepare_layer = 0;
- }
-}
-
fn fragment_target(
texture_format: wgpu::TextureFormat,
) -> wgpu::ColorTargetState {
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 482d705b..e2b01477 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -4,18 +4,19 @@ use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::error;
use crate::graphics::{self, Viewport};
-use crate::{Backend, Primitive, Renderer, Settings};
+use crate::{Engine, Renderer, Settings};
/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
- settings: Settings,
instance: wgpu::Instance,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
format: wgpu::TextureFormat,
alpha_mode: wgpu::CompositeAlphaMode,
+ engine: Engine,
+ settings: Settings,
}
/// A compositor error.
@@ -167,15 +168,24 @@ impl Compositor {
match result {
Ok((device, queue)) => {
+ let engine = Engine::new(
+ &adapter,
+ &device,
+ &queue,
+ format,
+ settings.antialiasing,
+ );
+
return Ok(Compositor {
instance,
- settings,
adapter,
device,
queue,
format,
alpha_mode,
- })
+ engine,
+ settings,
+ });
}
Err(error) => {
errors.push((required_limits, error));
@@ -185,17 +195,6 @@ impl Compositor {
Err(Error::RequestDeviceFailed(errors))
}
-
- /// Creates a new rendering [`Backend`] for this [`Compositor`].
- pub fn create_backend(&self) -> Backend {
- Backend::new(
- &self.adapter,
- &self.device,
- &self.queue,
- self.settings,
- self.format,
- )
- }
}
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
@@ -210,9 +209,8 @@ pub async fn new<W: compositor::Window>(
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
pub fn present<T: AsRef<str>>(
compositor: &mut Compositor,
- backend: &mut Backend,
+ renderer: &mut Renderer,
surface: &mut wgpu::Surface<'static>,
- primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
@@ -229,21 +227,21 @@ pub fn present<T: AsRef<str>>(
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
- backend.present(
+ renderer.present(
+ &mut compositor.engine,
&compositor.device,
&compositor.queue,
&mut encoder,
Some(background_color),
frame.texture.format(),
view,
- primitives,
viewport,
overlay,
);
- // Submit work
- let _submission = compositor.queue.submit(Some(encoder.finish()));
- backend.recall();
+ let _ = compositor.engine.submit(&compositor.queue, encoder);
+
+ // Present the frame
frame.present();
Ok(())
@@ -292,11 +290,7 @@ impl graphics::Compositor for Compositor {
}
fn create_renderer(&self) -> Self::Renderer {
- Renderer::new(
- self.create_backend(),
- self.settings.default_font,
- self.settings.default_text_size,
- )
+ Renderer::new(self.settings, &self.engine)
}
fn create_surface<W: compositor::Window>(
@@ -328,7 +322,7 @@ impl graphics::Compositor for Compositor {
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.format,
- present_mode: self.settings.present_mode,
+ present_mode: wgpu::PresentMode::Immediate,
width,
height,
alpha_mode: self.alpha_mode,
@@ -355,17 +349,7 @@ impl graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
- renderer.with_primitives(|backend, primitives| {
- present(
- self,
- backend,
- surface,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- })
+ present(self, renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
@@ -376,16 +360,7 @@ impl graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
- renderer.with_primitives(|backend, primitives| {
- screenshot(
- self,
- backend,
- primitives,
- viewport,
- background_color,
- overlay,
- )
- })
+ screenshot(self, renderer, viewport, background_color, overlay)
}
}
@@ -393,19 +368,12 @@ impl graphics::Compositor for Compositor {
///
/// Returns RGBA bytes of the texture data.
pub fn screenshot<T: AsRef<str>>(
- compositor: &Compositor,
- backend: &mut Backend,
- primitives: &[Primitive],
+ compositor: &mut Compositor,
+ renderer: &mut Renderer,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
- let mut encoder = compositor.device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor {
- label: Some("iced_wgpu.offscreen.encoder"),
- },
- );
-
let dimensions = BufferDimensions::new(viewport.physical_size());
let texture_extent = wgpu::Extent3d {
@@ -429,14 +397,20 @@ pub fn screenshot<T: AsRef<str>>(
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
- backend.present(
+ let mut encoder = compositor.device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor {
+ label: Some("iced_wgpu.offscreen.encoder"),
+ },
+ );
+
+ renderer.present(
+ &mut compositor.engine,
&compositor.device,
&compositor.queue,
&mut encoder,
Some(background_color),
texture.format(),
&view,
- primitives,
viewport,
overlay,
);
@@ -474,7 +448,7 @@ pub fn screenshot<T: AsRef<str>>(
texture_extent,
);
- let index = compositor.queue.submit(Some(encoder.finish()));
+ let index = compositor.engine.submit(&compositor.queue, encoder);
let slice = output_buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| {});
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 84e9ac15..668c5372 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -651,7 +651,7 @@ where
defaults: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
- _viewport: &Rectangle,
+ viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
@@ -767,8 +767,8 @@ where
renderer.with_layer(
Rectangle {
- width: bounds.width + 2.0,
- height: bounds.height + 2.0,
+ width: (bounds.width + 2.0).min(viewport.width),
+ height: (bounds.height + 2.0).min(viewport.height),
..bounds
},
|renderer| {