Decouple caching from layering and simplify everything
This commit is contained in:
parent
4a356cfc16
commit
6d3e1d835e
15 changed files with 896 additions and 1199 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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>>) {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
428
wgpu/src/lib.rs
428
wgpu/src/lib.rs
|
|
@ -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);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
180
wgpu/src/quad.rs
180
wgpu/src/quad.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
447
wgpu/src/text.rs
447
wgpu/src/text.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue