diff options
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| { |