From ff409ce66c03f45e13fe7db583925efb5113ce21 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 20 Mar 2024 16:40:14 +0100 Subject: Fix empty `wgpu` draw calls in `image` pipeline --- wgpu/src/image.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c8e4a4c2..067b77ab 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -161,13 +161,21 @@ impl Data { queue: &wgpu::Queue, instances: &[Instance], ) { + self.instance_count = instances.len(); + + if self.instance_count == 0 { + return; + } + let _ = self.instances.resize(device, instances.len()); let _ = self.instances.write(queue, 0, instances); - - self.instance_count = instances.len(); } 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(..)); -- cgit From a6130790832c1e30d138ebebafce7065f957cc96 Mon Sep 17 00:00:00 2001 From: Daniel Yoon <101683475+Koranir@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:27:36 +1100 Subject: Revert "Remove `PreMultiplied` alpha mode selection in `wgpu::window::compositor`" This reverts commit 33066bca1af6c67e5188c0481403f28afabcbe1f. --- wgpu/src/window/compositor.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 328ad781..fa6b9373 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -92,6 +92,10 @@ impl Compositor { .contains(&wgpu::CompositeAlphaMode::PostMultiplied) { wgpu::CompositeAlphaMode::PostMultiplied + } else if alpha_modes + .contains(&wgpu::CompositeAlphaMode::PreMultiplied) + { + wgpu::CompositeAlphaMode::PreMultiplied } else { wgpu::CompositeAlphaMode::Auto }; -- cgit From 3645d34d6a1ba1247238e830e9eefd52d9e5b986 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 21 Mar 2024 22:27:17 +0100 Subject: Implement composable, type-safe renderer fallback --- wgpu/src/backend.rs | 9 + wgpu/src/geometry.rs | 493 ++++++++++++++++++++++---------------------------- wgpu/src/primitive.rs | 8 + 3 files changed, 234 insertions(+), 276 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 09ddbe4d..924aacf1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -397,3 +397,12 @@ impl backend::Svg for Backend { 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/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..4f6b67b1 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,7 +6,7 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; @@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; + use std::borrow::Cow; /// A frame for drawing some geometry. @@ -27,191 +28,87 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } -enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient(tessellation::VertexBuffers), -} - -struct BufferStack { - stack: Vec, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(_) => match self.stack.last() { - Some(Buffer::Gradient(_)) => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - )); - } +impl Frame { + /// Creates a new [`Frame`] with the given [`Size`]. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform(lyon::math::Transform::identity()), }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), } - - self.stack.last_mut().unwrap() } - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) + fn into_primitives(mut self) -> Vec { + 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, + }), + )); + } + } } - _ => unreachable!(), } - } - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } + self.primitives } } -#[derive(Debug)] -struct Transforms { - previous: Vec, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } +impl geometry::Frame for Frame { + type Geometry = Primitive; - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } -} - -impl Frame { /// 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. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - /// Returns the width of the [`Frame`]. #[inline] - pub fn width(&self) -> f32 { + fn width(&self) -> f32 { self.size.width } - /// Returns the height of the [`Frame`]. #[inline] - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - /// Returns the dimensions of the [`Frame`]. #[inline] - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - /// Returns the coordinate of the center of the [`Frame`]. #[inline] - pub fn center(&self) -> Point { + fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) } - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into) { + fn fill(&mut self, path: &Path, fill: impl Into) { let Fill { style, rule } = fill.into(); let mut buffer = self @@ -239,9 +136,7 @@ impl Frame { .expect("Tessellate path."); } - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - pub fn fill_rectangle( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -276,9 +171,7 @@ impl Frame { .expect("Fill rectangle"); } - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { let stroke = stroke.into(); let mut buffer = self @@ -315,20 +208,7 @@ impl Frame { .expect("Stroke path"); } - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a `Canvas`. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - pub fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -384,57 +264,55 @@ impl Frame { } } - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. #[inline] - pub fn with_save(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result + fn translate(&mut self, translation: Vector) { + self.transforms.current.0 = + self.transforms + .current + .0 + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); } - /// Pushes the current transform in the transform stack. - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); + #[inline] + fn rotate(&mut self, angle: impl Into) { + self.transforms.current.0 = self + .transforms + .current + .0 + .pre_rotate(lyon::math::Angle::radians(angle.into().0)); } - /// Pops a transform from the transform stack and sets it as the current transform. - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); + #[inline] + fn scale(&mut self, scale: impl Into) { + let scale = scale.into(); + + self.scale_nonuniform(Vector { x: scale, y: scale }); } - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. #[inline] - pub fn with_clip( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Frame) -> R, - ) -> R { - let mut frame = Frame::new(region.size()); + fn scale_nonuniform(&mut self, scale: impl Into) { + let scale = scale.into(); - let result = f(&mut frame); + self.transforms.current.0 = + self.transforms.current.0.pre_scale(scale.x, scale.y); + } - let origin = Point::new(region.x, region.y); + fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } - self.clip(frame, origin); + fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } - result + fn draft(&mut self, size: Size) -> Frame { + Frame::new(size) } - /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn clip(&mut self, frame: Frame, at: Point) { + 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); @@ -461,90 +339,153 @@ impl Frame { ], }); } +} +impl From for Primitive { + fn from(frame: Frame) -> Self { + Self::Group { + primitives: frame.into_primitives(), + } + } +} - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.0 = - self.transforms - .current - .0 - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); +enum Buffer { + Solid(tessellation::VertexBuffers), + Gradient(tessellation::VertexBuffers), +} + +struct BufferStack { + stack: Vec, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } } - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: impl Into) { - self.transforms.current.0 = self - .transforms - .current - .0 - .pre_rotate(lyon::math::Angle::radians(angle.into().0)); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + )); + } + }, + } + + self.stack.last_mut().unwrap() } - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } + } - self.scale_nonuniform(Vector { x: scale, y: scale }); + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } } +} - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into) { - let scale = scale.into(); +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} - self.transforms.current.0 = - self.transforms.current.0.pre_scale(scale.x, scale.y); +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { + fn is_identity(&self) -> bool { + self.0 == lyon::math::Transform::identity() } - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), + fn is_scale_translation(&self) -> bool { + self.0.m12.abs() < 2.0 * f32::EPSILON + && self.0.m21.abs() < 2.0 * f32::EPSILON + } + + fn scale(&self) -> (f32, f32) { + (self.0.m11, self.0.m22) + } + + fn transform_point(&self, point: Point) -> Point { + let transformed = self + .0 + .transform_point(euclid::Point2D::new(point.x, point.y)); + + Point { + x: transformed.x, + y: transformed.y, } } - fn into_primitives(mut self) -> Vec { - 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 transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } + } - self.primitives + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + match &mut gradient { + Gradient::Linear(linear) => { + linear.start = self.transform_point(linear.start); + linear.end = self.transform_point(linear.end); + } + } + + gradient } } - struct GradientVertex2DBuilder { gradient: gradient::Packed, } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index fff927ea..ee9af93c 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -28,3 +28,11 @@ impl Damage for Custom { } } } + +impl TryFrom for Custom { + type Error = &'static str; + + fn try_from(mesh: Mesh) -> Result { + Ok(Custom::Mesh(mesh)) + } +} -- cgit From 53a183fe0d6aed460fbb8155ac9541757277aab3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 01:35:14 +0100 Subject: Restore `canvas::Frame` API --- wgpu/src/geometry.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 4f6b67b1..ba56c59d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -80,7 +80,7 @@ impl Frame { } } -impl geometry::Frame for Frame { +impl geometry::frame::Backend for Frame { type Geometry = Primitive; /// Creates a new empty [`Frame`] with the given dimensions. @@ -339,11 +339,10 @@ impl geometry::Frame for Frame { ], }); } -} -impl From for Primitive { - fn from(frame: Frame) -> Self { - Self::Group { - primitives: frame.into_primitives(), + + fn into_geometry(self) -> Self::Geometry { + Primitive::Group { + primitives: self.into_primitives(), } } } -- cgit From 1f13a91361258a1607c71f4840a26a6437f88612 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 05:27:31 +0100 Subject: Make `iced_tiny_skia` optional with a `tiny-skia` feature --- wgpu/src/backend.rs | 2 ++ wgpu/src/primitive/pipeline.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 924aacf1..3675d50b 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; use crate::triangle; +use crate::window; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -372,6 +373,7 @@ impl Backend { } impl crate::graphics::Backend for Backend { + type Compositor = window::Compositor; type Primitive = primitive::Custom; } diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index c6b7c5e2..814440ba 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,5 +1,5 @@ //! Draw primitives using custom pipelines. -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use std::any::{Any, TypeId}; use std::collections::HashMap; @@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static { } /// A renderer than can draw custom pipeline primitives. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// Draws a custom pipeline primitive. fn draw_pipeline_primitive( &mut self, -- cgit From 5137d655e6bbd29581fc1469d0385515113f2999 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 07:09:51 +0100 Subject: Allow custom renderers in `Program` and `Application` --- wgpu/src/settings.rs | 13 ++++++++++++- wgpu/src/window/compositor.rs | 8 +++----- 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c9338fec..9943aa3e 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,6 @@ //! Configure a renderer. use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::graphics::{self, Antialiasing}; /// The settings of a [`Backend`]. /// @@ -64,3 +64,14 @@ impl Default for Settings { } } } + +impl From for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..Settings::default() + } + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 328ad781..74dfadba 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,9 +1,8 @@ //! Connect a window with a renderer. use crate::core::{Color, Size}; -use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{Error, Viewport}; +use crate::graphics::{self, Error, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::future::Future; @@ -225,15 +224,14 @@ pub fn present>( } impl graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface<'static>; fn new( - settings: Self::Settings, + settings: graphics::Settings, compatible_window: W, ) -> impl Future> { - new(settings, compatible_window) + new(settings.into(), compatible_window) } fn create_renderer(&self) -> Self::Renderer { -- cgit From 441e9237cd1c9c9b61d9b144b5b4dafa236ace28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 22 Mar 2024 19:35:19 +0100 Subject: Rename `compositor::Renderer` to `Default` --- wgpu/src/backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 3675d50b..5019191c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -372,9 +372,9 @@ impl Backend { } } -impl crate::graphics::Backend for Backend { - type Compositor = window::Compositor; +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { -- cgit From 4f5b63f1f4cd7d3ab72289c697f4abc767114eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Mar 2024 08:04:28 +0100 Subject: Reintroduce backend selection through `ICED_BACKEND` env var --- wgpu/Cargo.toml | 1 + wgpu/src/settings.rs | 24 ------- wgpu/src/window/compositor.rs | 142 +++++++++++++++++++++++++++++------------- 3 files changed, 98 insertions(+), 69 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4a0d89f0..f6162e0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +thiserror.workspace = true wgpu.workspace = true lyon.workspace = true diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 9943aa3e..828d9e09 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -29,30 +29,6 @@ pub struct Settings { pub antialiasing: Option, } -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Specifically: - /// - /// - The `internal_backend` can be configured using the `WGPU_BACKEND` - /// environment variable. If the variable is not set, the primary backend - /// will be used. The following values are allowed: - /// - `vulkan` - /// - `metal` - /// - `dx12` - /// - `dx11` - /// - `gl` - /// - `webgpu` - /// - `primary` - pub fn from_env() -> Self { - Settings { - internal_backend: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..Self::default() - } - } -} - impl Default for Settings { fn default() -> Settings { Settings { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 74dfadba..7b769923 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -2,11 +2,10 @@ use crate::core::{Color, Size}; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{self, Error, Viewport}; +use crate::graphics::error; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use std::future::Future; - /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { @@ -19,6 +18,32 @@ pub struct Compositor { alpha_mode: wgpu::CompositeAlphaMode, } +/// A compositor error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// The surface creation failed. + #[error("the surface creation failed: {0}")] + SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), + /// The surface is not compatible. + #[error("the surface is not compatible")] + IncompatibleSurface, + /// No adapter was found for the options requested. + #[error("no adapter was found for the options requested: {0:?}")] + NoAdapterFound(String), + /// No device request succeeded. + #[error("no device request succeeded: {0:?}")] + RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), +} + +impl From for graphics::Error { + fn from(error: Error) -> Self { + Self::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::RequestFailed(error.to_string()), + } + } +} + impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// @@ -26,7 +51,7 @@ impl Compositor { pub async fn request( settings: Settings, compatible_window: Option, - ) -> Option { + ) -> Result { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: settings.internal_backend, ..Default::default() @@ -48,23 +73,27 @@ impl Compositor { let compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); + let adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), + compatible_surface: compatible_surface.as_ref(), + force_fallback_adapter: false, + }; + let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::util::power_preference_from_env() - .unwrap_or(if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }), - compatible_surface: compatible_surface.as_ref(), - force_fallback_adapter: false, - }) - .await?; + .request_adapter(&adapter_options) + .await + .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; log::info!("Selected: {:#?}", adapter.get_info()); - let (format, alpha_mode) = - compatible_surface.as_ref().and_then(|surface| { + let (format, alpha_mode) = compatible_surface + .as_ref() + .and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); let mut formats = capabilities.formats.iter().copied(); @@ -96,7 +125,8 @@ impl Compositor { }; format.zip(Some(preferred_alpha)) - })?; + }) + .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -110,39 +140,46 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let mut limits = limits.into_iter().map(|limits| wgpu::Limits { + let limits = limits.into_iter().map(|limits| wgpu::Limits { max_bind_groups: 2, ..limits }); - let (device, queue) = - loop { - let required_limits = limits.next()?; - let device = adapter.request_device( + let mut errors = Vec::new(); + + for required_limits in limits { + let result = adapter + .request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), required_features: wgpu::Features::empty(), - required_limits, + required_limits: required_limits.clone(), }, None, - ).await.ok(); - - if let Some(device) = device { - break Some(device); + ) + .await; + + match result { + Ok((device, queue)) => { + return Ok(Compositor { + instance, + settings, + adapter, + device, + queue, + format, + alpha_mode, + }) } - }?; - - Some(Compositor { - instance, - settings, - adapter, - device, - queue, - format, - alpha_mode, - }) + Err(error) => { + errors.push((required_limits, error)); + } + } + } + + Err(Error::RequestDeviceFailed(errors)) } /// Creates a new rendering [`Backend`] for this [`Compositor`]. @@ -163,9 +200,7 @@ pub async fn new( settings: Settings, compatible_window: W, ) -> Result { - Compositor::request(settings, Some(compatible_window)) - .await - .ok_or(Error::GraphicsAdapterNotFound) + Compositor::request(settings, Some(compatible_window)).await } /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. @@ -227,11 +262,28 @@ impl graphics::Compositor for Compositor { type Renderer = Renderer; type Surface = wgpu::Surface<'static>; - fn new( + async fn with_backend( settings: graphics::Settings, compatible_window: W, - ) -> impl Future> { - new(settings.into(), compatible_window) + backend: Option<&str>, + ) -> Result { + match backend { + None | Some("wgpu") => Ok(new( + Settings { + internal_backend: wgpu::util::backend_bits_from_env() + .unwrap_or(wgpu::Backends::all()), + ..settings.into() + }, + compatible_window, + ) + .await?), + Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { -- cgit From 8c401be207f013831d0fe1f8fd72f5c00b16346e Mon Sep 17 00:00:00 2001 From: Daniel Yoon <101683475+Koranir@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:50:11 +1100 Subject: Update solid.wgsl --- wgpu/src/shader/quad/solid.wgsl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index 1274f814..d908afbc 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -107,13 +107,19 @@ fn solid_fs_main( let quad_color = vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); if input.shadow_color.a > 0.0 { - let shadow_distance = rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, border_radius); + let shadow_radius = select_border_radius( + input.border_radius, + input.position.xy - input.shadow_offset, + (input.pos + input.scale * 0.5).xy + ); + let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.); + let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance); let shadow_color = input.shadow_color; - let base_color = select( + let base_color = mix( vec4(shadow_color.x, shadow_color.y, shadow_color.z, 0.0), quad_color, - quad_color.a > 0.0 + quad_color.a ); return mix(base_color, shadow_color, (1.0 - radius_alpha) * shadow_alpha); -- cgit From 2bb53ad6e7ea2689f2f56662e5840a8d363b3108 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 29 Mar 2024 04:02:24 +0100 Subject: Use a `StagingBelt` in `iced_wgpu` for regular buffer uploads --- wgpu/src/backend.rs | 26 +++++++++++++++++++++----- wgpu/src/buffer.rs | 14 ++++++++++++-- wgpu/src/image.rs | 36 ++++++++++++++++++++++++------------ wgpu/src/quad.rs | 28 ++++++++++++++++++++-------- wgpu/src/quad/gradient.rs | 5 +++-- wgpu/src/quad/solid.rs | 5 +++-- wgpu/src/text.rs | 2 ++ wgpu/src/triangle.rs | 35 ++++++++++++++++++++++++++--------- wgpu/src/window/compositor.rs | 1 + 9 files changed, 112 insertions(+), 40 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 5019191c..129e9bca 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -30,6 +30,7 @@ pub struct Backend { pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + staging_belt: wgpu::util::StagingBelt, } impl Backend { @@ -61,6 +62,11 @@ impl Backend { #[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(1024 * 100), } } @@ -105,6 +111,8 @@ impl Backend { &layers, ); + self.staging_belt.finish(); + self.render( device, encoder, @@ -123,12 +131,17 @@ impl Backend { self.image_pipeline.end_frame(); } + /// + 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, + encoder: &mut wgpu::CommandEncoder, scale_factor: f32, target_size: Size, transformation: Transformation, @@ -144,7 +157,8 @@ impl Backend { if !layer.quads.is_empty() { self.quad_pipeline.prepare( device, - queue, + encoder, + &mut self.staging_belt, &layer.quads, transformation, scale_factor, @@ -157,7 +171,8 @@ impl Backend { self.triangle_pipeline.prepare( device, - queue, + encoder, + &mut self.staging_belt, &layer.meshes, scaled, ); @@ -171,8 +186,8 @@ impl Backend { self.image_pipeline.prepare( device, - queue, - _encoder, + encoder, + &mut self.staging_belt, &layer.images, scaled, scale_factor, @@ -184,6 +199,7 @@ impl Backend { self.text_pipeline.prepare( device, queue, + encoder, &layer.text, layer.bounds, scale_factor, diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index ef00c58f..f8828d46 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -61,12 +61,22 @@ impl Buffer { /// Returns the size of the written bytes. pub fn write( &mut self, - queue: &wgpu::Queue, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, offset: usize, contents: &[T], ) -> usize { let bytes: &[u8] = bytemuck::cast_slice(contents); - queue.write_buffer(&self.raw, offset as u64, bytes); + + belt.write_buffer( + encoder, + &self.raw, + offset as u64, + (bytes.len() as u64).try_into().expect("Non-empty write"), + device, + ) + .copy_from_slice(bytes); self.offsets.push(offset as u64); diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 067b77ab..d0bf1182 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -83,21 +83,31 @@ impl Layer { fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, nearest_instances: &[Instance], linear_instances: &[Instance], transformation: Transformation, ) { - queue.write_buffer( + let uniforms = Uniforms { + transform: transformation.into(), + }; + + let bytes = bytemuck::bytes_of(&uniforms); + + belt.write_buffer( + encoder, &self.uniforms, 0, - bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - }), - ); + (bytes.len() as u64).try_into().expect("Sized uniforms"), + device, + ) + .copy_from_slice(bytes); + + self.nearest + .upload(device, encoder, belt, nearest_instances); - self.nearest.upload(device, queue, nearest_instances); - self.linear.upload(device, queue, linear_instances); + self.linear.upload(device, encoder, belt, linear_instances); } fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { @@ -158,7 +168,8 @@ impl Data { fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Instance], ) { self.instance_count = instances.len(); @@ -168,7 +179,7 @@ impl Data { } let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); } fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { @@ -383,8 +394,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, images: &[layer::Image], transformation: Transformation, _scale: f32, @@ -501,7 +512,8 @@ impl Pipeline { layer.prepare( device, - queue, + encoder, + belt, nearest_instances, linear_instances, transformation, diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index b932f54f..0717a031 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -57,7 +57,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, quads: &Batch, transformation: Transformation, scale: f32, @@ -67,7 +68,7 @@ impl Pipeline { } let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, quads, transformation, scale); + layer.prepare(device, encoder, belt, quads, transformation, scale); self.prepare_layer += 1; } @@ -162,7 +163,8 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, quads: &Batch, transformation: Transformation, scale: f32, @@ -171,15 +173,25 @@ impl Layer { let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); let uniforms = Uniforms::new(transformation, scale); + let bytes = bytemuck::bytes_of(&uniforms); - queue.write_buffer( + belt.write_buffer( + encoder, &self.constants_buffer, 0, - bytemuck::bytes_of(&uniforms), - ); + (bytes.len() as u64).try_into().expect("Sized uniforms"), + device, + ) + .copy_from_slice(bytes); - self.solid.prepare(device, queue, &quads.solids); - self.gradient.prepare(device, queue, &quads.gradients); + 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); + } } } diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 560fcad2..5b32c52a 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -46,11 +46,12 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Gradient], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); self.instance_count = instances.len(); } diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 771eee34..1cead367 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -40,11 +40,12 @@ impl Layer { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, instances: &[Solid], ) { let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(queue, 0, instances); + let _ = self.instances.write(device, encoder, belt, 0, instances); self.instance_count = instances.len(); } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6fa1922d..97ff77f5 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -53,6 +53,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, sections: &[Text<'_>], layer_bounds: Rectangle, scale_factor: f32, @@ -262,6 +263,7 @@ impl Pipeline { let result = renderer.prepare( device, queue, + encoder, font_system, &mut self.atlas, glyphon::Resolution { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 2bb6f307..b6be54d4 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -48,7 +48,8 @@ impl Layer { fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, solid: &solid::Pipeline, gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], @@ -103,33 +104,47 @@ impl Layer { let uniforms = Uniforms::new(transformation * mesh.transformation()); - index_offset += - self.index_buffer.write(queue, index_offset, indices); + index_offset += self.index_buffer.write( + device, + encoder, + belt, + index_offset, + indices, + ); + self.index_strides.push(indices.len() as u32); match mesh { Mesh::Solid { buffers, .. } => { solid_vertex_offset += self.solid.vertices.write( - queue, + device, + encoder, + belt, solid_vertex_offset, &buffers.vertices, ); solid_uniform_offset += self.solid.uniforms.write( - queue, + device, + encoder, + belt, solid_uniform_offset, &[uniforms], ); } Mesh::Gradient { buffers, .. } => { gradient_vertex_offset += self.gradient.vertices.write( - queue, + device, + encoder, + belt, gradient_vertex_offset, &buffers.vertices, ); gradient_uniform_offset += self.gradient.uniforms.write( - queue, + device, + encoder, + belt, gradient_uniform_offset, &[uniforms], ); @@ -237,7 +252,8 @@ impl Pipeline { pub fn prepare( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, meshes: &[Mesh<'_>], transformation: Transformation, ) { @@ -252,7 +268,8 @@ impl Pipeline { let layer = &mut self.layers[self.prepare_layer]; layer.prepare( device, - queue, + encoder, + belt, &self.solid, &self.gradient, meshes, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 9a3e3b34..482d705b 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -243,6 +243,7 @@ pub fn present>( // Submit work let _submission = compositor.queue.submit(Some(encoder.finish())); + backend.recall(); frame.present(); Ok(()) -- cgit From 0a97b9e37ae115bb0db33193c8a6b62590a3cd2c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 29 Mar 2024 09:57:11 +0100 Subject: Add documentation to `Backend::recall` in `iced_wgpu` --- wgpu/src/backend.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 129e9bca..20809373 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -131,7 +131,10 @@ impl Backend { 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(); } -- cgit From 5f1eb43161d70b4ef157aae1ebc2b5fb25eb5b27 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 29 Mar 2024 14:29:31 +0100 Subject: Split big `Buffer` writes into multiple chunks --- wgpu/src/backend.rs | 5 ++++- wgpu/src/buffer.rs | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 20809373..6ccf4111 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,3 +1,4 @@ +use crate::buffer; use crate::core::{Color, Size, Transformation}; use crate::graphics::backend; use crate::graphics::color; @@ -66,7 +67,9 @@ impl Backend { // 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(1024 * 100), + staging_belt: wgpu::util::StagingBelt::new( + buffer::MAX_WRITE_SIZE as u64, + ), } } diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index f8828d46..c9d6b828 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,6 +1,8 @@ use std::marker::PhantomData; use std::ops::RangeBounds; +pub const MAX_WRITE_SIZE: usize = 1024 * 100; + #[derive(Debug)] pub struct Buffer { label: &'static str, @@ -69,14 +71,38 @@ impl Buffer { ) -> usize { let bytes: &[u8] = bytemuck::cast_slice(contents); - belt.write_buffer( - encoder, - &self.raw, - offset as u64, - (bytes.len() as u64).try_into().expect("Non-empty write"), - device, - ) - .copy_from_slice(bytes); + if bytes.len() <= MAX_WRITE_SIZE { + belt.write_buffer( + encoder, + &self.raw, + offset as u64, + (bytes.len() as u64).try_into().expect("Non-empty write"), + device, + ) + .copy_from_slice(bytes); + } else { + let mut bytes_written = 0; + + let bytes_per_chunk = (bytes.len().min(MAX_WRITE_SIZE) as u64) + .try_into() + .expect("Non-empty write"); + + while bytes_written < bytes.len() { + belt.write_buffer( + encoder, + &self.raw, + (offset + bytes_written) as u64, + bytes_per_chunk, + device, + ) + .copy_from_slice( + &bytes[bytes_written + ..bytes_written + bytes_per_chunk.get() as usize], + ); + + bytes_written += bytes_per_chunk.get() as usize; + } + } self.offsets.push(offset as u64); -- cgit From 35af0aa84f76daddbb6d6959f9746bd09e306278 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 30 Mar 2024 13:50:40 +0100 Subject: Fix batched writes logic in `iced_wgpu::buffer` --- wgpu/src/buffer.rs | 59 +++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 27 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index c9d6b828..463ea24a 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,7 +1,12 @@ use std::marker::PhantomData; +use std::num::NonZeroU64; use std::ops::RangeBounds; -pub const MAX_WRITE_SIZE: usize = 1024 * 100; +pub const MAX_WRITE_SIZE: usize = 100 * 1024; + +#[allow(unsafe_code)] +const MAX_WRITE_SIZE_U64: NonZeroU64 = + unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) }; #[derive(Debug)] pub struct Buffer { @@ -70,40 +75,40 @@ impl Buffer { contents: &[T], ) -> usize { let bytes: &[u8] = bytemuck::cast_slice(contents); + let mut bytes_written = 0; - if bytes.len() <= MAX_WRITE_SIZE { + // Split write into multiple chunks if necessary + while bytes_written + MAX_WRITE_SIZE < bytes.len() { belt.write_buffer( encoder, &self.raw, - offset as u64, - (bytes.len() as u64).try_into().expect("Non-empty write"), + (offset + bytes_written) as u64, + MAX_WRITE_SIZE_U64, device, ) - .copy_from_slice(bytes); - } else { - let mut bytes_written = 0; - - let bytes_per_chunk = (bytes.len().min(MAX_WRITE_SIZE) as u64) - .try_into() - .expect("Non-empty write"); - - while bytes_written < bytes.len() { - belt.write_buffer( - encoder, - &self.raw, - (offset + bytes_written) as u64, - bytes_per_chunk, - device, - ) - .copy_from_slice( - &bytes[bytes_written - ..bytes_written + bytes_per_chunk.get() as usize], - ); - - bytes_written += bytes_per_chunk.get() as usize; - } + .copy_from_slice( + &bytes[bytes_written..bytes_written + MAX_WRITE_SIZE], + ); + + bytes_written += MAX_WRITE_SIZE; } + // There will always be some bytes left, since the previous + // loop guarantees `bytes_written < bytes.len()` + let bytes_left = ((bytes.len() - bytes_written) as u64) + .try_into() + .expect("non-empty write"); + + // Write them + belt.write_buffer( + encoder, + &self.raw, + (offset + bytes_written) as u64, + bytes_left, + device, + ) + .copy_from_slice(&bytes[bytes_written..]); + self.offsets.push(offset as u64); bytes.len() -- cgit From f5bcfec8211c04c4b05f63d01d52d3e5d2cc123e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 1 Apr 2024 11:59:46 +0200 Subject: Use `rustc-hash` for most of our `HashMap` and `HashSet` instances --- wgpu/Cargo.toml | 1 + wgpu/src/image/raster.rs | 6 +++--- wgpu/src/image/vector.rs | 10 +++++----- wgpu/src/primitive/pipeline.rs | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f6162e0f..0b713784 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +rustc-hash.workspace = true thiserror.workspace = true wgpu.workspace = true diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index a6cba76a..441b294f 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,7 +4,7 @@ use crate::graphics; use crate::graphics::image::image_rs; use crate::image::atlas::{self, Atlas}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; /// Entry in cache corresponding to an image handle #[derive(Debug)] @@ -38,8 +38,8 @@ impl Memory { /// Caches image raster data #[derive(Debug, Default)] pub struct Cache { - map: HashMap, - hits: HashSet, + map: FxHashMap, + hits: FxHashSet, } impl Cache { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index d9be50d7..d681b2e6 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; use resvg::usvg::{self, TreeTextToPath}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet}; use std::fs; /// Entry in cache corresponding to an svg handle @@ -33,10 +33,10 @@ impl Svg { /// Caches svg vector and raster data #[derive(Debug, Default)] pub struct Cache { - svgs: HashMap, - rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, - svg_hits: HashSet, - rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, + svgs: FxHashMap, + rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, + svg_hits: FxHashSet, + rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, } type ColorFilter = Option<[u8; 4]>; diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 814440ba..59c54db9 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,8 +1,8 @@ //! Draw primitives using custom pipelines. use crate::core::{self, Rectangle, Size}; +use rustc_hash::FxHashMap; use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; @@ -82,7 +82,7 @@ impl Renderer for crate::Renderer { /// Stores custom, user-provided pipelines. #[derive(Default, Debug)] pub struct Storage { - pipelines: HashMap>, + pipelines: FxHashMap>, } impl Storage { -- cgit From b05e61f5c8ae61c9f3c7cc08cded53901ebbccfd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 21:07:54 +0200 Subject: Redesign `iced_wgpu` layering architecture --- wgpu/Cargo.toml | 3 - wgpu/src/backend.rs | 432 ---------------------------- wgpu/src/engine.rs | 79 ++++++ wgpu/src/geometry.rs | 175 +++++++----- wgpu/src/image.rs | 644 ------------------------------------------ wgpu/src/image/cache.rs | 107 +++++++ wgpu/src/image/mod.rs | 571 +++++++++++++++++++++++++++++++++++++ wgpu/src/image/null.rs | 10 + wgpu/src/layer.rs | 617 ++++++++++++++++++++-------------------- wgpu/src/layer/image.rs | 30 -- wgpu/src/layer/mesh.rs | 97 ------- wgpu/src/layer/pipeline.rs | 17 -- wgpu/src/layer/text.rs | 70 ----- wgpu/src/lib.rs | 578 ++++++++++++++++++++++++++++++++++++- wgpu/src/primitive.rs | 20 +- wgpu/src/quad.rs | 294 +++++++++++++------ wgpu/src/text.rs | 628 ++++++++++++++++++++++++++-------------- wgpu/src/triangle.rs | 396 ++++++++++++++++++-------- wgpu/src/window/compositor.rs | 96 +++---- 19 files changed, 2677 insertions(+), 2187 deletions(-) delete mode 100644 wgpu/src/backend.rs create mode 100644 wgpu/src/engine.rs delete mode 100644 wgpu/src/image.rs create mode 100644 wgpu/src/image/cache.rs create mode 100644 wgpu/src/image/mod.rs create mode 100644 wgpu/src/image/null.rs delete mode 100644 wgpu/src/layer/image.rs delete mode 100644 wgpu/src/layer/mesh.rs delete mode 100644 wgpu/src/layer/pipeline.rs delete mode 100644 wgpu/src/layer/text.rs (limited to 'wgpu') 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>( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - clear_color: Option, - 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, - 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, - scale_factor: f32, - target_size: Size, - 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 { - self.image_pipeline.dimensions(handle) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &crate::core::svg::Handle, - ) -> Size { - 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, // 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, + layers: Vec, + text: text::Batch, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, stroke_tessellator: tessellation::StrokeTessellator, } +pub enum Geometry { + Live(Vec), + Cached(Rc<[Rc>]>), +} + +impl Cached for Geometry { + type Cache = Rc<[Rc>]>; + + fn load(cache: &Self::Cache) -> Self { + Geometry::Cached(cache.clone()) + } + + fn cache(self, previous: Option) -> 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 { - 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 { + 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.rs b/wgpu/src/image.rs deleted file mode 100644 index d0bf1182..00000000 --- a/wgpu/src/image.rs +++ /dev/null @@ -1,644 +0,0 @@ -mod atlas; - -#[cfg(feature = "image")] -mod raster; - -#[cfg(feature = "svg")] -mod vector; - -use atlas::Atlas; - -use crate::core::{Rectangle, Size, Transformation}; -use crate::layer; -use crate::Buffer; - -use std::cell::RefCell; -use std::mem; - -use bytemuck::{Pod, Zeroable}; - -#[cfg(feature = "image")] -use crate::core::image; - -#[cfg(feature = "svg")] -use crate::core::svg; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -#[derive(Debug)] -pub struct Pipeline { - #[cfg(feature = "image")] - raster_cache: RefCell, - #[cfg(feature = "svg")] - vector_cache: RefCell, - - 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, - - layers: Vec, - 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::() 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_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, - format: wgpu::TextureFormat, - backend: wgpu::Backend, - ) -> Self { - let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - min_filter: wgpu::FilterMode::Nearest, - mag_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - min_filter: wgpu::FilterMode::Linear, - mag_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::image constants layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - mem::size_of::() as u64, - ), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::Filtering, - ), - count: None, - }, - ], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::image texture atlas layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: true, - }, - view_dimension: wgpu::TextureViewDimension::D2Array, - multisampled: false, - }, - count: None, - }], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::image pipeline layout"), - push_constant_ranges: &[], - bind_group_layouts: &[&constant_layout, &texture_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu image shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - concat!( - include_str!("shader/vertex.wgsl"), - "\n", - include_str!("shader/image.wgsl"), - ), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::image pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - // Scale - 1 => Float32x2, - // Atlas position - 2 => Float32x2, - // Atlas scale - 3 => Float32x2, - // Layer - 4 => Sint32, - ), - }], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - 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(), - ), - }], - }); - - 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_layout, - constant_layout, - - layers: Vec::new(), - prepare_layer: 0, - } - } - - #[cfg(feature = "image")] - pub fn dimensions(&self, handle: &image::Handle) -> Size { - 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 { - let mut cache = self.vector_cache.borrow_mut(); - let svg = cache.load(handle); - - svg.viewport_dimensions() - } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - images: &[layer::Image], - transformation: Transformation, - _scale: f32, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Image", "PREPARE").entered(); - - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Image", "DRAW").entered(); - - let nearest_instances: &mut Vec = &mut Vec::new(); - let linear_instances: &mut Vec = &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(); - - for image in images { - match &image { - #[cfg(feature = "image")] - layer::Image::Raster { - handle, - filter_method, - bounds, - } => { - if let Some(atlas_entry) = raster_cache.upload( - device, - encoder, - handle, - &mut self.texture_atlas, - ) { - add_instances( - [bounds.x, bounds.y], - [bounds.width, bounds.height], - atlas_entry, - match filter_method { - image::FilterMethod::Nearest => { - nearest_instances - } - image::FilterMethod::Linear => linear_instances, - }, - ); - } - } - #[cfg(not(feature = "image"))] - layer::Image::Raster { .. } => {} - - #[cfg(feature = "svg")] - layer::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, - ) { - add_instances( - [bounds.x, bounds.y], - size, - atlas_entry, - nearest_instances, - ); - } - } - #[cfg(not(feature = "svg"))] - layer::Image::Vector { .. } => {} - } - } - - if nearest_instances.is_empty() && linear_instances.is_empty() { - return; - } - - let texture_version = self.texture_atlas.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(), - ), - }], - }); - - self.texture_version = texture_version; - } - - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &self.constant_layout, - &self.nearest_sampler, - &self.linear_sampler, - )); - } - - let layer = &mut self.layers[self.prepare_layer]; - - layer.prepare( - device, - encoder, - belt, - nearest_instances, - linear_instances, - transformation, - ); - - self.prepare_layer += 1; - } - - pub fn render<'a>( - &'a self, - layer: usize, - bounds: Rectangle, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - if let Some(layer) = self.layers.get(layer) { - render_pass.set_pipeline(&self.pipeline); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.set_bind_group(1, &self.texture, &[]); - - layer.render(render_pass); - } - } - - pub fn end_frame(&mut self) { - #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); - - #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); - - self.prepare_layer = 0; - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] -struct Instance { - _position: [f32; 2], - _size: [f32; 2], - _position_in_atlas: [f32; 2], - _size_in_atlas: [f32; 2], - _layer: u32, -} - -impl Instance { - pub const INITIAL: usize = 20; -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] -struct Uniforms { - transform: [f32; 16], -} - -fn add_instances( - image_position: [f32; 2], - image_size: [f32; 2], - entry: &atlas::Entry, - instances: &mut Vec, -) { - match entry { - atlas::Entry::Contiguous(allocation) => { - add_instance(image_position, image_size, allocation, instances); - } - atlas::Entry::Fragmented { fragments, size } => { - let scaling_x = image_size[0] / size.width as f32; - let scaling_y = image_size[1] / size.height as f32; - - for fragment in fragments { - let allocation = &fragment.allocation; - - let [x, y] = image_position; - let (fragment_x, fragment_y) = fragment.position; - let Size { - width: fragment_width, - height: fragment_height, - } = allocation.size(); - - let position = [ - x + fragment_x as f32 * scaling_x, - y + fragment_y as f32 * scaling_y, - ]; - - let size = [ - fragment_width as f32 * scaling_x, - fragment_height as f32 * scaling_y, - ]; - - add_instance(position, size, allocation, instances); - } - } - } -} - -#[inline] -fn add_instance( - position: [f32; 2], - size: [f32; 2], - allocation: &atlas::Allocation, - instances: &mut Vec, -) { - let (x, y) = allocation.position(); - let Size { width, height } = allocation.size(); - let layer = allocation.layer(); - - let instance = Instance { - _position: position, - _size: size, - _position_in_atlas: [ - (x as f32 + 0.5) / atlas::SIZE as f32, - (y as f32 + 0.5) / atlas::SIZE as f32, - ], - _size_in_atlas: [ - (width as f32 - 1.0) / atlas::SIZE as f32, - (height as f32 - 1.0) / atlas::SIZE as f32, - ], - _layer: layer as u32, - }; - - instances.push(instance); -} 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 { + self.raster.load(handle).dimensions() + } + + #[cfg(feature = "svg")] + pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size { + 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, + 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>); + +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/mod.rs b/wgpu/src/image/mod.rs new file mode 100644 index 00000000..88e6bdb9 --- /dev/null +++ b/wgpu/src/image/mod.rs @@ -0,0 +1,571 @@ +pub(crate) mod cache; +pub(crate) use cache::Cache; + +mod atlas; + +#[cfg(feature = "image")] +mod raster; + +#[cfg(feature = "svg")] +mod vector; + +use crate::core::image; +use crate::core::{Rectangle, Size, Transformation}; +use crate::Buffer; + +use bytemuck::{Pod, Zeroable}; +use std::mem; + +pub use crate::graphics::Image; + +pub type Batch = Vec; + +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + nearest_sampler: wgpu::Sampler, + linear_sampler: wgpu::Sampler, + texture: wgpu::BindGroup, + texture_version: usize, + texture_layout: wgpu::BindGroupLayout, + constant_layout: wgpu::BindGroupLayout, + cache: cache::Shared, + layers: Vec, + prepare_layer: usize, +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + backend: wgpu::Backend, + ) -> Self { + let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + min_filter: wgpu::FilterMode::Nearest, + mag_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + min_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu::image constants layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new( + mem::size_of::() as u64, + ), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::Filtering, + ), + count: None, + }, + ], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu::image texture atlas layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: true, + }, + view_dimension: wgpu::TextureViewDimension::D2Array, + multisampled: false, + }, + count: None, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu::image pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&constant_layout, &texture_layout], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu image shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + concat!( + include_str!("../shader/vertex.wgsl"), + "\n", + include_str!("../shader/image.wgsl"), + ), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::image pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + // Scale + 1 => Float32x2, + // Atlas position + 2 => Float32x2, + // Atlas scale + 3 => Float32x2, + // Layer + 4 => Sint32, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + let cache = Cache::new(device, backend); + let texture = cache.create_bind_group(device, &texture_layout); + + Pipeline { + pipeline, + nearest_sampler, + linear_sampler, + texture, + texture_version: cache.layer_count(), + texture_layout, + constant_layout, + cache: cache::Shared::new(cache), + layers: Vec::new(), + prepare_layer: 0, + } + } + + pub fn cache(&self) -> &cache::Shared { + &self.cache + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + images: &Batch, + transformation: Transformation, + scale: f32, + ) { + let transformation = transformation * Transformation::scale(scale); + + let nearest_instances: &mut Vec = &mut Vec::new(); + let linear_instances: &mut Vec = &mut Vec::new(); + + let mut cache = self.cache.lock(); + + for image in images { + match &image { + #[cfg(feature = "image")] + Image::Raster { + handle, + filter_method, + bounds, + } => { + if let Some(atlas_entry) = + cache.upload_raster(device, encoder, handle) + { + add_instances( + [bounds.x, bounds.y], + [bounds.width, bounds.height], + atlas_entry, + match filter_method { + image::FilterMethod::Nearest => { + nearest_instances + } + image::FilterMethod::Linear => linear_instances, + }, + ); + } + } + #[cfg(not(feature = "image"))] + Image::Raster { .. } => {} + + #[cfg(feature = "svg")] + Image::Vector { + handle, + color, + bounds, + } => { + let size = [bounds.width, bounds.height]; + + if let Some(atlas_entry) = cache.upload_vector( + device, encoder, handle, *color, size, scale, + ) { + add_instances( + [bounds.x, bounds.y], + size, + atlas_entry, + nearest_instances, + ); + } + } + #[cfg(not(feature = "svg"))] + Image::Vector { .. } => {} + } + } + + if nearest_instances.is_empty() && linear_instances.is_empty() { + return; + } + + let texture_version = cache.layer_count(); + + if self.texture_version != texture_version { + log::info!("Atlas has grown. Recreating bind group..."); + + self.texture = + cache.create_bind_group(device, &self.texture_layout); + self.texture_version = texture_version; + } + + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.constant_layout, + &self.nearest_sampler, + &self.linear_sampler, + )); + } + + let layer = &mut self.layers[self.prepare_layer]; + + layer.prepare( + device, + encoder, + belt, + nearest_instances, + linear_instances, + transformation, + ); + + self.prepare_layer += 1; + } + + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.set_bind_group(1, &self.texture, &[]); + + layer.render(render_pass); + } + } + + pub fn end_frame(&mut self) { + self.cache.lock().trim(); + self.prepare_layer = 0; + } +} + +#[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::() 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_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); + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] +struct Instance { + _position: [f32; 2], + _size: [f32; 2], + _position_in_atlas: [f32; 2], + _size_in_atlas: [f32; 2], + _layer: u32, +} + +impl Instance { + pub const INITIAL: usize = 20; +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Zeroable, Pod)] +struct Uniforms { + transform: [f32; 16], +} + +fn add_instances( + image_position: [f32; 2], + image_size: [f32; 2], + entry: &atlas::Entry, + instances: &mut Vec, +) { + match entry { + atlas::Entry::Contiguous(allocation) => { + add_instance(image_position, image_size, allocation, instances); + } + atlas::Entry::Fragmented { fragments, size } => { + let scaling_x = image_size[0] / size.width as f32; + let scaling_y = image_size[1] / size.height as f32; + + for fragment in fragments { + let allocation = &fragment.allocation; + + let [x, y] = image_position; + let (fragment_x, fragment_y) = fragment.position; + let Size { + width: fragment_width, + height: fragment_height, + } = allocation.size(); + + let position = [ + x + fragment_x as f32 * scaling_x, + y + fragment_y as f32 * scaling_y, + ]; + + let size = [ + fragment_width as f32 * scaling_x, + fragment_height as f32 * scaling_y, + ]; + + add_instance(position, size, allocation, instances); + } + } + } +} + +#[inline] +fn add_instance( + position: [f32; 2], + size: [f32; 2], + allocation: &atlas::Allocation, + instances: &mut Vec, +) { + let (x, y) = allocation.position(); + let Size { width, height } = allocation.size(); + let layer = allocation.layer(); + + let instance = Instance { + _position: position, + _size: size, + _position_in_atlas: [ + (x as f32 + 0.5) / atlas::SIZE as f32, + (y as f32 + 0.5) / atlas::SIZE as f32, + ], + _size_in_atlas: [ + (width as f32 - 1.0) / atlas::SIZE as f32, + (height as f32 - 1.0) / atlas::SIZE as f32, + ], + _layer: layer as u32, + }; + + instances.push(instance); +} 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>, - - /// The text of the [`Layer`]. - pub text: Vec>, +pub enum Layer<'a> { + Live(&'a Live), + Cached(cell::Ref<'a, Cached>), +} - /// The images of the [`Layer`]. - pub images: Vec, +pub enum LayerMut<'a> { + Live(&'a mut Live), + Cached(cell::RefMut<'a, Cached>), +} - /// The custom pipelines of this [`Layer`]. - pub pipelines: Vec, +pub struct Stack { + live: Vec, + cached: Vec>>, + order: Vec, + transformations: Vec, + previous: Vec, + 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], 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 { - 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, - 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, + 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>) { + { + 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) { + 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> { + 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> { + 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, + 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, + pub transformation: Transformation, + pub last_transformation: Option, + 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, - - /// 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, - - /// The clipping bounds of the [`Mesh`]. - clip_bounds: Rectangle, - }, - /// 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, - - /// The clipping bounds of the [`Mesh`]. - clip_bounds: Rectangle, - }, -} - -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 { - 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, -} 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; +#[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>( + &mut self, + engine: &mut Engine, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + clear_color: Option, + 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, + 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, + scale_factor: f32, + target_size: Size, + ) { + 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, + ) { + 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 { + 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 { + self.image_cache.lock().measure_svg(handle) + } + + fn draw_svg( + &mut self, + handle: core::svg::Handle, + color_filter: Option, + 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 for Custom { - type Error = &'static str; - - fn try_from(mesh: Mesh) -> Result { - 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, @@ -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, + 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, + 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, - /// 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; + #[allow(missing_debug_implementations)] pub struct Pipeline { - renderers: Vec, + format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, + renderers: Vec, prepare_layer: usize, - cache: RefCell, + cache: BufferCache, +} + +pub enum Cache { + Staged(Batch), + Uploaded { + batch: Batch, + renderer: glyphon::TextRenderer, + atlas: Option, + buffer_cache: Option, + scale_factor: f32, + target_size: Size, + 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, @@ -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), - } - - 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, + ) { + 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, @@ -306,10 +255,251 @@ impl Pipeline { .expect("Render text"); } + pub fn render_cache<'a>( + &'a self, + cache: &'a Cache, + bounds: Rectangle, + 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, +) -> 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), + } + + 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; + #[derive(Debug)] pub struct Pipeline { blit: Option, @@ -18,8 +20,270 @@ pub struct Pipeline { prepare_layer: usize, } +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option, + ) -> 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, + meshes: &Batch, + bounds: Rectangle, + 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, + cache: &Cache, + bounds: Rectangle, + 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, + meshes: &Batch, + bounds: Rectangle, + 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, index_strides: Vec, 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, 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::::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, - ) -> 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, - 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( /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. pub fn present>( 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>( .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( @@ -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>( @@ -376,16 +360,7 @@ impl graphics::Compositor for Compositor { background_color: Color, overlay: &[T], ) -> Vec { - 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>( - compositor: &Compositor, - backend: &mut Backend, - primitives: &[Primitive], + compositor: &mut Compositor, + renderer: &mut Renderer, viewport: &Viewport, background_color: Color, overlay: &[T], ) -> Vec { - 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>( 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>( 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, |_| {}); -- cgit From 88b72de282441367092b07f8075eb931eff495ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 22:13:00 +0200 Subject: Implement preliminary cache grouping for mesh primitives Due to AA, it's very expensive to render every cached layer independently. --- wgpu/src/lib.rs | 95 +++++++++++++++++++++++++++++++++------------------- wgpu/src/triangle.rs | 63 ++++++++++++++++++++++++---------- 2 files changed, 106 insertions(+), 52 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0e173e0a..b9869583 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -315,9 +315,10 @@ impl Renderer { // TODO: Can we avoid collecting here? let layers: Vec<_> = self.layers.iter().collect(); + let mut i = 0; - for layer in &layers { - match layer { + while i < layers.len() { + match layers[i] { Layer::Live(live) => { let bounds = live .bounds @@ -393,32 +394,54 @@ impl Renderer { image_layer += 1; } + + i += 1; } - Layer::Cached(cached) => { - let bounds = cached - .bounds - .map(|bounds| bounds * scale_factor) - .map(Rectangle::snap) - .unwrap_or(Rectangle::with_size(target_size)); + Layer::Cached(_) => { + let group_len = layers[i..] + .iter() + .position(|layer| matches!(layer, Layer::Live(_))) + .unwrap_or(layers.len()); - if !cached.quads.is_empty() { - engine.quad_pipeline.render_cache( - &cached.quads, - bounds, - &mut render_pass, - ); + let group = layers[i..i + group_len].iter().map(|layer| { + let Layer::Cached(cached) = layer else { + unreachable!() + }; + + let bounds = cached + .bounds + .map(|bounds| bounds * scale_factor) + .map(Rectangle::snap) + .unwrap_or(Rectangle::with_size(target_size)); + + (cached, bounds) + }); + + for (cached, bounds) in group.clone() { + if !cached.quads.is_empty() { + engine.quad_pipeline.render_cache( + &cached.quads, + bounds, + &mut render_pass, + ); + } } - if !cached.meshes.is_empty() { + let group_has_meshes = group + .clone() + .any(|(cached, _)| !cached.meshes.is_empty()); + + if group_has_meshes { let _ = ManuallyDrop::into_inner(render_pass); - engine.triangle_pipeline.render_cache( + engine.triangle_pipeline.render_cache_group( device, encoder, frame, target_size, - &cached.meshes, - bounds, + group.clone().map(|(cached, bounds)| { + (&cached.meshes, bounds) + }), scale_factor, ); @@ -443,24 +466,28 @@ impl Renderer { )); } - if !cached.text.is_empty() { - engine.text_pipeline.render_cache( - &cached.text, - bounds, - &mut render_pass, - ); + 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; + } } - #[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; } } } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 6df97a7b..9cd02f72 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -136,14 +136,13 @@ impl Pipeline { self.blit.as_mut(), &self.solid, &self.gradient, - &self.layers[layer], target_size, - meshes, - bounds, + std::iter::once((&self.layers[layer], meshes, bounds)), scale_factor, ); } + #[allow(dead_code)] pub fn render_cache( &mut self, device: &wgpu::Device, @@ -165,25 +164,51 @@ impl Pipeline { self.blit.as_mut(), &self.solid, &self.gradient, - layer, target_size, - batch, - bounds, + std::iter::once((layer, batch, bounds)), scale_factor, ); } - fn render( + pub fn render_cache_group<'a>( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + target_size: Size, + group: impl Iterator)>, + scale_factor: f32, + ) { + let group = group.filter_map(|(cache, bounds)| { + if let Cache::Uploaded { batch, layer, .. } = cache { + Some((layer, batch, bounds)) + } else { + None + } + }); + + Self::render( + device, + encoder, + target, + self.blit.as_mut(), + &self.solid, + &self.gradient, + target_size, + group, + scale_factor, + ); + } + + 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, - layer: &Layer, target_size: Size, - meshes: &Batch, - bounds: Rectangle, + group: impl Iterator)>, scale_factor: f32, ) { { @@ -220,14 +245,16 @@ impl Pipeline { occlusion_query_set: None, }); - layer.render( - solid, - gradient, - meshes, - bounds, - scale_factor, - &mut render_pass, - ); + for (layer, meshes, bounds) in group { + layer.render( + solid, + gradient, + meshes, + bounds, + scale_factor, + &mut render_pass, + ); + } } if let Some(blit) = blit { -- cgit From 1672b0d619a87fcbe8aa6c6699ac6b20f9a6181b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 22:57:28 +0200 Subject: Reintroduce debug overlay in `iced_wgpu` --- wgpu/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b9869583..2d023d8b 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -120,9 +120,7 @@ impl Renderer { let scale_factor = viewport.scale_factor() as f32; let transformation = viewport.projection(); - for line in overlay { - println!("{}", line.as_ref()); - } + self.draw_overlay(overlay, viewport); self.prepare( engine, @@ -494,6 +492,50 @@ impl Renderer { let _ = ManuallyDrop::into_inner(render_pass); } + + fn draw_overlay( + &mut self, + overlay: &[impl AsRef], + viewport: &Viewport, + ) { + use crate::core::alignment; + use crate::core::text::Renderer as _; + use crate::core::Renderer as _; + use crate::core::Vector; + + self.with_layer( + Rectangle::with_size(viewport.logical_size()), + |renderer| { + for (i, line) in overlay.iter().enumerate() { + let text = crate::core::Text { + content: line.as_ref().to_owned(), + bounds: viewport.logical_size(), + 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, + }; + + renderer.fill_text( + text.clone(), + Point::new(11.0, 11.0 + 25.0 * i as f32), + Color::new(0.9, 0.9, 0.9, 1.0), + Rectangle::with_size(Size::INFINITY), + ); + + renderer.fill_text( + text, + Point::new(11.0, 11.0 + 25.0 * i as f32) + + Vector::new(-1.0, -1.0), + Color::BLACK, + Rectangle::with_size(Size::INFINITY), + ); + } + }, + ); + } } impl core::Renderer for Renderer { -- cgit From e2c129c057b521009d451e474a6820601877cf11 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:14:16 +0200 Subject: Fix `geometry::Cache` not reusing previous geometry --- wgpu/src/triangle.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 9cd02f72..be7bb867 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -95,7 +95,6 @@ impl Pipeline { needs_reupload: false, } } - Cache::Uploaded { batch, layer, -- cgit From 346ea313fd41d893627bfe1a4dba351713c07ea4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:26:08 +0200 Subject: Set proper `present_mode` in `iced_wgpu` --- wgpu/src/window/compositor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index e2b01477..55490c39 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -322,12 +322,12 @@ impl graphics::Compositor for Compositor { &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.format, - present_mode: wgpu::PresentMode::Immediate, + present_mode: self.settings.present_mode, width, height, alpha_mode: self.alpha_mode, view_formats: vec![], - desired_maximum_frame_latency: 2, + desired_maximum_frame_latency: 1, }, ); } -- cgit From d461f23e8dd34c5de73e0aa176a3301b01564652 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:31:13 +0200 Subject: Use default tolerance for dashed paths in `iced_wgpu` --- wgpu/src/geometry.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 7c8c0a35..d153c764 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -626,7 +626,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { let mut draw_line = false; walk_along_path( - path.raw().iter().flattened(0.01), + path.raw().iter().flattened( + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + ), 0.0, lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, &mut RepeatedPattern { -- cgit From cc05cb9be4a1de5f0427f93ce64e658be0703f8b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 3 Apr 2024 23:39:38 +0200 Subject: Fix broken doc links in `iced_wgpu` and `iced_graphics` --- wgpu/src/settings.rs | 14 +++++++------- wgpu/src/window/compositor.rs | 11 +++++------ 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 828d9e09..a6aea0a5 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -2,18 +2,18 @@ use crate::core::{Font, Pixels}; use crate::graphics::{self, Antialiasing}; -/// The settings of a [`Backend`]. +/// The settings of a [`Renderer`]. /// -/// [`Backend`]: crate::Backend +/// [`Renderer`]: crate::Renderer #[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { - /// The present mode of the [`Backend`]. + /// The present mode of the [`Renderer`]. /// - /// [`Backend`]: crate::Backend + /// [`Renderer`]: crate::Renderer pub present_mode: wgpu::PresentMode, - /// The internal graphics backend to use. - pub internal_backend: wgpu::Backends, + /// The graphics backends to use. + pub backends: wgpu::Backends, /// The default [`Font`] to use. pub default_font: Font, @@ -33,7 +33,7 @@ impl Default for Settings { fn default() -> Settings { Settings { present_mode: wgpu::PresentMode::AutoVsync, - internal_backend: wgpu::Backends::all(), + backends: wgpu::Backends::all(), default_font: Font::default(), default_text_size: Pixels(16.0), antialiasing: None, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 55490c39..d546a6dc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -54,7 +54,7 @@ impl Compositor { compatible_window: Option, ) -> Result { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: settings.internal_backend, + backends: settings.backends, ..Default::default() }); @@ -63,7 +63,7 @@ impl Compositor { #[cfg(not(target_arch = "wasm32"))] if log::max_level() >= log::LevelFilter::Info { let available_adapters: Vec<_> = instance - .enumerate_adapters(settings.internal_backend) + .enumerate_adapters(settings.backends) .iter() .map(wgpu::Adapter::get_info) .collect(); @@ -197,8 +197,7 @@ impl Compositor { } } -/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and -/// window. +/// Creates a [`Compositor`] with the given [`Settings`] and window. pub async fn new( settings: Settings, compatible_window: W, @@ -206,7 +205,7 @@ pub async fn new( Compositor::request(settings, Some(compatible_window)).await } -/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +/// Presents the given primitives with the given [`Compositor`]. pub fn present>( compositor: &mut Compositor, renderer: &mut Renderer, @@ -273,7 +272,7 @@ impl graphics::Compositor for Compositor { match backend { None | Some("wgpu") => Ok(new( Settings { - internal_backend: wgpu::util::backend_bits_from_env() + backends: wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::all()), ..settings.into() }, -- cgit From 394e599c3a096b036aabdd6f850c4a7c518d44fa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 00:40:39 +0200 Subject: Fix layer transformations --- wgpu/src/geometry.rs | 39 +++++++------ wgpu/src/layer.rs | 65 ++++++++++----------- wgpu/src/lib.rs | 158 +++++++++++++++++++++++++++------------------------ wgpu/src/text.rs | 36 ++++++------ wgpu/src/triangle.rs | 72 +++++++++++++++-------- 5 files changed, 206 insertions(+), 164 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index d153c764..611e81f1 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -106,23 +106,28 @@ impl Frame { .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, - }, + .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(); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 5c23669a..1c9638b0 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -13,17 +13,17 @@ use std::rc::Rc; pub enum Layer<'a> { Live(&'a Live), - Cached(cell::Ref<'a, Cached>), + Cached(Transformation, cell::Ref<'a, Cached>), } pub enum LayerMut<'a> { Live(&'a mut Live), - Cached(cell::RefMut<'a, Cached>), + Cached(Transformation, cell::RefMut<'a, Cached>), } pub struct Stack { live: Vec, - cached: Vec>>, + cached: Vec<(Transformation, Rc>)>, order: Vec, transformations: Vec, previous: Vec, @@ -92,7 +92,7 @@ impl Stack { position, color, clip_bounds, - transformation: self.transformations.last().copied().unwrap(), + transformation: self.transformation(), }; self.live[self.current].text.push(paragraph); @@ -178,36 +178,31 @@ impl Stack { } pub fn draw_cached_layer(&mut self, layer: &Rc>) { - { - let mut layer = layer.borrow_mut(); - layer.transformation = self.transformation() * layer.transformation; - } - - self.cached.push(layer.clone()); + self.cached.push((self.transformation(), layer.clone())); self.order.push(Kind::Cache); } pub fn push_clip(&mut self, bounds: Option) { - 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, - ..Live::default() - }); - } else { - self.live[self.current].bounds = bounds; - } + // 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, + // ..Live::default() + // }); + // } else { + // self.live[self.current].bounds = bounds; + // } } pub fn pop_clip(&mut self) { - self.current = self.previous.pop().unwrap(); + // self.current = self.previous.pop().unwrap(); } pub fn push_transformation(&mut self, transformation: Transformation) { @@ -224,13 +219,17 @@ impl Stack { } pub fn iter_mut(&mut self) -> impl Iterator> { + dbg!(self.order.len()); 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()) + let (transformation, layer) = cached.next().unwrap(); + let layer = layer.borrow_mut(); + + LayerMut::Cached(*transformation * layer.transformation, layer) } }) } @@ -241,7 +240,12 @@ impl Stack { self.order.iter().map(move |kind| match kind { Kind::Live => Layer::Live(live.next().unwrap()), - Kind::Cache => Layer::Cached(cached.next().unwrap().borrow()), + Kind::Cache => { + let (transformation, layer) = cached.next().unwrap(); + let layer = layer.borrow(); + + Layer::Cached(*transformation * layer.transformation, layer) + } }) } @@ -288,7 +292,6 @@ impl Live { 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), @@ -301,7 +304,6 @@ impl Live { pub struct Cached { pub bounds: Option, pub transformation: Transformation, - pub last_transformation: Option, pub quads: quad::Cache, pub meshes: triangle::Cache, pub text: text::Cache, @@ -311,7 +313,6 @@ pub struct Cached { 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); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 2d023d8b..4705cfa0 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -116,32 +116,10 @@ impl Renderer { viewport: &Viewport, overlay: &[T], ) { - let target_size = viewport.physical_size(); - let scale_factor = viewport.scale_factor() as f32; - let transformation = viewport.projection(); - self.draw_overlay(overlay, viewport); - self.prepare( - engine, - device, - queue, - format, - encoder, - scale_factor, - target_size, - transformation, - ); - - self.render( - engine, - device, - encoder, - frame, - clear_color, - scale_factor, - target_size, - ); + self.prepare(engine, device, queue, format, encoder, viewport); + self.render(engine, device, encoder, frame, clear_color, viewport); } fn prepare( @@ -151,10 +129,10 @@ impl Renderer { queue: &wgpu::Queue, _format: wgpu::TextureFormat, encoder: &mut wgpu::CommandEncoder, - scale_factor: f32, - target_size: Size, - transformation: Transformation, + viewport: &Viewport, ) { + let scale_factor = viewport.scale_factor() as f32; + for layer in self.layers.iter_mut() { match layer { LayerMut::Live(live) => { @@ -164,7 +142,7 @@ impl Renderer { encoder, &mut engine.staging_belt, &live.quads, - transformation, + viewport.projection(), scale_factor, ); } @@ -175,7 +153,7 @@ impl Renderer { encoder, &mut engine.staging_belt, &live.meshes, - transformation + viewport.projection() * Transformation::scale(scale_factor), ); } @@ -187,10 +165,11 @@ impl Renderer { encoder, &live.text, live.bounds.unwrap_or(Rectangle::with_size( - Size::INFINITY, + viewport.logical_size(), )), - scale_factor, - target_size, + live.transformation + * Transformation::scale(scale_factor), + viewport.physical_size(), ); } @@ -201,38 +180,46 @@ impl Renderer { encoder, &mut engine.staging_belt, &live.images, - transformation, + viewport.projection(), scale_factor, ); } } - LayerMut::Cached(mut cached) => { + 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, - transformation, + 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, - transformation - * Transformation::scale(scale_factor), + viewport.projection(), + transformation, ); } if !cached.text.is_empty() { - let bounds = cached - .bounds - .unwrap_or(Rectangle::with_size(Size::INFINITY)); + 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, @@ -240,8 +227,8 @@ impl Renderer { encoder, &mut cached.text, bounds, - scale_factor, - target_size, + transformation, + viewport.physical_size(), ); } @@ -252,7 +239,7 @@ impl Renderer { encoder, &mut engine.staging_belt, &cached.images, - transformation, + viewport.projection(), scale_factor, ); } @@ -268,8 +255,7 @@ impl Renderer { encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, clear_color: Option, - scale_factor: f32, - target_size: Size, + viewport: &Viewport, ) { use std::mem::ManuallyDrop; @@ -312,22 +298,37 @@ impl Renderer { 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::::from(Rectangle::with_size( + viewport.physical_size(), + )); + let layers: Vec<_> = self.layers.iter().collect(); let mut i = 0; + // println!("RENDER"); + while i < layers.len() { match layers[i] { Layer::Live(live) => { - let bounds = live - .bounds - .map(|bounds| bounds * scale_factor) + let layer_transformation = + Transformation::scale(scale_factor) + * live.transformation; + + let layer_bounds = live.bounds.unwrap_or(screen_bounds); + + let Some(physical_bounds) = physical_bounds + .intersection(&(layer_bounds * layer_transformation)) .map(Rectangle::snap) - .unwrap_or(Rectangle::with_size(target_size)); + else { + continue; + }; if !live.quads.is_empty() { engine.quad_pipeline.render_batch( quad_layer, - bounds, + physical_bounds, &live.quads, &mut render_pass, ); @@ -336,6 +337,7 @@ impl Renderer { } if !live.meshes.is_empty() { + // println!("LIVE PASS"); let _ = ManuallyDrop::into_inner(render_pass); engine.triangle_pipeline.render_batch( @@ -343,10 +345,10 @@ impl Renderer { encoder, frame, mesh_layer, - target_size, + viewport.physical_size(), &live.meshes, - bounds, - scale_factor, + physical_bounds, + &layer_transformation, ); mesh_layer += 1; @@ -375,7 +377,7 @@ impl Renderer { if !live.text.is_empty() { engine.text_pipeline.render_batch( text_layer, - bounds, + physical_bounds, &mut render_pass, ); @@ -386,7 +388,7 @@ impl Renderer { if !live.images.is_empty() { engine.image_pipeline.render( image_layer, - bounds, + physical_bounds, &mut render_pass, ); @@ -395,25 +397,35 @@ impl Renderer { i += 1; } - Layer::Cached(_) => { + Layer::Cached(_, _) => { let group_len = layers[i..] .iter() .position(|layer| matches!(layer, Layer::Live(_))) - .unwrap_or(layers.len()); - - let group = layers[i..i + group_len].iter().map(|layer| { - let Layer::Cached(cached) = layer else { - unreachable!() - }; - - let bounds = cached - .bounds - .map(|bounds| bounds * scale_factor) - .map(Rectangle::snap) - .unwrap_or(Rectangle::with_size(target_size)); - - (cached, bounds) - }); + .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() { @@ -430,17 +442,17 @@ impl Renderer { .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, - target_size, + viewport.physical_size(), group.clone().map(|(cached, bounds)| { (&cached.meshes, bounds) }), - scale_factor, ); render_pass = diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 016ac92a..e0a5355c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -26,7 +26,7 @@ pub enum Cache { renderer: glyphon::TextRenderer, atlas: Option, buffer_cache: Option, - scale_factor: f32, + transformation: Transformation, target_size: Size, needs_reupload: bool, }, @@ -95,7 +95,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, sections: &Batch, layer_bounds: Rectangle, - scale_factor: f32, + layer_transformation: Transformation, target_size: Size, ) { if self.renderers.len() <= self.prepare_layer { @@ -117,7 +117,7 @@ impl Pipeline { &mut self.cache, sections, layer_bounds, - scale_factor, + layer_transformation, target_size, ); @@ -140,7 +140,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, cache: &mut Cache, layer_bounds: Rectangle, - new_scale_factor: f32, + new_transformation: Transformation, new_target_size: Size, ) { match cache { @@ -186,7 +186,7 @@ impl Pipeline { buffer_cache.as_mut().unwrap_or(&mut self.cache), &batch, layer_bounds, - new_scale_factor, + new_transformation, new_target_size, ); @@ -196,7 +196,7 @@ impl Pipeline { renderer, atlas, buffer_cache, - scale_factor: new_scale_factor, + transformation: new_transformation, target_size: new_target_size, } } @@ -206,13 +206,13 @@ impl Pipeline { renderer, atlas, buffer_cache, - scale_factor, + transformation, target_size, } => { if *needs_reupload || atlas.is_none() || buffer_cache.is_none() - || new_scale_factor != *scale_factor + || new_transformation != *transformation || new_target_size != *target_size { let _ = prepare( @@ -224,11 +224,11 @@ impl Pipeline { buffer_cache.as_mut().unwrap_or(&mut self.cache), batch, layer_bounds, - *scale_factor, - *target_size, + new_transformation, + new_target_size, ); - *scale_factor = new_scale_factor; + *transformation = new_transformation; *target_size = new_target_size; } } @@ -297,7 +297,7 @@ fn prepare( buffer_cache: &mut BufferCache, sections: &Batch, layer_bounds: Rectangle, - scale_factor: f32, + layer_transformation: Transformation, target_size: Size, ) -> Result<(), glyphon::PrepareError> { let mut font_system = font_system().write().expect("Write font system"); @@ -349,7 +349,7 @@ fn prepare( }) .collect(); - let layer_bounds = layer_bounds * scale_factor; + let layer_bounds = layer_bounds * layer_transformation; let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { @@ -456,7 +456,7 @@ fn prepare( } }; - let bounds = bounds * transformation * scale_factor; + let bounds = bounds * transformation * layer_transformation; let left = match horizontal_alignment { alignment::Horizontal::Left => bounds.x, @@ -470,14 +470,16 @@ fn prepare( alignment::Vertical::Bottom => bounds.y - bounds.height, }; - let clip_bounds = layer_bounds - .intersection(&(clip_bounds * transformation * scale_factor))?; + let clip_bounds = layer_bounds.intersection( + &(clip_bounds * transformation * layer_transformation), + )?; Some(glyphon::TextArea { buffer, left, top, - scale: scale_factor * transformation.scale_factor(), + scale: transformation.scale_factor() + * layer_transformation.scale_factor(), bounds: glyphon::TextBounds { left: clip_bounds.x as i32, top: clip_bounds.y as i32, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index be7bb867..3a184da2 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -68,8 +68,11 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, cache: &mut Cache, - transformation: Transformation, + new_projection: Transformation, + new_transformation: Transformation, ) { + let new_projection = new_projection * new_transformation; + match cache { Cache::Staged(_) => { let Cache::Staged(batch) = @@ -86,21 +89,25 @@ impl Pipeline { &self.solid, &self.gradient, &batch, - transformation, + new_projection, ); *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 { + if *needs_reupload || new_projection != *projection { layer.prepare( device, encoder, @@ -108,9 +115,11 @@ impl Pipeline { &self.solid, &self.gradient, batch, - transformation, + new_projection, ); + *transformation = new_transformation; + *projection = new_projection; *needs_reupload = false; } } @@ -126,7 +135,7 @@ impl Pipeline { target_size: Size, meshes: &Batch, bounds: Rectangle, - scale_factor: f32, + transformation: &Transformation, ) { Self::render( device, @@ -136,8 +145,12 @@ impl Pipeline { &self.solid, &self.gradient, target_size, - std::iter::once((&self.layers[layer], meshes, bounds)), - scale_factor, + std::iter::once(( + &self.layers[layer], + meshes, + transformation, + bounds, + )), ); } @@ -150,9 +163,14 @@ impl Pipeline { target_size: Size, cache: &Cache, bounds: Rectangle, - scale_factor: f32, ) { - let Cache::Uploaded { batch, layer, .. } = cache else { + let Cache::Uploaded { + batch, + layer, + transformation, + .. + } = cache + else { return; }; @@ -164,8 +182,7 @@ impl Pipeline { &self.solid, &self.gradient, target_size, - std::iter::once((layer, batch, bounds)), - scale_factor, + std::iter::once((layer, batch, transformation, bounds)), ); } @@ -176,11 +193,16 @@ impl Pipeline { target: &wgpu::TextureView, target_size: Size, group: impl Iterator)>, - scale_factor: f32, ) { let group = group.filter_map(|(cache, bounds)| { - if let Cache::Uploaded { batch, layer, .. } = cache { - Some((layer, batch, bounds)) + if let Cache::Uploaded { + batch, + layer, + transformation, + .. + } = cache + { + Some((layer, batch, transformation, bounds)) } else { None } @@ -195,7 +217,6 @@ impl Pipeline { &self.gradient, target_size, group, - scale_factor, ); } @@ -207,8 +228,9 @@ impl Pipeline { solid: &solid::Pipeline, gradient: &gradient::Pipeline, target_size: Size, - group: impl Iterator)>, - scale_factor: f32, + group: impl Iterator< + Item = (&'a Layer, &'a Batch, &'a Transformation, Rectangle), + >, ) { { let (attachment, resolve_target, load) = if let Some(blit) = @@ -244,13 +266,13 @@ impl Pipeline { occlusion_query_set: None, }); - for (layer, meshes, bounds) in group { + for (layer, meshes, transformation, bounds) in group { layer.render( solid, gradient, meshes, bounds, - scale_factor, + *transformation, &mut render_pass, ); } @@ -272,6 +294,8 @@ pub enum Cache { Uploaded { batch: Batch, layer: Layer, + transformation: Transformation, + projection: Transformation, needs_reupload: bool, }, } @@ -390,6 +414,7 @@ impl Layer { for mesh in meshes { let indices = mesh.indices(); + let uniforms = Uniforms::new(transformation * mesh.transformation()); @@ -448,7 +473,7 @@ impl Layer { gradient: &'a gradient::Pipeline, meshes: &Batch, layer_bounds: Rectangle, - scale_factor: f32, + transformation: Transformation, render_pass: &mut wgpu::RenderPass<'a>, ) { let mut num_solids = 0; @@ -457,15 +482,12 @@ impl Layer { for (index, mesh) in meshes.iter().enumerate() { let Some(clip_bounds) = Rectangle::::from(layer_bounds) - .intersection(&(mesh.clip_bounds() * scale_factor)) - .map(Rectangle::snap) + .intersection(&(mesh.clip_bounds() * transformation)) else { continue; }; - if clip_bounds.width < 1 || clip_bounds.height < 1 { - continue; - } + let clip_bounds = clip_bounds.snap(); render_pass.set_scissor_rect( clip_bounds.x, -- cgit From 4a356cfc16f3b45d64826732009d9feeac016b28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 01:24:34 +0200 Subject: Enable clipping and disable v-sync for now --- wgpu/src/layer.rs | 35 +++++++++++++++++------------------ wgpu/src/text.rs | 1 + wgpu/src/window/compositor.rs | 4 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 1c9638b0..d415da72 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -183,26 +183,26 @@ impl Stack { } pub fn push_clip(&mut self, bounds: Option) { - // 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, - // ..Live::default() - // }); - // } else { - // self.live[self.current].bounds = bounds; - // } + 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, + ..Live::default() + }); + } else { + self.live[self.current].bounds = bounds; + } } pub fn pop_clip(&mut self) { - // self.current = self.previous.pop().unwrap(); + self.current = self.previous.pop().unwrap(); } pub fn push_transformation(&mut self, transformation: Transformation) { @@ -219,7 +219,6 @@ impl Stack { } pub fn iter_mut(&mut self) -> impl Iterator> { - dbg!(self.order.len()); let mut live = self.live.iter_mut(); let mut cached = self.cached.iter_mut(); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e0a5355c..a7695b74 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -230,6 +230,7 @@ impl Pipeline { *transformation = new_transformation; *target_size = new_target_size; + *needs_reupload = false; } } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index d546a6dc..ab441fa0 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -321,12 +321,12 @@ 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, view_formats: vec![], - desired_maximum_frame_latency: 1, + desired_maximum_frame_latency: 0, }, ); } -- cgit From 6d3e1d835e1688fbc58622a03a784ed25ed3f0e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Apr 2024 23:59:21 +0200 Subject: Decouple caching from layering and simplify everything --- wgpu/src/geometry.rs | 188 ++++++++----------- wgpu/src/image/mod.rs | 7 +- wgpu/src/layer.rs | 258 +++++++++++++------------- wgpu/src/lib.rs | 444 +++++++++++++------------------------------- wgpu/src/quad.rs | 188 ++++--------------- wgpu/src/text.rs | 427 ++++++++++++++++++++++-------------------- wgpu/src/triangle.rs | 503 +++++++++++++++++++++++++------------------------- 7 files changed, 855 insertions(+), 1160 deletions(-) (limited to 'wgpu') 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, - text: text::Batch, + meshes: Vec, + text: Vec, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, stroke_tessellator: tessellation::StrokeTessellator, } pub enum Geometry { - Live(Vec), - Cached(Rc<[Rc>]>), + Live { meshes: Vec, text: Vec }, + Cached(Cache), +} + +#[derive(Clone)] +pub struct Cache { + pub meshes: triangle::Cache, + pub text: text::Cache, } impl Cached for Geometry { - type Cache = Rc<[Rc>]>; + 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 { 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 { - 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) { @@ -269,7 +217,7 @@ impl geometry::frame::Backend for Frame { .expect("Stroke path"); } - fn fill_text(&mut self, text: impl Into) { + fn fill_text(&mut self, text: impl Into) { 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 { + 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, - cached: Vec<(Transformation, Rc>)>, - order: Vec, + layers: Vec, transformations: Vec, previous: Vec, + pending_meshes: Vec>, + pending_text: Vec>, 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) { + 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>) { - 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) { + 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) { + 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> { - 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 { + self.flush_pending(); - LayerMut::Cached(*transformation * layer.transformation, layer) - } - }) + self.layers[..self.active_count].iter_mut() } - pub fn iter(&self) -> impl Iterator> { - 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 { + 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, - 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, - 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::::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, @@ -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, - 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, - 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; + } } } } @@ -227,48 +149,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, 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; +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, - prepare_layer: usize, - cache: BufferCache, -} +pub type Batch = Vec; -pub enum Cache { - Staged(Batch), - Uploaded { - batch: Batch, - renderer: glyphon::TextRenderer, - atlas: Option, - buffer_cache: Option, +#[derive(Debug)] +pub enum Item { + Group { transformation: Transformation, - target_size: Size, - needs_reupload: bool, + text: Vec, }, + 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) -> 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) { + 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, + recently_used: FxHashSet, +} + +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, ) { - 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, - ) { - 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, + 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, + ) { + 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, 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, - 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, @@ -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; +pub type Batch = Vec; + +pub enum Item { + Group { + transformation: Transformation, + meshes: Vec, + }, + 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) -> 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) { + 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, + recently_used: FxHashSet, +} + +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, - meshes: &Batch, - bounds: Rectangle, - 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, - cache: &Cache, - bounds: Rectangle, - ) { - 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, - group: impl Iterator)>, - ) { - 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, - group: impl Iterator< - Item = (&'a Layer, &'a Batch, &'a Transformation, Rectangle), - >, - ) { - { - 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, + bounds: Rectangle, + group: impl Iterator, +) { + { + 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, + 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::::from(layer_bounds) - .intersection(&(mesh.clip_bounds() * transformation)) + let Some(clip_bounds) = + bounds.intersection(&(mesh.clip_bounds() * transformation)) else { continue; }; -- cgit From 7eb16452f340fe228e6928b496f8df6e9e86e554 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 00:57:59 +0200 Subject: Avoid generating empty `Frame` geometry in `iced_wgpu` --- wgpu/src/geometry.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index c8c350c5..b689d2a7 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -422,24 +422,31 @@ impl BufferStack { } fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator { - 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, - }, - }) + self.stack + .into_iter() + .filter_map(move |buffer| match buffer { + Buffer::Solid(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }) + } + Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { + Some(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + clip_bounds, + transformation: Transformation::IDENTITY, + }) + } + _ => None, + }) } } -- cgit From 441aac25995290a83162a4728f22492ff69a5f4d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 03:06:40 +0200 Subject: Avoid generating empty caches in `iced_wgpu` --- wgpu/src/geometry.rs | 17 +++++++++++++---- wgpu/src/lib.rs | 9 +++++++-- wgpu/src/text.rs | 31 +++++++++++++++++++++++-------- wgpu/src/triangle.rs | 31 +++++++++++++++++++++++-------- 4 files changed, 66 insertions(+), 22 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index b689d2a7..c36ff38e 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -38,8 +38,8 @@ pub enum Geometry { #[derive(Clone)] pub struct Cache { - pub meshes: triangle::Cache, - pub text: text::Cache, + pub meshes: Option, + pub text: Option, } impl Cached for Geometry { @@ -53,8 +53,17 @@ impl Cached for Geometry { match self { Self::Live { meshes, text } => { if let Some(mut previous) = previous { - previous.meshes.update(meshes); - previous.text.update(text); + if let Some(cache) = &mut previous.meshes { + cache.update(meshes); + } else { + previous.meshes = triangle::Cache::new(meshes); + } + + if let Some(cache) = &mut previous.text { + cache.update(text); + } else { + previous.text = text::Cache::new(text); + } previous } else { diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d632919f..dfc1aad4 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -517,8 +517,13 @@ impl graphics::geometry::Renderer for Renderer { self.layers.draw_text_group(text); } Geometry::Cached(cache) => { - self.layers.draw_mesh_cache(cache.meshes); - self.layers.draw_text_cache(cache.text); + if let Some(meshes) = cache.meshes { + self.layers.draw_mesh_cache(meshes); + } + + if let Some(text) = cache.text { + self.layers.draw_text_cache(text); + } } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e84e675d..1b21bb1c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -43,14 +43,18 @@ pub struct Cache { pub struct Id(u64); impl Cache { - pub fn new(text: Vec) -> Self { + pub fn new(text: Vec) -> Option { static NEXT_ID: AtomicU64 = AtomicU64::new(0); - Self { + if text.is_empty() { + return None; + } + + Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), text: Rc::from(text), version: 0, - } + }) } pub fn update(&mut self, text: Vec) { @@ -78,8 +82,12 @@ impl Storage { Self::default() } - fn get(&self, id: Id) -> Option<&Upload> { - self.uploads.get(&id) + fn get(&self, cache: &Cache) -> Option<&Upload> { + if cache.text.is_empty() { + return None; + } + + self.uploads.get(&cache.id) } fn prepare( @@ -97,8 +105,9 @@ impl Storage { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if upload.version != cache.version - || upload.transformation != new_transformation + if !cache.text.is_empty() + && (upload.version != cache.version + || upload.transformation != new_transformation) { let _ = prepare( device, @@ -154,6 +163,12 @@ impl Storage { transformation: new_transformation, version: 0, }); + + log::info!( + "New text upload: {} (total: {})", + cache.id.0, + self.uploads.len() + ); } } @@ -291,7 +306,7 @@ impl Pipeline { layer_count += 1; } Item::Cached { cache, .. } => { - if let Some(upload) = storage.get(cache.id) { + if let Some(upload) = storage.get(cache) { upload .renderer .render(&upload.atlas, render_pass) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index a08b6987..de6c026a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -38,14 +38,18 @@ pub struct Cache { pub struct Id(u64); impl Cache { - pub fn new(meshes: Vec) -> Self { + pub fn new(meshes: Vec) -> Option { static NEXT_ID: AtomicU64 = AtomicU64::new(0); - Self { + if meshes.is_empty() { + return None; + } + + Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), batch: Rc::from(meshes), version: 0, - } + }) } pub fn update(&mut self, meshes: Vec) { @@ -72,8 +76,12 @@ impl Storage { Self::default() } - fn get(&self, id: Id) -> Option<&Upload> { - self.uploads.get(&id) + fn get(&self, cache: &Cache) -> Option<&Upload> { + if cache.batch.is_empty() { + return None; + } + + self.uploads.get(&cache.id) } fn prepare( @@ -90,8 +98,9 @@ impl Storage { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if upload.version != cache.version - || upload.transformation != new_transformation + if !cache.batch.is_empty() + && (upload.version != cache.version + || upload.transformation != new_transformation) { upload.layer.prepare( device, @@ -125,6 +134,12 @@ impl Storage { transformation: new_transformation, version: 0, }); + + log::info!( + "New mesh upload: {} (total: {})", + cache.id.0, + self.uploads.len() + ); } } @@ -247,7 +262,7 @@ impl Pipeline { transformation, cache, } => { - let upload = storage.get(cache.id)?; + let upload = storage.get(cache)?; Some(( &upload.layer, -- cgit From a699348bf3c7af5622b7cbb0b8f8b6bb9b16a22a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 6 Apr 2024 06:23:29 +0200 Subject: Reenable proper `present_mode` in `iced_wgpu` --- wgpu/src/window/compositor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index ab441fa0..d546a6dc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -321,12 +321,12 @@ impl graphics::Compositor for Compositor { &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.format, - present_mode: wgpu::PresentMode::Immediate, + present_mode: self.settings.present_mode, width, height, alpha_mode: self.alpha_mode, view_formats: vec![], - desired_maximum_frame_latency: 0, + desired_maximum_frame_latency: 1, }, ); } -- cgit From 5cd98f069dea8720bca7748d6c12fa410cbe79b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 7 Apr 2024 12:42:12 +0200 Subject: Use built-in `[lints]` table in `Cargo.toml` --- wgpu/Cargo.toml | 4 ++++ wgpu/src/lib.rs | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0b713784..5f464522 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -10,6 +10,9 @@ homepage.workspace = true categories.workspace = true keywords.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true @@ -44,3 +47,4 @@ resvg.optional = true tracing.workspace = true tracing.optional = true + diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b00e5c3c..2cb9cf04 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,14 +20,6 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![forbid(rust_2018_idioms)] -#![deny( - missing_debug_implementations, - missing_docs, - unsafe_code, - unused_results, - rustdoc::broken_intra_doc_links -)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod layer; pub mod primitive; -- cgit From 288f62bfb691a91e01b9ddbce9dbdc560ee9036a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 7 Apr 2024 18:45:30 +0200 Subject: Share `msaa::Blit` texture between multiple windows --- wgpu/src/lib.rs | 8 ++- wgpu/src/shader/blit.wgsl | 20 +++---- wgpu/src/triangle.rs | 19 +++---- wgpu/src/triangle/msaa.rs | 134 +++++++++++++++++++++++++++++++--------------- 4 files changed, 112 insertions(+), 69 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index ccad08d5..030bcade 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -117,7 +117,7 @@ impl Renderer { ) { self.draw_overlay(overlay, viewport); self.prepare(engine, device, queue, format, encoder, viewport); - self.render(engine, device, encoder, frame, clear_color, viewport); + self.render(engine, encoder, frame, clear_color, viewport); self.triangle_storage.trim(); self.text_storage.trim(); @@ -153,7 +153,8 @@ impl Renderer { &mut engine.staging_belt, &mut self.triangle_storage, &layer.triangles, - viewport.projection() * Transformation::scale(scale_factor), + Transformation::scale(scale_factor), + viewport.physical_size(), ); } @@ -187,7 +188,6 @@ impl Renderer { fn render( &mut self, engine: &mut Engine, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, clear_color: Option, @@ -264,13 +264,11 @@ impl Renderer { 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, ); diff --git a/wgpu/src/shader/blit.wgsl b/wgpu/src/shader/blit.wgsl index c2ea223f..d7633808 100644 --- a/wgpu/src/shader/blit.wgsl +++ b/wgpu/src/shader/blit.wgsl @@ -1,22 +1,14 @@ -var positions: array, 6> = array, 6>( - vec2(-1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(-1.0, 1.0), - vec2(1.0, 1.0), - vec2(1.0, -1.0) -); - var uvs: array, 6> = array, 6>( vec2(0.0, 0.0), - vec2(0.0, 1.0), + vec2(1.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), - vec2(1.0, 0.0), + vec2(0.0, 1.0), vec2(1.0, 1.0) ); @group(0) @binding(0) var u_sampler: sampler; +@group(0) @binding(1) var u_ratio: vec2; @group(1) @binding(0) var u_texture: texture_2d; struct VertexInput { @@ -30,9 +22,11 @@ struct VertexOutput { @vertex fn vs_main(input: VertexInput) -> VertexOutput { + let uv = uvs[input.vertex_index]; + var out: VertexOutput; - out.uv = uvs[input.vertex_index]; - out.position = vec4(positions[input.vertex_index], 0.0, 1.0); + out.uv = uv * u_ratio; + out.position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); return out; } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 06c0ef02..98ba41b3 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -184,8 +184,16 @@ impl Pipeline { belt: &mut wgpu::util::StagingBelt, storage: &mut Storage, items: &[Item], - projection: Transformation, + scale: Transformation, + target_size: Size, ) { + let projection = if let Some(blit) = &mut self.blit { + blit.prepare(device, encoder, belt, target_size) * scale + } else { + Transformation::orthographic(target_size.width, target_size.height) + * scale + }; + for item in items { match item { Item::Group { @@ -233,13 +241,11 @@ impl Pipeline { pub fn render( &mut self, - device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, storage: &Storage, start: usize, batch: &Batch, - target_size: Size, bounds: Rectangle, screen_transformation: Transformation, ) -> usize { @@ -274,13 +280,11 @@ impl Pipeline { }); render( - device, encoder, target, self.blit.as_mut(), &self.solid, &self.gradient, - target_size, bounds, items, ); @@ -294,20 +298,17 @@ impl Pipeline { } 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, bounds: Rectangle, group: impl Iterator, ) { { let (attachment, resolve_target, load) = if let Some(blit) = &mut blit { - let (attachment, resolve_target) = - blit.targets(device, target_size.width, target_size.height); + let (attachment, resolve_target) = blit.targets(); ( attachment, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 14abd20b..fcee6b3e 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,13 +1,18 @@ +use crate::core::{Size, Transformation}; use crate::graphics; +use std::num::NonZeroU64; + #[derive(Debug)] pub struct Blit { format: wgpu::TextureFormat, pipeline: wgpu::RenderPipeline, constants: wgpu::BindGroup, + ratio: wgpu::Buffer, texture_layout: wgpu::BindGroupLayout, sample_count: u32, targets: Option, + last_region: Option>, } impl Blit { @@ -19,27 +24,52 @@ impl Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + let ratio = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced-wgpu::triangle::msaa ratio"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle:msaa uniforms layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }], + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], }); let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::triangle::msaa uniforms bind group"), layout: &constant_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }], + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: ratio.as_entire_binding(), + }, + ], }); let texture_layout = @@ -88,9 +118,7 @@ impl Blit { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format, - blend: Some( - wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, - ), + blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], }), @@ -112,43 +140,61 @@ impl Blit { format, pipeline, constants: constant_bind_group, + ratio, texture_layout, sample_count: antialiasing.sample_count(), targets: None, + last_region: None, } } - pub fn targets( + pub fn prepare( &mut self, device: &wgpu::Device, - width: u32, - height: u32, - ) -> (&wgpu::TextureView, &wgpu::TextureView) { + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + region_size: Size, + ) -> Transformation { match &mut self.targets { - None => { + Some(targets) + if region_size.width <= targets.size.width + && region_size.height <= targets.size.height => {} + _ => { self.targets = Some(Targets::new( device, self.format, &self.texture_layout, self.sample_count, - width, - height, + region_size, )); } - Some(targets) => { - if targets.width != width || targets.height != height { - self.targets = Some(Targets::new( - device, - self.format, - &self.texture_layout, - self.sample_count, - width, - height, - )); - } - } } + let targets = self.targets.as_mut().unwrap(); + + if Some(region_size) != self.last_region { + let ratio = Ratio { + u: region_size.width as f32 / targets.size.width as f32, + v: region_size.height as f32 / targets.size.height as f32, + }; + + belt.write_buffer( + encoder, + &self.ratio, + 0, + NonZeroU64::new(std::mem::size_of::() as u64) + .expect("non-empty ratio"), + device, + ) + .copy_from_slice(bytemuck::bytes_of(&ratio)); + + self.last_region = Some(region_size); + } + + Transformation::orthographic(targets.size.width, targets.size.height) + } + + pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) { let targets = self.targets.as_ref().unwrap(); (&targets.attachment, &targets.resolve) @@ -191,8 +237,7 @@ struct Targets { attachment: wgpu::TextureView, resolve: wgpu::TextureView, bind_group: wgpu::BindGroup, - width: u32, - height: u32, + size: Size, } impl Targets { @@ -201,12 +246,11 @@ impl Targets { format: wgpu::TextureFormat, texture_layout: &wgpu::BindGroupLayout, sample_count: u32, - width: u32, - height: u32, + size: Size, ) -> Targets { let extent = wgpu::Extent3d { - width, - height, + width: size.width, + height: size.height, depth_or_array_layers: 1, }; @@ -252,8 +296,14 @@ impl Targets { attachment, resolve, bind_group, - width, - height, + size, } } } + +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Ratio { + u: f32, + v: f32, +} -- cgit From d922b478156488a7bc03c6e791e05c040d702634 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 Apr 2024 15:04:35 +0200 Subject: Reintroduce support for custom primitives in `iced_wgpu` --- wgpu/src/engine.rs | 13 +++-- wgpu/src/layer.rs | 16 ++++++ wgpu/src/lib.rs | 62 ++++++++++++++++++++-- wgpu/src/primitive.rs | 103 +++++++++++++++++++++++++++++++----- wgpu/src/primitive/pipeline.rs | 115 ----------------------------------------- wgpu/src/triangle.rs | 7 ++- 6 files changed, 176 insertions(+), 140 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index e45b62b2..96cd6db8 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -1,19 +1,21 @@ use crate::buffer; use crate::graphics::Antialiasing; -use crate::primitive::pipeline; +use crate::primitive; use crate::quad; use crate::text; use crate::triangle; #[allow(missing_debug_implementations)] pub struct Engine { + pub(crate) staging_belt: wgpu::util::StagingBelt, + pub(crate) format: wgpu::TextureFormat, + 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, + pub(crate) primitive_storage: primitive::Storage, } impl Engine { @@ -43,13 +45,16 @@ impl Engine { staging_belt: wgpu::util::StagingBelt::new( buffer::MAX_WRITE_SIZE as u64, ), + format, + quad_pipeline, text_pipeline, triangle_pipeline, - _pipeline_storage: pipeline::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + + primitive_storage: primitive::Storage::default(), } } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index c8c27c61..7a18e322 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -4,6 +4,7 @@ use crate::graphics::color; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Mesh; use crate::image::{self, Image}; +use crate::primitive::{self, Primitive}; use crate::quad::{self, Quad}; use crate::text::{self, Text}; use crate::triangle; @@ -13,6 +14,7 @@ pub struct Layer { pub bounds: Rectangle, pub quads: quad::Batch, pub triangles: triangle::Batch, + pub primitives: primitive::Batch, pub text: text::Batch, pub images: image::Batch, } @@ -23,6 +25,7 @@ impl Default for Layer { bounds: Rectangle::INFINITE, quads: quad::Batch::default(), triangles: triangle::Batch::default(), + primitives: primitive::Batch::default(), text: text::Batch::default(), images: image::Batch::default(), } @@ -222,6 +225,18 @@ impl Stack { }); } + pub fn draw_primitive( + &mut self, + bounds: Rectangle, + primitive: Box, + ) { + let bounds = bounds * self.transformation(); + + self.layers[self.current] + .primitives + .push(primitive::Instance { bounds, primitive }); + } + pub fn push_clip(&mut self, bounds: Rectangle) { self.previous.push(self.current); @@ -282,6 +297,7 @@ impl Stack { live.quads.clear(); live.triangles.clear(); + live.primitives.clear(); live.text.clear(); live.images.clear(); pending_meshes.clear(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 030bcade..0580399d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -101,8 +101,6 @@ impl Renderer { } } - pub fn draw_primitive(&mut self, _primitive: Primitive) {} - pub fn present>( &mut self, engine: &mut Engine, @@ -158,6 +156,19 @@ impl Renderer { ); } + if !layer.primitives.is_empty() { + for instance in &layer.primitives { + instance.primitive.prepare( + device, + queue, + engine.format, + &mut engine.primitive_storage, + &instance.bounds, + viewport, + ); + } + } + if !layer.text.is_empty() { engine.text_pipeline.prepare( device, @@ -247,7 +258,9 @@ impl Renderer { continue; }; - let scissor_rect = physical_bounds.snap(); + let Some(scissor_rect) = physical_bounds.snap() else { + continue; + }; if !layer.quads.is_empty() { engine.quad_pipeline.render( @@ -293,6 +306,43 @@ impl Renderer { )); } + if !layer.primitives.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + for instance in &layer.primitives { + if let Some(clip_bounds) = (instance.bounds * scale) + .intersection(&physical_bounds) + .and_then(Rectangle::snap) + { + instance.primitive.render( + encoder, + &engine.primitive_storage, + frame, + &clip_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.text.is_empty() { text_layer += engine.text_pipeline.render( &self.text_storage, @@ -520,6 +570,12 @@ impl graphics::geometry::Renderer for Renderer { } } +impl primitive::Renderer for Renderer { + fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { + self.layers.draw_primitive(bounds, Box::new(primitive)); + } +} + impl graphics::compositor::Default for crate::Renderer { type Compositor = window::Compositor; } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 8e311d2b..4ba1ed9a 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,20 +1,95 @@ -//! Draw using different graphical primitives. -pub mod pipeline; +//! Draw custom primitives. +use crate::core::{self, Rectangle}; +use crate::graphics::Viewport; -pub use pipeline::Pipeline; +use rustc_hash::FxHashMap; +use std::any::{Any, TypeId}; +use std::fmt::Debug; -use crate::graphics::Mesh; +/// A batch of primitives. +pub type Batch = Vec; -use std::fmt::Debug; +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { + /// Processes the [`Primitive`], allowing for GPU buffer allocation. + fn prepare( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + storage: &mut Storage, + bounds: &Rectangle, + viewport: &Viewport, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + storage: &Storage, + target: &wgpu::TextureView, + clip_bounds: &Rectangle, + ); +} + +#[derive(Debug)] +/// An instance of a specific [`Primitive`]. +pub struct Instance { + /// The bounds of the [`Instance`]. + pub bounds: Rectangle, + + /// The [`Primitive`] to render. + pub primitive: Box, +} + +impl Instance { + /// Creates a new [`Pipeline`] with the given [`Primitive`]. + pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { + Instance { + bounds, + primitive: Box::new(primitive), + } + } +} + +/// A renderer than can draw custom primitives. +pub trait Renderer: core::Renderer { + /// Draws a custom primitive. + fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive); +} + +/// Stores custom, user-provided types. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: FxHashMap>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a type `T`. + pub fn has(&self) -> bool { + self.pipelines.get(&TypeId::of::()).is_some() + } + + /// Inserts the data `T` in to [`Storage`]. + pub fn store(&mut self, data: T) { + let _ = self.pipelines.insert(TypeId::of::(), Box::new(data)); + } -/// The graphical primitives supported by `iced_wgpu`. -pub type Primitive = crate::graphics::Primitive; + /// Returns a reference to the data with type `T` if it exists in [`Storage`]. + pub fn get(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_ref::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } -/// The custom primitives supported by `iced_wgpu`. -#[derive(Debug, Clone, PartialEq)] -pub enum Custom { - /// A mesh primitive. - Mesh(Mesh), - /// A custom pipeline primitive. - Pipeline(Pipeline), + /// Returns a mutable reference to the data with type `T` if it exists in [`Storage`]. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { + pipeline + .downcast_mut::() + .expect("Pipeline with this type does not exist in Storage.") + }) + } } diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 59c54db9..8b137891 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,116 +1 @@ -//! Draw primitives using custom pipelines. -use crate::core::{self, Rectangle, Size}; -use rustc_hash::FxHashMap; -use std::any::{Any, TypeId}; -use std::fmt::Debug; -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 [`Primitive`] to render. - pub primitive: Arc, -} - -impl Pipeline { - /// Creates a new [`Pipeline`] with the given [`Primitive`]. - pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { - Pipeline { - bounds, - primitive: Arc::new(primitive), - } - } -} - -impl PartialEq for Pipeline { - fn eq(&self, other: &Self) -> bool { - self.primitive.type_id() == other.primitive.type_id() - } -} - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + Send + Sync + 'static { - /// Processes the [`Primitive`], allowing for GPU buffer allocation. - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - bounds: Rectangle, - target_size: Size, - scale_factor: f32, - storage: &mut Storage, - ); - - /// Renders the [`Primitive`]. - fn render( - &self, - storage: &Storage, - target: &wgpu::TextureView, - target_size: Size, - viewport: Rectangle, - encoder: &mut wgpu::CommandEncoder, - ); -} - -/// A renderer than can draw custom pipeline primitives. -pub trait Renderer: core::Renderer { - /// Draws a custom pipeline primitive. - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl Primitive, - ); -} - -impl Renderer for crate::Renderer { - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl Primitive, - ) { - self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( - Pipeline::new(bounds, primitive), - ))); - } -} - -/// Stores custom, user-provided pipelines. -#[derive(Default, Debug)] -pub struct Storage { - pipelines: FxHashMap>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a pipeline with type `T`. - pub fn has(&self) -> bool { - self.pipelines.get(&TypeId::of::()).is_some() - } - - /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store(&mut self, pipeline: T) { - let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); - } - - /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. - pub fn get(&self) -> Option<&T> { - self.pipelines.get(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_ref::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } - - /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. - pub fn get_mut(&mut self) -> Option<&mut T> { - self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { - pipeline - .downcast_mut::() - .expect("Pipeline with this type does not exist in Storage.") - }) - } -} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 98ba41b3..8470ea39 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -501,14 +501,13 @@ impl Layer { let mut last_is_solid = None; for (index, mesh) in meshes.iter().enumerate() { - let Some(clip_bounds) = - bounds.intersection(&(mesh.clip_bounds() * transformation)) + let Some(clip_bounds) = bounds + .intersection(&(mesh.clip_bounds() * transformation)) + .and_then(Rectangle::snap) else { continue; }; - let clip_bounds = clip_bounds.snap(); - render_pass.set_scissor_rect( clip_bounds.x, clip_bounds.y, -- cgit From f88488543f0b2d0ca95753d1e6527ba06981290a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 Apr 2024 15:05:12 +0200 Subject: Remove leftover `primitive::pipeline` module --- wgpu/src/primitive/pipeline.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 wgpu/src/primitive/pipeline.rs (limited to 'wgpu') diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs deleted file mode 100644 index 8b137891..00000000 --- a/wgpu/src/primitive/pipeline.rs +++ /dev/null @@ -1 +0,0 @@ - -- cgit From 2c6fd9ac14c5d270e05b97b7a7fab811d25834c4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 Apr 2024 15:35:54 +0200 Subject: Make arguments of `Renderer::new` explicit in `iced_wgpu` --- wgpu/src/lib.rs | 10 +++++++--- wgpu/src/window/compositor.rs | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0580399d..b1ae4e89 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -87,10 +87,14 @@ pub struct Renderer { } impl Renderer { - pub fn new(settings: Settings, _engine: &Engine) -> Self { + pub fn new( + _engine: &Engine, + default_font: Font, + default_text_size: Pixels, + ) -> Self { Self { - default_font: settings.default_font, - default_text_size: settings.default_text_size, + default_font, + default_text_size, layers: layer::Stack::new(), triangle_storage: triangle::Storage::new(), diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index d546a6dc..095afd48 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -289,7 +289,11 @@ impl graphics::Compositor for Compositor { } fn create_renderer(&self) -> Self::Renderer { - Renderer::new(self.settings, &self.engine) + Renderer::new( + &self.engine, + self.settings.default_font, + self.settings.default_text_size, + ) } fn create_surface( -- cgit From 6ad5bb3597f640ac329801adf735d633bf0a512f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 9 Apr 2024 22:25:16 +0200 Subject: Port `iced_tiny_skia` to new layering architecture --- wgpu/src/geometry.rs | 24 ++-- wgpu/src/layer.rs | 301 +++++++++++++++++++++----------------------------- wgpu/src/lib.rs | 58 +++++----- wgpu/src/primitive.rs | 6 +- 4 files changed, 173 insertions(+), 216 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 985650e2..60967082 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -19,18 +19,6 @@ use lyon::tessellation; use std::borrow::Cow; -/// A frame for drawing some geometry. -#[allow(missing_debug_implementations)] -pub struct Frame { - clip_bounds: Rectangle, - buffers: BufferStack, - meshes: Vec, - text: Vec, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, -} - #[derive(Debug)] pub enum Geometry { Live { meshes: Vec, text: Vec }, @@ -79,6 +67,18 @@ impl Cached for Geometry { } } +/// A frame for drawing some geometry. +#[allow(missing_debug_implementations)] +pub struct Frame { + clip_bounds: Rectangle, + buffers: BufferStack, + meshes: Vec, + text: Vec, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, +} + impl Frame { /// Creates a new [`Frame`] with the given [`Size`]. pub fn new(size: Size) -> Frame { diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 7a18e322..9526c5a8 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,6 +1,8 @@ use crate::core::renderer; use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::graphics; use crate::graphics::color; +use crate::graphics::layer; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Mesh; use crate::image::{self, Image}; @@ -9,6 +11,8 @@ use crate::quad::{self, Quad}; use crate::text::{self, Text}; use crate::triangle; +pub type Stack = layer::Stack; + #[derive(Debug)] pub struct Layer { pub bounds: Rectangle, @@ -17,48 +21,18 @@ pub struct Layer { pub primitives: primitive::Batch, pub text: text::Batch, pub images: image::Batch, + pending_meshes: Vec, + pending_text: Vec, } -impl Default for Layer { - fn default() -> Self { - Self { - bounds: Rectangle::INFINITE, - quads: quad::Batch::default(), - triangles: triangle::Batch::default(), - primitives: primitive::Batch::default(), - text: text::Batch::default(), - images: image::Batch::default(), - } - } -} - -#[derive(Debug)] -pub struct Stack { - layers: Vec, - transformations: Vec, - previous: Vec, - pending_meshes: Vec>, - pending_text: Vec>, - current: usize, - active_count: usize, -} - -impl Stack { - pub fn new() -> Self { - Self { - layers: vec![Layer::default()], - transformations: vec![Transformation::IDENTITY], - previous: vec![], - pending_meshes: vec![Vec::new()], - pending_text: vec![Vec::new()], - current: 0, - active_count: 1, - } - } - - pub fn draw_quad(&mut self, quad: renderer::Quad, background: Background) { - let transformation = self.transformations.last().unwrap(); - let bounds = quad.bounds * *transformation; +impl Layer { + pub fn draw_quad( + &mut self, + quad: renderer::Quad, + background: Background, + transformation: Transformation, + ) { + let bounds = quad.bounds * transformation; let quad = Quad { position: [bounds.x, bounds.y], @@ -71,7 +45,7 @@ impl Stack { shadow_blur_radius: quad.shadow.blur_radius, }; - self.layers[self.current].quads.add(quad, &background); + self.quads.add(quad, &background); } pub fn draw_paragraph( @@ -80,16 +54,17 @@ impl Stack { position: Point, color: Color, clip_bounds: Rectangle, + transformation: Transformation, ) { let paragraph = Text::Paragraph { paragraph: paragraph.downgrade(), position, color, clip_bounds, - transformation: self.transformations.last().copied().unwrap(), + transformation, }; - self.pending_text[self.current].push(paragraph); + self.pending_text.push(paragraph); } pub fn draw_editor( @@ -98,16 +73,17 @@ impl Stack { position: Point, color: Color, clip_bounds: Rectangle, + transformation: Transformation, ) { let editor = Text::Editor { editor: editor.downgrade(), position, color, clip_bounds, - transformation: self.transformation(), + transformation, }; - self.pending_text[self.current].push(editor); + self.pending_text.push(editor); } pub fn draw_text( @@ -116,9 +92,8 @@ impl Stack { position: Point, color: Color, clip_bounds: Rectangle, + transformation: Transformation, ) { - let transformation = self.transformation(); - let text = Text::Cached { content: text.content, bounds: Rectangle::new(position, text.bounds) * transformation, @@ -133,7 +108,7 @@ impl Stack { clip_bounds: clip_bounds * transformation, }; - self.pending_text[self.current].push(text); + self.pending_text.push(text); } pub fn draw_image( @@ -141,14 +116,15 @@ impl Stack { handle: crate::core::image::Handle, filter_method: crate::core::image::FilterMethod, bounds: Rectangle, + transformation: Transformation, ) { let image = Image::Raster { handle, filter_method, - bounds: bounds * self.transformation(), + bounds: bounds * transformation, }; - self.layers[self.current].images.push(image); + self.images.push(image); } pub fn draw_svg( @@ -156,72 +132,87 @@ impl Stack { handle: crate::core::svg::Handle, color: Option, bounds: Rectangle, + transformation: Transformation, ) { let svg = Image::Vector { handle, color, - bounds: bounds * self.transformation(), + bounds: bounds * transformation, }; - self.layers[self.current].images.push(svg); + self.images.push(svg); } - pub fn draw_mesh(&mut self, mut mesh: Mesh) { + pub fn draw_mesh( + &mut self, + mut mesh: Mesh, + transformation: Transformation, + ) { match &mut mesh { - Mesh::Solid { transformation, .. } - | Mesh::Gradient { transformation, .. } => { - *transformation = *transformation * self.transformation(); + Mesh::Solid { + transformation: local_transformation, + .. + } + | Mesh::Gradient { + transformation: local_transformation, + .. + } => { + *local_transformation = *local_transformation * transformation; } } - self.pending_meshes[self.current].push(mesh); + self.pending_meshes.push(mesh); } - pub fn draw_mesh_group(&mut self, meshes: Vec) { - self.flush_pending(); - - let transformation = self.transformation(); + pub fn draw_mesh_group( + &mut self, + meshes: Vec, + transformation: Transformation, + ) { + self.flush_meshes(); - self.layers[self.current] - .triangles - .push(triangle::Item::Group { - transformation, - meshes, - }); + self.triangles.push(triangle::Item::Group { + meshes, + transformation, + }); } - pub fn draw_mesh_cache(&mut self, cache: triangle::Cache) { - self.flush_pending(); - - let transformation = self.transformation(); + pub fn draw_mesh_cache( + &mut self, + cache: triangle::Cache, + transformation: Transformation, + ) { + self.flush_meshes(); - self.layers[self.current] - .triangles - .push(triangle::Item::Cached { - transformation, - cache, - }); + self.triangles.push(triangle::Item::Cached { + cache, + transformation, + }); } - pub fn draw_text_group(&mut self, text: Vec) { - self.flush_pending(); - - let transformation = self.transformation(); + pub fn draw_text_group( + &mut self, + text: Vec, + transformation: Transformation, + ) { + self.flush_text(); - self.layers[self.current].text.push(text::Item::Group { - transformation, + self.text.push(text::Item::Group { text, + transformation, }); } - pub fn draw_text_cache(&mut self, cache: text::Cache) { - self.flush_pending(); - - let transformation = self.transformation(); + pub fn draw_text_cache( + &mut self, + cache: text::Cache, + transformation: Transformation, + ) { + self.flush_text(); - self.layers[self.current].text.push(text::Item::Cached { - transformation, + self.text.push(text::Item::Cached { cache, + transformation, }); } @@ -229,112 +220,74 @@ impl Stack { &mut self, bounds: Rectangle, primitive: Box, + transformation: Transformation, ) { - let bounds = bounds * self.transformation(); + let bounds = bounds * transformation; - self.layers[self.current] - .primitives + self.primitives .push(primitive::Instance { bounds, primitive }); } - pub fn push_clip(&mut self, bounds: Rectangle) { - self.previous.push(self.current); - - self.current = self.active_count; - self.active_count += 1; - - let bounds = bounds * self.transformation(); - - if self.current == self.layers.len() { - self.layers.push(Layer { - bounds, - ..Layer::default() + fn flush_meshes(&mut self) { + if !self.pending_meshes.is_empty() { + self.triangles.push(triangle::Item::Group { + transformation: Transformation::IDENTITY, + meshes: self.pending_meshes.drain(..).collect(), }); - self.pending_meshes.push(Vec::new()); - self.pending_text.push(Vec::new()); - } else { - 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); - } - - pub fn pop_transformation(&mut self) { - let _ = self.transformations.pop(); - } - - fn transformation(&self) -> Transformation { - self.transformations.last().copied().unwrap() + fn flush_text(&mut self) { + if !self.pending_text.is_empty() { + self.text.push(text::Item::Group { + transformation: Transformation::IDENTITY, + text: self.pending_text.drain(..).collect(), + }); + } } +} - pub fn iter_mut(&mut self) -> impl Iterator { - self.flush_pending(); - - self.layers[..self.active_count].iter_mut() +impl graphics::Layer for Layer { + fn with_bounds(bounds: Rectangle) -> Self { + Self { + bounds, + ..Self::default() + } } - pub fn iter(&self) -> impl Iterator { - self.layers[..self.active_count].iter() + fn flush(&mut self) { + self.flush_meshes(); + self.flush_text(); } - pub fn clear(&mut self) { - 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.triangles.clear(); - live.primitives.clear(); - live.text.clear(); - live.images.clear(); - pending_meshes.clear(); - } - - self.current = 0; - self.active_count = 1; - self.previous.clear(); + fn resize(&mut self, bounds: Rectangle) { + self.bounds = bounds; } - // We want to keep the allocated memory - #[allow(clippy::drain_collect)] - fn flush_pending(&mut self) { - let transformation = self.transformation(); - - 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(), - }); - } + fn reset(&mut self) { + self.bounds = Rectangle::INFINITE; - 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(), - }); - } + self.quads.clear(); + self.triangles.clear(); + self.primitives.clear(); + self.text.clear(); + self.images.clear(); + self.pending_meshes.clear(); + self.pending_text.clear(); } } -impl Default for Stack { +impl Default for Layer { fn default() -> Self { - Self::new() + Self { + bounds: Rectangle::INFINITE, + quads: quad::Batch::default(), + triangles: triangle::Batch::default(), + primitives: primitive::Batch::default(), + text: text::Batch::default(), + images: image::Batch::default(), + pending_meshes: Vec::new(), + pending_text: Vec::new(), + } } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b1ae4e89..178522de 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -66,8 +66,6 @@ use crate::core::{ 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 @@ -422,7 +420,7 @@ impl core::Renderer for Renderer { self.layers.push_clip(bounds); } - fn end_layer(&mut self, _bounds: Rectangle) { + fn end_layer(&mut self) { self.layers.pop_clip(); } @@ -430,7 +428,7 @@ impl core::Renderer for Renderer { self.layers.push_transformation(transformation); } - fn end_transformation(&mut self, _transformation: Transformation) { + fn end_transformation(&mut self) { self.layers.pop_transformation(); } @@ -439,7 +437,8 @@ impl core::Renderer for Renderer { quad: core::renderer::Quad, background: impl Into, ) { - self.layers.draw_quad(quad, background.into()); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_quad(quad, background.into(), transformation); } fn clear(&mut self) { @@ -464,15 +463,6 @@ impl core::text::Renderer for Renderer { 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, @@ -480,8 +470,15 @@ impl core::text::Renderer for Renderer { color: Color, clip_bounds: Rectangle, ) { - self.layers - .draw_paragraph(text, position, color, clip_bounds); + let (layer, transformation) = self.layers.current_mut(); + + layer.draw_paragraph( + text, + position, + color, + clip_bounds, + transformation, + ); } fn fill_editor( @@ -491,8 +488,8 @@ impl core::text::Renderer for Renderer { color: Color, clip_bounds: Rectangle, ) { - self.layers - .draw_editor(editor, position, color, clip_bounds); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_editor(editor, position, color, clip_bounds, transformation); } fn fill_text( @@ -502,7 +499,8 @@ impl core::text::Renderer for Renderer { color: Color, clip_bounds: Rectangle, ) { - self.layers.draw_text(text, position, color, clip_bounds); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_text(text, position, color, clip_bounds, transformation); } } @@ -520,7 +518,8 @@ impl core::image::Renderer for Renderer { filter_method: core::image::FilterMethod, bounds: Rectangle, ) { - self.layers.draw_image(handle, filter_method, bounds); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_image(handle, filter_method, bounds, transformation); } } @@ -536,13 +535,15 @@ impl core::svg::Renderer for Renderer { color_filter: Option, bounds: Rectangle, ) { - self.layers.draw_svg(handle, color_filter, bounds); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_svg(handle, color_filter, bounds, transformation); } } impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, mesh: graphics::Mesh) { - self.layers.draw_mesh(mesh); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_mesh(mesh, transformation); } } @@ -556,18 +557,20 @@ impl graphics::geometry::Renderer for Renderer { } fn draw_geometry(&mut self, geometry: Self::Geometry) { + let (layer, transformation) = self.layers.current_mut(); + match geometry { Geometry::Live { meshes, text } => { - self.layers.draw_mesh_group(meshes); - self.layers.draw_text_group(text); + layer.draw_mesh_group(meshes, transformation); + layer.draw_text_group(text, transformation); } Geometry::Cached(cache) => { if let Some(meshes) = cache.meshes { - self.layers.draw_mesh_cache(meshes); + layer.draw_mesh_cache(meshes, transformation); } if let Some(text) = cache.text { - self.layers.draw_text_cache(text); + layer.draw_text_cache(text, transformation); } } } @@ -576,7 +579,8 @@ impl graphics::geometry::Renderer for Renderer { impl primitive::Renderer for Renderer { fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { - self.layers.draw_primitive(bounds, Box::new(primitive)); + let (layer, transformation) = self.layers.current_mut(); + layer.draw_primitive(bounds, Box::new(primitive), transformation); } } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 4ba1ed9a..1313e752 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -43,7 +43,7 @@ pub struct Instance { } impl Instance { - /// Creates a new [`Pipeline`] with the given [`Primitive`]. + /// Creates a new [`Instance`] with the given [`Primitive`]. pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { Instance { bounds, @@ -80,7 +80,7 @@ impl Storage { self.pipelines.get(&TypeId::of::()).map(|pipeline| { pipeline .downcast_ref::() - .expect("Pipeline with this type does not exist in Storage.") + .expect("Value with this type does not exist in Storage.") }) } @@ -89,7 +89,7 @@ impl Storage { self.pipelines.get_mut(&TypeId::of::()).map(|pipeline| { pipeline .downcast_mut::() - .expect("Pipeline with this type does not exist in Storage.") + .expect("Value with this type does not exist in Storage.") }) } } -- cgit From d0233da8a25c48a17942a3652cf3775f37c1420f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 12 Apr 2024 18:37:38 +0200 Subject: Fix applying local transformation to `layer_bounds` in `iced_wgpu::text` --- wgpu/src/text.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1b21bb1c..0d01faca 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -242,7 +242,7 @@ impl Pipeline { &mut self.atlas, &mut self.cache, text, - layer_bounds, + layer_bounds * layer_transformation, layer_transformation * *transformation, target_size, ); @@ -269,7 +269,7 @@ impl Pipeline { self.format, cache, layer_transformation * *transformation, - layer_bounds, + layer_bounds * layer_transformation, target_size, ); } @@ -388,8 +388,6 @@ fn prepare( }) .collect(); - let layer_bounds = layer_bounds * layer_transformation; - let text_areas = sections.iter().zip(allocations.iter()).filter_map( |(section, allocation)| { let ( -- cgit From dbbbadfc950dfdfd02c7abbbf993e0685ca0f64a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 12 Apr 2024 18:46:48 +0200 Subject: Restore `PREMULTIPLIED_ALPHA_BLENDING` in `triangle::msaa` pipeline --- wgpu/src/triangle/msaa.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index fcee6b3e..71c16925 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -118,7 +118,9 @@ impl Blit { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), + blend: Some( + wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, + ), write_mask: wgpu::ColorWrites::ALL, })], }), -- cgit From 493c36ac712ef04523065b94988a88cc4db16b1a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 24 Apr 2024 21:29:30 +0200 Subject: Make image `Cache` eviction strategy less aggressive in `iced_wgpu` Instead of trimming unconditionally at the end of a frame, we now trim the cache only when there is a cache miss. This way, images that are not visible but still a part of the layout will stay cached. Eviction will only happen when the images are not a part of the UI for two consectuive frames. --- wgpu/src/image/atlas.rs | 15 +++++++++++---- wgpu/src/image/atlas/allocator.rs | 4 ++++ wgpu/src/image/atlas/layer.rs | 8 ++++++++ wgpu/src/image/mod.rs | 2 +- wgpu/src/image/raster.rs | 9 +++++++++ wgpu/src/image/vector.rs | 8 ++++++++ 6 files changed, 41 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index ea36e06d..ae43c3b4 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -94,7 +94,7 @@ impl Atlas { entry }; - log::info!("Allocated atlas entry: {entry:?}"); + log::debug!("Allocated atlas entry: {entry:?}"); // It is a webgpu requirement that: // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 @@ -147,13 +147,20 @@ impl Atlas { } } - log::info!("Current atlas: {self:?}"); + if log::log_enabled!(log::Level::Debug) { + log::debug!( + "Atlas layers: {} (busy: {}, allocations: {})", + self.layer_count(), + self.layers.iter().filter(|layer| !layer.is_empty()).count(), + self.layers.iter().map(Layer::allocations).sum::(), + ); + } Some(entry) } pub fn remove(&mut self, entry: &Entry) { - log::info!("Removing atlas entry: {entry:?}"); + log::debug!("Removing atlas entry: {entry:?}"); match entry { Entry::Contiguous(allocation) => { @@ -266,7 +273,7 @@ impl Atlas { } fn deallocate(&mut self, allocation: &Allocation) { - log::info!("Deallocating atlas: {allocation:?}"); + log::debug!("Deallocating atlas: {allocation:?}"); match allocation { Allocation::Full { layer } => { diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 204a5c26..a51ac1f5 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -33,6 +33,10 @@ impl Allocator { pub fn is_empty(&self) -> bool { self.allocations == 0 } + + pub fn allocations(&self) -> usize { + self.allocations + } } pub struct Region { diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs index cf089601..fd6788d9 100644 --- a/wgpu/src/image/atlas/layer.rs +++ b/wgpu/src/image/atlas/layer.rs @@ -11,4 +11,12 @@ impl Layer { pub fn is_empty(&self) -> bool { matches!(self, Layer::Empty) } + + pub fn allocations(&self) -> usize { + match self { + Layer::Empty => 0, + Layer::Busy(allocator) => allocator.allocations(), + Layer::Full => 1, + } + } } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 86731cbf..8b831a3c 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -277,7 +277,7 @@ impl Pipeline { let texture_version = cache.layer_count(); if self.texture_version != texture_version { - log::info!("Atlas has grown. Recreating bind group..."); + log::debug!("Atlas has grown. Recreating bind group..."); self.texture = cache.create_bind_group(device, &self.texture_layout); diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 441b294f..7a837f28 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -40,6 +40,7 @@ impl Memory { pub struct Cache { map: FxHashMap, hits: FxHashSet, + should_trim: bool, } impl Cache { @@ -55,6 +56,8 @@ impl Cache { Err(_) => Memory::Invalid, }; + self.should_trim = true; + self.insert(handle, memory); self.get(handle).unwrap() } @@ -86,6 +89,11 @@ impl Cache { /// Trim cache misses from cache pub fn trim(&mut self, atlas: &mut Atlas) { + // Only trim if new entries have landed in the `Cache` + if !self.should_trim { + return; + } + let hits = &self.hits; self.map.retain(|k, memory| { @@ -101,6 +109,7 @@ impl Cache { }); self.hits.clear(); + self.should_trim = false; } fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index d681b2e6..c6d829af 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -37,6 +37,7 @@ pub struct Cache { rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, svg_hits: FxHashSet, rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, + should_trim: bool, } type ColorFilter = Option<[u8; 4]>; @@ -76,6 +77,8 @@ impl Cache { } } + self.should_trim = true; + let _ = self.svgs.insert(handle.id(), svg); self.svgs.get(&handle.id()).unwrap() } @@ -176,6 +179,10 @@ impl Cache { /// Load svg and upload raster data pub fn trim(&mut self, atlas: &mut Atlas) { + if !self.should_trim { + return; + } + let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; @@ -191,6 +198,7 @@ impl Cache { }); self.svg_hits.clear(); self.rasterized_hits.clear(); + self.should_trim = false; } } -- cgit From 2dcd4f916e0ea71f925212c8277498c6f995155b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 27 Apr 2024 14:16:12 +0200 Subject: Retain caches in `iced_wgpu` as long as `Rc` values are alive This allows reusing a `canvas::Cache` at no cost even if it is not presented every frame. --- wgpu/src/text.rs | 13 ++++++------- wgpu/src/triangle.rs | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0d01faca..f20db026 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -4,9 +4,9 @@ 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 rustc_hash::FxHashMap; use std::collections::hash_map; -use std::rc::Rc; +use std::rc::{self, Rc}; use std::sync::atomic::{self, AtomicU64}; use std::sync::Arc; @@ -69,12 +69,12 @@ struct Upload { buffer_cache: BufferCache, transformation: Transformation, version: usize, + text: rc::Weak<[Text]>, } #[derive(Default)] pub struct Storage { uploads: FxHashMap, - recently_used: FxHashSet, } impl Storage { @@ -162,6 +162,7 @@ impl Storage { buffer_cache, transformation: new_transformation, version: 0, + text: Rc::downgrade(&cache.text), }); log::info!( @@ -171,13 +172,11 @@ impl Storage { ); } } - - 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(); + self.uploads + .retain(|_id, upload| upload.text.strong_count() > 0); } } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 8470ea39..53a5502a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -6,9 +6,9 @@ use crate::graphics::mesh::{self, Mesh}; use crate::graphics::Antialiasing; use crate::Buffer; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use std::collections::hash_map; -use std::rc::Rc; +use std::rc::{self, Rc}; use std::sync::atomic::{self, AtomicU64}; const INITIAL_INDEX_COUNT: usize = 1_000; @@ -64,12 +64,12 @@ struct Upload { layer: Layer, transformation: Transformation, version: usize, + batch: rc::Weak<[Mesh]>, } #[derive(Debug, Default)] pub struct Storage { uploads: FxHashMap, - recently_used: FxHashSet, } impl Storage { @@ -134,6 +134,7 @@ impl Storage { layer, transformation: new_transformation, version: 0, + batch: Rc::downgrade(&cache.batch), }); log::info!( @@ -143,13 +144,11 @@ impl Storage { ); } } - - 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(); + self.uploads + .retain(|_id, upload| upload.batch.strong_count() > 0); } } -- cgit From 24501fd73b5ae884367a2d112ff44625058b876b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 05:13:24 +0200 Subject: Fix `text` and `triangle` uploads being dropped on `canvas` cache clears --- wgpu/src/text.rs | 1 + wgpu/src/triangle.rs | 1 + 2 files changed, 2 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f20db026..38712660 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -122,6 +122,7 @@ impl Storage { target_size, ); + upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; upload.transformation = new_transformation; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 53a5502a..ca36de82 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -113,6 +113,7 @@ impl Storage { new_transformation, ); + upload.batch = Rc::downgrade(&cache.batch); upload.version = cache.version; upload.transformation = new_transformation; } -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- wgpu/src/geometry.rs | 13 ++++++--- wgpu/src/text.rs | 74 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 22 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 60967082..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,6 +3,7 @@ use crate::core::text::LineHeight; use crate::core::{ Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, }; +use crate::graphics::cache::{self, Cached}; use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ @@ -10,7 +11,7 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached, Text}; +use crate::graphics::{self, Text}; use crate::text; use crate::triangle; @@ -38,7 +39,11 @@ impl Cached for Geometry { Geometry::Cached(cache.clone()) } - fn cache(self, previous: Option) -> Self::Cache { + fn cache( + self, + group: cache::Group, + previous: Option, + ) -> Self::Cache { match self { Self::Live { meshes, text } => { if let Some(mut previous) = previous { @@ -51,14 +56,14 @@ impl Cached for Geometry { if let Some(cache) = &mut previous.text { cache.update(text); } else { - previous.text = text::Cache::new(text); + previous.text = text::Cache::new(group, text); } previous } else { Cache { meshes: triangle::Cache::new(meshes), - text: text::Cache::new(text), + text: text::Cache::new(group, text), } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 38712660..5806863c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,7 +1,8 @@ use crate::core::alignment; use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::cache; use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache as BufferCache}; +use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache}; use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; use rustc_hash::FxHashMap; @@ -35,6 +36,7 @@ pub enum Item { #[derive(Debug, Clone)] pub struct Cache { id: Id, + group: cache::Group, text: Rc<[Text]>, version: usize, } @@ -43,7 +45,7 @@ pub struct Cache { pub struct Id(u64); impl Cache { - pub fn new(text: Vec) -> Option { + pub fn new(group: cache::Group, text: Vec) -> Option { static NEXT_ID: AtomicU64 = AtomicU64::new(0); if text.is_empty() { @@ -52,6 +54,7 @@ impl Cache { Some(Self { id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + group, text: Rc::from(text), version: 0, }) @@ -65,29 +68,39 @@ impl Cache { struct Upload { renderer: glyphon::TextRenderer, - atlas: glyphon::TextAtlas, buffer_cache: BufferCache, transformation: Transformation, version: usize, text: rc::Weak<[Text]>, + _atlas: rc::Weak<()>, } #[derive(Default)] pub struct Storage { + groups: FxHashMap, uploads: FxHashMap, } +struct Group { + atlas: glyphon::TextAtlas, + previous_uploads: usize, + handle: Rc<()>, +} + impl Storage { pub fn new() -> Self { Self::default() } - fn get(&self, cache: &Cache) -> Option<&Upload> { + fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> { if cache.text.is_empty() { return None; } - self.uploads.get(&cache.id) + self.groups + .get(&cache.group) + .map(|group| &group.atlas) + .zip(self.uploads.get(&cache.id)) } fn prepare( @@ -101,6 +114,20 @@ impl Storage { bounds: Rectangle, target_size: Size, ) { + let group_count = self.groups.len(); + + let group = self.groups.entry(cache.group).or_insert_with(|| { + log::info!("New text atlas created (total: {})", group_count + 1); + + Group { + atlas: glyphon::TextAtlas::with_color_mode( + device, queue, format, COLOR_MODE, + ), + previous_uploads: 0, + handle: Rc::new(()), + } + }); + match self.uploads.entry(cache.id) { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); @@ -114,7 +141,7 @@ impl Storage { queue, encoder, &mut upload.renderer, - &mut upload.atlas, + &mut group.atlas, &mut upload.buffer_cache, &cache.text, bounds, @@ -127,16 +154,11 @@ impl Storage { upload.transformation = new_transformation; 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( - &mut atlas, + &mut group.atlas, device, wgpu::MultisampleState::default(), None, @@ -149,7 +171,7 @@ impl Storage { queue, encoder, &mut renderer, - &mut atlas, + &mut group.atlas, &mut buffer_cache, &cache.text, bounds, @@ -159,11 +181,11 @@ impl Storage { let _ = entry.insert(Upload { renderer, - atlas, buffer_cache, transformation: new_transformation, version: 0, text: Rc::downgrade(&cache.text), + _atlas: Rc::downgrade(&group.handle), }); log::info!( @@ -178,6 +200,22 @@ impl Storage { pub fn trim(&mut self) { self.uploads .retain(|_id, upload| upload.text.strong_count() > 0); + + self.groups.retain(|_id, group| { + let uploads_alive = Rc::weak_count(&group.handle); + + if uploads_alive == 0 { + return false; + } + + if uploads_alive < group.previous_uploads { + group.atlas.trim(); + } + + group.previous_uploads = uploads_alive; + + true + }); } } @@ -306,10 +344,10 @@ impl Pipeline { layer_count += 1; } Item::Cached { cache, .. } => { - if let Some(upload) = storage.get(cache) { + if let Some((atlas, upload)) = storage.get(cache) { upload .renderer - .render(&upload.atlas, render_pass) + .render(atlas, render_pass) .expect("Render cached text"); } } @@ -345,7 +383,7 @@ fn prepare( enum Allocation { Paragraph(Paragraph), Editor(Editor), - Cache(cache::KeyHash), + Cache(text_cache::KeyHash), Raw(Arc), } @@ -369,7 +407,7 @@ fn prepare( } => { let (key, _) = buffer_cache.allocate( font_system, - cache::Key { + text_cache::Key { content, size: f32::from(*size), line_height: f32::from(*line_height), -- cgit From c51b85e7ab067f5e7411eccd10a5ae192e6ee0a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 21:59:46 +0200 Subject: Invalidate text uploads after atlas trimming --- wgpu/src/text.rs | 36 +++++++++++++++++++++++++----------- wgpu/src/triangle.rs | 2 +- 2 files changed, 26 insertions(+), 12 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 5806863c..8ec1d56a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -71,6 +71,7 @@ struct Upload { buffer_cache: BufferCache, transformation: Transformation, version: usize, + group_version: usize, text: rc::Weak<[Text]>, _atlas: rc::Weak<()>, } @@ -83,8 +84,9 @@ pub struct Storage { struct Group { atlas: glyphon::TextAtlas, - previous_uploads: usize, - handle: Rc<()>, + version: usize, + should_trim: bool, + handle: Rc<()>, // Keeps track of active uploads } impl Storage { @@ -117,13 +119,18 @@ impl Storage { let group_count = self.groups.len(); let group = self.groups.entry(cache.group).or_insert_with(|| { - log::info!("New text atlas created (total: {})", group_count + 1); + log::debug!( + "New text atlas: {:?} (total: {})", + cache.group, + group_count + 1 + ); Group { atlas: glyphon::TextAtlas::with_color_mode( device, queue, format, COLOR_MODE, ), - previous_uploads: 0, + version: 0, + should_trim: false, handle: Rc::new(()), } }); @@ -134,6 +141,7 @@ impl Storage { if !cache.text.is_empty() && (upload.version != cache.version + || upload.group_version != group.version || upload.transformation != new_transformation) { let _ = prepare( @@ -151,9 +159,11 @@ impl Storage { upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; + upload.group_version = group.version; upload.transformation = new_transformation; upload.buffer_cache.trim(); + group.should_trim = true; } } hash_map::Entry::Vacant(entry) => { @@ -184,11 +194,12 @@ impl Storage { buffer_cache, transformation: new_transformation, version: 0, + group_version: group.version, text: Rc::downgrade(&cache.text), _atlas: Rc::downgrade(&group.handle), }); - log::info!( + log::debug!( "New text upload: {} (total: {})", cache.id.0, self.uploads.len() @@ -201,18 +212,21 @@ impl Storage { self.uploads .retain(|_id, upload| upload.text.strong_count() > 0); - self.groups.retain(|_id, group| { - let uploads_alive = Rc::weak_count(&group.handle); + self.groups.retain(|id, group| { + if Rc::weak_count(&group.handle) == 0 { + log::debug!("Dropping text atlas: {id:?}"); - if uploads_alive == 0 { return false; } - if uploads_alive < group.previous_uploads { + if group.should_trim { + log::debug!("Trimming text atlas: {id:?}"); + group.atlas.trim(); - } - group.previous_uploads = uploads_alive; + group.version += 1; + group.should_trim = false; + } true }); diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index ca36de82..b0551f55 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -138,7 +138,7 @@ impl Storage { batch: Rc::downgrade(&cache.batch), }); - log::info!( + log::debug!( "New mesh upload: {} (total: {})", cache.id.0, self.uploads.len() -- cgit From b276a603a17eda219b32f207aa53e2b6a1321a9f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:15:04 +0200 Subject: Fix cache trimming loop in `iced_wgpu::text` --- wgpu/src/text.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 8ec1d56a..2508906f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -157,13 +157,16 @@ impl Storage { target_size, ); + // Only trim if glyphs have changed + group.should_trim = + group.should_trim || upload.version != cache.version; + upload.text = Rc::downgrade(&cache.text); upload.version = cache.version; upload.group_version = group.version; upload.transformation = new_transformation; upload.buffer_cache.trim(); - group.should_trim = true; } } hash_map::Entry::Vacant(entry) => { @@ -213,19 +216,28 @@ impl Storage { .retain(|_id, upload| upload.text.strong_count() > 0); self.groups.retain(|id, group| { - if Rc::weak_count(&group.handle) == 0 { + let active_uploads = Rc::weak_count(&group.handle); + + if active_uploads == 0 { log::debug!("Dropping text atlas: {id:?}"); return false; } - if group.should_trim { - log::debug!("Trimming text atlas: {id:?}"); + if id.is_singleton() || group.should_trim { + log::debug!( + "Trimming text atlas: {id:?} (uploads: {active_uploads})" + ); group.atlas.trim(); - - group.version += 1; group.should_trim = false; + + // We only need to worry about glyph fighting + // when the atlas may be shared by multiple + // uploads. + if !id.is_singleton() { + group.version += 1; + } } true -- cgit From 7e2d0dc931a4409e661851a1c7dffdcfd5b2f93a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:51:00 +0200 Subject: Keep text atlases alive during temporary empty uploads --- wgpu/src/text.rs | 76 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 32 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2508906f..7e683c77 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -61,6 +61,10 @@ impl Cache { } pub fn update(&mut self, text: Vec) { + if self.text.is_empty() && text.is_empty() { + return; + } + self.text = Rc::from(text); self.version += 1; } @@ -139,23 +143,24 @@ impl Storage { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if !cache.text.is_empty() - && (upload.version != cache.version - || upload.group_version != group.version - || upload.transformation != new_transformation) + if upload.version != cache.version + || upload.group_version != group.version + || upload.transformation != new_transformation { - let _ = prepare( - device, - queue, - encoder, - &mut upload.renderer, - &mut group.atlas, - &mut upload.buffer_cache, - &cache.text, - bounds, - new_transformation, - target_size, - ); + if !cache.text.is_empty() { + let _ = prepare( + device, + queue, + encoder, + &mut upload.renderer, + &mut group.atlas, + &mut upload.buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); + } // Only trim if glyphs have changed group.should_trim = @@ -179,18 +184,20 @@ impl Storage { let mut buffer_cache = BufferCache::new(); - let _ = prepare( - device, - queue, - encoder, - &mut renderer, - &mut group.atlas, - &mut buffer_cache, - &cache.text, - bounds, - new_transformation, - target_size, - ); + if !cache.text.is_empty() { + let _ = prepare( + device, + queue, + encoder, + &mut renderer, + &mut group.atlas, + &mut buffer_cache, + &cache.text, + bounds, + new_transformation, + target_size, + ); + } let _ = entry.insert(Upload { renderer, @@ -202,6 +209,8 @@ impl Storage { _atlas: Rc::downgrade(&group.handle), }); + group.should_trim = cache.group.is_singleton(); + log::debug!( "New text upload: {} (total: {})", cache.id.0, @@ -224,10 +233,8 @@ impl Storage { return false; } - if id.is_singleton() || group.should_trim { - log::debug!( - "Trimming text atlas: {id:?} (uploads: {active_uploads})" - ); + if group.should_trim { + log::trace!("Trimming text atlas: {id:?}"); group.atlas.trim(); group.should_trim = false; @@ -236,6 +243,11 @@ impl Storage { // when the atlas may be shared by multiple // uploads. if !id.is_singleton() { + log::debug!( + "Invalidating text atlas: {id:?} \ + (uploads: {active_uploads})" + ); + group.version += 1; } } -- cgit From 45254ab88c6ca76759523069c2fb8734de626f02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 00:55:49 +0200 Subject: Use `Bytes` as the `Container` of `ImageBuffer` Since we don't need to mutate images once loaded, we avoid unnecessary extra allocations. --- wgpu/src/image/raster.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 7a837f28..60e9cbad 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Debug)] pub enum Memory { /// Image data on host - Host(image_rs::ImageBuffer, Vec>), + Host(image_rs::ImageBuffer, image::Bytes>), /// Storage entry Device(atlas::Entry), /// Image not found @@ -51,7 +51,7 @@ impl Cache { } let memory = match graphics::image::load(handle) { - Ok(image) => Memory::Host(image.to_rgba8()), + Ok(image) => Memory::Host(image), Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, Err(_) => Memory::Invalid, }; -- cgit From b52c7bb610f593fffc624d461dca17ac50c81626 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 01:39:43 +0200 Subject: Use an opaque `Id` type for `image::Handle` Hashing pointers is a terrible idea. --- wgpu/src/image/raster.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 60e9cbad..4d3c3125 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -38,8 +38,8 @@ impl Memory { /// Caches image raster data #[derive(Debug, Default)] pub struct Cache { - map: FxHashMap, - hits: FxHashSet, + map: FxHashMap, + hits: FxHashSet, should_trim: bool, } -- cgit From ffa6614026cfc0dbdd636ad5a334008c1ee5532d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 08:44:09 +0200 Subject: Fix panic in `wgpu::color::convert` --- wgpu/src/color.rs | 68 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 18 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index 890f3f89..9d593d9c 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use wgpu::util::DeviceExt; + pub fn convert( device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, @@ -15,28 +17,58 @@ pub fn convert( ..wgpu::SamplerDescriptor::default() }); - //sampler in 0 - let sampler_layout = + #[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] + #[repr(C)] + struct Ratio { + u: f32, + v: f32, + } + + let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("iced-wgpu::triangle::msaa ratio"), + contents: bytemuck::bytes_of(&Ratio { u: 1.0, v: 1.0 }), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + }); + + let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu.offscreen.blit.sampler_layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }], + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], }); - let sampler_bind_group = + let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu.offscreen.sampler.bind_group"), - layout: &sampler_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }], + layout: &constant_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: ratio.as_entire_binding(), + }, + ], }); let texture_layout = @@ -59,7 +91,7 @@ pub fn convert( let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), - bind_group_layouts: &[&sampler_layout, &texture_layout], + bind_group_layouts: &[&constant_layout, &texture_layout], push_constant_ranges: &[], }); @@ -152,7 +184,7 @@ pub fn convert( }); pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &sampler_bind_group, &[]); + pass.set_bind_group(0, &constant_bind_group, &[]); pass.set_bind_group(1, &texture_bind_group, &[]); pass.draw(0..6, 0..1); -- cgit From aae8e4f5cfabfc3725ac938023fa29a6737a380c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 17:23:32 +0200 Subject: Fix `clippy` lints for new `1.78` stable toolchain --- wgpu/src/primitive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 1313e752..8641f27a 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -67,7 +67,7 @@ pub struct Storage { impl Storage { /// Returns `true` if `Storage` contains a type `T`. pub fn has(&self) -> bool { - self.pipelines.get(&TypeId::of::()).is_some() + self.pipelines.contains_key(&TypeId::of::()) } /// Inserts the data `T` in to [`Storage`]. -- cgit From 09a6bcfffc24f5abdc8709403bab7ae1e01563f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 13:15:17 +0200 Subject: Add `Image` rotation support Co-authored-by: DKolter <68352124+DKolter@users.noreply.github.com> --- wgpu/src/image/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++------- wgpu/src/layer.rs | 13 ++++++++-- wgpu/src/lib.rs | 22 ++++++++++++++-- wgpu/src/shader/image.wgsl | 37 ++++++++++++++++++++------ 4 files changed, 116 insertions(+), 21 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 8b831a3c..69f8a8ca 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -135,14 +135,20 @@ impl Pipeline { attributes: &wgpu::vertex_attr_array!( // Position 0 => Float32x2, - // Scale + // Center 1 => Float32x2, - // Atlas position + // Image size 2 => Float32x2, + // Rotation + 3 => Float32, + // Scale + 4 => Float32x2, + // Atlas position + 5 => Float32x2, // Atlas scale - 3 => Float32x2, + 6 => Float32x2, // Layer - 4 => Sint32, + 7 => Sint32, ), }], }, @@ -208,9 +214,10 @@ impl Pipeline { belt: &mut wgpu::util::StagingBelt, images: &Batch, transformation: Transformation, - scale: f32, + global_scale: f32, ) { - let transformation = transformation * Transformation::scale(scale); + let transformation = + transformation * Transformation::scale(global_scale); let nearest_instances: &mut Vec = &mut Vec::new(); let linear_instances: &mut Vec = &mut Vec::new(); @@ -224,6 +231,8 @@ impl Pipeline { handle, filter_method, bounds, + rotation, + scale, } => { if let Some(atlas_entry) = cache.upload_raster(device, encoder, handle) @@ -231,6 +240,8 @@ impl Pipeline { add_instances( [bounds.x, bounds.y], [bounds.width, bounds.height], + *rotation, + [scale.width, scale.height], atlas_entry, match filter_method { crate::core::image::FilterMethod::Nearest => { @@ -251,15 +262,24 @@ impl Pipeline { handle, color, bounds, + rotation, + scale, } => { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = cache.upload_vector( - device, encoder, handle, *color, size, scale, + device, + encoder, + handle, + *color, + size, + global_scale, ) { add_instances( [bounds.x, bounds.y], size, + *rotation, + [scale.width, scale.height], atlas_entry, nearest_instances, ); @@ -487,7 +507,10 @@ impl Data { #[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Instance { _position: [f32; 2], + _center: [f32; 2], _size: [f32; 2], + _rotation: f32, + _scale: [f32; 2], _position_in_atlas: [f32; 2], _size_in_atlas: [f32; 2], _layer: u32, @@ -506,12 +529,27 @@ struct Uniforms { fn add_instances( image_position: [f32; 2], image_size: [f32; 2], + rotation: f32, + scale: [f32; 2], entry: &atlas::Entry, instances: &mut Vec, ) { + let center = [ + image_position[0] + image_size[0] / 2.0, + image_position[1] + image_size[1] / 2.0, + ]; + match entry { atlas::Entry::Contiguous(allocation) => { - add_instance(image_position, image_size, allocation, instances); + add_instance( + image_position, + center, + image_size, + rotation, + scale, + allocation, + instances, + ); } atlas::Entry::Fragmented { fragments, size } => { let scaling_x = image_size[0] / size.width as f32; @@ -537,7 +575,10 @@ fn add_instances( fragment_height as f32 * scaling_y, ]; - add_instance(position, size, allocation, instances); + add_instance( + position, center, size, rotation, scale, allocation, + instances, + ); } } } @@ -546,7 +587,10 @@ fn add_instances( #[inline] fn add_instance( position: [f32; 2], + center: [f32; 2], size: [f32; 2], + rotation: f32, + scale: [f32; 2], allocation: &atlas::Allocation, instances: &mut Vec, ) { @@ -556,7 +600,10 @@ fn add_instance( let instance = Instance { _position: position, + _center: center, _size: size, + _rotation: rotation, + _scale: scale, _position_in_atlas: [ (x as f32 + 0.5) / atlas::SIZE as f32, (y as f32 + 0.5) / atlas::SIZE as f32, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 9526c5a8..648ec476 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,5 +1,6 @@ -use crate::core::renderer; -use crate::core::{Background, Color, Point, Rectangle, Transformation}; +use crate::core::{ + renderer, Background, Color, Point, Rectangle, Size, Transformation, +}; use crate::graphics; use crate::graphics::color; use crate::graphics::layer; @@ -117,11 +118,15 @@ impl Layer { filter_method: crate::core::image::FilterMethod, bounds: Rectangle, transformation: Transformation, + rotation: f32, + scale: Size, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, + rotation, + scale, }; self.images.push(image); @@ -133,11 +138,15 @@ impl Layer { color: Option, bounds: Rectangle, transformation: Transformation, + rotation: f32, + scale: Size, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, + rotation, + scale, }; self.images.push(svg); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 178522de..a42d71c3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -517,9 +517,18 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, + rotation: f32, + scale: Size, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_image(handle, filter_method, bounds, transformation); + layer.draw_image( + handle, + filter_method, + bounds, + transformation, + rotation, + scale, + ); } } @@ -534,9 +543,18 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color_filter: Option, bounds: Rectangle, + rotation: f32, + scale: Size, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(handle, color_filter, bounds, transformation); + layer.draw_svg( + handle, + color_filter, + bounds, + transformation, + rotation, + scale, + ); } } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 7b2e5238..de962098 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -9,10 +9,13 @@ struct Globals { struct VertexInput { @builtin(vertex_index) vertex_index: u32, @location(0) pos: vec2, - @location(1) scale: vec2, - @location(2) atlas_pos: vec2, - @location(3) atlas_scale: vec2, - @location(4) layer: i32, + @location(1) center: vec2, + @location(2) image_size: vec2, + @location(3) rotation: f32, + @location(4) scale: vec2, + @location(5) atlas_pos: vec2, + @location(6) atlas_scale: vec2, + @location(7) layer: i32, } struct VertexOutput { @@ -25,24 +28,42 @@ struct VertexOutput { fn vs_main(input: VertexInput) -> VertexOutput { var out: VertexOutput; - let v_pos = vertex_position(input.vertex_index); + // Generate a vertex position in the range [0, 1] from the vertex index. + var v_pos = vertex_position(input.vertex_index); + // Map the vertex position to the atlas texture. out.uv = vec2(v_pos * input.atlas_scale + input.atlas_pos); out.layer = f32(input.layer); - var transform: mat4x4 = mat4x4( + // Calculate the vertex position and move the center to the origin + v_pos = input.pos + v_pos * input.image_size - input.center; + + // Apply the rotation around the center of the image + let cos_rot = cos(input.rotation); + let sin_rot = sin(input.rotation); + let rotate = mat4x4( + vec4(cos_rot, sin_rot, 0.0, 0.0), + vec4(-sin_rot, cos_rot, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + + // Scale the image and then translate to the final position by moving the center to the position + let scale_translate = mat4x4( vec4(input.scale.x, 0.0, 0.0, 0.0), vec4(0.0, input.scale.y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), - vec4(input.pos, 0.0, 1.0) + vec4(input.center, 0.0, 1.0) ); - out.position = globals.transform * transform * vec4(v_pos, 0.0, 1.0); + // Calculate the final position of the vertex + out.position = globals.transform * scale_translate * rotate * vec4(v_pos, 0.0, 1.0); return out; } @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4 { + // Sample the texture at the given UV coordinate and layer. return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)); } -- cgit From a57313b23ecb9843856ca0ea08635b6121fcb2cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 15:21:22 +0200 Subject: Simplify image rotation API and its internals --- wgpu/src/image/mod.rs | 24 ++++++------------------ wgpu/src/layer.rs | 10 +++------- wgpu/src/lib.rs | 20 +++++--------------- wgpu/src/shader/image.wgsl | 21 ++++++--------------- 4 files changed, 20 insertions(+), 55 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 69f8a8ca..3ec341fc 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -141,14 +141,12 @@ impl Pipeline { 2 => Float32x2, // Rotation 3 => Float32, - // Scale - 4 => Float32x2, // Atlas position - 5 => Float32x2, + 4 => Float32x2, // Atlas scale - 6 => Float32x2, + 5 => Float32x2, // Layer - 7 => Sint32, + 6 => Sint32, ), }], }, @@ -232,7 +230,6 @@ impl Pipeline { filter_method, bounds, rotation, - scale, } => { if let Some(atlas_entry) = cache.upload_raster(device, encoder, handle) @@ -240,8 +237,7 @@ impl Pipeline { add_instances( [bounds.x, bounds.y], [bounds.width, bounds.height], - *rotation, - [scale.width, scale.height], + f32::from(*rotation), atlas_entry, match filter_method { crate::core::image::FilterMethod::Nearest => { @@ -263,7 +259,6 @@ impl Pipeline { color, bounds, rotation, - scale, } => { let size = [bounds.width, bounds.height]; @@ -278,8 +273,7 @@ impl Pipeline { add_instances( [bounds.x, bounds.y], size, - *rotation, - [scale.width, scale.height], + f32::from(*rotation), atlas_entry, nearest_instances, ); @@ -510,7 +504,6 @@ struct Instance { _center: [f32; 2], _size: [f32; 2], _rotation: f32, - _scale: [f32; 2], _position_in_atlas: [f32; 2], _size_in_atlas: [f32; 2], _layer: u32, @@ -530,7 +523,6 @@ fn add_instances( image_position: [f32; 2], image_size: [f32; 2], rotation: f32, - scale: [f32; 2], entry: &atlas::Entry, instances: &mut Vec, ) { @@ -546,7 +538,6 @@ fn add_instances( center, image_size, rotation, - scale, allocation, instances, ); @@ -576,8 +567,7 @@ fn add_instances( ]; add_instance( - position, center, size, rotation, scale, allocation, - instances, + position, center, size, rotation, allocation, instances, ); } } @@ -590,7 +580,6 @@ fn add_instance( center: [f32; 2], size: [f32; 2], rotation: f32, - scale: [f32; 2], allocation: &atlas::Allocation, instances: &mut Vec, ) { @@ -603,7 +592,6 @@ fn add_instance( _center: center, _size: size, _rotation: rotation, - _scale: scale, _position_in_atlas: [ (x as f32 + 0.5) / atlas::SIZE as f32, (y as f32 + 0.5) / atlas::SIZE as f32, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 648ec476..e0242c59 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,5 +1,5 @@ use crate::core::{ - renderer, Background, Color, Point, Rectangle, Size, Transformation, + renderer, Background, Color, Point, Radians, Rectangle, Transformation, }; use crate::graphics; use crate::graphics::color; @@ -118,15 +118,13 @@ impl Layer { filter_method: crate::core::image::FilterMethod, bounds: Rectangle, transformation: Transformation, - rotation: f32, - scale: Size, + rotation: Radians, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, rotation, - scale, }; self.images.push(image); @@ -138,15 +136,13 @@ impl Layer { color: Option, bounds: Rectangle, transformation: Transformation, - rotation: f32, - scale: Size, + rotation: Radians, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, rotation, - scale, }; self.images.push(svg); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index a42d71c3..6920067b 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -61,7 +61,8 @@ pub use settings::Settings; pub use geometry::Geometry; use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, + Background, Color, Font, Pixels, Point, Radians, Rectangle, Size, + Transformation, Vector, }; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; @@ -378,7 +379,6 @@ impl Renderer { use crate::core::alignment; use crate::core::text::Renderer as _; use crate::core::Renderer as _; - use crate::core::Vector; self.with_layer( Rectangle::with_size(viewport.logical_size()), @@ -517,8 +517,7 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, - rotation: f32, - scale: Size, + rotation: Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -527,7 +526,6 @@ impl core::image::Renderer for Renderer { bounds, transformation, rotation, - scale, ); } } @@ -543,18 +541,10 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color_filter: Option, bounds: Rectangle, - rotation: f32, - scale: Size, + rotation: Radians, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg( - handle, - color_filter, - bounds, - transformation, - rotation, - scale, - ); + layer.draw_svg(handle, color_filter, bounds, transformation, rotation); } } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index de962098..71bf939c 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -10,12 +10,11 @@ struct VertexInput { @builtin(vertex_index) vertex_index: u32, @location(0) pos: vec2, @location(1) center: vec2, - @location(2) image_size: vec2, + @location(2) scale: vec2, @location(3) rotation: f32, - @location(4) scale: vec2, - @location(5) atlas_pos: vec2, - @location(6) atlas_scale: vec2, - @location(7) layer: i32, + @location(4) atlas_pos: vec2, + @location(5) atlas_scale: vec2, + @location(6) layer: i32, } struct VertexOutput { @@ -36,7 +35,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { out.layer = f32(input.layer); // Calculate the vertex position and move the center to the origin - v_pos = input.pos + v_pos * input.image_size - input.center; + v_pos = input.pos + v_pos * input.scale - input.center; // Apply the rotation around the center of the image let cos_rot = cos(input.rotation); @@ -48,16 +47,8 @@ fn vs_main(input: VertexInput) -> VertexOutput { vec4(0.0, 0.0, 0.0, 1.0) ); - // Scale the image and then translate to the final position by moving the center to the position - let scale_translate = mat4x4( - vec4(input.scale.x, 0.0, 0.0, 0.0), - vec4(0.0, input.scale.y, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(input.center, 0.0, 1.0) - ); - // Calculate the final position of the vertex - out.position = globals.transform * scale_translate * rotate * vec4(v_pos, 0.0, 1.0); + out.position = globals.transform * (vec4(input.center, 0.0, 0.0) + rotate * vec4(v_pos, 0.0, 1.0)); return out; } -- cgit From 610394b6957d9424aec1c50d927e34a0fb3fe5fd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 15:28:46 +0200 Subject: Rename `global_scale` to `scale` in `wgpu::image` --- wgpu/src/image/mod.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 3ec341fc..285eb2f6 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -212,10 +212,9 @@ impl Pipeline { belt: &mut wgpu::util::StagingBelt, images: &Batch, transformation: Transformation, - global_scale: f32, + scale: f32, ) { - let transformation = - transformation * Transformation::scale(global_scale); + let transformation = transformation * Transformation::scale(scale); let nearest_instances: &mut Vec = &mut Vec::new(); let linear_instances: &mut Vec = &mut Vec::new(); @@ -263,12 +262,7 @@ impl Pipeline { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = cache.upload_vector( - device, - encoder, - handle, - *color, - size, - global_scale, + device, encoder, handle, *color, size, scale, ) { add_instances( [bounds.x, bounds.y], -- cgit From 15057a05c118dafcb8cf90d4119e66caaa6026c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 09:11:46 +0200 Subject: Introduce `center` widget helper ... and also make `center_x` and `center_y` set `width` and `height` to `Length::Fill`, respectively. This targets the most common use case when centering things and removes a bunch of boilerplate as a result. --- wgpu/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6920067b..eb600dde 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -61,8 +61,8 @@ pub use settings::Settings; pub use geometry::Geometry; use crate::core::{ - Background, Color, Font, Pixels, Point, Radians, Rectangle, Size, - Transformation, Vector, + Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, + Vector, }; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; @@ -517,7 +517,7 @@ impl core::image::Renderer for Renderer { handle: Self::Handle, filter_method: core::image::FilterMethod, bounds: Rectangle, - rotation: Radians, + rotation: core::Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -541,7 +541,7 @@ impl core::svg::Renderer for Renderer { handle: core::svg::Handle, color_filter: Option, bounds: Rectangle, - rotation: Radians, + rotation: core::Radians, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_svg(handle, color_filter, bounds, transformation, rotation); -- cgit From fa9e1d96ea1924b51749b775ea0e67e69bc8a305 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 13:25:58 +0200 Subject: Introduce dynamic `opacity` support for `Image` and `Svg` --- wgpu/src/image/mod.rs | 22 +++++++++++++++++----- wgpu/src/layer.rs | 4 ++++ wgpu/src/lib.rs | 12 +++++++++++- wgpu/src/shader/image.wgsl | 11 +++++++---- 4 files changed, 39 insertions(+), 10 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 285eb2f6..063822aa 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -137,16 +137,18 @@ impl Pipeline { 0 => Float32x2, // Center 1 => Float32x2, - // Image size + // Scale 2 => Float32x2, // Rotation 3 => Float32, + // Opacity + 4 => Float32, // Atlas position - 4 => Float32x2, - // Atlas scale 5 => Float32x2, + // Atlas scale + 6 => Float32x2, // Layer - 6 => Sint32, + 7 => Sint32, ), }], }, @@ -229,6 +231,7 @@ impl Pipeline { filter_method, bounds, rotation, + opacity, } => { if let Some(atlas_entry) = cache.upload_raster(device, encoder, handle) @@ -237,6 +240,7 @@ impl Pipeline { [bounds.x, bounds.y], [bounds.width, bounds.height], f32::from(*rotation), + *opacity, atlas_entry, match filter_method { crate::core::image::FilterMethod::Nearest => { @@ -258,6 +262,7 @@ impl Pipeline { color, bounds, rotation, + opacity, } => { let size = [bounds.width, bounds.height]; @@ -268,6 +273,7 @@ impl Pipeline { [bounds.x, bounds.y], size, f32::from(*rotation), + *opacity, atlas_entry, nearest_instances, ); @@ -498,6 +504,7 @@ struct Instance { _center: [f32; 2], _size: [f32; 2], _rotation: f32, + _opacity: f32, _position_in_atlas: [f32; 2], _size_in_atlas: [f32; 2], _layer: u32, @@ -517,6 +524,7 @@ fn add_instances( image_position: [f32; 2], image_size: [f32; 2], rotation: f32, + opacity: f32, entry: &atlas::Entry, instances: &mut Vec, ) { @@ -532,6 +540,7 @@ fn add_instances( center, image_size, rotation, + opacity, allocation, instances, ); @@ -561,7 +570,8 @@ fn add_instances( ]; add_instance( - position, center, size, rotation, allocation, instances, + position, center, size, rotation, opacity, allocation, + instances, ); } } @@ -574,6 +584,7 @@ fn add_instance( center: [f32; 2], size: [f32; 2], rotation: f32, + opacity: f32, allocation: &atlas::Allocation, instances: &mut Vec, ) { @@ -586,6 +597,7 @@ fn add_instance( _center: center, _size: size, _rotation: rotation, + _opacity: opacity, _position_in_atlas: [ (x as f32 + 0.5) / atlas::SIZE as f32, (y as f32 + 0.5) / atlas::SIZE as f32, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index e0242c59..9551311d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -119,12 +119,14 @@ impl Layer { bounds: Rectangle, transformation: Transformation, rotation: Radians, + opacity: f32, ) { let image = Image::Raster { handle, filter_method, bounds: bounds * transformation, rotation, + opacity, }; self.images.push(image); @@ -137,12 +139,14 @@ impl Layer { bounds: Rectangle, transformation: Transformation, rotation: Radians, + opacity: f32, ) { let svg = Image::Vector { handle, color, bounds: bounds * transformation, rotation, + opacity, }; self.images.push(svg); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index eb600dde..4c168029 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -518,6 +518,7 @@ impl core::image::Renderer for Renderer { filter_method: core::image::FilterMethod, bounds: Rectangle, rotation: core::Radians, + opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); layer.draw_image( @@ -526,6 +527,7 @@ impl core::image::Renderer for Renderer { bounds, transformation, rotation, + opacity, ); } } @@ -542,9 +544,17 @@ impl core::svg::Renderer for Renderer { color_filter: Option, bounds: Rectangle, rotation: core::Radians, + opacity: f32, ) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(handle, color_filter, bounds, transformation, rotation); + layer.draw_svg( + handle, + color_filter, + bounds, + transformation, + rotation, + opacity, + ); } } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 71bf939c..accefc17 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -12,15 +12,17 @@ struct VertexInput { @location(1) center: vec2, @location(2) scale: vec2, @location(3) rotation: f32, - @location(4) atlas_pos: vec2, - @location(5) atlas_scale: vec2, - @location(6) layer: i32, + @location(4) opacity: f32, + @location(5) atlas_pos: vec2, + @location(6) atlas_scale: vec2, + @location(7) layer: i32, } struct VertexOutput { @builtin(position) position: vec4, @location(0) uv: vec2, @location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation. + @location(2) opacity: f32, } @vertex @@ -33,6 +35,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { // Map the vertex position to the atlas texture. out.uv = vec2(v_pos * input.atlas_scale + input.atlas_pos); out.layer = f32(input.layer); + out.opacity = input.opacity; // Calculate the vertex position and move the center to the origin v_pos = input.pos + v_pos * input.scale - input.center; @@ -56,5 +59,5 @@ fn vs_main(input: VertexInput) -> VertexOutput { @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4 { // Sample the texture at the given UV coordinate and layer. - return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)); + return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4(1.0, 1.0, 1.0, input.opacity); } -- cgit From 547446f0de076149a4c61e6a4179308b266fd9fd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 May 2024 12:14:42 +0200 Subject: Fix windows fighting over shared `image::Cache` Image caches are local to each window now. --- wgpu/src/engine.rs | 7 +++++-- wgpu/src/image/atlas.rs | 38 +++++++++++++++++++++++++++++++++++--- wgpu/src/image/cache.rs | 43 +++++++++++-------------------------------- wgpu/src/image/mod.rs | 38 +++++++++++--------------------------- wgpu/src/lib.rs | 15 +++++++++++---- wgpu/src/window/compositor.rs | 1 + 6 files changed, 74 insertions(+), 68 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index 96cd6db8..782fd58c 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -59,8 +59,11 @@ impl Engine { } #[cfg(any(feature = "image", feature = "svg"))] - pub fn image_cache(&self) -> &crate::image::cache::Shared { - self.image_pipeline.cache() + pub fn create_image_cache( + &self, + device: &wgpu::Device, + ) -> crate::image::Cache { + self.image_pipeline.create_cache(device) } pub fn submit( diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index ae43c3b4..a1ec0d7b 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048; use crate::core::Size; use crate::graphics::color; +use std::sync::Arc; + #[derive(Debug)] pub struct Atlas { texture: wgpu::Texture, texture_view: wgpu::TextureView, + texture_bind_group: wgpu::BindGroup, + texture_layout: Arc, layers: Vec, } impl Atlas { - pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self { + pub fn new( + device: &wgpu::Device, + backend: wgpu::Backend, + texture_layout: Arc, + ) -> Self { let layers = match backend { // On the GL backend we start with 2 layers, to help wgpu figure // out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D` @@ -60,15 +68,27 @@ impl Atlas { ..Default::default() }); + let texture_bind_group = + 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_view), + }], + }); + Atlas { texture, texture_view, + texture_bind_group, + texture_layout, layers, } } - pub fn view(&self) -> &wgpu::TextureView { - &self.texture_view + pub fn bind_group(&self) -> &wgpu::BindGroup { + &self.texture_bind_group } pub fn layer_count(&self) -> usize { @@ -421,5 +441,17 @@ impl Atlas { dimension: Some(wgpu::TextureViewDimension::D2Array), ..Default::default() }); + + self.texture_bind_group = + 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_view, + ), + }], + }); } } diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 32118ed6..94f7071d 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -1,8 +1,7 @@ use crate::core::{self, Size}; use crate::image::atlas::{self, Atlas}; -use std::cell::{RefCell, RefMut}; -use std::rc::Rc; +use std::sync::Arc; #[derive(Debug)] pub struct Cache { @@ -14,9 +13,13 @@ pub struct Cache { } impl Cache { - pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self { + pub fn new( + device: &wgpu::Device, + backend: wgpu::Backend, + layout: Arc, + ) -> Self { Self { - atlas: Atlas::new(device, backend), + atlas: Atlas::new(device, backend, layout), #[cfg(feature = "image")] raster: crate::image::raster::Cache::default(), #[cfg(feature = "svg")] @@ -24,6 +27,10 @@ impl Cache { } } + pub fn bind_group(&self) -> &wgpu::BindGroup { + self.atlas.bind_group() + } + pub fn layer_count(&self) -> usize { self.atlas.layer_count() } @@ -69,21 +76,6 @@ impl Cache { ) } - 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); @@ -92,16 +84,3 @@ impl Cache { self.vector.trim(&mut self.atlas); } } - -#[derive(Debug, Clone)] -pub struct Shared(Rc>); - -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/mod.rs b/wgpu/src/image/mod.rs index 063822aa..daa2fe16 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -13,7 +13,9 @@ use crate::core::{Rectangle, Size, Transformation}; use crate::Buffer; use bytemuck::{Pod, Zeroable}; + use std::mem; +use std::sync::Arc; pub use crate::graphics::Image; @@ -22,13 +24,11 @@ pub type Batch = Vec; #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, + backend: wgpu::Backend, nearest_sampler: wgpu::Sampler, linear_sampler: wgpu::Sampler, - texture: wgpu::BindGroup, - texture_version: usize, - texture_layout: wgpu::BindGroupLayout, + texture_layout: Arc, constant_layout: wgpu::BindGroupLayout, - cache: cache::Shared, layers: Vec, prepare_layer: usize, } @@ -186,25 +186,20 @@ impl Pipeline { multiview: None, }); - let cache = Cache::new(device, backend); - let texture = cache.create_bind_group(device, &texture_layout); - Pipeline { pipeline, + backend, nearest_sampler, linear_sampler, - texture, - texture_version: cache.layer_count(), - texture_layout, + texture_layout: Arc::new(texture_layout), constant_layout, - cache: cache::Shared::new(cache), layers: Vec::new(), prepare_layer: 0, } } - pub fn cache(&self) -> &cache::Shared { - &self.cache + pub fn create_cache(&self, device: &wgpu::Device) -> Cache { + Cache::new(device, self.backend, self.texture_layout.clone()) } pub fn prepare( @@ -212,6 +207,7 @@ impl Pipeline { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, + cache: &mut Cache, images: &Batch, transformation: Transformation, scale: f32, @@ -221,8 +217,6 @@ impl Pipeline { let nearest_instances: &mut Vec = &mut Vec::new(); let linear_instances: &mut Vec = &mut Vec::new(); - let mut cache = self.cache.lock(); - for image in images { match &image { #[cfg(feature = "image")] @@ -288,16 +282,6 @@ impl Pipeline { return; } - let texture_version = cache.layer_count(); - - if self.texture_version != texture_version { - log::debug!("Atlas has grown. Recreating bind group..."); - - self.texture = - cache.create_bind_group(device, &self.texture_layout); - self.texture_version = texture_version; - } - if self.layers.len() <= self.prepare_layer { self.layers.push(Layer::new( device, @@ -323,6 +307,7 @@ impl Pipeline { pub fn render<'a>( &'a self, + cache: &'a Cache, layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, @@ -337,14 +322,13 @@ impl Pipeline { bounds.height, ); - render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_bind_group(1, cache.bind_group(), &[]); layer.render(render_pass); } } pub fn end_frame(&mut self) { - self.cache.lock().trim(); self.prepare_layer = 0; } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4c168029..35da3211 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -67,6 +67,8 @@ use crate::core::{ use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; +use std::cell::RefCell; + /// A [`wgpu`] graphics renderer for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -82,11 +84,12 @@ pub struct Renderer { // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] - image_cache: image::cache::Shared, + image_cache: RefCell, } impl Renderer { pub fn new( + _device: &wgpu::Device, _engine: &Engine, default_font: Font, default_text_size: Pixels, @@ -100,7 +103,7 @@ impl Renderer { text_storage: text::Storage::new(), #[cfg(any(feature = "svg", feature = "image"))] - image_cache: _engine.image_cache().clone(), + image_cache: RefCell::new(_engine.create_image_cache(_device)), } } @@ -191,6 +194,7 @@ impl Renderer { device, encoder, &mut engine.staging_belt, + &mut self.image_cache.borrow_mut(), &layer.images, viewport.projection(), scale_factor, @@ -246,6 +250,8 @@ impl Renderer { #[cfg(any(feature = "svg", feature = "image"))] let mut image_layer = 0; + #[cfg(any(feature = "svg", feature = "image"))] + let image_cache = self.image_cache.borrow(); let scale_factor = viewport.scale_factor() as f32; let physical_bounds = Rectangle::::from(Rectangle::with_size( @@ -359,6 +365,7 @@ impl Renderer { #[cfg(any(feature = "svg", feature = "image"))] if !layer.images.is_empty() { engine.image_pipeline.render( + &image_cache, image_layer, scissor_rect, &mut render_pass, @@ -509,7 +516,7 @@ impl core::image::Renderer for Renderer { type Handle = core::image::Handle; fn measure_image(&self, handle: &Self::Handle) -> Size { - self.image_cache.lock().measure_image(handle) + self.image_cache.borrow_mut().measure_image(handle) } fn draw_image( @@ -535,7 +542,7 @@ impl core::image::Renderer for Renderer { #[cfg(feature = "svg")] impl core::svg::Renderer for Renderer { fn measure_svg(&self, handle: &core::svg::Handle) -> Size { - self.image_cache.lock().measure_svg(handle) + self.image_cache.borrow_mut().measure_svg(handle) } fn draw_svg( diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 095afd48..baf9f315 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -290,6 +290,7 @@ impl graphics::Compositor for Compositor { fn create_renderer(&self) -> Self::Renderer { Renderer::new( + &self.device, &self.engine, self.settings.default_font, self.settings.default_text_size, -- cgit From ea64e4f63af7a7af1bde869ff6acd9203122b151 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 May 2024 12:17:43 +0200 Subject: Trim `image::Cache` after `wgpu::Renderer::present` --- wgpu/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 35da3211..ad8ac591 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -125,6 +125,9 @@ impl Renderer { self.triangle_storage.trim(); self.text_storage.trim(); + + #[cfg(any(feature = "svg", feature = "image"))] + self.image_cache.borrow_mut().trim(); } fn prepare( -- cgit From 2645524f88414393d8b3ca9c6fe801b32b5ebd33 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 15:50:18 +0200 Subject: Update `winit` to `0.30` --- wgpu/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index ad8ac591..1fbdbe9a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -67,8 +67,6 @@ use crate::core::{ use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Viewport; -use std::cell::RefCell; - /// A [`wgpu`] graphics renderer for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -84,7 +82,7 @@ pub struct Renderer { // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] - image_cache: RefCell, + image_cache: std::cell::RefCell, } impl Renderer { @@ -103,7 +101,9 @@ impl Renderer { text_storage: text::Storage::new(), #[cfg(any(feature = "svg", feature = "image"))] - image_cache: RefCell::new(_engine.create_image_cache(_device)), + image_cache: std::cell::RefCell::new( + _engine.create_image_cache(_device), + ), } } -- cgit From 8a0701a0d95989769341846b05ffcc705ae4ee5f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 21:05:29 +0200 Subject: Introduce `ICED_PRESENT_MODE` env var to pick a `wgpu::PresentMode` --- wgpu/src/settings.rs | 14 ++++++++++++++ wgpu/src/window/compositor.rs | 25 +++++++++++++++---------- 2 files changed, 29 insertions(+), 10 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index a6aea0a5..5e2603ee 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -51,3 +51,17 @@ impl From for Settings { } } } + +pub fn present_mode_from_env() -> Option { + let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; + + match present_mode.to_lowercase().as_str() { + "vsync" => Some(wgpu::PresentMode::AutoVsync), + "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync), + "immediate" => Some(wgpu::PresentMode::Immediate), + "fifo" => Some(wgpu::PresentMode::Fifo), + "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed), + "mailbox" => Some(wgpu::PresentMode::Mailbox), + _ => None, + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index baf9f315..2e938c77 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -4,7 +4,8 @@ use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::error; use crate::graphics::{self, Viewport}; -use crate::{Engine, Renderer, Settings}; +use crate::settings::{self, Settings}; +use crate::{Engine, Renderer}; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] @@ -270,15 +271,19 @@ impl graphics::Compositor for Compositor { backend: Option<&str>, ) -> Result { match backend { - None | Some("wgpu") => Ok(new( - Settings { - backends: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..settings.into() - }, - compatible_window, - ) - .await?), + None | Some("wgpu") => { + let mut settings = Settings::from(settings); + + if let Some(backends) = wgpu::util::backend_bits_from_env() { + settings.backends = backends; + } + + if let Some(present_mode) = settings::present_mode_from_env() { + settings.present_mode = present_mode; + } + + Ok(new(settings, compatible_window).await?) + } Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { backend: "wgpu", reason: error::Reason::DidNotMatch { -- cgit From 5b6f3499e114c1694a5878466f8d46e7022e1bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 May 2024 21:13:51 +0200 Subject: Document `present_mode_from_env` in `iced_wgpu` --- wgpu/src/settings.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5e2603ee..b3c3cf6a 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -52,6 +52,18 @@ impl From for Settings { } } +/// Obtains a [`wgpu::PresentMode`] from the current environment +/// configuration, if set. +/// +/// The value returned by this function can be changed by setting +/// the `ICED_PRESENT_MODE` env variable. The possible values are: +/// +/// - `vsync` → [`wgpu::PresentMode::AutoVsync`] +/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`] +/// - `immediate` → [`wgpu::PresentMode::Immediate`] +/// - `fifo` → [`wgpu::PresentMode::Fifo`] +/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`] +/// - `mailbox` → [`wgpu::PresentMode::Mailbox`] pub fn present_mode_from_env() -> Option { let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; -- cgit From 447f3a2d14ab1c4bc20e232df1aa2375623af2de Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 12:29:17 +0200 Subject: Reuse `glyphon::Pipeline` state in `iced_wgpu` --- wgpu/src/text.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 7e683c77..68741461 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -115,6 +115,7 @@ impl Storage { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, + pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -131,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, + device, queue, pipeline, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -259,6 +260,7 @@ impl Storage { #[allow(missing_debug_implementations)] pub struct Pipeline { + state: glyphon::Pipeline, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -272,12 +274,16 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { + let state = glyphon::Pipeline::new(device); + let atlas = glyphon::TextAtlas::with_color_mode( + device, queue, &state, format, COLOR_MODE, + ); + Pipeline { + state, format, renderers: Vec::new(), - atlas: glyphon::TextAtlas::with_color_mode( - device, queue, format, COLOR_MODE, - ), + atlas, prepare_layer: 0, cache: BufferCache::new(), } @@ -343,6 +349,7 @@ impl Pipeline { queue, encoder, self.format, + &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, -- cgit From bed53f81436c595e479009427e1fa4ff4a1048d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 13:22:22 +0200 Subject: Reuse `glyphon::Viewport` explicitly --- wgpu/src/lib.rs | 13 +++++++++---- wgpu/src/text.rs | 42 +++++++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 17 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1fbdbe9a..ad88ce3e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -79,6 +79,7 @@ pub struct Renderer { triangle_storage: triangle::Storage, text_storage: text::Storage, + text_viewport: text::Viewport, // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] @@ -87,8 +88,8 @@ pub struct Renderer { impl Renderer { pub fn new( - _device: &wgpu::Device, - _engine: &Engine, + device: &wgpu::Device, + engine: &Engine, default_font: Font, default_text_size: Pixels, ) -> Self { @@ -99,10 +100,11 @@ impl Renderer { triangle_storage: triangle::Storage::new(), text_storage: text::Storage::new(), + text_viewport: engine.text_pipeline.create_viewport(device), #[cfg(any(feature = "svg", feature = "image"))] image_cache: std::cell::RefCell::new( - _engine.create_image_cache(_device), + engine.create_image_cache(device), ), } } @@ -141,6 +143,8 @@ impl Renderer { ) { let scale_factor = viewport.scale_factor() as f32; + self.text_viewport.update(queue, viewport.physical_size()); + for layer in self.layers.iter_mut() { if !layer.quads.is_empty() { engine.quad_pipeline.prepare( @@ -182,12 +186,12 @@ impl Renderer { engine.text_pipeline.prepare( device, queue, + &self.text_viewport, encoder, &mut self.text_storage, &layer.text, layer.bounds, Transformation::scale(scale_factor), - viewport.physical_size(), ); } @@ -357,6 +361,7 @@ impl Renderer { if !layer.text.is_empty() { text_layer += engine.text_pipeline.render( + &self.text_viewport, &self.text_storage, text_layer, &layer.text, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 68741461..9ecd5c99 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -113,13 +113,13 @@ impl Storage { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, pipeline: &glyphon::Pipeline, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, - target_size: Size, ) { let group_count = self.groups.len(); @@ -152,6 +152,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut upload.renderer, &mut group.atlas, @@ -159,7 +160,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -189,6 +189,7 @@ impl Storage { let _ = prepare( device, queue, + viewport, encoder, &mut renderer, &mut group.atlas, @@ -196,7 +197,6 @@ impl Storage { &cache.text, bounds, new_transformation, - target_size, ); } @@ -258,6 +258,20 @@ impl Storage { } } +pub struct Viewport(glyphon::Viewport); + +impl Viewport { + pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size) { + self.0.update( + queue, + glyphon::Resolution { + width: resolution.width, + height: resolution.height, + }, + ); + } +} + #[allow(missing_debug_implementations)] pub struct Pipeline { state: glyphon::Pipeline, @@ -293,12 +307,12 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &Viewport, encoder: &mut wgpu::CommandEncoder, storage: &mut Storage, batch: &Batch, layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) { for item in batch { match item { @@ -319,6 +333,7 @@ impl Pipeline { let result = prepare( device, queue, + &viewport.0, encoder, renderer, &mut self.atlas, @@ -326,7 +341,6 @@ impl Pipeline { text, layer_bounds * layer_transformation, layer_transformation * *transformation, - target_size, ); match result { @@ -347,13 +361,13 @@ impl Pipeline { storage.prepare( device, queue, + &viewport.0, encoder, self.format, &self.state, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, - target_size, ); } } @@ -362,6 +376,7 @@ impl Pipeline { pub fn render<'a>( &'a self, + viewport: &'a Viewport, storage: &'a Storage, start: usize, batch: &'a Batch, @@ -383,7 +398,7 @@ impl Pipeline { let renderer = &self.renderers[start + layer_count]; renderer - .render(&self.atlas, render_pass) + .render(&self.atlas, &viewport.0, render_pass) .expect("Render text"); layer_count += 1; @@ -392,7 +407,7 @@ impl Pipeline { if let Some((atlas, upload)) = storage.get(cache) { upload .renderer - .render(atlas, render_pass) + .render(atlas, &viewport.0, render_pass) .expect("Render cached text"); } } @@ -402,6 +417,10 @@ impl Pipeline { layer_count } + pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { + Viewport(glyphon::Viewport::new(device, &self.state)) + } + pub fn end_frame(&mut self) { self.atlas.trim(); self.cache.trim(); @@ -413,6 +432,7 @@ impl Pipeline { fn prepare( device: &wgpu::Device, queue: &wgpu::Queue, + viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, renderer: &mut glyphon::TextRenderer, atlas: &mut glyphon::TextAtlas, @@ -420,7 +440,6 @@ fn prepare( sections: &[Text], layer_bounds: Rectangle, layer_transformation: Transformation, - target_size: Size, ) -> Result<(), glyphon::PrepareError> { let mut font_system = font_system().write().expect("Write font system"); let font_system = font_system.raw(); @@ -617,10 +636,7 @@ fn prepare( encoder, font_system, atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, + viewport, text_areas, &mut glyphon::SwashCache::new(), ) -- cgit From 99c1464cc16979381b3a531d293ee34fd6697480 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 May 2024 19:34:43 +0200 Subject: Update `glyphon` fork to a cleaner branch --- wgpu/src/text.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 9ecd5c99..05db5f80 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -116,7 +116,7 @@ impl Storage { viewport: &glyphon::Viewport, encoder: &mut wgpu::CommandEncoder, format: wgpu::TextureFormat, - pipeline: &glyphon::Pipeline, + state: &glyphon::Cache, cache: &Cache, new_transformation: Transformation, bounds: Rectangle, @@ -132,7 +132,7 @@ impl Storage { Group { atlas: glyphon::TextAtlas::with_color_mode( - device, queue, pipeline, format, COLOR_MODE, + device, queue, state, format, COLOR_MODE, ), version: 0, should_trim: false, @@ -274,7 +274,7 @@ impl Viewport { #[allow(missing_debug_implementations)] pub struct Pipeline { - state: glyphon::Pipeline, + state: glyphon::Cache, format: wgpu::TextureFormat, atlas: glyphon::TextAtlas, renderers: Vec, @@ -288,7 +288,7 @@ impl Pipeline { queue: &wgpu::Queue, format: wgpu::TextureFormat, ) -> Self { - let state = glyphon::Pipeline::new(device); + let state = glyphon::Cache::new(device); let atlas = glyphon::TextAtlas::with_color_mode( device, queue, &state, format, COLOR_MODE, ); -- cgit From 74a4ba65d6fea0c68dfbf721230c4abf36505e61 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 12 May 2024 13:44:13 +0200 Subject: Align images to the pixel grid in `iced_wgpu` This should fix some graphical glitches, at the expense of potential alignment issues with small icons / images. --- wgpu/src/shader/image.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index accefc17..0eeb100f 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -38,7 +38,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { out.opacity = input.opacity; // Calculate the vertex position and move the center to the origin - v_pos = input.pos + v_pos * input.scale - input.center; + v_pos = round(input.pos) + v_pos * input.scale - input.center; // Apply the rotation around the center of the image let cos_rot = cos(input.rotation); -- cgit