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