diff options
author | 2024-04-05 23:59:21 +0200 | |
---|---|---|
committer | 2024-04-05 23:59:21 +0200 | |
commit | 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 (patch) | |
tree | b1a14b0ec7b2da4368d5c98850fe9e9eebc5490a | |
parent | 4a356cfc16f3b45d64826732009d9feeac016b28 (diff) | |
download | iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.tar.gz iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.tar.bz2 iced-6d3e1d835e1688fbc58622a03a784ed25ed3f0e1.zip |
Decouple caching from layering and simplify everything
-rw-r--r-- | core/src/rectangle.rs | 5 | ||||
-rw-r--r-- | examples/geometry/src/main.rs | 2 | ||||
-rw-r--r-- | graphics/src/geometry/frame.rs | 16 | ||||
-rw-r--r-- | graphics/src/mesh.rs | 16 | ||||
-rw-r--r-- | graphics/src/text.rs | 4 | ||||
-rw-r--r-- | renderer/src/fallback.rs | 8 | ||||
-rw-r--r-- | tiny_skia/src/geometry.rs | 4 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 188 | ||||
-rw-r--r-- | wgpu/src/image/mod.rs | 7 | ||||
-rw-r--r-- | wgpu/src/layer.rs | 258 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 444 | ||||
-rw-r--r-- | wgpu/src/quad.rs | 188 | ||||
-rw-r--r-- | wgpu/src/text.rs | 427 | ||||
-rw-r--r-- | wgpu/src/triangle.rs | 503 | ||||
-rw-r--r-- | widget/src/text_input.rs | 9 |
15 files changed, 888 insertions, 1191 deletions
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 45acd5ac..446d3769 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -33,9 +33,12 @@ where } impl Rectangle<f32> { + /// A rectangle starting at [`Point::ORIGIN`] with infinite width and height. + pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY); + /// 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 { + pub const fn new(top_left: Point, size: Size) -> Self { Self { x: top_left.x, y: top_left.y, diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9532a24a..31b8a4ce 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -82,7 +82,6 @@ mod rainbow { let posn_l = [0.0, bounds.height / 2.0]; let mesh = Mesh::Solid { - size: bounds.size(), buffers: mesh::Indexed { vertices: vec![ SolidVertex2D { @@ -134,6 +133,7 @@ mod rainbow { ], }, transformation: Transformation::IDENTITY, + clip_bounds: Rectangle::INFINITE, }; renderer.with_translation( diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs index ad35e8ec..377589d7 100644 --- a/graphics/src/geometry/frame.rs +++ b/graphics/src/geometry/frame.rs @@ -113,13 +113,11 @@ where region: Rectangle, f: impl FnOnce(&mut Self) -> R, ) -> R { - let mut frame = self.draft(region.size()); + let mut frame = self.draft(region); let result = f(&mut frame); - let origin = Point::new(region.x, region.y); - - self.paste(frame, origin); + self.paste(frame, Point::new(region.x, region.y)); result } @@ -129,14 +127,14 @@ where /// Draw its contents back to this [`Frame`] with [`paste`]. /// /// [`paste`]: Self::paste - pub fn draft(&mut self, size: Size) -> Self { + fn draft(&mut self, clip_bounds: Rectangle) -> Self { Self { - raw: self.raw.draft(size), + raw: self.raw.draft(clip_bounds), } } /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn paste(&mut self, frame: Self, at: Point) { + fn paste(&mut self, frame: Self, at: Point) { self.raw.paste(frame.raw, at); } @@ -187,7 +185,7 @@ pub trait Backend: Sized { fn scale(&mut self, scale: impl Into<f32>); fn scale_nonuniform(&mut self, scale: impl Into<Vector>); - fn draft(&mut self, size: Size) -> Self; + fn draft(&mut self, clip_bounds: Rectangle) -> Self; fn paste(&mut self, frame: Self, at: Point); fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); @@ -232,7 +230,7 @@ impl Backend for () { fn scale(&mut self, _scale: impl Into<f32>) {} fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} - fn draft(&mut self, _size: Size) -> Self {} + fn draft(&mut self, _clip_bounds: Rectangle) -> Self {} fn paste(&mut self, _frame: Self, _at: Point) {} fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {} diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index d3e7ffaf..76602319 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -1,6 +1,6 @@ //! Draw triangles! use crate::color; -use crate::core::{Rectangle, Size, Transformation}; +use crate::core::{Rectangle, Transformation}; use crate::gradient; use bytemuck::{Pod, Zeroable}; @@ -16,8 +16,8 @@ pub enum Mesh { /// The [`Transformation`] for the vertices of the [`Mesh`]. transformation: Transformation, - /// The [`Size`] of the [`Mesh`]. - size: Size, + /// The clip bounds of the [`Mesh`]. + clip_bounds: Rectangle, }, /// A mesh with a gradient. Gradient { @@ -27,8 +27,8 @@ pub enum Mesh { /// The [`Transformation`] for the vertices of the [`Mesh`]. transformation: Transformation, - /// The [`Size`] of the [`Mesh`]. - size: Size, + /// The clip bounds of the [`Mesh`]. + clip_bounds: Rectangle, }, } @@ -53,15 +53,15 @@ impl Mesh { pub fn clip_bounds(&self) -> Rectangle { match self { Self::Solid { - size, + clip_bounds, transformation, .. } | Self::Gradient { - size, + clip_bounds, transformation, .. - } => Rectangle::with_size(*size) * *transformation, + } => *clip_bounds * *transformation, } } } diff --git a/graphics/src/text.rs b/graphics/src/text.rs index c9c821c0..f9fc1fec 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -11,7 +11,7 @@ pub use cosmic_text; use crate::core::alignment; use crate::core::font::{self, Font}; -use crate::core::text::{LineHeight, Shaping}; +use crate::core::text::Shaping; use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation}; use once_cell::sync::OnceCell; @@ -50,7 +50,7 @@ pub enum Text { /// The size of the text in logical pixels. size: Pixels, /// The line height of the text. - line_height: LineHeight, + line_height: Pixels, /// The font of the text. font: Font, /// The horizontal alignment of the text. diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index b6459243..60b57604 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -405,7 +405,7 @@ where #[cfg(feature = "geometry")] mod geometry { use super::Renderer; - use crate::core::{Point, Radians, Size, Vector}; + use crate::core::{Point, Radians, Rectangle, Size, Vector}; use crate::graphics::geometry::{self, Fill, Path, Stroke, Text}; use crate::graphics::Cached; @@ -533,10 +533,10 @@ mod geometry { delegate!(self, frame, frame.pop_transform()); } - fn draft(&mut self, size: Size) -> Self { + fn draft(&mut self, bounds: Rectangle) -> Self { match self { - Self::Left(frame) => Self::Left(frame.draft(size)), - Self::Right(frame) => Self::Right(frame.draft(size)), + Self::Left(frame) => Self::Left(frame.draft(bounds)), + Self::Right(frame) => Self::Right(frame.draft(bounds)), } } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 76482e12..f1d11dce 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -195,8 +195,8 @@ impl geometry::frame::Backend for Frame { self.transform = self.stack.pop().expect("Pop transform"); } - fn draft(&mut self, size: Size) -> Self { - Self::new(size) + fn draft(&mut self, clip_bounds: Rectangle) -> Self { + Self::new(clip_bounds.size()) } fn paste(&mut self, frame: Self, at: Point) { diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 611e81f1..c8c350c5 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,40 +6,44 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached}; -use crate::layer; +use crate::graphics::{self, Cached, Text}; use crate::text; +use crate::triangle; 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, + clip_bounds: Rectangle, buffers: BufferStack, - layers: Vec<layer::Live>, - text: text::Batch, + meshes: Vec<Mesh>, + text: Vec<Text>, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, stroke_tessellator: tessellation::StrokeTessellator, } pub enum Geometry { - Live(Vec<layer::Live>), - Cached(Rc<[Rc<RefCell<layer::Cached>>]>), + Live { meshes: Vec<Mesh>, text: Vec<Text> }, + Cached(Cache), +} + +#[derive(Clone)] +pub struct Cache { + pub meshes: triangle::Cache, + pub text: text::Cache, } impl Cached for Geometry { - type Cache = Rc<[Rc<RefCell<layer::Cached>>]>; + type Cache = Cache; fn load(cache: &Self::Cache) -> Self { Geometry::Cached(cache.clone()) @@ -47,31 +51,18 @@ impl Cached for Geometry { 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::Live { meshes, text } => { + if let Some(mut previous) = previous { + previous.meshes.update(meshes); + previous.text.update(text); + + previous + } else { + Cache { + meshes: triangle::Cache::new(meshes), + text: text::Cache::new(text), + } + } } Self::Cached(cache) => cache, } @@ -81,69 +72,26 @@ impl Cached for Geometry { impl Frame { /// Creates a new [`Frame`] with the given [`Size`]. pub fn new(size: Size) -> Frame { + Self::with_clip(Rectangle::with_size(size)) + } + + /// Creates a new [`Frame`] with the given clip bounds. + pub fn with_clip(bounds: Rectangle) -> Frame { Frame { - size, + clip_bounds: bounds, buffers: BufferStack::new(), - layers: Vec::new(), - text: text::Batch::new(), + meshes: Vec::new(), + text: Vec::new(), transforms: Transforms { previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), + current: Transform(lyon::math::Transform::translation( + bounds.x, bounds.y, + )), }, fill_tessellator: tessellation::FillTessellator::new(), stroke_tessellator: tessellation::StrokeTessellator::new(), } } - - 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() - .filter_map(|buffer| match buffer { - Buffer::Solid(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }) - } - Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - transformation: Transformation::IDENTITY, - size: self.size, - }) - } - _ => None, - }) - .collect(); - - let layer = layer::Live { - bounds: Some(clip_bounds), - transformation, - meshes, - text: self.text, - ..layer::Live::default() - }; - - self.layers.push(layer); - } - - self.layers - } } impl geometry::frame::Backend for Frame { @@ -151,22 +99,22 @@ impl geometry::frame::Backend for Frame { #[inline] fn width(&self) -> f32 { - self.size.width + self.clip_bounds.width } #[inline] fn height(&self) -> f32 { - self.size.height + self.clip_bounds.height } #[inline] fn size(&self) -> Size { - self.size + self.clip_bounds.size() } #[inline] fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) + Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0) } fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { @@ -269,7 +217,7 @@ impl geometry::frame::Backend for Frame { .expect("Stroke path"); } - fn fill_text(&mut self, text: impl Into<Text>) { + fn fill_text(&mut self, text: impl Into<geometry::Text>) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -312,12 +260,12 @@ impl geometry::frame::Backend for Frame { bounds, color: text.color, size, - line_height, + line_height: line_height.to_absolute(size), font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, shaping: text.shaping, - clip_bounds: Rectangle::with_size(Size::INFINITY), + clip_bounds: self.clip_bounds, }); } else { text.draw_with(|path, color| self.fill(&path, color)); @@ -368,22 +316,25 @@ impl geometry::frame::Backend for Frame { self.transforms.current = self.transforms.previous.pop().unwrap(); } - fn draft(&mut self, size: Size) -> Frame { - Frame::new(size) + fn draft(&mut self, clip_bounds: Rectangle) -> Frame { + Frame::with_clip(clip_bounds) } - fn paste(&mut self, frame: Frame, at: Point) { - let translation = Transformation::translate(at.x, at.y); + fn paste(&mut self, frame: Frame, _at: Point) { + self.meshes + .extend(frame.buffers.into_meshes(frame.clip_bounds)); - self.layers - .extend(frame.into_layers().into_iter().map(|mut layer| { - layer.transformation = layer.transformation * translation; - layer - })); + self.text.extend(frame.text); } - fn into_geometry(self) -> Self::Geometry { - Geometry::Live(self.into_layers()) + fn into_geometry(mut self) -> Self::Geometry { + self.meshes + .extend(self.buffers.into_meshes(self.clip_bounds)); + + Geometry::Live { + meshes: self.meshes, + text: self.text, + } } } @@ -469,6 +420,27 @@ impl BufferStack { _ => unreachable!(), } } + + fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> { + self.stack.into_iter().map(move |buffer| match buffer { + Buffer::Solid(buffer) => Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }, + Buffer::Gradient(buffer) => Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }, + }) + } } #[derive(Debug)] diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 88e6bdb9..86731cbf 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -9,7 +9,6 @@ mod raster; #[cfg(feature = "svg")] mod vector; -use crate::core::image; use crate::core::{Rectangle, Size, Transformation}; use crate::Buffer; @@ -234,10 +233,12 @@ impl Pipeline { [bounds.width, bounds.height], atlas_entry, match filter_method { - image::FilterMethod::Nearest => { + crate::core::image::FilterMethod::Nearest => { nearest_instances } - image::FilterMethod::Linear => linear_instances, + crate::core::image::FilterMethod::Linear => { + linear_instances + } }, ); } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index d415da72..4c864cb0 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -8,39 +8,46 @@ use crate::quad::{self, Quad}; use crate::text::{self, Text}; use crate::triangle; -use std::cell::{self, RefCell}; -use std::rc::Rc; - -pub enum Layer<'a> { - Live(&'a Live), - Cached(Transformation, cell::Ref<'a, Cached>), +pub struct Layer { + pub bounds: Rectangle, + pub quads: quad::Batch, + pub triangles: triangle::Batch, + pub text: text::Batch, + pub images: image::Batch, } -pub enum LayerMut<'a> { - Live(&'a mut Live), - Cached(Transformation, cell::RefMut<'a, Cached>), +impl Default for Layer { + fn default() -> Self { + Self { + bounds: Rectangle::INFINITE, + quads: quad::Batch::default(), + triangles: triangle::Batch::default(), + text: text::Batch::default(), + images: image::Batch::default(), + } + } } pub struct Stack { - live: Vec<Live>, - cached: Vec<(Transformation, Rc<RefCell<Cached>>)>, - order: Vec<Kind>, + layers: Vec<Layer>, transformations: Vec<Transformation>, previous: Vec<usize>, + pending_meshes: Vec<Vec<Mesh>>, + pending_text: Vec<Vec<Text>>, current: usize, - live_count: usize, + active_count: usize, } impl Stack { pub fn new() -> Self { Self { - live: vec![Live::default()], - cached: Vec::new(), - order: vec![Kind::Live], + layers: vec![Layer::default()], transformations: vec![Transformation::IDENTITY], - previous: Vec::new(), + previous: vec![], + pending_meshes: vec![Vec::new()], + pending_text: vec![Vec::new()], current: 0, - live_count: 1, + active_count: 1, } } @@ -59,7 +66,7 @@ impl Stack { shadow_blur_radius: quad.shadow.blur_radius, }; - self.live[self.current].quads.add(quad, &background); + self.layers[self.current].quads.add(quad, &background); } pub fn draw_paragraph( @@ -77,7 +84,7 @@ impl Stack { transformation: self.transformations.last().copied().unwrap(), }; - self.live[self.current].text.push(paragraph); + self.pending_text[self.current].push(paragraph); } pub fn draw_editor( @@ -87,7 +94,7 @@ impl Stack { color: Color, clip_bounds: Rectangle, ) { - let paragraph = Text::Editor { + let editor = Text::Editor { editor: editor.downgrade(), position, color, @@ -95,7 +102,7 @@ impl Stack { transformation: self.transformation(), }; - self.live[self.current].text.push(paragraph); + self.pending_text[self.current].push(editor); } pub fn draw_text( @@ -107,12 +114,13 @@ impl Stack { ) { let transformation = self.transformation(); - let paragraph = Text::Cached { + let text = Text::Cached { content: text.content, bounds: Rectangle::new(position, text.bounds) * transformation, color, size: text.size * transformation.scale_factor(), - line_height: text.line_height, + line_height: text.line_height.to_absolute(text.size) + * transformation.scale_factor(), font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, @@ -120,7 +128,7 @@ impl Stack { clip_bounds: clip_bounds * transformation, }; - self.live[self.current].text.push(paragraph); + self.pending_text[self.current].push(text); } pub fn draw_image( @@ -135,7 +143,7 @@ impl Stack { bounds: bounds * self.transformation(), }; - self.live[self.current].images.push(image); + self.layers[self.current].images.push(image); } pub fn draw_svg( @@ -150,7 +158,7 @@ impl Stack { bounds: bounds * self.transformation(), }; - self.live[self.current].images.push(svg); + self.layers[self.current].images.push(svg); } pub fn draw_mesh(&mut self, mut mesh: Mesh) { @@ -161,51 +169,86 @@ impl Stack { } } - self.live[self.current].meshes.push(mesh); + self.pending_meshes[self.current].push(mesh); } - pub fn draw_layer(&mut self, mut layer: Live) { - layer.transformation = layer.transformation * self.transformation(); + pub fn draw_mesh_group(&mut self, meshes: Vec<Mesh>) { + self.flush_pending(); - if self.live_count == self.live.len() { - self.live.push(layer); - } else { - self.live[self.live_count] = layer; - } + let transformation = self.transformation(); - self.live_count += 1; - self.order.push(Kind::Live); + self.layers[self.current] + .triangles + .push(triangle::Item::Group { + transformation, + meshes, + }); } - pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) { - self.cached.push((self.transformation(), layer.clone())); - self.order.push(Kind::Cache); + pub fn draw_mesh_cache(&mut self, cache: triangle::Cache) { + self.flush_pending(); + + let transformation = self.transformation(); + + self.layers[self.current] + .triangles + .push(triangle::Item::Cached { + transformation, + cache, + }); + } + + pub fn draw_text_group(&mut self, text: Vec<Text>) { + self.flush_pending(); + + let transformation = self.transformation(); + + self.layers[self.current].text.push(text::Item::Group { + transformation, + text, + }); + } + + pub fn draw_text_cache(&mut self, cache: text::Cache) { + self.flush_pending(); + + let transformation = self.transformation(); + + self.layers[self.current].text.push(text::Item::Cached { + transformation, + cache, + }); } - pub fn push_clip(&mut self, bounds: Option<Rectangle>) { + pub fn push_clip(&mut self, bounds: Rectangle) { self.previous.push(self.current); - self.order.push(Kind::Live); - self.current = self.live_count; - self.live_count += 1; + self.current = self.active_count; + self.active_count += 1; - let bounds = bounds.map(|bounds| bounds * self.transformation()); + let bounds = bounds * self.transformation(); - if self.current == self.live.len() { - self.live.push(Live { + if self.current == self.layers.len() { + self.layers.push(Layer { bounds, - ..Live::default() + ..Layer::default() }); + self.pending_meshes.push(Vec::new()); + self.pending_text.push(Vec::new()); } else { - self.live[self.current].bounds = bounds; + self.layers[self.current].bounds = bounds; } } pub fn pop_clip(&mut self) { + self.flush_pending(); + self.current = self.previous.pop().unwrap(); } pub fn push_transformation(&mut self, transformation: Transformation) { + self.flush_pending(); + self.transformations .push(self.transformation() * transformation); } @@ -218,109 +261,62 @@ impl Stack { 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 => { - let (transformation, layer) = cached.next().unwrap(); - let layer = layer.borrow_mut(); + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Layer> { + self.flush_pending(); - LayerMut::Cached(*transformation * layer.transformation, layer) - } - }) + self.layers[..self.active_count].iter_mut() } - 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 => { - let (transformation, layer) = cached.next().unwrap(); - let layer = layer.borrow(); - - Layer::Cached(*transformation * layer.transformation, layer) - } - }) + pub fn iter(&self) -> impl Iterator<Item = &Layer> { + self.layers[..self.active_count].iter() } pub fn clear(&mut self) { - for live in &mut self.live[..self.live_count] { - live.bounds = None; - live.transformation = Transformation::IDENTITY; + for (live, pending_meshes) in self.layers[..self.active_count] + .iter_mut() + .zip(self.pending_meshes.iter_mut()) + { + live.bounds = Rectangle::INFINITE; live.quads.clear(); - live.meshes.clear(); + live.triangles.clear(); live.text.clear(); live.images.clear(); + pending_meshes.clear(); } self.current = 0; - self.live_count = 1; - - self.order.clear(); - self.order.push(Kind::Live); - - self.cached.clear(); + self.active_count = 1; self.previous.clear(); } -} -impl Default for Stack { - fn default() -> Self { - Self::new() - } -} + // We want to keep the allocated memory + #[allow(clippy::drain_collect)] + fn flush_pending(&mut self) { + let transformation = self.transformation(); -#[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, -} + let pending_meshes = &mut self.pending_meshes[self.current]; + if !pending_meshes.is_empty() { + self.layers[self.current] + .triangles + .push(triangle::Item::Group { + transformation, + meshes: pending_meshes.drain(..).collect(), + }); + } -impl Live { - pub fn into_cached(self) -> Cached { - Cached { - bounds: self.bounds, - transformation: self.transformation, - quads: quad::Cache::Staged(self.quads), - meshes: triangle::Cache::Staged(self.meshes), - text: text::Cache::Staged(self.text), - images: self.images, + let pending_text = &mut self.pending_text[self.current]; + if !pending_text.is_empty() { + self.layers[self.current].text.push(text::Item::Group { + transformation, + text: pending_text.drain(..).collect(), + }); } } } -#[derive(Default)] -pub struct Cached { - pub bounds: Option<Rectangle>, - pub transformation: 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.quads.update(live.quads); - self.meshes.update(live.meshes); - self.text.update(live.text); - self.images = live.images; +impl Default for Stack { + fn default() -> Self { + Self::new() } } - -enum Kind { - Live, - Cache, -} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4705cfa0..d632919f 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -60,7 +60,7 @@ pub use iced_graphics::core; pub use wgpu; pub use engine::Engine; -pub use layer::{Layer, LayerMut}; +pub use layer::Layer; pub use primitive::Primitive; pub use settings::Settings; @@ -85,6 +85,9 @@ pub struct Renderer { default_text_size: Pixels, layers: layer::Stack, + triangle_storage: triangle::Storage, + text_storage: text::Storage, + // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] image_cache: image::cache::Shared, @@ -97,6 +100,9 @@ impl Renderer { default_text_size: settings.default_text_size, layers: layer::Stack::new(), + triangle_storage: triangle::Storage::new(), + text_storage: text::Storage::new(), + #[cfg(any(feature = "svg", feature = "image"))] image_cache: _engine.image_cache().clone(), } @@ -117,9 +123,11 @@ impl Renderer { overlay: &[T], ) { self.draw_overlay(overlay, viewport); - self.prepare(engine, device, queue, format, encoder, viewport); self.render(engine, device, encoder, frame, clear_color, viewport); + + self.triangle_storage.trim(); + self.text_storage.trim(); } fn prepare( @@ -134,116 +142,51 @@ impl Renderer { let scale_factor = viewport.scale_factor() as f32; 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, - viewport.projection(), - scale_factor, - ); - } - - if !live.meshes.is_empty() { - engine.triangle_pipeline.prepare_batch( - device, - encoder, - &mut engine.staging_belt, - &live.meshes, - viewport.projection() - * 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( - viewport.logical_size(), - )), - live.transformation - * Transformation::scale(scale_factor), - viewport.physical_size(), - ); - } - - #[cfg(any(feature = "svg", feature = "image"))] - if !live.images.is_empty() { - engine.image_pipeline.prepare( - device, - encoder, - &mut engine.staging_belt, - &live.images, - viewport.projection(), - scale_factor, - ); - } - } - LayerMut::Cached(layer_transformation, mut cached) => { - if !cached.quads.is_empty() { - engine.quad_pipeline.prepare_cache( - device, - encoder, - &mut engine.staging_belt, - &mut cached.quads, - viewport.projection(), - scale_factor, - ); - } - - if !cached.meshes.is_empty() { - let transformation = - Transformation::scale(scale_factor) - * layer_transformation; - - engine.triangle_pipeline.prepare_cache( - device, - encoder, - &mut engine.staging_belt, - &mut cached.meshes, - viewport.projection(), - transformation, - ); - } - - if !cached.text.is_empty() { - let bounds = cached.bounds.unwrap_or( - Rectangle::with_size(viewport.logical_size()), - ); - - let transformation = - Transformation::scale(scale_factor) - * layer_transformation; - - engine.text_pipeline.prepare_cache( - device, - queue, - encoder, - &mut cached.text, - bounds, - transformation, - viewport.physical_size(), - ); - } - - #[cfg(any(feature = "svg", feature = "image"))] - if !cached.images.is_empty() { - engine.image_pipeline.prepare( - device, - encoder, - &mut engine.staging_belt, - &cached.images, - viewport.projection(), - scale_factor, - ); - } - } + if !layer.quads.is_empty() { + engine.quad_pipeline.prepare( + device, + encoder, + &mut engine.staging_belt, + &layer.quads, + viewport.projection(), + scale_factor, + ); + } + + if !layer.triangles.is_empty() { + engine.triangle_pipeline.prepare( + device, + encoder, + &mut engine.staging_belt, + &mut self.triangle_storage, + &layer.triangles, + viewport.projection() * Transformation::scale(scale_factor), + ); + } + + if !layer.text.is_empty() { + engine.text_pipeline.prepare( + device, + queue, + encoder, + &mut self.text_storage, + &layer.text, + layer.bounds, + Transformation::scale(scale_factor), + viewport.physical_size(), + ); + } + + #[cfg(any(feature = "svg", feature = "image"))] + if !layer.images.is_empty() { + engine.image_pipeline.prepare( + device, + encoder, + &mut engine.staging_belt, + &layer.images, + viewport.projection(), + scale_factor, + ); } } } @@ -297,208 +240,87 @@ impl Renderer { #[cfg(any(feature = "svg", feature = "image"))] let mut image_layer = 0; - // TODO: Can we avoid collecting here? let scale_factor = viewport.scale_factor() as f32; - let screen_bounds = Rectangle::with_size(viewport.logical_size()); let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size( viewport.physical_size(), )); - let layers: Vec<_> = self.layers.iter().collect(); - let mut i = 0; + let scale = Transformation::scale(scale_factor); - // println!("RENDER"); + for layer in self.layers.iter() { + let Some(physical_bounds) = + physical_bounds.intersection(&(layer.bounds * scale)) + else { + continue; + }; - while i < layers.len() { - match layers[i] { - Layer::Live(live) => { - let layer_transformation = - Transformation::scale(scale_factor) - * live.transformation; + let scissor_rect = physical_bounds.snap(); - let layer_bounds = live.bounds.unwrap_or(screen_bounds); + if !layer.quads.is_empty() { + engine.quad_pipeline.render( + quad_layer, + scissor_rect, + &layer.quads, + &mut render_pass, + ); - let Some(physical_bounds) = physical_bounds - .intersection(&(layer_bounds * layer_transformation)) - .map(Rectangle::snap) - else { - continue; - }; + quad_layer += 1; + } - if !live.quads.is_empty() { - engine.quad_pipeline.render_batch( - quad_layer, - physical_bounds, - &live.quads, - &mut render_pass, - ); - - quad_layer += 1; - } - - if !live.meshes.is_empty() { - // println!("LIVE PASS"); - let _ = ManuallyDrop::into_inner(render_pass); - - engine.triangle_pipeline.render_batch( - device, - encoder, - frame, - mesh_layer, - viewport.physical_size(), - &live.meshes, - physical_bounds, - &layer_transformation, - ); - - 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, - physical_bounds, - &mut render_pass, - ); - - text_layer += 1; - } - - #[cfg(any(feature = "svg", feature = "image"))] - if !live.images.is_empty() { - engine.image_pipeline.render( - image_layer, - physical_bounds, - &mut render_pass, - ); - - image_layer += 1; - } - - i += 1; - } - Layer::Cached(_, _) => { - let group_len = layers[i..] - .iter() - .position(|layer| matches!(layer, Layer::Live(_))) - .unwrap_or(layers.len() - i); - - let group = - layers[i..i + group_len].iter().filter_map(|layer| { - let Layer::Cached(transformation, cached) = layer - else { - unreachable!() - }; - - let physical_bounds = cached - .bounds - .and_then(|bounds| { - physical_bounds.intersection( - &(bounds - * *transformation - * Transformation::scale( - scale_factor, - )), - ) - }) - .unwrap_or(physical_bounds) - .snap(); - - Some((cached, physical_bounds)) - }); - - for (cached, bounds) in group.clone() { - if !cached.quads.is_empty() { - engine.quad_pipeline.render_cache( - &cached.quads, - bounds, - &mut render_pass, - ); - } - } - - let group_has_meshes = group - .clone() - .any(|(cached, _)| !cached.meshes.is_empty()); - - if group_has_meshes { - // println!("CACHE PASS"); - let _ = ManuallyDrop::into_inner(render_pass); - - engine.triangle_pipeline.render_cache_group( - device, - encoder, - frame, - viewport.physical_size(), - group.clone().map(|(cached, bounds)| { - (&cached.meshes, bounds) - }), - ); - - 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 !layer.triangles.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + mesh_layer += engine.triangle_pipeline.render( + device, + encoder, + frame, + &self.triangle_storage, + mesh_layer, + &layer.triangles, + viewport.physical_size(), + physical_bounds, + scale, + ); + + 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, }, - )); - } - - for (cached, bounds) in group { - 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; - } - } - - i += group_len; - } + }, + )], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }, + )); + } + + if !layer.text.is_empty() { + text_layer += engine.text_pipeline.render( + &self.text_storage, + text_layer, + &layer.text, + scissor_rect, + &mut render_pass, + ); + } + + #[cfg(any(feature = "svg", feature = "image"))] + if !layer.images.is_empty() { + engine.image_pipeline.render( + image_layer, + scissor_rect, + &mut render_pass, + ); + + image_layer += 1; } } @@ -552,7 +374,7 @@ impl Renderer { impl core::Renderer for Renderer { fn start_layer(&mut self, bounds: Rectangle) { - self.layers.push_clip(Some(bounds)); + self.layers.push_clip(bounds); } fn end_layer(&mut self, _bounds: Rectangle) { @@ -690,15 +512,13 @@ impl graphics::geometry::Renderer for Renderer { fn draw_geometry(&mut self, geometry: Self::Geometry) { match geometry { - Geometry::Live(layers) => { - for layer in layers { - self.layers.draw_layer(layer); - } + Geometry::Live { meshes, text } => { + self.layers.draw_mesh_group(meshes); + self.layers.draw_text_group(text); } - Geometry::Cached(layers) => { - for layer in layers.as_ref() { - self.layers.draw_cached_layer(layer); - } + Geometry::Cached(cache) => { + self.layers.draw_mesh_cache(cache.meshes); + self.layers.draw_text_cache(cache.text); } } } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 16d50b04..de432d2f 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -80,7 +80,7 @@ impl Pipeline { } } - pub fn prepare_batch( + pub fn prepare( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, @@ -99,64 +99,7 @@ impl Pipeline { self.prepare_layer += 1; } - 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>( + pub fn render<'a>( &'a self, layer: usize, bounds: Rectangle<u32>, @@ -164,59 +107,38 @@ impl Pipeline { render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer) = self.layers.get(layer) { - 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; + 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; + } } } } @@ -228,48 +150,6 @@ impl Pipeline { } #[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)] pub struct Layer { constants: wgpu::BindGroup, constants_buffer: wgpu::Buffer, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a7695b74..e84e675d 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -4,245 +4,273 @@ use crate::graphics::color; use crate::graphics::text::cache::{self, Cache as BufferCache}; use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map; +use std::rc::Rc; +use std::sync::atomic::{self, AtomicU64}; use std::sync::Arc; pub use crate::graphics::Text; -pub type Batch = Vec<Text>; +const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION { + glyphon::ColorMode::Accurate +} else { + glyphon::ColorMode::Web +}; -#[allow(missing_debug_implementations)] -pub struct Pipeline { - format: wgpu::TextureFormat, - atlas: glyphon::TextAtlas, - renderers: Vec<glyphon::TextRenderer>, - prepare_layer: usize, - cache: BufferCache, -} +pub type Batch = Vec<Item>; -pub enum Cache { - Staged(Batch), - Uploaded { - batch: Batch, - renderer: glyphon::TextRenderer, - atlas: Option<glyphon::TextAtlas>, - buffer_cache: Option<BufferCache>, +#[derive(Debug)] +pub enum Item { + Group { transformation: Transformation, - target_size: Size<u32>, - needs_reupload: bool, + text: Vec<Text>, }, + Cached { + transformation: Transformation, + cache: Cache, + }, +} + +#[derive(Debug, Clone)] +pub struct Cache { + id: Id, + text: Rc<[Text]>, + version: usize, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + impl Cache { - pub fn is_empty(&self) -> bool { - match self { - Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { - batch.is_empty() - } + pub fn new(text: Vec<Text>) -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + + Self { + id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + text: Rc::from(text), + version: 0, } } - 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; - } - } + pub fn update(&mut self, text: Vec<Text>) { + self.text = Rc::from(text); + self.version += 1; } } -impl Default for Cache { - fn default() -> Self { - Self::Staged(Batch::default()) - } +struct Upload { + renderer: glyphon::TextRenderer, + atlas: glyphon::TextAtlas, + buffer_cache: BufferCache, + transformation: Transformation, + version: usize, } -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self { - Pipeline { - format, - renderers: Vec::new(), - atlas: glyphon::TextAtlas::with_color_mode( - device, - queue, - format, - if color::GAMMA_CORRECTION { - glyphon::ColorMode::Accurate - } else { - glyphon::ColorMode::Web - }, - ), - prepare_layer: 0, - cache: BufferCache::new(), - } +#[derive(Default)] +pub struct Storage { + uploads: FxHashMap<Id, Upload>, + recently_used: FxHashSet<Id>, +} + +impl Storage { + pub fn new() -> Self { + Self::default() + } + + fn get(&self, id: Id) -> Option<&Upload> { + self.uploads.get(&id) } - pub fn prepare_batch( + fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, - sections: &Batch, - layer_bounds: Rectangle, - layer_transformation: Transformation, + format: wgpu::TextureFormat, + cache: &Cache, + new_transformation: Transformation, + bounds: Rectangle, target_size: Size<u32>, ) { - if self.renderers.len() <= self.prepare_layer { - self.renderers.push(glyphon::TextRenderer::new( - &mut self.atlas, - device, - wgpu::MultisampleState::default(), - None, - )); - } + match self.uploads.entry(cache.id) { + hash_map::Entry::Occupied(entry) => { + let upload = entry.into_mut(); - let renderer = &mut self.renderers[self.prepare_layer]; - let result = prepare( - device, - queue, - encoder, - renderer, - &mut self.atlas, - &mut self.cache, - sections, - layer_bounds, - layer_transformation, - target_size, - ); + if upload.version != cache.version + || upload.transformation != new_transformation + { + let _ = prepare( + device, + queue, + encoder, + &mut upload.renderer, + &mut upload.atlas, + &mut upload.buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); - match result { - Ok(()) => { - self.prepare_layer += 1; - } - Err(glyphon::PrepareError::AtlasFull) => { - // If the atlas cannot grow, then all bets are off. - // Instead of panicking, we will just pray that the result - // will be somewhat readable... - } - } - } + upload.version = cache.version; + upload.transformation = new_transformation; - pub fn prepare_cache( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - cache: &mut Cache, - layer_bounds: Rectangle, - new_transformation: Transformation, - 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) - }; + upload.buffer_cache.trim(); + upload.atlas.trim(); + } + } + hash_map::Entry::Vacant(entry) => { + let mut atlas = glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ); let mut renderer = glyphon::TextRenderer::new( - atlas.as_mut().unwrap_or(&mut self.atlas), + &mut atlas, device, wgpu::MultisampleState::default(), None, ); + let mut buffer_cache = BufferCache::new(); + 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, + &mut atlas, + &mut buffer_cache, + &cache.text, + bounds, new_transformation, - new_target_size, + target_size, ); - *cache = Cache::Uploaded { - batch, - needs_reupload: false, + let _ = entry.insert(Upload { renderer, atlas, buffer_cache, transformation: new_transformation, - target_size: new_target_size, - } + version: 0, + }); } - Cache::Uploaded { - batch, - needs_reupload, - renderer, - atlas, - buffer_cache, - transformation, - target_size, - } => { - if *needs_reupload - || atlas.is_none() - || buffer_cache.is_none() - || new_transformation != *transformation - || new_target_size != *target_size - { - let _ = prepare( + } + + let _ = self.recently_used.insert(cache.id); + } + + pub fn trim(&mut self) { + self.uploads.retain(|id, _| self.recently_used.contains(id)); + self.recently_used.clear(); + } +} + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + format: wgpu::TextureFormat, + atlas: glyphon::TextAtlas, + renderers: Vec<glyphon::TextRenderer>, + prepare_layer: usize, + cache: BufferCache, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + ) -> Self { + Pipeline { + format, + renderers: Vec::new(), + atlas: glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ), + prepare_layer: 0, + cache: BufferCache::new(), + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + storage: &mut Storage, + batch: &Batch, + layer_bounds: Rectangle, + layer_transformation: Transformation, + target_size: Size<u32>, + ) { + for item in batch { + match item { + Item::Group { + transformation, + text, + } => { + if self.renderers.len() <= self.prepare_layer { + self.renderers.push(glyphon::TextRenderer::new( + &mut self.atlas, + device, + wgpu::MultisampleState::default(), + None, + )); + } + + let renderer = &mut self.renderers[self.prepare_layer]; + let result = prepare( device, queue, encoder, renderer, - atlas.as_mut().unwrap_or(&mut self.atlas), - buffer_cache.as_mut().unwrap_or(&mut self.cache), - batch, + &mut self.atlas, + &mut self.cache, + text, layer_bounds, - new_transformation, - new_target_size, + layer_transformation * *transformation, + target_size, ); - *transformation = new_transformation; - *target_size = new_target_size; - *needs_reupload = false; + match result { + Ok(()) => { + self.prepare_layer += 1; + } + Err(glyphon::PrepareError::AtlasFull) => { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... + } + } + } + Item::Cached { + transformation, + cache, + } => { + storage.prepare( + device, + queue, + encoder, + self.format, + cache, + layer_transformation * *transformation, + layer_bounds, + target_size, + ); } } } } - pub fn render_batch<'a>( + pub fn render<'a>( &'a self, - layer: usize, + storage: &'a Storage, + start: usize, + batch: &'a Batch, bounds: Rectangle<u32>, render_pass: &mut wgpu::RenderPass<'a>, - ) { - let renderer = &self.renderers[layer]; + ) -> usize { + let mut layer_count = 0; render_pass.set_scissor_rect( bounds.x, @@ -251,34 +279,29 @@ impl Pipeline { bounds.height, ); - renderer - .render(&self.atlas, render_pass) - .expect("Render text"); - } + for item in batch { + match item { + Item::Group { .. } => { + let renderer = &self.renderers[start + layer_count]; - 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; - }; + renderer + .render(&self.atlas, render_pass) + .expect("Render text"); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); + layer_count += 1; + } + Item::Cached { cache, .. } => { + if let Some(upload) = storage.get(cache.id) { + upload + .renderer + .render(&upload.atlas, render_pass) + .expect("Render cached text"); + } + } + } + } - renderer - .render(atlas.as_ref().unwrap_or(&self.atlas), render_pass) - .expect("Render text"); + layer_count } pub fn end_frame(&mut self) { @@ -296,7 +319,7 @@ fn prepare( renderer: &mut glyphon::TextRenderer, atlas: &mut glyphon::TextAtlas, buffer_cache: &mut BufferCache, - sections: &Batch, + sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, target_size: Size<u32>, @@ -333,8 +356,8 @@ fn prepare( font_system, cache::Key { content, - size: (*size).into(), - line_height: f32::from(line_height.to_absolute(*size)), + size: f32::from(*size), + line_height: f32::from(*line_height), font: *font, bounds: Size { width: bounds.width, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 3a184da2..a08b6987 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -6,10 +6,136 @@ use crate::graphics::mesh::{self, Mesh}; use crate::graphics::Antialiasing; use crate::Buffer; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map; +use std::rc::Rc; +use std::sync::atomic::{self, AtomicU64}; + const INITIAL_INDEX_COUNT: usize = 1_000; const INITIAL_VERTEX_COUNT: usize = 1_000; -pub type Batch = Vec<Mesh>; +pub type Batch = Vec<Item>; + +pub enum Item { + Group { + transformation: Transformation, + meshes: Vec<Mesh>, + }, + Cached { + transformation: Transformation, + cache: Cache, + }, +} + +#[derive(Debug, Clone)] +pub struct Cache { + id: Id, + batch: Rc<[Mesh]>, + version: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + +impl Cache { + pub fn new(meshes: Vec<Mesh>) -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + + Self { + id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + batch: Rc::from(meshes), + version: 0, + } + } + + pub fn update(&mut self, meshes: Vec<Mesh>) { + self.batch = Rc::from(meshes); + self.version += 1; + } +} + +#[derive(Debug)] +struct Upload { + layer: Layer, + transformation: Transformation, + version: usize, +} + +#[derive(Debug, Default)] +pub struct Storage { + uploads: FxHashMap<Id, Upload>, + recently_used: FxHashSet<Id>, +} + +impl Storage { + pub fn new() -> Self { + Self::default() + } + + fn get(&self, id: Id) -> Option<&Upload> { + self.uploads.get(&id) + } + + fn prepare( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + solid: &solid::Pipeline, + gradient: &gradient::Pipeline, + cache: &Cache, + new_transformation: Transformation, + ) { + match self.uploads.entry(cache.id) { + hash_map::Entry::Occupied(entry) => { + let upload = entry.into_mut(); + + if upload.version != cache.version + || upload.transformation != new_transformation + { + upload.layer.prepare( + device, + encoder, + belt, + solid, + gradient, + &cache.batch, + new_transformation, + ); + + upload.version = cache.version; + upload.transformation = new_transformation; + } + } + hash_map::Entry::Vacant(entry) => { + let mut layer = Layer::new(device, solid, gradient); + + layer.prepare( + device, + encoder, + belt, + solid, + gradient, + &cache.batch, + new_transformation, + ); + + let _ = entry.insert(Upload { + layer, + transformation: new_transformation, + version: 0, + }); + } + } + + let _ = self.recently_used.insert(cache.id); + } + + pub fn trim(&mut self) { + self.uploads.retain(|id, _| self.recently_used.contains(id)); + self.recently_used.clear(); + } +} #[derive(Debug)] pub struct Pipeline { @@ -35,180 +161,103 @@ impl Pipeline { } } - 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( + pub fn prepare( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, - cache: &mut Cache, - new_projection: Transformation, - new_transformation: Transformation, + storage: &mut Storage, + items: &[Item], + projection: Transformation, ) { - let new_projection = new_projection * new_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, - new_projection, - ); + for item in items { + match item { + Item::Group { + transformation, + meshes, + } => { + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.solid, + &self.gradient, + )); + } - *cache = Cache::Uploaded { - layer, - batch, - transformation: new_transformation, - projection: new_projection, - needs_reupload: false, - } - } - Cache::Uploaded { - batch, - layer, - transformation, - projection, - needs_reupload, - } => { - if *needs_reupload || new_projection != *projection { + let layer = &mut self.layers[self.prepare_layer]; layer.prepare( device, encoder, belt, &self.solid, &self.gradient, - batch, - new_projection, + meshes, + projection * *transformation, ); - *transformation = new_transformation; - *projection = new_projection; - *needs_reupload = false; + self.prepare_layer += 1; + } + Item::Cached { + transformation, + cache, + } => { + storage.prepare( + device, + encoder, + belt, + &self.solid, + &self.gradient, + cache, + projection * *transformation, + ); } } } } - 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>, - transformation: &Transformation, - ) { - Self::render( - device, - encoder, - target, - self.blit.as_mut(), - &self.solid, - &self.gradient, - target_size, - std::iter::once(( - &self.layers[layer], - meshes, - transformation, - bounds, - )), - ); - } - - #[allow(dead_code)] - pub fn render_cache( + pub fn render( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + storage: &Storage, + start: usize, + batch: &Batch, target_size: Size<u32>, - cache: &Cache, - bounds: Rectangle<u32>, - ) { - let Cache::Uploaded { - batch, - layer, - transformation, - .. - } = cache - else { - return; - }; + bounds: Rectangle, + screen_transformation: Transformation, + ) -> usize { + let mut layer_count = 0; - Self::render( - device, - encoder, - target, - self.blit.as_mut(), - &self.solid, - &self.gradient, - target_size, - std::iter::once((layer, batch, transformation, bounds)), - ); - } + let items = batch.iter().filter_map(|item| match item { + Item::Group { + transformation, + meshes, + } => { + let layer = &self.layers[start + layer_count]; + layer_count += 1; - pub fn render_cache_group<'a>( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - target_size: Size<u32>, - group: impl Iterator<Item = (&'a Cache, Rectangle<u32>)>, - ) { - let group = group.filter_map(|(cache, bounds)| { - if let Cache::Uploaded { - batch, - layer, + Some(( + layer, + meshes.as_slice(), + screen_transformation * *transformation, + )) + } + Item::Cached { transformation, - .. - } = cache - { - Some((layer, batch, transformation, bounds)) - } else { - None + cache, + } => { + let upload = storage.get(cache.id)?; + + Some(( + &upload.layer, + &cache.batch, + screen_transformation * *transformation, + )) } }); - Self::render( + render( device, encoder, target, @@ -216,71 +265,11 @@ impl Pipeline { &self.solid, &self.gradient, target_size, - group, + bounds, + items, ); - } - - fn render<'a>( - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - mut blit: Option<&mut msaa::Blit>, - solid: &solid::Pipeline, - gradient: &gradient::Pipeline, - target_size: Size<u32>, - group: impl Iterator< - Item = (&'a Layer, &'a Batch, &'a Transformation, Rectangle<u32>), - >, - ) { - { - 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, - }); - - for (layer, meshes, transformation, bounds) in group { - layer.render( - solid, - gradient, - meshes, - bounds, - *transformation, - &mut render_pass, - ); - } - } - if let Some(blit) = blit { - blit.draw(encoder, target); - } + layer_count } pub fn end_frame(&mut self) { @@ -288,47 +277,61 @@ impl Pipeline { } } -#[derive(Debug)] -pub enum Cache { - Staged(Batch), - Uploaded { - batch: Batch, - layer: Layer, - transformation: Transformation, - projection: Transformation, - needs_reupload: bool, - }, -} - -impl Cache { - pub fn is_empty(&self) -> bool { - match self { - Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { - batch.is_empty() - } - } - } +fn render<'a>( + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + mut blit: Option<&mut msaa::Blit>, + solid: &solid::Pipeline, + gradient: &gradient::Pipeline, + target_size: Size<u32>, + bounds: Rectangle, + group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>, +) { + { + 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) + }; - 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; - } + 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, + }); + + for (layer, meshes, transformation) in group { + layer.render( + solid, + gradient, + meshes, + bounds, + transformation, + &mut render_pass, + ); } } -} -impl Default for Cache { - fn default() -> Self { - Self::Staged(Batch::default()) + if let Some(blit) = blit { + blit.draw(encoder, target); } } @@ -366,7 +369,7 @@ impl Layer { belt: &mut wgpu::util::StagingBelt, solid: &solid::Pipeline, gradient: &gradient::Pipeline, - meshes: &Batch, + meshes: &[Mesh], transformation: Transformation, ) { // Count the total amount of vertices & indices we need to handle @@ -471,8 +474,8 @@ impl Layer { &'a self, solid: &'a solid::Pipeline, gradient: &'a gradient::Pipeline, - meshes: &Batch, - layer_bounds: Rectangle<u32>, + meshes: &[Mesh], + bounds: Rectangle, transformation: Transformation, render_pass: &mut wgpu::RenderPass<'a>, ) { @@ -481,8 +484,8 @@ impl Layer { let mut last_is_solid = None; for (index, mesh) in meshes.iter().enumerate() { - let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds) - .intersection(&(mesh.clip_bounds() * transformation)) + let Some(clip_bounds) = + bounds.intersection(&(mesh.clip_bounds() * transformation)) else { continue; }; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 8cfb0408..05dd87b1 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -368,7 +368,7 @@ where let text = value.to_string(); - let (cursor, offset) = if let Some(focus) = state + let (cursor, offset, is_selecting) = if let Some(focus) = state .is_focused .as_ref() .filter(|focus| focus.is_window_focused) @@ -406,7 +406,7 @@ where None }; - (cursor, offset) + (cursor, offset, false) } cursor::State::Selection { start, end } => { let left = start.min(end); @@ -446,11 +446,12 @@ where } else { left_offset }, + true, ) } } } else { - (None, 0.0) + (None, 0.0, false) }; let draw = |renderer: &mut Renderer, viewport| { @@ -482,7 +483,7 @@ where ); }; - if cursor.is_some() { + if is_selecting { renderer .with_layer(text_bounds, |renderer| draw(renderer, *viewport)); } else { |