Decouple caching from layering and simplify everything

This commit is contained in:
Héctor Ramón Jiménez 2024-04-05 23:59:21 +02:00
parent 4a356cfc16
commit 6d3e1d835e
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
15 changed files with 896 additions and 1199 deletions

View file

@ -33,9 +33,12 @@ where
} }
impl Rectangle<f32> { 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 /// Creates a new [`Rectangle`] with its top-left corner in the given
/// [`Point`] and with the provided [`Size`]. /// [`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 { Self {
x: top_left.x, x: top_left.x,
y: top_left.y, y: top_left.y,

View file

@ -82,7 +82,6 @@ mod rainbow {
let posn_l = [0.0, bounds.height / 2.0]; let posn_l = [0.0, bounds.height / 2.0];
let mesh = Mesh::Solid { let mesh = Mesh::Solid {
size: bounds.size(),
buffers: mesh::Indexed { buffers: mesh::Indexed {
vertices: vec![ vertices: vec![
SolidVertex2D { SolidVertex2D {
@ -134,6 +133,7 @@ mod rainbow {
], ],
}, },
transformation: Transformation::IDENTITY, transformation: Transformation::IDENTITY,
clip_bounds: Rectangle::INFINITE,
}; };
renderer.with_translation( renderer.with_translation(

View file

@ -113,13 +113,11 @@ where
region: Rectangle, region: Rectangle,
f: impl FnOnce(&mut Self) -> R, f: impl FnOnce(&mut Self) -> R,
) -> R { ) -> R {
let mut frame = self.draft(region.size()); let mut frame = self.draft(region);
let result = f(&mut frame); let result = f(&mut frame);
let origin = Point::new(region.x, region.y); self.paste(frame, Point::new(region.x, region.y));
self.paste(frame, origin);
result result
} }
@ -129,14 +127,14 @@ where
/// Draw its contents back to this [`Frame`] with [`paste`]. /// Draw its contents back to this [`Frame`] with [`paste`].
/// ///
/// [`paste`]: Self::paste /// [`paste`]: Self::paste
pub fn draft(&mut self, size: Size) -> Self { fn draft(&mut self, clip_bounds: Rectangle) -> Self {
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`]. /// 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); self.raw.paste(frame.raw, at);
} }
@ -187,7 +185,7 @@ pub trait Backend: Sized {
fn scale(&mut self, scale: impl Into<f32>); fn scale(&mut self, scale: impl Into<f32>);
fn scale_nonuniform(&mut self, scale: impl Into<Vector>); 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 paste(&mut self, frame: Self, at: Point);
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); 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(&mut self, _scale: impl Into<f32>) {}
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} 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 paste(&mut self, _frame: Self, _at: Point) {}
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {} fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}

View file

@ -1,6 +1,6 @@
//! Draw triangles! //! Draw triangles!
use crate::color; use crate::color;
use crate::core::{Rectangle, Size, Transformation}; use crate::core::{Rectangle, Transformation};
use crate::gradient; use crate::gradient;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
@ -16,8 +16,8 @@ pub enum Mesh {
/// The [`Transformation`] for the vertices of the [`Mesh`]. /// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation, transformation: Transformation,
/// The [`Size`] of the [`Mesh`]. /// The clip bounds of the [`Mesh`].
size: Size, clip_bounds: Rectangle,
}, },
/// A mesh with a gradient. /// A mesh with a gradient.
Gradient { Gradient {
@ -27,8 +27,8 @@ pub enum Mesh {
/// The [`Transformation`] for the vertices of the [`Mesh`]. /// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation, transformation: Transformation,
/// The [`Size`] of the [`Mesh`]. /// The clip bounds of the [`Mesh`].
size: Size, clip_bounds: Rectangle,
}, },
} }
@ -53,15 +53,15 @@ impl Mesh {
pub fn clip_bounds(&self) -> Rectangle { pub fn clip_bounds(&self) -> Rectangle {
match self { match self {
Self::Solid { Self::Solid {
size, clip_bounds,
transformation, transformation,
.. ..
} }
| Self::Gradient { | Self::Gradient {
size, clip_bounds,
transformation, transformation,
.. ..
} => Rectangle::with_size(*size) * *transformation, } => *clip_bounds * *transformation,
} }
} }
} }

View file

@ -11,7 +11,7 @@ pub use cosmic_text;
use crate::core::alignment; use crate::core::alignment;
use crate::core::font::{self, Font}; 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 crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@ -50,7 +50,7 @@ pub enum Text {
/// The size of the text in logical pixels. /// The size of the text in logical pixels.
size: Pixels, size: Pixels,
/// The line height of the text. /// The line height of the text.
line_height: LineHeight, line_height: Pixels,
/// The font of the text. /// The font of the text.
font: Font, font: Font,
/// The horizontal alignment of the text. /// The horizontal alignment of the text.

View file

@ -405,7 +405,7 @@ where
#[cfg(feature = "geometry")] #[cfg(feature = "geometry")]
mod geometry { mod geometry {
use super::Renderer; 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::geometry::{self, Fill, Path, Stroke, Text};
use crate::graphics::Cached; use crate::graphics::Cached;
@ -533,10 +533,10 @@ mod geometry {
delegate!(self, frame, frame.pop_transform()); delegate!(self, frame, frame.pop_transform());
} }
fn draft(&mut self, size: Size) -> Self { fn draft(&mut self, bounds: Rectangle) -> Self {
match self { match self {
Self::Left(frame) => Self::Left(frame.draft(size)), Self::Left(frame) => Self::Left(frame.draft(bounds)),
Self::Right(frame) => Self::Right(frame.draft(size)), Self::Right(frame) => Self::Right(frame.draft(bounds)),
} }
} }

View file

@ -195,8 +195,8 @@ impl geometry::frame::Backend for Frame {
self.transform = self.stack.pop().expect("Pop transform"); self.transform = self.stack.pop().expect("Pop transform");
} }
fn draft(&mut self, size: Size) -> Self { fn draft(&mut self, clip_bounds: Rectangle) -> Self {
Self::new(size) Self::new(clip_bounds.size())
} }
fn paste(&mut self, frame: Self, at: Point) { fn paste(&mut self, frame: Self, at: Point) {

View file

@ -6,40 +6,44 @@ use crate::core::{
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{ 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::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh}; use crate::graphics::mesh::{self, Mesh};
use crate::graphics::{self, Cached}; use crate::graphics::{self, Cached, Text};
use crate::layer;
use crate::text; use crate::text;
use crate::triangle;
use lyon::geom::euclid; use lyon::geom::euclid;
use lyon::tessellation; use lyon::tessellation;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
/// A frame for drawing some geometry. /// A frame for drawing some geometry.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Frame { pub struct Frame {
size: Size, clip_bounds: Rectangle,
buffers: BufferStack, buffers: BufferStack,
layers: Vec<layer::Live>, meshes: Vec<Mesh>,
text: text::Batch, text: Vec<Text>,
transforms: Transforms, transforms: Transforms,
fill_tessellator: tessellation::FillTessellator, fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator, stroke_tessellator: tessellation::StrokeTessellator,
} }
pub enum Geometry { pub enum Geometry {
Live(Vec<layer::Live>), Live { meshes: Vec<Mesh>, text: Vec<Text> },
Cached(Rc<[Rc<RefCell<layer::Cached>>]>), Cached(Cache),
}
#[derive(Clone)]
pub struct Cache {
pub meshes: triangle::Cache,
pub text: text::Cache,
} }
impl Cached for Geometry { impl Cached for Geometry {
type Cache = Rc<[Rc<RefCell<layer::Cached>>]>; type Cache = Cache;
fn load(cache: &Self::Cache) -> Self { fn load(cache: &Self::Cache) -> Self {
Geometry::Cached(cache.clone()) Geometry::Cached(cache.clone())
@ -47,31 +51,18 @@ impl Cached for Geometry {
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache { fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
match self { match self {
Self::Live(live) => { Self::Live { meshes, text } => {
let mut layers = live.into_iter(); if let Some(mut previous) = previous {
previous.meshes.update(meshes);
previous.text.update(text);
let mut new: Vec<_> = previous previous
.map(|previous| { } else {
previous Cache {
.iter() meshes: triangle::Cache::new(meshes),
.cloned() text: text::Cache::new(text),
.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, Self::Cached(cache) => cache,
} }
@ -81,69 +72,26 @@ impl Cached for Geometry {
impl Frame { impl Frame {
/// Creates a new [`Frame`] with the given [`Size`]. /// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame { 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 { Frame {
size, clip_bounds: bounds,
buffers: BufferStack::new(), buffers: BufferStack::new(),
layers: Vec::new(), meshes: Vec::new(),
text: text::Batch::new(), text: Vec::new(),
transforms: Transforms { transforms: Transforms {
previous: Vec::new(), previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()), current: Transform(lyon::math::Transform::translation(
bounds.x, bounds.y,
)),
}, },
fill_tessellator: tessellation::FillTessellator::new(), fill_tessellator: tessellation::FillTessellator::new(),
stroke_tessellator: tessellation::StrokeTessellator::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 { impl geometry::frame::Backend for Frame {
@ -151,22 +99,22 @@ impl geometry::frame::Backend for Frame {
#[inline] #[inline]
fn width(&self) -> f32 { fn width(&self) -> f32 {
self.size.width self.clip_bounds.width
} }
#[inline] #[inline]
fn height(&self) -> f32 { fn height(&self) -> f32 {
self.size.height self.clip_bounds.height
} }
#[inline] #[inline]
fn size(&self) -> Size { fn size(&self) -> Size {
self.size self.clip_bounds.size()
} }
#[inline] #[inline]
fn center(&self) -> Point { 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>) { fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
@ -269,7 +217,7 @@ impl geometry::frame::Backend for Frame {
.expect("Stroke path"); .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 text = text.into();
let (scale_x, scale_y) = self.transforms.current.scale(); let (scale_x, scale_y) = self.transforms.current.scale();
@ -312,12 +260,12 @@ impl geometry::frame::Backend for Frame {
bounds, bounds,
color: text.color, color: text.color,
size, size,
line_height, line_height: line_height.to_absolute(size),
font: text.font, font: text.font,
horizontal_alignment: text.horizontal_alignment, horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment, vertical_alignment: text.vertical_alignment,
shaping: text.shaping, shaping: text.shaping,
clip_bounds: Rectangle::with_size(Size::INFINITY), clip_bounds: self.clip_bounds,
}); });
} else { } else {
text.draw_with(|path, color| self.fill(&path, color)); 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(); self.transforms.current = self.transforms.previous.pop().unwrap();
} }
fn draft(&mut self, size: Size) -> Frame { fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
Frame::new(size) Frame::with_clip(clip_bounds)
} }
fn paste(&mut self, frame: Frame, at: Point) { fn paste(&mut self, frame: Frame, _at: Point) {
let translation = Transformation::translate(at.x, at.y); self.meshes
.extend(frame.buffers.into_meshes(frame.clip_bounds));
self.layers self.text.extend(frame.text);
.extend(frame.into_layers().into_iter().map(|mut layer| {
layer.transformation = layer.transformation * translation;
layer
}));
} }
fn into_geometry(self) -> Self::Geometry { fn into_geometry(mut self) -> Self::Geometry {
Geometry::Live(self.into_layers()) 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!(), _ => 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)] #[derive(Debug)]

View file

@ -9,7 +9,6 @@ mod raster;
#[cfg(feature = "svg")] #[cfg(feature = "svg")]
mod vector; mod vector;
use crate::core::image;
use crate::core::{Rectangle, Size, Transformation}; use crate::core::{Rectangle, Size, Transformation};
use crate::Buffer; use crate::Buffer;
@ -234,10 +233,12 @@ impl Pipeline {
[bounds.width, bounds.height], [bounds.width, bounds.height],
atlas_entry, atlas_entry,
match filter_method { match filter_method {
image::FilterMethod::Nearest => { crate::core::image::FilterMethod::Nearest => {
nearest_instances nearest_instances
} }
image::FilterMethod::Linear => linear_instances, crate::core::image::FilterMethod::Linear => {
linear_instances
}
}, },
); );
} }

View file

@ -8,39 +8,46 @@ use crate::quad::{self, Quad};
use crate::text::{self, Text}; use crate::text::{self, Text};
use crate::triangle; use crate::triangle;
use std::cell::{self, RefCell}; pub struct Layer {
use std::rc::Rc; pub bounds: Rectangle,
pub quads: quad::Batch,
pub enum Layer<'a> { pub triangles: triangle::Batch,
Live(&'a Live), pub text: text::Batch,
Cached(Transformation, cell::Ref<'a, Cached>), pub images: image::Batch,
} }
pub enum LayerMut<'a> { impl Default for Layer {
Live(&'a mut Live), fn default() -> Self {
Cached(Transformation, cell::RefMut<'a, Cached>), Self {
bounds: Rectangle::INFINITE,
quads: quad::Batch::default(),
triangles: triangle::Batch::default(),
text: text::Batch::default(),
images: image::Batch::default(),
}
}
} }
pub struct Stack { pub struct Stack {
live: Vec<Live>, layers: Vec<Layer>,
cached: Vec<(Transformation, Rc<RefCell<Cached>>)>,
order: Vec<Kind>,
transformations: Vec<Transformation>, transformations: Vec<Transformation>,
previous: Vec<usize>, previous: Vec<usize>,
pending_meshes: Vec<Vec<Mesh>>,
pending_text: Vec<Vec<Text>>,
current: usize, current: usize,
live_count: usize, active_count: usize,
} }
impl Stack { impl Stack {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
live: vec![Live::default()], layers: vec![Layer::default()],
cached: Vec::new(),
order: vec![Kind::Live],
transformations: vec![Transformation::IDENTITY], transformations: vec![Transformation::IDENTITY],
previous: Vec::new(), previous: vec![],
pending_meshes: vec![Vec::new()],
pending_text: vec![Vec::new()],
current: 0, current: 0,
live_count: 1, active_count: 1,
} }
} }
@ -59,7 +66,7 @@ impl Stack {
shadow_blur_radius: quad.shadow.blur_radius, 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( pub fn draw_paragraph(
@ -77,7 +84,7 @@ impl Stack {
transformation: self.transformations.last().copied().unwrap(), transformation: self.transformations.last().copied().unwrap(),
}; };
self.live[self.current].text.push(paragraph); self.pending_text[self.current].push(paragraph);
} }
pub fn draw_editor( pub fn draw_editor(
@ -87,7 +94,7 @@ impl Stack {
color: Color, color: Color,
clip_bounds: Rectangle, clip_bounds: Rectangle,
) { ) {
let paragraph = Text::Editor { let editor = Text::Editor {
editor: editor.downgrade(), editor: editor.downgrade(),
position, position,
color, color,
@ -95,7 +102,7 @@ impl Stack {
transformation: self.transformation(), transformation: self.transformation(),
}; };
self.live[self.current].text.push(paragraph); self.pending_text[self.current].push(editor);
} }
pub fn draw_text( pub fn draw_text(
@ -107,12 +114,13 @@ impl Stack {
) { ) {
let transformation = self.transformation(); let transformation = self.transformation();
let paragraph = Text::Cached { let text = Text::Cached {
content: text.content, content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation, bounds: Rectangle::new(position, text.bounds) * transformation,
color, color,
size: text.size * transformation.scale_factor(), 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, font: text.font,
horizontal_alignment: text.horizontal_alignment, horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment, vertical_alignment: text.vertical_alignment,
@ -120,7 +128,7 @@ impl Stack {
clip_bounds: clip_bounds * transformation, clip_bounds: clip_bounds * transformation,
}; };
self.live[self.current].text.push(paragraph); self.pending_text[self.current].push(text);
} }
pub fn draw_image( pub fn draw_image(
@ -135,7 +143,7 @@ impl Stack {
bounds: bounds * self.transformation(), bounds: bounds * self.transformation(),
}; };
self.live[self.current].images.push(image); self.layers[self.current].images.push(image);
} }
pub fn draw_svg( pub fn draw_svg(
@ -150,7 +158,7 @@ impl Stack {
bounds: bounds * self.transformation(), 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) { 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) { pub fn draw_mesh_group(&mut self, meshes: Vec<Mesh>) {
layer.transformation = layer.transformation * self.transformation(); self.flush_pending();
if self.live_count == self.live.len() { let transformation = self.transformation();
self.live.push(layer);
} else {
self.live[self.live_count] = layer;
}
self.live_count += 1; self.layers[self.current]
self.order.push(Kind::Live); .triangles
} .push(triangle::Item::Group {
transformation,
pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) { meshes,
self.cached.push((self.transformation(), layer.clone()));
self.order.push(Kind::Cache);
}
pub fn push_clip(&mut self, bounds: Option<Rectangle>) {
self.previous.push(self.current);
self.order.push(Kind::Live);
self.current = self.live_count;
self.live_count += 1;
let bounds = bounds.map(|bounds| bounds * self.transformation());
if self.current == self.live.len() {
self.live.push(Live {
bounds,
..Live::default()
}); });
}
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: 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()
});
self.pending_meshes.push(Vec::new());
self.pending_text.push(Vec::new());
} else { } else {
self.live[self.current].bounds = bounds; self.layers[self.current].bounds = bounds;
} }
} }
pub fn pop_clip(&mut self) { pub fn pop_clip(&mut self) {
self.flush_pending();
self.current = self.previous.pop().unwrap(); self.current = self.previous.pop().unwrap();
} }
pub fn push_transformation(&mut self, transformation: Transformation) { pub fn push_transformation(&mut self, transformation: Transformation) {
self.flush_pending();
self.transformations self.transformations
.push(self.transformation() * transformation); .push(self.transformation() * transformation);
} }
@ -218,56 +261,58 @@ impl Stack {
self.transformations.last().copied().unwrap() self.transformations.last().copied().unwrap()
} }
pub fn iter_mut(&mut self) -> impl Iterator<Item = LayerMut<'_>> { pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Layer> {
let mut live = self.live.iter_mut(); self.flush_pending();
let mut cached = self.cached.iter_mut();
self.order.iter().map(move |kind| match kind { self.layers[..self.active_count].iter_mut()
Kind::Live => LayerMut::Live(live.next().unwrap()),
Kind::Cache => {
let (transformation, layer) = cached.next().unwrap();
let layer = layer.borrow_mut();
LayerMut::Cached(*transformation * layer.transformation, layer)
}
})
} }
pub fn iter(&self) -> impl Iterator<Item = Layer<'_>> { pub fn iter(&self) -> impl Iterator<Item = &Layer> {
let mut live = self.live.iter(); self.layers[..self.active_count].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 clear(&mut self) { pub fn clear(&mut self) {
for live in &mut self.live[..self.live_count] { for (live, pending_meshes) in self.layers[..self.active_count]
live.bounds = None; .iter_mut()
live.transformation = Transformation::IDENTITY; .zip(self.pending_meshes.iter_mut())
{
live.bounds = Rectangle::INFINITE;
live.quads.clear(); live.quads.clear();
live.meshes.clear(); live.triangles.clear();
live.text.clear(); live.text.clear();
live.images.clear(); live.images.clear();
pending_meshes.clear();
} }
self.current = 0; self.current = 0;
self.live_count = 1; self.active_count = 1;
self.order.clear();
self.order.push(Kind::Live);
self.cached.clear();
self.previous.clear(); self.previous.clear();
} }
// 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(),
});
}
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(),
});
}
}
} }
impl Default for Stack { impl Default for Stack {
@ -275,52 +320,3 @@ impl Default for Stack {
Self::new() Self::new()
} }
} }
#[derive(Default)]
pub struct Live {
pub bounds: Option<Rectangle>,
pub transformation: Transformation,
pub quads: quad::Batch,
pub meshes: triangle::Batch,
pub text: text::Batch,
pub images: image::Batch,
}
impl Live {
pub fn into_cached(self) -> Cached {
Cached {
bounds: self.bounds,
transformation: self.transformation,
quads: quad::Cache::Staged(self.quads),
meshes: triangle::Cache::Staged(self.meshes),
text: text::Cache::Staged(self.text),
images: self.images,
}
}
}
#[derive(Default)]
pub struct Cached {
pub bounds: Option<Rectangle>,
pub transformation: Transformation,
pub 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;
}
}
enum Kind {
Live,
Cache,
}

View file

@ -60,7 +60,7 @@ pub use iced_graphics::core;
pub use wgpu; pub use wgpu;
pub use engine::Engine; pub use engine::Engine;
pub use layer::{Layer, LayerMut}; pub use layer::Layer;
pub use primitive::Primitive; pub use primitive::Primitive;
pub use settings::Settings; pub use settings::Settings;
@ -85,6 +85,9 @@ pub struct Renderer {
default_text_size: Pixels, default_text_size: Pixels,
layers: layer::Stack, layers: layer::Stack,
triangle_storage: triangle::Storage,
text_storage: text::Storage,
// TODO: Centralize all the image feature handling // TODO: Centralize all the image feature handling
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
image_cache: image::cache::Shared, image_cache: image::cache::Shared,
@ -97,6 +100,9 @@ impl Renderer {
default_text_size: settings.default_text_size, default_text_size: settings.default_text_size,
layers: layer::Stack::new(), layers: layer::Stack::new(),
triangle_storage: triangle::Storage::new(),
text_storage: text::Storage::new(),
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
image_cache: _engine.image_cache().clone(), image_cache: _engine.image_cache().clone(),
} }
@ -117,9 +123,11 @@ impl Renderer {
overlay: &[T], overlay: &[T],
) { ) {
self.draw_overlay(overlay, viewport); self.draw_overlay(overlay, viewport);
self.prepare(engine, device, queue, format, encoder, viewport); self.prepare(engine, device, queue, format, encoder, viewport);
self.render(engine, device, encoder, frame, clear_color, viewport); self.render(engine, device, encoder, frame, clear_color, viewport);
self.triangle_storage.trim();
self.text_storage.trim();
} }
fn prepare( fn prepare(
@ -134,116 +142,51 @@ impl Renderer {
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor() as f32;
for layer in self.layers.iter_mut() { for layer in self.layers.iter_mut() {
match layer { if !layer.quads.is_empty() {
LayerMut::Live(live) => { engine.quad_pipeline.prepare(
if !live.quads.is_empty() { device,
engine.quad_pipeline.prepare_batch( encoder,
device, &mut engine.staging_belt,
encoder, &layer.quads,
&mut engine.staging_belt, viewport.projection(),
&live.quads, scale_factor,
viewport.projection(), );
scale_factor, }
);
}
if !live.meshes.is_empty() { if !layer.triangles.is_empty() {
engine.triangle_pipeline.prepare_batch( engine.triangle_pipeline.prepare(
device, device,
encoder, encoder,
&mut engine.staging_belt, &mut engine.staging_belt,
&live.meshes, &mut self.triangle_storage,
viewport.projection() &layer.triangles,
* Transformation::scale(scale_factor), viewport.projection() * Transformation::scale(scale_factor),
); );
} }
if !live.text.is_empty() { if !layer.text.is_empty() {
engine.text_pipeline.prepare_batch( engine.text_pipeline.prepare(
device, device,
queue, queue,
encoder, encoder,
&live.text, &mut self.text_storage,
live.bounds.unwrap_or(Rectangle::with_size( &layer.text,
viewport.logical_size(), layer.bounds,
)), Transformation::scale(scale_factor),
live.transformation viewport.physical_size(),
* Transformation::scale(scale_factor), );
viewport.physical_size(), }
);
}
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
if !live.images.is_empty() { if !layer.images.is_empty() {
engine.image_pipeline.prepare( engine.image_pipeline.prepare(
device, device,
encoder, encoder,
&mut engine.staging_belt, &mut engine.staging_belt,
&live.images, &layer.images,
viewport.projection(), viewport.projection(),
scale_factor, 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,
);
}
}
} }
} }
} }
@ -297,208 +240,87 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
let mut image_layer = 0; let mut image_layer = 0;
// TODO: Can we avoid collecting here?
let scale_factor = viewport.scale_factor() as f32; 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( let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
viewport.physical_size(), viewport.physical_size(),
)); ));
let layers: Vec<_> = self.layers.iter().collect(); let scale = Transformation::scale(scale_factor);
let mut i = 0;
// println!("RENDER"); for layer in self.layers.iter() {
let Some(physical_bounds) =
physical_bounds.intersection(&(layer.bounds * scale))
else {
continue;
};
while i < layers.len() { let scissor_rect = physical_bounds.snap();
match layers[i] {
Layer::Live(live) => {
let layer_transformation =
Transformation::scale(scale_factor)
* live.transformation;
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 quad_layer += 1;
.intersection(&(layer_bounds * layer_transformation)) }
.map(Rectangle::snap)
else {
continue;
};
if !live.quads.is_empty() { if !layer.triangles.is_empty() {
engine.quad_pipeline.render_batch( let _ = ManuallyDrop::into_inner(render_pass);
quad_layer,
physical_bounds,
&live.quads,
&mut render_pass,
);
quad_layer += 1; mesh_layer += engine.triangle_pipeline.render(
} device,
encoder,
frame,
&self.triangle_storage,
mesh_layer,
&layer.triangles,
viewport.physical_size(),
physical_bounds,
scale,
);
if !live.meshes.is_empty() { render_pass = ManuallyDrop::new(encoder.begin_render_pass(
// println!("LIVE PASS"); &wgpu::RenderPassDescriptor {
let _ = ManuallyDrop::into_inner(render_pass); label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
engine.triangle_pipeline.render_batch( wgpu::RenderPassColorAttachment {
device, view: frame,
encoder, resolve_target: None,
frame, ops: wgpu::Operations {
mesh_layer, load: wgpu::LoadOp::Load,
viewport.physical_size(), store: wgpu::StoreOp::Store,
&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,
}, },
)); },
} )],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
if !live.text.is_empty() { if !layer.text.is_empty() {
engine.text_pipeline.render_batch( text_layer += engine.text_pipeline.render(
text_layer, &self.text_storage,
physical_bounds, text_layer,
&mut render_pass, &layer.text,
); scissor_rect,
&mut render_pass,
);
}
text_layer += 1; #[cfg(any(feature = "svg", feature = "image"))]
} if !layer.images.is_empty() {
engine.image_pipeline.render(
image_layer,
scissor_rect,
&mut render_pass,
);
#[cfg(any(feature = "svg", feature = "image"))] image_layer += 1;
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,
},
));
}
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;
}
} }
} }
@ -552,7 +374,7 @@ impl Renderer {
impl core::Renderer for Renderer { impl core::Renderer for Renderer {
fn start_layer(&mut self, bounds: Rectangle) { 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) { 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) { fn draw_geometry(&mut self, geometry: Self::Geometry) {
match geometry { match geometry {
Geometry::Live(layers) => { Geometry::Live { meshes, text } => {
for layer in layers { self.layers.draw_mesh_group(meshes);
self.layers.draw_layer(layer); self.layers.draw_text_group(text);
}
} }
Geometry::Cached(layers) => { Geometry::Cached(cache) => {
for layer in layers.as_ref() { self.layers.draw_mesh_cache(cache.meshes);
self.layers.draw_cached_layer(layer); self.layers.draw_text_cache(cache.text);
}
} }
} }
} }

View file

@ -80,7 +80,7 @@ impl Pipeline {
} }
} }
pub fn prepare_batch( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
@ -99,64 +99,7 @@ impl Pipeline {
self.prepare_layer += 1; self.prepare_layer += 1;
} }
pub fn prepare_cache( pub fn render<'a>(
&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, &'a self,
layer: usize, layer: usize,
bounds: Rectangle<u32>, bounds: Rectangle<u32>,
@ -164,59 +107,38 @@ impl Pipeline {
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) { ) {
if let Some(layer) = self.layers.get(layer) { if let Some(layer) = self.layers.get(layer) {
self.render(bounds, layer, &quads.order, render_pass); render_pass.set_scissor_rect(
} bounds.x,
} bounds.y,
bounds.width,
bounds.height,
);
pub fn render_cache<'a>( let mut solid_offset = 0;
&'a self, let mut gradient_offset = 0;
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>( for (kind, count) in &quads.order {
&'a self, match kind {
bounds: Rectangle<u32>, Kind::Solid => {
layer: &'a Layer, self.solid.render(
order: &Order, render_pass,
render_pass: &mut wgpu::RenderPass<'a>, &layer.constants,
) { &layer.solid,
render_pass.set_scissor_rect( solid_offset..(solid_offset + count),
bounds.x, );
bounds.y,
bounds.width,
bounds.height,
);
let mut solid_offset = 0; solid_offset += count;
let mut gradient_offset = 0; }
Kind::Gradient => {
self.gradient.render(
render_pass,
&layer.constants,
&layer.gradient,
gradient_offset..(gradient_offset + count),
);
for (kind, count) in order { gradient_offset += count;
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)] #[derive(Debug)]
pub struct Layer { pub struct Layer {
constants: wgpu::BindGroup, constants: wgpu::BindGroup,

View file

@ -4,11 +4,167 @@ use crate::graphics::color;
use crate::graphics::text::cache::{self, Cache as BufferCache}; use crate::graphics::text::cache::{self, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; 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; use std::sync::Arc;
pub use crate::graphics::Text; 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
};
pub type Batch = Vec<Item>;
#[derive(Debug)]
pub enum Item {
Group {
transformation: Transformation,
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 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, text: Vec<Text>) {
self.text = Rc::from(text);
self.version += 1;
}
}
struct Upload {
renderer: glyphon::TextRenderer,
atlas: glyphon::TextAtlas,
buffer_cache: BufferCache,
transformation: Transformation,
version: usize,
}
#[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)
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
format: wgpu::TextureFormat,
cache: &Cache,
new_transformation: Transformation,
bounds: Rectangle,
target_size: Size<u32>,
) {
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
{
let _ = prepare(
device,
queue,
encoder,
&mut upload.renderer,
&mut upload.atlas,
&mut upload.buffer_cache,
&cache.text,
bounds,
new_transformation,
target_size,
);
upload.version = cache.version;
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,
device,
wgpu::MultisampleState::default(),
None,
);
let mut buffer_cache = BufferCache::new();
let _ = prepare(
device,
queue,
encoder,
&mut renderer,
&mut atlas,
&mut buffer_cache,
&cache.text,
bounds,
new_transformation,
target_size,
);
let _ = entry.insert(Upload {
renderer,
atlas,
buffer_cache,
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();
}
}
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Pipeline { pub struct Pipeline {
@ -19,51 +175,6 @@ pub struct Pipeline {
cache: BufferCache, cache: BufferCache,
} }
pub enum Cache {
Staged(Batch),
Uploaded {
batch: Batch,
renderer: glyphon::TextRenderer,
atlas: Option<glyphon::TextAtlas>,
buffer_cache: Option<BufferCache>,
transformation: Transformation,
target_size: Size<u32>,
needs_reupload: bool,
},
}
impl Cache {
pub fn is_empty(&self) -> bool {
match self {
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
batch.is_empty()
}
}
}
pub fn update(&mut self, new_batch: Batch) {
match self {
Self::Staged(batch) => {
*batch = new_batch;
}
Self::Uploaded {
batch,
needs_reupload,
..
} => {
*batch = new_batch;
*needs_reupload = true;
}
}
}
}
impl Default for Cache {
fn default() -> Self {
Self::Staged(Batch::default())
}
}
impl Pipeline { impl Pipeline {
pub fn new( pub fn new(
device: &wgpu::Device, device: &wgpu::Device,
@ -74,175 +185,92 @@ impl Pipeline {
format, format,
renderers: Vec::new(), renderers: Vec::new(),
atlas: glyphon::TextAtlas::with_color_mode( atlas: glyphon::TextAtlas::with_color_mode(
device, device, queue, format, COLOR_MODE,
queue,
format,
if color::GAMMA_CORRECTION {
glyphon::ColorMode::Accurate
} else {
glyphon::ColorMode::Web
},
), ),
prepare_layer: 0, prepare_layer: 0,
cache: BufferCache::new(), cache: BufferCache::new(),
} }
} }
pub fn prepare_batch( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
sections: &Batch, storage: &mut Storage,
batch: &Batch,
layer_bounds: Rectangle, layer_bounds: Rectangle,
layer_transformation: Transformation, layer_transformation: Transformation,
target_size: Size<u32>, target_size: Size<u32>,
) { ) {
if self.renderers.len() <= self.prepare_layer { for item in batch {
self.renderers.push(glyphon::TextRenderer::new( match item {
&mut self.atlas, Item::Group {
device, transformation,
wgpu::MultisampleState::default(), text,
None, } => {
)); if self.renderers.len() <= self.prepare_layer {
} self.renderers.push(glyphon::TextRenderer::new(
&mut self.atlas,
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,
);
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...
}
}
}
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, device,
queue, wgpu::MultisampleState::default(),
self.format, None,
if color::GAMMA_CORRECTION { ));
glyphon::ColorMode::Accurate }
} else {
glyphon::ColorMode::Web
},
)),
Some(BufferCache::new()),
)
} else {
(None, None)
};
let mut renderer = glyphon::TextRenderer::new( let renderer = &mut self.renderers[self.prepare_layer];
atlas.as_mut().unwrap_or(&mut self.atlas), let result = prepare(
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_transformation,
new_target_size,
);
*cache = Cache::Uploaded {
batch,
needs_reupload: false,
renderer,
atlas,
buffer_cache,
transformation: new_transformation,
target_size: new_target_size,
}
}
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(
device, device,
queue, queue,
encoder, encoder,
renderer, renderer,
atlas.as_mut().unwrap_or(&mut self.atlas), &mut self.atlas,
buffer_cache.as_mut().unwrap_or(&mut self.cache), &mut self.cache,
batch, text,
layer_bounds, layer_bounds,
new_transformation, layer_transformation * *transformation,
new_target_size, target_size,
); );
*transformation = new_transformation; match result {
*target_size = new_target_size; Ok(()) => {
*needs_reupload = false; 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, &'a self,
layer: usize, storage: &'a Storage,
start: usize,
batch: &'a Batch,
bounds: Rectangle<u32>, bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) { ) -> usize {
let renderer = &self.renderers[layer]; let mut layer_count = 0;
render_pass.set_scissor_rect( render_pass.set_scissor_rect(
bounds.x, bounds.x,
@ -251,34 +279,29 @@ impl Pipeline {
bounds.height, bounds.height,
); );
renderer for item in batch {
.render(&self.atlas, render_pass) match item {
.expect("Render text"); Item::Group { .. } => {
} let renderer = &self.renderers[start + layer_count];
pub fn render_cache<'a>( renderer
&'a self, .render(&self.atlas, render_pass)
cache: &'a Cache, .expect("Render text");
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let Cache::Uploaded {
renderer, atlas, ..
} = cache
else {
return;
};
render_pass.set_scissor_rect( layer_count += 1;
bounds.x, }
bounds.y, Item::Cached { cache, .. } => {
bounds.width, if let Some(upload) = storage.get(cache.id) {
bounds.height, upload
); .renderer
.render(&upload.atlas, render_pass)
.expect("Render cached text");
}
}
}
}
renderer layer_count
.render(atlas.as_ref().unwrap_or(&self.atlas), render_pass)
.expect("Render text");
} }
pub fn end_frame(&mut self) { pub fn end_frame(&mut self) {
@ -296,7 +319,7 @@ fn prepare(
renderer: &mut glyphon::TextRenderer, renderer: &mut glyphon::TextRenderer,
atlas: &mut glyphon::TextAtlas, atlas: &mut glyphon::TextAtlas,
buffer_cache: &mut BufferCache, buffer_cache: &mut BufferCache,
sections: &Batch, sections: &[Text],
layer_bounds: Rectangle, layer_bounds: Rectangle,
layer_transformation: Transformation, layer_transformation: Transformation,
target_size: Size<u32>, target_size: Size<u32>,
@ -333,8 +356,8 @@ fn prepare(
font_system, font_system,
cache::Key { cache::Key {
content, content,
size: (*size).into(), size: f32::from(*size),
line_height: f32::from(line_height.to_absolute(*size)), line_height: f32::from(*line_height),
font: *font, font: *font,
bounds: Size { bounds: Size {
width: bounds.width, width: bounds.width,

View file

@ -6,10 +6,136 @@ use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing; use crate::graphics::Antialiasing;
use crate::Buffer; 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_INDEX_COUNT: usize = 1_000;
const INITIAL_VERTEX_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)] #[derive(Debug)]
pub struct Pipeline { pub struct Pipeline {
@ -35,180 +161,103 @@ impl Pipeline {
} }
} }
pub fn prepare_batch( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt, belt: &mut wgpu::util::StagingBelt,
meshes: &Batch, storage: &mut Storage,
transformation: Transformation, items: &[Item],
projection: Transformation,
) { ) {
if self.layers.len() <= self.prepare_layer { for item in items {
self.layers match item {
.push(Layer::new(device, &self.solid, &self.gradient)); Item::Group {
} transformation,
meshes,
} => {
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]; 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,
new_projection: Transformation,
new_transformation: 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,
);
*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 {
layer.prepare( layer.prepare(
device, device,
encoder, encoder,
belt, belt,
&self.solid, &self.solid,
&self.gradient, &self.gradient,
batch, meshes,
new_projection, projection * *transformation,
); );
*transformation = new_transformation; self.prepare_layer += 1;
*projection = new_projection; }
*needs_reupload = false; Item::Cached {
transformation,
cache,
} => {
storage.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
cache,
projection * *transformation,
);
} }
} }
} }
} }
pub fn render_batch( pub fn render(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView, target: &wgpu::TextureView,
layer: usize, storage: &Storage,
start: usize,
batch: &Batch,
target_size: Size<u32>, target_size: Size<u32>,
meshes: &Batch, bounds: Rectangle,
bounds: Rectangle<u32>, screen_transformation: Transformation,
transformation: &Transformation, ) -> usize {
) { let mut layer_count = 0;
Self::render(
device, let items = batch.iter().filter_map(|item| match item {
encoder, Item::Group {
target, transformation,
self.blit.as_mut(),
&self.solid,
&self.gradient,
target_size,
std::iter::once((
&self.layers[layer],
meshes, meshes,
} => {
let layer = &self.layers[start + layer_count];
layer_count += 1;
Some((
layer,
meshes.as_slice(),
screen_transformation * *transformation,
))
}
Item::Cached {
transformation, transformation,
bounds, cache,
)), } => {
); let upload = storage.get(cache.id)?;
}
#[allow(dead_code)] Some((
pub fn render_cache( &upload.layer,
&mut self, &cache.batch,
device: &wgpu::Device, screen_transformation * *transformation,
encoder: &mut wgpu::CommandEncoder, ))
target: &wgpu::TextureView,
target_size: Size<u32>,
cache: &Cache,
bounds: Rectangle<u32>,
) {
let Cache::Uploaded {
batch,
layer,
transformation,
..
} = cache
else {
return;
};
Self::render(
device,
encoder,
target,
self.blit.as_mut(),
&self.solid,
&self.gradient,
target_size,
std::iter::once((layer, batch, transformation, bounds)),
);
}
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,
transformation,
..
} = cache
{
Some((layer, batch, transformation, bounds))
} else {
None
} }
}); });
Self::render( render(
device, device,
encoder, encoder,
target, target,
@ -216,71 +265,11 @@ impl Pipeline {
&self.solid, &self.solid,
&self.gradient, &self.gradient,
target_size, target_size,
group, bounds,
items,
); );
}
fn render<'a>( layer_count
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);
}
} }
pub fn end_frame(&mut self) { pub fn end_frame(&mut self) {
@ -288,47 +277,61 @@ impl Pipeline {
} }
} }
#[derive(Debug)] fn render<'a>(
pub enum Cache { device: &wgpu::Device,
Staged(Batch), encoder: &mut wgpu::CommandEncoder,
Uploaded { target: &wgpu::TextureView,
batch: Batch, mut blit: Option<&mut msaa::Blit>,
layer: Layer, solid: &solid::Pipeline,
transformation: Transformation, gradient: &gradient::Pipeline,
projection: Transformation, target_size: Size<u32>,
needs_reupload: bool, 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);
impl Cache { (
pub fn is_empty(&self) -> bool { attachment,
match self { Some(resolve_target),
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
batch.is_empty() )
} } 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) in group {
layer.render(
solid,
gradient,
meshes,
bounds,
transformation,
&mut render_pass,
);
} }
} }
pub fn update(&mut self, new_batch: Batch) { if let Some(blit) = blit {
match self { blit.draw(encoder, target);
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())
} }
} }
@ -366,7 +369,7 @@ impl Layer {
belt: &mut wgpu::util::StagingBelt, belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline, solid: &solid::Pipeline,
gradient: &gradient::Pipeline, gradient: &gradient::Pipeline,
meshes: &Batch, meshes: &[Mesh],
transformation: Transformation, transformation: Transformation,
) { ) {
// Count the total amount of vertices & indices we need to handle // Count the total amount of vertices & indices we need to handle
@ -471,8 +474,8 @@ impl Layer {
&'a self, &'a self,
solid: &'a solid::Pipeline, solid: &'a solid::Pipeline,
gradient: &'a gradient::Pipeline, gradient: &'a gradient::Pipeline,
meshes: &Batch, meshes: &[Mesh],
layer_bounds: Rectangle<u32>, bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) { ) {
@ -481,8 +484,8 @@ impl Layer {
let mut last_is_solid = None; let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() { for (index, mesh) in meshes.iter().enumerate() {
let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds) let Some(clip_bounds) =
.intersection(&(mesh.clip_bounds() * transformation)) bounds.intersection(&(mesh.clip_bounds() * transformation))
else { else {
continue; continue;
}; };

View file

@ -368,7 +368,7 @@ where
let text = value.to_string(); 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 .is_focused
.as_ref() .as_ref()
.filter(|focus| focus.is_window_focused) .filter(|focus| focus.is_window_focused)
@ -406,7 +406,7 @@ where
None None
}; };
(cursor, offset) (cursor, offset, false)
} }
cursor::State::Selection { start, end } => { cursor::State::Selection { start, end } => {
let left = start.min(end); let left = start.min(end);
@ -446,11 +446,12 @@ where
} else { } else {
left_offset left_offset
}, },
true,
) )
} }
} }
} else { } else {
(None, 0.0) (None, 0.0, false)
}; };
let draw = |renderer: &mut Renderer, viewport| { let draw = |renderer: &mut Renderer, viewport| {
@ -482,7 +483,7 @@ where
); );
}; };
if cursor.is_some() { if is_selecting {
renderer renderer
.with_layer(text_bounds, |renderer| draw(renderer, *viewport)); .with_layer(text_bounds, |renderer| draw(renderer, *viewport));
} else { } else {