Redesign iced_wgpu layering architecture
This commit is contained in:
parent
99a904112c
commit
b05e61f5c8
36 changed files with 2781 additions and 2048 deletions
|
|
@ -16,6 +16,22 @@ pub struct Rectangle<T = f32> {
|
|||
pub height: T,
|
||||
}
|
||||
|
||||
impl<T> Rectangle<T>
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||
/// and with the provided [`Size`].
|
||||
pub fn with_size(size: Size<T>) -> Self {
|
||||
Self {
|
||||
x: T::default(),
|
||||
y: T::default(),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rectangle<f32> {
|
||||
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
||||
/// [`Point`] and with the provided [`Size`].
|
||||
|
|
@ -28,17 +44,6 @@ impl Rectangle<f32> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||
/// and with the provided [`Size`].
|
||||
pub fn with_size(size: Size) -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Point`] at the center of the [`Rectangle`].
|
||||
pub fn center(&self) -> Point {
|
||||
Point::new(self.center_x(), self.center_y())
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
/// A component that can be used by widgets to draw themselves on a screen.
|
||||
pub trait Renderer {
|
||||
/// Starts recording a new layer.
|
||||
fn start_layer(&mut self);
|
||||
fn start_layer(&mut self, bounds: Rectangle);
|
||||
|
||||
/// Ends recording a new layer.
|
||||
///
|
||||
|
|
@ -20,13 +20,13 @@ pub trait Renderer {
|
|||
///
|
||||
/// The layer will clip its contents to the provided `bounds`.
|
||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||
self.start_layer();
|
||||
self.start_layer(bounds);
|
||||
f(self);
|
||||
self.end_layer(bounds);
|
||||
}
|
||||
|
||||
/// Starts recording with a new [`Transformation`].
|
||||
fn start_transformation(&mut self);
|
||||
fn start_transformation(&mut self, transformation: Transformation);
|
||||
|
||||
/// Ends recording a new layer.
|
||||
///
|
||||
|
|
@ -39,7 +39,7 @@ pub trait Renderer {
|
|||
transformation: Transformation,
|
||||
f: impl FnOnce(&mut Self),
|
||||
) {
|
||||
self.start_transformation();
|
||||
self.start_transformation(transformation);
|
||||
f(self);
|
||||
self.end_transformation(transformation);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ use crate::{
|
|||
use std::borrow::Cow;
|
||||
|
||||
impl Renderer for () {
|
||||
fn start_layer(&mut self) {}
|
||||
fn start_layer(&mut self, _bounds: Rectangle) {}
|
||||
|
||||
fn end_layer(&mut self, _bounds: Rectangle) {}
|
||||
|
||||
fn start_transformation(&mut self) {}
|
||||
fn start_transformation(&mut self, _transformation: Transformation) {}
|
||||
|
||||
fn end_transformation(&mut self, _transformation: Transformation) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,3 +99,17 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Mul<T> for Size<T>
|
||||
where
|
||||
T: std::ops::Mul<Output = T> + Copy,
|
||||
{
|
||||
type Output = Size<T>;
|
||||
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
Size {
|
||||
width: self.width * rhs,
|
||||
height: self.height * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ impl Transformation {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Transformation {
|
||||
fn default() -> Self {
|
||||
Transformation::IDENTITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Transformation {
|
||||
type Output = Self;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ mod rainbow {
|
|||
use iced::advanced::renderer;
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
|
||||
use iced::{
|
||||
Element, Length, Rectangle, Renderer, Size, Theme, Transformation,
|
||||
Vector,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Rainbow;
|
||||
|
|
@ -130,6 +133,7 @@ mod rainbow {
|
|||
0, 8, 1, // L
|
||||
],
|
||||
},
|
||||
transformation: Transformation::IDENTITY,
|
||||
};
|
||||
|
||||
renderer.with_translation(
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ use controls::Controls;
|
|||
use scene::Scene;
|
||||
|
||||
use iced_wgpu::graphics::Viewport;
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
||||
use iced_wgpu::{wgpu, Engine, Renderer, Settings};
|
||||
use iced_winit::conversion;
|
||||
use iced_winit::core::mouse;
|
||||
use iced_winit::core::renderer;
|
||||
use iced_winit::core::window;
|
||||
use iced_winit::core::{Color, Font, Pixels, Size, Theme};
|
||||
use iced_winit::core::{Color, Size, Theme};
|
||||
use iced_winit::futures;
|
||||
use iced_winit::runtime::program;
|
||||
use iced_winit::runtime::Debug;
|
||||
|
|
@ -155,11 +155,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// Initialize iced
|
||||
let mut debug = Debug::new();
|
||||
let mut renderer = Renderer::new(
|
||||
Backend::new(&adapter, &device, &queue, Settings::default(), format),
|
||||
Font::default(),
|
||||
Pixels(16.0),
|
||||
);
|
||||
let mut engine = Engine::new(&adapter, &device, &queue, format, None);
|
||||
let mut renderer = Renderer::new(Settings::default(), &engine);
|
||||
|
||||
let mut state = program::State::new(
|
||||
controls,
|
||||
|
|
@ -228,19 +225,17 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
// And then iced on top
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
renderer.present(
|
||||
&mut engine,
|
||||
&device,
|
||||
&queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
frame.texture.format(),
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
});
|
||||
|
||||
// Then we submit the work
|
||||
queue.submit(Some(encoder.finish()));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
|||
/// A piece of data that can be cached.
|
||||
pub trait Cached: Sized {
|
||||
/// The type of cache produced.
|
||||
type Cache;
|
||||
type Cache: Clone;
|
||||
|
||||
/// Loads the [`Cache`] into a proper instance.
|
||||
///
|
||||
|
|
@ -15,7 +15,7 @@ pub trait Cached: Sized {
|
|||
/// Caches this value, producing its corresponding [`Cache`].
|
||||
///
|
||||
/// [`Cache`]: Self::Cache
|
||||
fn cache(self) -> Self::Cache;
|
||||
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache;
|
||||
}
|
||||
|
||||
impl<T> Cached for Primitive<T> {
|
||||
|
|
@ -27,7 +27,7 @@ impl<T> Cached for Primitive<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn cache(self) -> Arc<Self> {
|
||||
fn cache(self, _previous: Option<Arc<Self>>) -> Arc<Self> {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,5 +38,5 @@ impl Cached for () {
|
|||
|
||||
fn load(_cache: &Self::Cache) -> Self {}
|
||||
|
||||
fn cache(self) -> Self::Cache {}
|
||||
fn cache(self, _previous: Option<Self::Cache>) -> Self::Cache {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ where
|
|||
) -> Renderer::Geometry {
|
||||
use std::ops::Deref;
|
||||
|
||||
if let State::Filled {
|
||||
let previous = if let State::Filled {
|
||||
bounds: cached_bounds,
|
||||
geometry,
|
||||
} = self.state.borrow().deref()
|
||||
|
|
@ -57,12 +57,16 @@ where
|
|||
if *cached_bounds == bounds {
|
||||
return Cached::load(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
Some(geometry.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut frame = Frame::new(renderer, bounds);
|
||||
draw_fn(&mut frame);
|
||||
|
||||
let geometry = frame.into_geometry().cache();
|
||||
let geometry = frame.into_geometry().cache(previous);
|
||||
let result = Cached::load(&geometry);
|
||||
|
||||
*self.state.borrow_mut() = State::Filled { bounds, geometry };
|
||||
|
|
|
|||
|
|
@ -1,64 +1,54 @@
|
|||
//! Load and operate on images.
|
||||
use crate::core::image::{Data, Handle};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub use ::image as image_rs;
|
||||
|
||||
/// Tries to load an image by its [`Handle`].
|
||||
pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
|
||||
match handle.data() {
|
||||
Data::Path(path) => {
|
||||
let image = ::image::open(path)?;
|
||||
use crate::core::image;
|
||||
use crate::core::svg;
|
||||
use crate::core::{Color, Rectangle};
|
||||
|
||||
let operation = std::fs::File::open(path)
|
||||
.ok()
|
||||
.map(std::io::BufReader::new)
|
||||
.and_then(|mut reader| Operation::from_exif(&mut reader).ok())
|
||||
.unwrap_or_else(Operation::empty);
|
||||
/// A raster or vector image.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Image {
|
||||
/// A raster image.
|
||||
Raster {
|
||||
/// The handle of a raster image.
|
||||
handle: image::Handle,
|
||||
|
||||
Ok(operation.perform(image))
|
||||
}
|
||||
Data::Bytes(bytes) => {
|
||||
let image = ::image::load_from_memory(bytes)?;
|
||||
let operation =
|
||||
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
||||
.ok()
|
||||
.unwrap_or_else(Operation::empty);
|
||||
/// The filter method of a raster image.
|
||||
filter_method: image::FilterMethod,
|
||||
|
||||
Ok(operation.perform(image))
|
||||
}
|
||||
Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
} => {
|
||||
if let Some(image) = image_rs::ImageBuffer::from_vec(
|
||||
*width,
|
||||
*height,
|
||||
pixels.to_vec(),
|
||||
) {
|
||||
Ok(image_rs::DynamicImage::ImageRgba8(image))
|
||||
} else {
|
||||
Err(image_rs::error::ImageError::Limits(
|
||||
image_rs::error::LimitError::from_kind(
|
||||
image_rs::error::LimitErrorKind::DimensionError,
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The bounds of the image.
|
||||
bounds: Rectangle,
|
||||
},
|
||||
/// A vector image.
|
||||
Vector {
|
||||
/// The handle of a vector image.
|
||||
handle: svg::Handle,
|
||||
|
||||
/// The [`Color`] filter
|
||||
color: Option<Color>,
|
||||
|
||||
/// The bounds of the image.
|
||||
bounds: Rectangle,
|
||||
},
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[cfg(feature = "image")]
|
||||
/// Tries to load an image by its [`Handle`].
|
||||
pub fn load(
|
||||
handle: &image::Handle,
|
||||
) -> ::image::ImageResult<::image::DynamicImage> {
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
struct Operation: u8 {
|
||||
const FLIP_HORIZONTALLY = 0b001;
|
||||
const ROTATE_180 = 0b010;
|
||||
const FLIP_DIAGONALLY = 0b100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
impl Operation {
|
||||
// Meaning of the returned value is described e.g. at:
|
||||
// https://magnushoff.com/articles/jpeg-orientation/
|
||||
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
|
||||
|
|
@ -75,8 +65,11 @@ impl Operation {
|
|||
.unwrap_or_else(Self::empty))
|
||||
}
|
||||
|
||||
fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage {
|
||||
use image::imageops;
|
||||
fn perform(
|
||||
self,
|
||||
mut image: ::image::DynamicImage,
|
||||
) -> ::image::DynamicImage {
|
||||
use ::image::imageops;
|
||||
|
||||
if self.contains(Self::FLIP_DIAGONALLY) {
|
||||
imageops::flip_vertical_in_place(&mut image);
|
||||
|
|
@ -92,4 +85,45 @@ impl Operation {
|
|||
|
||||
image
|
||||
}
|
||||
}
|
||||
|
||||
match handle.data() {
|
||||
image::Data::Path(path) => {
|
||||
let image = ::image::open(path)?;
|
||||
|
||||
let operation = std::fs::File::open(path)
|
||||
.ok()
|
||||
.map(std::io::BufReader::new)
|
||||
.and_then(|mut reader| Operation::from_exif(&mut reader).ok())
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Ok(operation.perform(image))
|
||||
}
|
||||
image::Data::Bytes(bytes) => {
|
||||
let image = ::image::load_from_memory(bytes)?;
|
||||
let operation =
|
||||
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
||||
.ok()
|
||||
.unwrap_or_else(Operation::empty);
|
||||
|
||||
Ok(operation.perform(image))
|
||||
}
|
||||
image::Data::Rgba {
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
} => {
|
||||
if let Some(image) =
|
||||
::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec())
|
||||
{
|
||||
Ok(::image::DynamicImage::ImageRgba8(image))
|
||||
} else {
|
||||
Err(::image::error::ImageError::Limits(
|
||||
::image::error::LimitError::from_kind(
|
||||
::image::error::LimitErrorKind::DimensionError,
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
graphics/src/layer.rs
Normal file
47
graphics/src/layer.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
pub trait Layer {
|
||||
type Cache;
|
||||
|
||||
fn new() -> Self;
|
||||
|
||||
fn clear(&mut self);
|
||||
}
|
||||
|
||||
pub struct Recorder<T: Layer> {
|
||||
layers: Vec<T>,
|
||||
caches: Vec<T::Cache>,
|
||||
stack: Vec<Kind>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
enum Kind {
|
||||
Fresh(usize),
|
||||
Cache(usize),
|
||||
}
|
||||
|
||||
impl<T: Layer> Recorder<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
layers: vec![Layer::new()],
|
||||
caches: Vec::new(),
|
||||
stack: Vec::new(),
|
||||
current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_quad(&mut self) {}
|
||||
|
||||
pub fn push_cache(&mut self, cache: T::Cache) {
|
||||
self.caches.push(cache);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.caches.clear();
|
||||
self.stack.clear();
|
||||
|
||||
for mut layer in self.layers {
|
||||
layer.clear();
|
||||
}
|
||||
|
||||
self.current = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ pub mod compositor;
|
|||
pub mod damage;
|
||||
pub mod error;
|
||||
pub mod gradient;
|
||||
pub mod image;
|
||||
pub mod mesh;
|
||||
pub mod renderer;
|
||||
pub mod text;
|
||||
|
|
@ -35,9 +36,6 @@ pub mod text;
|
|||
#[cfg(feature = "geometry")]
|
||||
pub mod geometry;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub mod image;
|
||||
|
||||
pub use antialiasing::Antialiasing;
|
||||
pub use backend::Backend;
|
||||
pub use cached::Cached;
|
||||
|
|
@ -45,10 +43,12 @@ pub use compositor::Compositor;
|
|||
pub use damage::Damage;
|
||||
pub use error::Error;
|
||||
pub use gradient::Gradient;
|
||||
pub use image::Image;
|
||||
pub use mesh::Mesh;
|
||||
pub use primitive::Primitive;
|
||||
pub use renderer::Renderer;
|
||||
pub use settings::Settings;
|
||||
pub use text::Text;
|
||||
pub use viewport::Viewport;
|
||||
|
||||
pub use iced_core as core;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
//! Draw triangles!
|
||||
use crate::color;
|
||||
use crate::core::{Rectangle, Size};
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::gradient;
|
||||
use crate::Damage;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
|
|
@ -14,9 +13,10 @@ pub enum Mesh {
|
|||
/// The vertices and indices of the mesh.
|
||||
buffers: Indexed<SolidVertex2D>,
|
||||
|
||||
/// The size of the drawable region of the mesh.
|
||||
///
|
||||
/// Any geometry that falls out of this region will be clipped.
|
||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||
transformation: Transformation,
|
||||
|
||||
/// The [`Size`] of the [`Mesh`].
|
||||
size: Size,
|
||||
},
|
||||
/// A mesh with a gradient.
|
||||
|
|
@ -24,20 +24,45 @@ pub enum Mesh {
|
|||
/// The vertices and indices of the mesh.
|
||||
buffers: Indexed<GradientVertex2D>,
|
||||
|
||||
/// The size of the drawable region of the mesh.
|
||||
///
|
||||
/// Any geometry that falls out of this region will be clipped.
|
||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||
transformation: Transformation,
|
||||
|
||||
/// The [`Size`] of the [`Mesh`].
|
||||
size: Size,
|
||||
},
|
||||
}
|
||||
|
||||
impl Damage for Mesh {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
impl Mesh {
|
||||
/// Returns the indices of the [`Mesh`].
|
||||
pub fn indices(&self) -> &[u32] {
|
||||
match self {
|
||||
Self::Solid { size, .. } | Self::Gradient { size, .. } => {
|
||||
Rectangle::with_size(*size)
|
||||
Self::Solid { buffers, .. } => &buffers.indices,
|
||||
Self::Gradient { buffers, .. } => &buffers.indices,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Transformation`] of the [`Mesh`].
|
||||
pub fn transformation(&self) -> Transformation {
|
||||
match self {
|
||||
Self::Solid { transformation, .. }
|
||||
| Self::Gradient { transformation, .. } => *transformation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the clip bounds of the [`Mesh`].
|
||||
pub fn clip_bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Self::Solid {
|
||||
size,
|
||||
transformation,
|
||||
..
|
||||
}
|
||||
| Self::Gradient {
|
||||
size,
|
||||
transformation,
|
||||
..
|
||||
} => Rectangle::with_size(*size) * *transformation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +100,47 @@ pub struct GradientVertex2D {
|
|||
pub gradient: gradient::Packed,
|
||||
}
|
||||
|
||||
/// The result of counting the attributes of a set of meshes.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct AttributeCount {
|
||||
/// The total amount of solid vertices.
|
||||
pub solid_vertices: usize,
|
||||
|
||||
/// The total amount of solid meshes.
|
||||
pub solids: usize,
|
||||
|
||||
/// The total amount of gradient vertices.
|
||||
pub gradient_vertices: usize,
|
||||
|
||||
/// The total amount of gradient meshes.
|
||||
pub gradients: usize,
|
||||
|
||||
/// The total amount of indices.
|
||||
pub indices: usize,
|
||||
}
|
||||
|
||||
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||
pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount {
|
||||
meshes
|
||||
.iter()
|
||||
.fold(AttributeCount::default(), |mut count, mesh| {
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
count.solids += 1;
|
||||
count.solid_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
count.gradients += 1;
|
||||
count.gradient_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
})
|
||||
}
|
||||
|
||||
/// A renderer capable of drawing a [`Mesh`].
|
||||
pub trait Renderer {
|
||||
/// Draws the given [`Mesh`].
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ impl<B: Backend> Renderer<B> {
|
|||
}
|
||||
|
||||
impl<B: Backend> iced_core::Renderer for Renderer<B> {
|
||||
fn start_layer(&mut self) {
|
||||
fn start_layer(&mut self, _bounds: Rectangle) {
|
||||
self.stack.push(std::mem::take(&mut self.primitives));
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ impl<B: Backend> iced_core::Renderer for Renderer<B> {
|
|||
self.primitives.push(Primitive::group(layer).clip(bounds));
|
||||
}
|
||||
|
||||
fn start_transformation(&mut self) {
|
||||
fn start_transformation(&mut self, _transformation: Transformation) {
|
||||
self.stack.push(std::mem::take(&mut self.primitives));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,14 +9,67 @@ pub use paragraph::Paragraph;
|
|||
|
||||
pub use cosmic_text;
|
||||
|
||||
use crate::core::alignment;
|
||||
use crate::core::font::{self, Font};
|
||||
use crate::core::text::Shaping;
|
||||
use crate::core::{Color, Point, Rectangle, Size};
|
||||
use crate::core::text::{LineHeight, Shaping};
|
||||
use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
|
||||
/// A text primitive.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Text {
|
||||
/// A paragraph.
|
||||
#[allow(missing_docs)]
|
||||
Paragraph {
|
||||
paragraph: paragraph::Weak,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
},
|
||||
/// An editor.
|
||||
#[allow(missing_docs)]
|
||||
Editor {
|
||||
editor: editor::Weak,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
},
|
||||
/// Some cached text.
|
||||
Cached {
|
||||
/// The contents of the text.
|
||||
content: String,
|
||||
/// The bounds of the text.
|
||||
bounds: Rectangle,
|
||||
/// The color of the text.
|
||||
color: Color,
|
||||
/// The size of the text in logical pixels.
|
||||
size: Pixels,
|
||||
/// The line height of the text.
|
||||
line_height: LineHeight,
|
||||
/// The font of the text.
|
||||
font: Font,
|
||||
/// The horizontal alignment of the text.
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
/// The vertical alignment of the text.
|
||||
vertical_alignment: alignment::Vertical,
|
||||
/// The shaping strategy of the text.
|
||||
shaping: Shaping,
|
||||
/// The clip bounds of the text.
|
||||
clip_bounds: Rectangle,
|
||||
},
|
||||
/// Some raw text.
|
||||
#[allow(missing_docs)]
|
||||
Raw {
|
||||
raw: Raw,
|
||||
transformation: Transformation,
|
||||
},
|
||||
}
|
||||
|
||||
/// The regular variant of the [Fira Sans] font.
|
||||
///
|
||||
/// It is loaded as part of the default fonts in Wasm builds.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ tiny-skia = ["iced_tiny_skia"]
|
|||
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
|
||||
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
|
||||
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
|
||||
tracing = ["iced_wgpu?/tracing"]
|
||||
web-colors = ["iced_wgpu?/web-colors"]
|
||||
webgl = ["iced_wgpu?/webgl"]
|
||||
fira-sans = ["iced_graphics/fira-sans"]
|
||||
|
|
|
|||
|
|
@ -39,16 +39,20 @@ where
|
|||
delegate!(self, renderer, renderer.clear());
|
||||
}
|
||||
|
||||
fn start_layer(&mut self) {
|
||||
delegate!(self, renderer, renderer.start_layer());
|
||||
fn start_layer(&mut self, bounds: Rectangle) {
|
||||
delegate!(self, renderer, renderer.start_layer(bounds));
|
||||
}
|
||||
|
||||
fn end_layer(&mut self, bounds: Rectangle) {
|
||||
delegate!(self, renderer, renderer.end_layer(bounds));
|
||||
}
|
||||
|
||||
fn start_transformation(&mut self) {
|
||||
delegate!(self, renderer, renderer.start_transformation());
|
||||
fn start_transformation(&mut self, transformation: Transformation) {
|
||||
delegate!(
|
||||
self,
|
||||
renderer,
|
||||
renderer.start_transformation(transformation)
|
||||
);
|
||||
}
|
||||
|
||||
fn end_transformation(&mut self, transformation: Transformation) {
|
||||
|
|
@ -433,6 +437,7 @@ mod geometry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Geometry<L, R> {
|
||||
Left(L),
|
||||
Right(R),
|
||||
|
|
@ -452,10 +457,21 @@ mod geometry {
|
|||
}
|
||||
}
|
||||
|
||||
fn cache(self) -> Self::Cache {
|
||||
match self {
|
||||
Self::Left(geometry) => Geometry::Left(geometry.cache()),
|
||||
Self::Right(geometry) => Geometry::Right(geometry.cache()),
|
||||
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
|
||||
match (self, previous) {
|
||||
(Self::Left(geometry), Some(Geometry::Left(previous))) => {
|
||||
Geometry::Left(geometry.cache(Some(previous)))
|
||||
}
|
||||
(Self::Left(geometry), None) => {
|
||||
Geometry::Left(geometry.cache(None))
|
||||
}
|
||||
(Self::Right(geometry), Some(Geometry::Right(previous))) => {
|
||||
Geometry::Right(geometry.cache(Some(previous)))
|
||||
}
|
||||
(Self::Right(geometry), None) => {
|
||||
Geometry::Right(geometry.cache(None))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,3 @@ lyon.optional = true
|
|||
|
||||
resvg.workspace = true
|
||||
resvg.optional = true
|
||||
|
||||
tracing.workspace = true
|
||||
tracing.optional = true
|
||||
|
|
|
|||
|
|
@ -1,432 +0,0 @@
|
|||
use crate::buffer;
|
||||
use crate::core::{Color, Size, Transformation};
|
||||
use crate::graphics::backend;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::Viewport;
|
||||
use crate::primitive::pipeline;
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
use crate::window;
|
||||
use crate::{Layer, Settings};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
use crate::image;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A [`wgpu`] graphics backend for [`iced`].
|
||||
///
|
||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Backend {
|
||||
quad_pipeline: quad::Pipeline,
|
||||
text_pipeline: text::Pipeline,
|
||||
triangle_pipeline: triangle::Pipeline,
|
||||
pipeline_storage: pipeline::Storage,
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
staging_belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
/// Creates a new [`Backend`].
|
||||
pub fn new(
|
||||
_adapter: &wgpu::Adapter,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
settings: Settings,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let text_pipeline = text::Pipeline::new(device, queue, format);
|
||||
let quad_pipeline = quad::Pipeline::new(device, format);
|
||||
let triangle_pipeline =
|
||||
triangle::Pipeline::new(device, format, settings.antialiasing);
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
let image_pipeline = {
|
||||
let backend = _adapter.get_info().backend;
|
||||
|
||||
image::Pipeline::new(device, format, backend)
|
||||
};
|
||||
|
||||
Self {
|
||||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
pipeline_storage: pipeline::Storage::default(),
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
|
||||
// TODO: Resize belt smartly (?)
|
||||
// It would be great if the `StagingBelt` API exposed methods
|
||||
// for introspection to detect when a resize may be worth it.
|
||||
staging_belt: wgpu::util::StagingBelt::new(
|
||||
buffer::MAX_WRITE_SIZE as u64,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the provided primitives in the given `TextureView`.
|
||||
///
|
||||
/// The text provided as overlay will be rendered on top of the primitives.
|
||||
/// This is useful for rendering debug information.
|
||||
pub fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
clear_color: Option<Color>,
|
||||
format: wgpu::TextureFormat,
|
||||
frame: &wgpu::TextureView,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
overlay_text: &[T],
|
||||
) {
|
||||
log::debug!("Drawing");
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Backend", "PRESENT").entered();
|
||||
|
||||
let target_size = viewport.physical_size();
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let transformation = viewport.projection();
|
||||
|
||||
let mut layers = Layer::generate(primitives, viewport);
|
||||
|
||||
if !overlay_text.is_empty() {
|
||||
layers.push(Layer::overlay(overlay_text, viewport));
|
||||
}
|
||||
|
||||
self.prepare(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
encoder,
|
||||
scale_factor,
|
||||
target_size,
|
||||
transformation,
|
||||
&layers,
|
||||
);
|
||||
|
||||
self.staging_belt.finish();
|
||||
|
||||
self.render(
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
clear_color,
|
||||
scale_factor,
|
||||
target_size,
|
||||
&layers,
|
||||
);
|
||||
|
||||
self.quad_pipeline.end_frame();
|
||||
self.text_pipeline.end_frame();
|
||||
self.triangle_pipeline.end_frame();
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.end_frame();
|
||||
}
|
||||
|
||||
/// Recalls staging memory for future uploads.
|
||||
///
|
||||
/// This method should be called after the command encoder
|
||||
/// has been submitted.
|
||||
pub fn recall(&mut self) {
|
||||
self.staging_belt.recall();
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
transformation: Transformation,
|
||||
layers: &[Layer<'_>],
|
||||
) {
|
||||
for layer in layers {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
self.quad_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut self.staging_belt,
|
||||
&layer.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.meshes.is_empty() {
|
||||
let scaled =
|
||||
transformation * Transformation::scale(scale_factor);
|
||||
|
||||
self.triangle_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut self.staging_belt,
|
||||
&layer.meshes,
|
||||
scaled,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
let scaled =
|
||||
transformation * Transformation::scale(scale_factor);
|
||||
|
||||
self.image_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut self.staging_belt,
|
||||
&layer.images,
|
||||
scaled,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
self.text_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&layer.text,
|
||||
layer.bounds,
|
||||
scale_factor,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.pipelines.is_empty() {
|
||||
for pipeline in &layer.pipelines {
|
||||
pipeline.primitive.prepare(
|
||||
format,
|
||||
device,
|
||||
queue,
|
||||
pipeline.bounds,
|
||||
target_size,
|
||||
scale_factor,
|
||||
&mut self.pipeline_storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clear_color: Option<Color>,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
layers: &[Layer<'_>],
|
||||
) {
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut quad_layer = 0;
|
||||
let mut triangle_layer = 0;
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
let mut image_layer = 0;
|
||||
let mut text_layer = 0;
|
||||
|
||||
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: match clear_color {
|
||||
Some(background_color) => wgpu::LoadOp::Clear({
|
||||
let [r, g, b, a] =
|
||||
color::pack(background_color).components();
|
||||
|
||||
wgpu::Color {
|
||||
r: f64::from(r),
|
||||
g: f64::from(g),
|
||||
b: f64::from(b),
|
||||
a: f64::from(a),
|
||||
}
|
||||
}),
|
||||
None => wgpu::LoadOp::Load,
|
||||
},
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
|
||||
for layer in layers {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
self.quad_pipeline.render(
|
||||
quad_layer,
|
||||
bounds,
|
||||
&layer.quads,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
quad_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.meshes.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
self.triangle_pipeline.render(
|
||||
device,
|
||||
encoder,
|
||||
target,
|
||||
triangle_layer,
|
||||
target_size,
|
||||
&layer.meshes,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
triangle_layer += 1;
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
self.image_pipeline.render(
|
||||
image_layer,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
image_layer += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
self.text_pipeline
|
||||
.render(text_layer, bounds, &mut render_pass);
|
||||
|
||||
text_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.pipelines.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
for pipeline in &layer.pipelines {
|
||||
let viewport = (pipeline.viewport * scale_factor).snap();
|
||||
|
||||
if viewport.width < 1 || viewport.height < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
pipeline.primitive.render(
|
||||
&self.pipeline_storage,
|
||||
target,
|
||||
target_size,
|
||||
viewport,
|
||||
encoder,
|
||||
);
|
||||
}
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
impl backend::Backend for Backend {
|
||||
type Primitive = primitive::Custom;
|
||||
type Compositor = window::Compositor;
|
||||
}
|
||||
|
||||
impl backend::Text for Backend {
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
self.text_pipeline.load_font(font);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
impl backend::Image for Backend {
|
||||
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
|
||||
self.image_pipeline.dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl backend::Svg for Backend {
|
||||
fn viewport_dimensions(
|
||||
&self,
|
||||
handle: &crate::core::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
self.image_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
impl crate::graphics::geometry::Backend for Backend {
|
||||
type Frame = crate::geometry::Frame;
|
||||
|
||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||
crate::geometry::Frame::new(size)
|
||||
}
|
||||
}
|
||||
79
wgpu/src/engine.rs
Normal file
79
wgpu/src/engine.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::buffer;
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::primitive::pipeline;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Engine {
|
||||
pub(crate) quad_pipeline: quad::Pipeline,
|
||||
pub(crate) text_pipeline: text::Pipeline,
|
||||
pub(crate) triangle_pipeline: triangle::Pipeline,
|
||||
pub(crate) _pipeline_storage: pipeline::Storage,
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub(crate) image_pipeline: crate::image::Pipeline,
|
||||
pub(crate) staging_belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(
|
||||
_adapter: &wgpu::Adapter,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
|
||||
) -> Self {
|
||||
let text_pipeline = text::Pipeline::new(device, queue, format);
|
||||
let quad_pipeline = quad::Pipeline::new(device, format);
|
||||
let triangle_pipeline =
|
||||
triangle::Pipeline::new(device, format, antialiasing);
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
let image_pipeline = {
|
||||
let backend = _adapter.get_info().backend;
|
||||
|
||||
crate::image::Pipeline::new(device, format, backend)
|
||||
};
|
||||
|
||||
Self {
|
||||
// TODO: Resize belt smartly (?)
|
||||
// It would be great if the `StagingBelt` API exposed methods
|
||||
// for introspection to detect when a resize may be worth it.
|
||||
staging_belt: wgpu::util::StagingBelt::new(
|
||||
buffer::MAX_WRITE_SIZE as u64,
|
||||
),
|
||||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
_pipeline_storage: pipeline::Storage::default(),
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub fn image_cache(&self) -> &crate::image::cache::Shared {
|
||||
self.image_pipeline.cache()
|
||||
}
|
||||
|
||||
pub fn submit(
|
||||
&mut self,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: wgpu::CommandEncoder,
|
||||
) -> wgpu::SubmissionIndex {
|
||||
self.staging_belt.finish();
|
||||
let index = queue.submit(Some(encoder.finish()));
|
||||
self.staging_belt.recall();
|
||||
|
||||
self.quad_pipeline.end_frame();
|
||||
self.text_pipeline.end_frame();
|
||||
self.triangle_pipeline.end_frame();
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.end_frame();
|
||||
|
||||
index
|
||||
}
|
||||
}
|
||||
|
|
@ -10,31 +10,82 @@ use crate::graphics::geometry::{
|
|||
};
|
||||
use crate::graphics::gradient::{self, Gradient};
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::graphics::{self, Cached};
|
||||
use crate::layer;
|
||||
use crate::text;
|
||||
|
||||
use lyon::geom::euclid;
|
||||
use lyon::tessellation;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A frame for drawing some geometry.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Frame {
|
||||
size: Size,
|
||||
buffers: BufferStack,
|
||||
primitives: Vec<Primitive>,
|
||||
layers: Vec<layer::Live>,
|
||||
text: text::Batch,
|
||||
transforms: Transforms,
|
||||
fill_tessellator: tessellation::FillTessellator,
|
||||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
pub enum Geometry {
|
||||
Live(Vec<layer::Live>),
|
||||
Cached(Rc<[Rc<RefCell<layer::Cached>>]>),
|
||||
}
|
||||
|
||||
impl Cached for Geometry {
|
||||
type Cache = Rc<[Rc<RefCell<layer::Cached>>]>;
|
||||
|
||||
fn load(cache: &Self::Cache) -> Self {
|
||||
Geometry::Cached(cache.clone())
|
||||
}
|
||||
|
||||
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
|
||||
match self {
|
||||
Self::Live(live) => {
|
||||
let mut layers = live.into_iter();
|
||||
|
||||
let mut new: Vec<_> = previous
|
||||
.map(|previous| {
|
||||
previous
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(layers.by_ref())
|
||||
.map(|(cached, live)| {
|
||||
cached.borrow_mut().update(live);
|
||||
cached
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
new.extend(
|
||||
layers
|
||||
.map(layer::Live::into_cached)
|
||||
.map(RefCell::new)
|
||||
.map(Rc::new),
|
||||
);
|
||||
|
||||
Rc::from(new)
|
||||
}
|
||||
Self::Cached(cache) => cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new [`Frame`] with the given [`Size`].
|
||||
pub fn new(size: Size) -> Frame {
|
||||
Frame {
|
||||
size,
|
||||
buffers: BufferStack::new(),
|
||||
primitives: Vec::new(),
|
||||
layers: Vec::new(),
|
||||
text: text::Batch::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::identity()),
|
||||
|
|
@ -44,49 +95,54 @@ impl Frame {
|
|||
}
|
||||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Solid {
|
||||
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()
|
||||
.map(|buffer| match buffer {
|
||||
Buffer::Solid(buffer) => Mesh::Solid {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
transformation: Transformation::IDENTITY,
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
Buffer::Gradient(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::Custom(
|
||||
primitive::Custom::Mesh(Mesh::Gradient {
|
||||
},
|
||||
Buffer::Gradient(buffer) => Mesh::Gradient {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
transformation: Transformation::IDENTITY,
|
||||
size: self.size,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let layer = layer::Live {
|
||||
bounds: Some(clip_bounds),
|
||||
transformation,
|
||||
meshes,
|
||||
text: self.text,
|
||||
..layer::Live::default()
|
||||
};
|
||||
|
||||
self.layers.push(layer);
|
||||
}
|
||||
|
||||
self.primitives
|
||||
self.layers
|
||||
}
|
||||
}
|
||||
|
||||
impl geometry::frame::Backend for Frame {
|
||||
type Geometry = Primitive;
|
||||
|
||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||
///
|
||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
||||
/// top-left corner of its bounds.
|
||||
type Geometry = Geometry;
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> f32 {
|
||||
|
|
@ -246,8 +302,7 @@ impl geometry::frame::Backend for Frame {
|
|||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
self.text.push(graphics::Text::Cached {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
|
|
@ -313,37 +368,17 @@ impl geometry::frame::Backend for Frame {
|
|||
}
|
||||
|
||||
fn paste(&mut self, frame: Frame, at: Point) {
|
||||
let size = frame.size();
|
||||
let primitives = frame.into_primitives();
|
||||
let transformation = Transformation::translate(at.x, at.y);
|
||||
let translation = Transformation::translate(at.x, at.y);
|
||||
|
||||
let (text, meshes) = primitives
|
||||
.into_iter()
|
||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
||||
|
||||
self.primitives.push(Primitive::Group {
|
||||
primitives: vec![
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
||||
},
|
||||
Primitive::Transform {
|
||||
transformation,
|
||||
content: Box::new(Primitive::Clip {
|
||||
bounds: Rectangle::with_size(size),
|
||||
content: Box::new(Primitive::Group {
|
||||
primitives: text,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
self.layers
|
||||
.extend(frame.into_layers().into_iter().map(|mut layer| {
|
||||
layer.transformation = layer.transformation * translation;
|
||||
layer
|
||||
}));
|
||||
}
|
||||
|
||||
fn into_geometry(self) -> Self::Geometry {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
Geometry::Live(self.into_layers())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
107
wgpu/src/image/cache.rs
Normal file
107
wgpu/src/image/cache.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use crate::core::{self, Size};
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
atlas: Atlas,
|
||||
#[cfg(feature = "image")]
|
||||
raster: crate::image::raster::Cache,
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
|
||||
Self {
|
||||
atlas: Atlas::new(device, backend),
|
||||
#[cfg(feature = "image")]
|
||||
raster: crate::image::raster::Cache::default(),
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_count(&self) -> usize {
|
||||
self.atlas.layer_count()
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||
self.raster.load(handle).dimensions()
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
self.vector.load(handle).viewport_dimensions()
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn upload_raster(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<&atlas::Entry> {
|
||||
self.raster.upload(device, encoder, handle, &mut self.atlas)
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn upload_vector(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
handle: &core::svg::Handle,
|
||||
color: Option<core::Color>,
|
||||
size: [f32; 2],
|
||||
scale: f32,
|
||||
) -> Option<&atlas::Entry> {
|
||||
self.vector.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
color,
|
||||
size,
|
||||
scale,
|
||||
&mut self.atlas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_bind_group(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(self.atlas.view()),
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster.trim(&mut self.atlas);
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector.trim(&mut self.atlas);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Shared(Rc<RefCell<Cache>>);
|
||||
|
||||
impl Shared {
|
||||
pub fn new(cache: Cache) -> Self {
|
||||
Self(Rc::new(RefCell::new(cache)))
|
||||
}
|
||||
|
||||
pub fn lock(&self) -> RefMut<'_, Cache> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
pub(crate) mod cache;
|
||||
pub(crate) use cache::Cache;
|
||||
|
||||
mod atlas;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -6,46 +9,331 @@ mod raster;
|
|||
#[cfg(feature = "svg")]
|
||||
mod vector;
|
||||
|
||||
use atlas::Atlas;
|
||||
|
||||
use crate::core::image;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::layer;
|
||||
use crate::Buffer;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
pub use crate::graphics::Image;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use crate::core::image;
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
use crate::core::svg;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
pub type Batch = Vec<Image>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
#[cfg(feature = "image")]
|
||||
raster_cache: RefCell<raster::Cache>,
|
||||
#[cfg(feature = "svg")]
|
||||
vector_cache: RefCell<vector::Cache>,
|
||||
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
nearest_sampler: wgpu::Sampler,
|
||||
linear_sampler: wgpu::Sampler,
|
||||
texture: wgpu::BindGroup,
|
||||
texture_version: usize,
|
||||
texture_atlas: Atlas,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
|
||||
cache: cache::Shared,
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
backend: wgpu::Backend,
|
||||
) -> Self {
|
||||
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image constants layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
mem::size_of::<Uniforms>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::Filtering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu image shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
concat!(
|
||||
include_str!("../shader/vertex.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/image.wgsl"),
|
||||
),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu::image pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<Instance>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Scale
|
||||
1 => Float32x2,
|
||||
// Atlas position
|
||||
2 => Float32x2,
|
||||
// Atlas scale
|
||||
3 => Float32x2,
|
||||
// Layer
|
||||
4 => Sint32,
|
||||
),
|
||||
}],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
front_face: wgpu::FrontFace::Cw,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let cache = Cache::new(device, backend);
|
||||
let texture = cache.create_bind_group(device, &texture_layout);
|
||||
|
||||
Pipeline {
|
||||
pipeline,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
texture,
|
||||
texture_version: cache.layer_count(),
|
||||
texture_layout,
|
||||
constant_layout,
|
||||
cache: cache::Shared::new(cache),
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache(&self) -> &cache::Shared {
|
||||
&self.cache
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
images: &Batch,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
let transformation = transformation * Transformation::scale(scale);
|
||||
|
||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
|
||||
let mut cache = self.cache.lock();
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
#[cfg(feature = "image")]
|
||||
Image::Raster {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
} => {
|
||||
if let Some(atlas_entry) =
|
||||
cache.upload_raster(device, encoder, handle)
|
||||
{
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
[bounds.width, bounds.height],
|
||||
atlas_entry,
|
||||
match filter_method {
|
||||
image::FilterMethod::Nearest => {
|
||||
nearest_instances
|
||||
}
|
||||
image::FilterMethod::Linear => linear_instances,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
Image::Raster { .. } => {}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds,
|
||||
} => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
|
||||
if let Some(atlas_entry) = cache.upload_vector(
|
||||
device, encoder, handle, *color, size, scale,
|
||||
) {
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
size,
|
||||
atlas_entry,
|
||||
nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
Image::Vector { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let texture_version = cache.layer_count();
|
||||
|
||||
if self.texture_version != texture_version {
|
||||
log::info!("Atlas has grown. Recreating bind group...");
|
||||
|
||||
self.texture =
|
||||
cache.create_bind_group(device, &self.texture_layout);
|
||||
self.texture_version = texture_version;
|
||||
}
|
||||
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
&self.constant_layout,
|
||||
&self.nearest_sampler,
|
||||
&self.linear_sampler,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||
|
||||
layer.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.cache.lock().trim();
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
|
|
@ -194,367 +482,6 @@ impl Data {
|
|||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
backend: wgpu::Backend,
|
||||
) -> Self {
|
||||
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image constants layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
mem::size_of::<Uniforms>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::Filtering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: true,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu image shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
concat!(
|
||||
include_str!("shader/vertex.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/image.wgsl"),
|
||||
),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu::image pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<Instance>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Scale
|
||||
1 => Float32x2,
|
||||
// Atlas position
|
||||
2 => Float32x2,
|
||||
// Atlas scale
|
||||
3 => Float32x2,
|
||||
// Layer
|
||||
4 => Sint32,
|
||||
),
|
||||
}],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
front_face: wgpu::FrontFace::Cw,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let texture_atlas = Atlas::new(device, backend);
|
||||
|
||||
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
texture_atlas.view(),
|
||||
),
|
||||
}],
|
||||
});
|
||||
|
||||
Pipeline {
|
||||
#[cfg(feature = "image")]
|
||||
raster_cache: RefCell::new(raster::Cache::default()),
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
vector_cache: RefCell::new(vector::Cache::default()),
|
||||
|
||||
pipeline,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
texture,
|
||||
texture_version: texture_atlas.layer_count(),
|
||||
texture_atlas,
|
||||
texture_layout,
|
||||
constant_layout,
|
||||
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
|
||||
let mut cache = self.raster_cache.borrow_mut();
|
||||
let memory = cache.load(handle);
|
||||
|
||||
memory.dimensions()
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
|
||||
let mut cache = self.vector_cache.borrow_mut();
|
||||
let svg = cache.load(handle);
|
||||
|
||||
svg.viewport_dimensions()
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
images: &[layer::Image],
|
||||
transformation: Transformation,
|
||||
_scale: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
||||
|
||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
let mut raster_cache = self.raster_cache.borrow_mut();
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
let mut vector_cache = self.vector_cache.borrow_mut();
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
#[cfg(feature = "image")]
|
||||
layer::Image::Raster {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
} => {
|
||||
if let Some(atlas_entry) = raster_cache.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
&mut self.texture_atlas,
|
||||
) {
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
[bounds.width, bounds.height],
|
||||
atlas_entry,
|
||||
match filter_method {
|
||||
image::FilterMethod::Nearest => {
|
||||
nearest_instances
|
||||
}
|
||||
image::FilterMethod::Linear => linear_instances,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
layer::Image::Raster { .. } => {}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
layer::Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds,
|
||||
} => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
|
||||
if let Some(atlas_entry) = vector_cache.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
*color,
|
||||
size,
|
||||
_scale,
|
||||
&mut self.texture_atlas,
|
||||
) {
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
size,
|
||||
atlas_entry,
|
||||
nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
layer::Image::Vector { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let texture_version = self.texture_atlas.layer_count();
|
||||
|
||||
if self.texture_version != texture_version {
|
||||
log::info!("Atlas has grown. Recreating bind group...");
|
||||
|
||||
self.texture =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &self.texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
self.texture_atlas.view(),
|
||||
),
|
||||
}],
|
||||
});
|
||||
|
||||
self.texture_version = texture_version;
|
||||
}
|
||||
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
&self.constant_layout,
|
||||
&self.nearest_sampler,
|
||||
&self.linear_sampler,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||
|
||||
layer.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Instance {
|
||||
10
wgpu/src/image/null.rs
Normal file
10
wgpu/src/image/null.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
pub use crate::graphics::Image;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Batch;
|
||||
|
||||
impl Batch {
|
||||
pub fn push(&mut self, _image: Image) {}
|
||||
|
||||
pub fn clear(&mut self) {}
|
||||
}
|
||||
|
|
@ -1,343 +1,326 @@
|
|||
//! Organize rendering primitives into a flattened list of layers.
|
||||
mod image;
|
||||
mod pipeline;
|
||||
mod text;
|
||||
|
||||
pub mod mesh;
|
||||
|
||||
pub use image::Image;
|
||||
pub use mesh::Mesh;
|
||||
pub use pipeline::Pipeline;
|
||||
pub use text::Text;
|
||||
|
||||
use crate::core;
|
||||
use crate::core::alignment;
|
||||
use crate::core::{
|
||||
Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector,
|
||||
};
|
||||
use crate::graphics;
|
||||
use crate::core::renderer;
|
||||
use crate::core::{Background, Color, Point, Rectangle, Transformation};
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::Viewport;
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::graphics::Mesh;
|
||||
use crate::image::{self, Image};
|
||||
use crate::quad::{self, Quad};
|
||||
use crate::text::{self, Text};
|
||||
use crate::triangle;
|
||||
|
||||
/// A group of primitives that should be clipped together.
|
||||
#[derive(Debug)]
|
||||
pub struct Layer<'a> {
|
||||
/// The clipping bounds of the [`Layer`].
|
||||
pub bounds: Rectangle,
|
||||
use std::cell::{self, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// The quads of the [`Layer`].
|
||||
pub quads: quad::Batch,
|
||||
|
||||
/// The triangle meshes of the [`Layer`].
|
||||
pub meshes: Vec<Mesh<'a>>,
|
||||
|
||||
/// The text of the [`Layer`].
|
||||
pub text: Vec<Text<'a>>,
|
||||
|
||||
/// The images of the [`Layer`].
|
||||
pub images: Vec<Image>,
|
||||
|
||||
/// The custom pipelines of this [`Layer`].
|
||||
pub pipelines: Vec<Pipeline>,
|
||||
pub enum Layer<'a> {
|
||||
Live(&'a Live),
|
||||
Cached(cell::Ref<'a, Cached>),
|
||||
}
|
||||
|
||||
impl<'a> Layer<'a> {
|
||||
/// Creates a new [`Layer`] with the given clipping bounds.
|
||||
pub fn new(bounds: Rectangle) -> Self {
|
||||
pub enum LayerMut<'a> {
|
||||
Live(&'a mut Live),
|
||||
Cached(cell::RefMut<'a, Cached>),
|
||||
}
|
||||
|
||||
pub struct Stack {
|
||||
live: Vec<Live>,
|
||||
cached: Vec<Rc<RefCell<Cached>>>,
|
||||
order: Vec<Kind>,
|
||||
transformations: Vec<Transformation>,
|
||||
previous: Vec<usize>,
|
||||
current: usize,
|
||||
live_count: usize,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
quads: quad::Batch::default(),
|
||||
meshes: Vec::new(),
|
||||
text: Vec::new(),
|
||||
images: Vec::new(),
|
||||
pipelines: Vec::new(),
|
||||
live: vec![Live::default()],
|
||||
cached: Vec::new(),
|
||||
order: vec![Kind::Live],
|
||||
transformations: vec![Transformation::IDENTITY],
|
||||
previous: Vec::new(),
|
||||
current: 0,
|
||||
live_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Layer`] for the provided overlay text.
|
||||
///
|
||||
/// This can be useful for displaying debug information.
|
||||
pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
|
||||
let mut overlay =
|
||||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
let text = text::Cached {
|
||||
content: line.as_ref(),
|
||||
bounds: Rectangle::new(
|
||||
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
||||
Size::INFINITY,
|
||||
),
|
||||
color: Color::new(0.9, 0.9, 0.9, 1.0),
|
||||
size: Pixels(20.0),
|
||||
line_height: core::text::LineHeight::default(),
|
||||
font: Font::MONOSPACE,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: core::text::Shaping::Basic,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
};
|
||||
|
||||
overlay.text.push(Text::Cached(text.clone()));
|
||||
|
||||
overlay.text.push(Text::Cached(text::Cached {
|
||||
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
||||
color: Color::BLACK,
|
||||
..text
|
||||
}));
|
||||
}
|
||||
|
||||
overlay
|
||||
}
|
||||
|
||||
/// Distributes the given [`Primitive`] and generates a list of layers based
|
||||
/// on its contents.
|
||||
pub fn generate(
|
||||
primitives: &'a [Primitive],
|
||||
viewport: &Viewport,
|
||||
) -> Vec<Self> {
|
||||
let first_layer =
|
||||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
||||
|
||||
let mut layers = vec![first_layer];
|
||||
|
||||
for primitive in primitives {
|
||||
Self::process_primitive(
|
||||
&mut layers,
|
||||
Transformation::IDENTITY,
|
||||
primitive,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
layers
|
||||
}
|
||||
|
||||
fn process_primitive(
|
||||
layers: &mut Vec<Self>,
|
||||
transformation: Transformation,
|
||||
primitive: &'a Primitive,
|
||||
current_layer: usize,
|
||||
) {
|
||||
match primitive {
|
||||
Primitive::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text::Paragraph {
|
||||
paragraph: paragraph.clone(),
|
||||
position: *position,
|
||||
color: *color,
|
||||
clip_bounds: *clip_bounds,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
Primitive::Editor {
|
||||
editor,
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text::Editor {
|
||||
editor: editor.clone(),
|
||||
position: *position,
|
||||
color: *color,
|
||||
clip_bounds: *clip_bounds,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
Primitive::Text {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
line_height,
|
||||
color,
|
||||
font,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
shaping,
|
||||
clip_bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text::Cached(text::Cached {
|
||||
content,
|
||||
bounds: *bounds + transformation.translation(),
|
||||
size: *size * transformation.scale_factor(),
|
||||
line_height: *line_height,
|
||||
color: *color,
|
||||
font: *font,
|
||||
horizontal_alignment: *horizontal_alignment,
|
||||
vertical_alignment: *vertical_alignment,
|
||||
shaping: *shaping,
|
||||
clip_bounds: *clip_bounds * transformation,
|
||||
}));
|
||||
}
|
||||
graphics::Primitive::RawText(raw) => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.text.push(Text::Raw {
|
||||
raw: raw.clone(),
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
Primitive::Quad {
|
||||
bounds,
|
||||
background,
|
||||
border,
|
||||
shadow,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
let bounds = *bounds * transformation;
|
||||
pub fn draw_quad(&mut self, quad: renderer::Quad, background: Background) {
|
||||
let transformation = self.transformations.last().unwrap();
|
||||
let bounds = quad.bounds * *transformation;
|
||||
|
||||
let quad = Quad {
|
||||
position: [bounds.x, bounds.y],
|
||||
size: [bounds.width, bounds.height],
|
||||
border_color: color::pack(border.color),
|
||||
border_radius: border.radius.into(),
|
||||
border_width: border.width,
|
||||
shadow_color: shadow.color.into_linear(),
|
||||
shadow_offset: shadow.offset.into(),
|
||||
shadow_blur_radius: shadow.blur_radius,
|
||||
border_color: color::pack(quad.border.color),
|
||||
border_radius: quad.border.radius.into(),
|
||||
border_width: quad.border.width,
|
||||
shadow_color: color::pack(quad.shadow.color),
|
||||
shadow_offset: quad.shadow.offset.into(),
|
||||
shadow_blur_radius: quad.shadow.blur_radius,
|
||||
};
|
||||
|
||||
layer.quads.add(quad, background);
|
||||
self.live[self.current].quads.add(quad, &background);
|
||||
}
|
||||
Primitive::Image {
|
||||
|
||||
pub fn draw_paragraph(
|
||||
&mut self,
|
||||
paragraph: &Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let paragraph = Text::Paragraph {
|
||||
paragraph: paragraph.downgrade(),
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation: self.transformations.last().copied().unwrap(),
|
||||
};
|
||||
|
||||
self.live[self.current].text.push(paragraph);
|
||||
}
|
||||
|
||||
pub fn draw_editor(
|
||||
&mut self,
|
||||
editor: &Editor,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let paragraph = Text::Editor {
|
||||
editor: editor.downgrade(),
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation: self.transformations.last().copied().unwrap(),
|
||||
};
|
||||
|
||||
self.live[self.current].text.push(paragraph);
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
&mut self,
|
||||
text: crate::core::Text,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let transformation = self.transformation();
|
||||
|
||||
let paragraph = Text::Cached {
|
||||
content: text.content,
|
||||
bounds: Rectangle::new(position, text.bounds) * transformation,
|
||||
color,
|
||||
size: text.size * transformation.scale_factor(),
|
||||
line_height: text.line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.live[self.current].text.push(paragraph);
|
||||
}
|
||||
|
||||
pub fn draw_image(
|
||||
&mut self,
|
||||
handle: crate::core::image::Handle,
|
||||
filter_method: crate::core::image::FilterMethod,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
let image = Image::Raster {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
bounds: bounds * self.transformation(),
|
||||
};
|
||||
|
||||
layer.images.push(Image::Raster {
|
||||
handle: handle.clone(),
|
||||
filter_method: *filter_method,
|
||||
bounds: *bounds * transformation,
|
||||
});
|
||||
self.live[self.current].images.push(image);
|
||||
}
|
||||
Primitive::Svg {
|
||||
|
||||
pub fn draw_svg(
|
||||
&mut self,
|
||||
handle: crate::core::svg::Handle,
|
||||
color: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
let svg = Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds: bounds * self.transformation(),
|
||||
};
|
||||
|
||||
self.live[self.current].images.push(svg);
|
||||
}
|
||||
|
||||
pub fn draw_mesh(&mut self, mut mesh: Mesh) {
|
||||
match &mut mesh {
|
||||
Mesh::Solid { transformation, .. }
|
||||
| Mesh::Gradient { transformation, .. } => {
|
||||
*transformation = *transformation * self.transformation();
|
||||
}
|
||||
}
|
||||
|
||||
self.live[self.current].meshes.push(mesh);
|
||||
}
|
||||
|
||||
pub fn draw_layer(&mut self, mut layer: Live) {
|
||||
layer.transformation = layer.transformation * self.transformation();
|
||||
|
||||
if self.live_count == self.live.len() {
|
||||
self.live.push(layer);
|
||||
} else {
|
||||
self.live[self.live_count] = layer;
|
||||
}
|
||||
|
||||
self.live_count += 1;
|
||||
self.order.push(Kind::Live);
|
||||
}
|
||||
|
||||
pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) {
|
||||
{
|
||||
let mut layer = layer.borrow_mut();
|
||||
layer.transformation = self.transformation() * layer.transformation;
|
||||
}
|
||||
|
||||
self.cached.push(layer.clone());
|
||||
self.order.push(Kind::Cache);
|
||||
}
|
||||
|
||||
pub fn push_clip(&mut self, bounds: Option<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,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
layer.images.push(Image::Vector {
|
||||
handle: handle.clone(),
|
||||
color: *color,
|
||||
bounds: *bounds * transformation,
|
||||
..Live::default()
|
||||
});
|
||||
}
|
||||
Primitive::Group { primitives } => {
|
||||
// TODO: Inspect a bit and regroup (?)
|
||||
for primitive in primitives {
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
transformation,
|
||||
primitive,
|
||||
current_layer,
|
||||
);
|
||||
} else {
|
||||
self.live[self.current].bounds = bounds;
|
||||
}
|
||||
}
|
||||
Primitive::Clip { bounds, content } => {
|
||||
let layer = &mut layers[current_layer];
|
||||
let translated_bounds = *bounds * transformation;
|
||||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) =
|
||||
layer.bounds.intersection(&translated_bounds)
|
||||
{
|
||||
let clip_layer = Layer::new(clip_bounds);
|
||||
layers.push(clip_layer);
|
||||
pub fn pop_clip(&mut self) {
|
||||
self.current = self.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
transformation,
|
||||
content,
|
||||
layers.len() - 1,
|
||||
);
|
||||
pub fn push_transformation(&mut self, transformation: Transformation) {
|
||||
self.transformations
|
||||
.push(self.transformation() * transformation);
|
||||
}
|
||||
}
|
||||
Primitive::Transform {
|
||||
transformation: new_transformation,
|
||||
content,
|
||||
} => {
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
transformation * *new_transformation,
|
||||
content,
|
||||
current_layer,
|
||||
);
|
||||
}
|
||||
Primitive::Cache { content } => {
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
transformation,
|
||||
content,
|
||||
current_layer,
|
||||
);
|
||||
}
|
||||
Primitive::Custom(custom) => match custom {
|
||||
primitive::Custom::Mesh(mesh) => match mesh {
|
||||
graphics::Mesh::Solid { buffers, size } => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
let bounds =
|
||||
Rectangle::with_size(*size) * transformation;
|
||||
pub fn pop_transformation(&mut self) {
|
||||
let _ = self.transformations.pop();
|
||||
}
|
||||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) =
|
||||
layer.bounds.intersection(&bounds)
|
||||
{
|
||||
layer.meshes.push(Mesh::Solid {
|
||||
transformation,
|
||||
buffers,
|
||||
clip_bounds,
|
||||
});
|
||||
fn transformation(&self) -> Transformation {
|
||||
self.transformations.last().copied().unwrap()
|
||||
}
|
||||
}
|
||||
graphics::Mesh::Gradient { buffers, size } => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
let bounds =
|
||||
Rectangle::with_size(*size) * transformation;
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = LayerMut<'_>> {
|
||||
let mut live = self.live.iter_mut();
|
||||
let mut cached = self.cached.iter_mut();
|
||||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) =
|
||||
layer.bounds.intersection(&bounds)
|
||||
{
|
||||
layer.meshes.push(Mesh::Gradient {
|
||||
transformation,
|
||||
buffers,
|
||||
clip_bounds,
|
||||
});
|
||||
self.order.iter().map(move |kind| match kind {
|
||||
Kind::Live => LayerMut::Live(live.next().unwrap()),
|
||||
Kind::Cache => {
|
||||
LayerMut::Cached(cached.next().unwrap().borrow_mut())
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
primitive::Custom::Pipeline(pipeline) => {
|
||||
let layer = &mut layers[current_layer];
|
||||
let bounds = pipeline.bounds * transformation;
|
||||
|
||||
if let Some(clip_bounds) =
|
||||
layer.bounds.intersection(&bounds)
|
||||
{
|
||||
layer.pipelines.push(Pipeline {
|
||||
bounds,
|
||||
viewport: clip_bounds,
|
||||
primitive: pipeline.primitive.clone(),
|
||||
});
|
||||
pub fn iter(&self) -> impl Iterator<Item = Layer<'_>> {
|
||||
let mut live = self.live.iter();
|
||||
let mut cached = self.cached.iter();
|
||||
|
||||
self.order.iter().map(move |kind| match kind {
|
||||
Kind::Live => Layer::Live(live.next().unwrap()),
|
||||
Kind::Cache => Layer::Cached(cached.next().unwrap().borrow()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for live in &mut self.live[..self.live_count] {
|
||||
live.bounds = None;
|
||||
live.transformation = Transformation::IDENTITY;
|
||||
|
||||
live.quads.clear();
|
||||
live.meshes.clear();
|
||||
live.text.clear();
|
||||
live.images.clear();
|
||||
}
|
||||
},
|
||||
|
||||
self.current = 0;
|
||||
self.live_count = 1;
|
||||
|
||||
self.order.clear();
|
||||
self.order.push(Kind::Live);
|
||||
|
||||
self.cached.clear();
|
||||
self.previous.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Stack {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Live {
|
||||
pub bounds: Option<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,
|
||||
last_transformation: None,
|
||||
quads: quad::Cache::Staged(self.quads),
|
||||
meshes: triangle::Cache::Staged(self.meshes),
|
||||
text: text::Cache::Staged(self.text),
|
||||
images: self.images,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cached {
|
||||
pub bounds: Option<Rectangle>,
|
||||
pub transformation: Transformation,
|
||||
pub last_transformation: Option<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.transformation = live.transformation;
|
||||
|
||||
self.quads.update(live.quads);
|
||||
self.meshes.update(live.meshes);
|
||||
self.text.update(live.text);
|
||||
self.images = live.images;
|
||||
}
|
||||
}
|
||||
|
||||
enum Kind {
|
||||
Live,
|
||||
Cache,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
use crate::core::image;
|
||||
use crate::core::svg;
|
||||
use crate::core::{Color, Rectangle};
|
||||
|
||||
/// A raster or vector image.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Image {
|
||||
/// A raster image.
|
||||
Raster {
|
||||
/// The handle of a raster image.
|
||||
handle: image::Handle,
|
||||
|
||||
/// The filter method of a raster image.
|
||||
filter_method: image::FilterMethod,
|
||||
|
||||
/// The bounds of the image.
|
||||
bounds: Rectangle,
|
||||
},
|
||||
/// A vector image.
|
||||
Vector {
|
||||
/// The handle of a vector image.
|
||||
handle: svg::Handle,
|
||||
|
||||
/// The [`Color`] filter
|
||||
color: Option<Color>,
|
||||
|
||||
/// The bounds of the image.
|
||||
bounds: Rectangle,
|
||||
},
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
//! A collection of triangle primitives.
|
||||
use crate::core::{Rectangle, Transformation};
|
||||
use crate::graphics::mesh;
|
||||
|
||||
/// A mesh of triangles.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Mesh<'a> {
|
||||
/// A mesh of triangles with a solid color.
|
||||
Solid {
|
||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||
transformation: Transformation,
|
||||
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
|
||||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
clip_bounds: Rectangle<f32>,
|
||||
},
|
||||
/// A mesh of triangles with a gradient color.
|
||||
Gradient {
|
||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||
transformation: Transformation,
|
||||
|
||||
/// The vertex and index buffers of the [`Mesh`].
|
||||
buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
|
||||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
clip_bounds: Rectangle<f32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Mesh<'_> {
|
||||
/// Returns the origin of the [`Mesh`].
|
||||
pub fn transformation(&self) -> Transformation {
|
||||
match self {
|
||||
Self::Solid { transformation, .. }
|
||||
| Self::Gradient { transformation, .. } => *transformation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the indices of the [`Mesh`].
|
||||
pub fn indices(&self) -> &[u32] {
|
||||
match self {
|
||||
Self::Solid { buffers, .. } => &buffers.indices,
|
||||
Self::Gradient { buffers, .. } => &buffers.indices,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the clip bounds of the [`Mesh`].
|
||||
pub fn clip_bounds(&self) -> Rectangle<f32> {
|
||||
match self {
|
||||
Self::Solid { clip_bounds, .. }
|
||||
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of counting the attributes of a set of meshes.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct AttributeCount {
|
||||
/// The total amount of solid vertices.
|
||||
pub solid_vertices: usize,
|
||||
|
||||
/// The total amount of solid meshes.
|
||||
pub solids: usize,
|
||||
|
||||
/// The total amount of gradient vertices.
|
||||
pub gradient_vertices: usize,
|
||||
|
||||
/// The total amount of gradient meshes.
|
||||
pub gradients: usize,
|
||||
|
||||
/// The total amount of indices.
|
||||
pub indices: usize,
|
||||
}
|
||||
|
||||
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
|
||||
meshes
|
||||
.iter()
|
||||
.fold(AttributeCount::default(), |mut count, mesh| {
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
count.solids += 1;
|
||||
count.solid_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
count.gradients += 1;
|
||||
count.gradient_vertices += buffers.vertices.len();
|
||||
count.indices += buffers.indices.len();
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
})
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
use crate::core::Rectangle;
|
||||
use crate::primitive::pipeline::Primitive;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
|
||||
pub struct Pipeline {
|
||||
/// The bounds of the [`Pipeline`].
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// The viewport of the [`Pipeline`].
|
||||
pub viewport: Rectangle,
|
||||
|
||||
/// The [`Primitive`] to render.
|
||||
pub primitive: Arc<dyn Primitive>,
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::text;
|
||||
use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
|
||||
use crate::graphics;
|
||||
use crate::graphics::text::editor;
|
||||
use crate::graphics::text::paragraph;
|
||||
|
||||
/// A text primitive.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Text<'a> {
|
||||
/// A paragraph.
|
||||
#[allow(missing_docs)]
|
||||
Paragraph {
|
||||
paragraph: paragraph::Weak,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
},
|
||||
/// An editor.
|
||||
#[allow(missing_docs)]
|
||||
Editor {
|
||||
editor: editor::Weak,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
},
|
||||
/// Some cached text.
|
||||
Cached(Cached<'a>),
|
||||
/// Some raw text.
|
||||
#[allow(missing_docs)]
|
||||
Raw {
|
||||
raw: graphics::text::Raw,
|
||||
transformation: Transformation,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cached<'a> {
|
||||
/// The content of the [`Text`].
|
||||
pub content: &'a str,
|
||||
|
||||
/// The layout bounds of the [`Text`].
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// The color of the [`Text`], in __linear RGB_.
|
||||
pub color: Color,
|
||||
|
||||
/// The size of the [`Text`] in logical pixels.
|
||||
pub size: Pixels,
|
||||
|
||||
/// The line height of the [`Text`].
|
||||
pub line_height: text::LineHeight,
|
||||
|
||||
/// The font of the [`Text`].
|
||||
pub font: Font,
|
||||
|
||||
/// The horizontal alignment of the [`Text`].
|
||||
pub horizontal_alignment: alignment::Horizontal,
|
||||
|
||||
/// The vertical alignment of the [`Text`].
|
||||
pub vertical_alignment: alignment::Vertical,
|
||||
|
||||
/// The shaping strategy of the text.
|
||||
pub shaping: text::Shaping,
|
||||
|
||||
/// The clip bounds of the text.
|
||||
pub clip_bounds: Rectangle,
|
||||
}
|
||||
578
wgpu/src/lib.rs
578
wgpu/src/lib.rs
|
|
@ -22,8 +22,8 @@
|
|||
)]
|
||||
#![forbid(rust_2018_idioms)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
// missing_debug_implementations,
|
||||
//missing_docs,
|
||||
unsafe_code,
|
||||
unused_results,
|
||||
rustdoc::broken_intra_doc_links
|
||||
|
|
@ -37,13 +37,21 @@ pub mod window;
|
|||
#[cfg(feature = "geometry")]
|
||||
pub mod geometry;
|
||||
|
||||
mod backend;
|
||||
mod buffer;
|
||||
mod color;
|
||||
mod engine;
|
||||
mod quad;
|
||||
mod text;
|
||||
mod triangle;
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
#[path = "image/mod.rs"]
|
||||
mod image;
|
||||
|
||||
#[cfg(not(any(feature = "image", feature = "svg")))]
|
||||
#[path = "image/null.rs"]
|
||||
mod image;
|
||||
|
||||
use buffer::Buffer;
|
||||
|
||||
pub use iced_graphics as graphics;
|
||||
|
|
@ -51,16 +59,570 @@ pub use iced_graphics::core;
|
|||
|
||||
pub use wgpu;
|
||||
|
||||
pub use backend::Backend;
|
||||
pub use layer::Layer;
|
||||
pub use engine::Engine;
|
||||
pub use layer::{Layer, LayerMut};
|
||||
pub use primitive::Primitive;
|
||||
pub use settings::Settings;
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
mod image;
|
||||
#[cfg(feature = "geometry")]
|
||||
pub use geometry::Geometry;
|
||||
|
||||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||
};
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::graphics::Viewport;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A [`wgpu`] graphics renderer for [`iced`].
|
||||
///
|
||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
pub type Renderer = iced_graphics::Renderer<Backend>;
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Renderer {
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
layers: layer::Stack,
|
||||
|
||||
// TODO: Centralize all the image feature handling
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: image::cache::Shared,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(settings: Settings, _engine: &Engine) -> Self {
|
||||
Self {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
layers: layer::Stack::new(),
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: _engine.image_cache().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_primitive(&mut self, _primitive: Primitive) {}
|
||||
|
||||
pub fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
clear_color: Option<Color>,
|
||||
format: wgpu::TextureFormat,
|
||||
frame: &wgpu::TextureView,
|
||||
viewport: &Viewport,
|
||||
overlay: &[T],
|
||||
) {
|
||||
let target_size = viewport.physical_size();
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let transformation = viewport.projection();
|
||||
|
||||
for line in overlay {
|
||||
println!("{}", line.as_ref());
|
||||
}
|
||||
|
||||
self.prepare(
|
||||
engine,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
encoder,
|
||||
scale_factor,
|
||||
target_size,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.render(
|
||||
engine,
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
clear_color,
|
||||
scale_factor,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_format: wgpu::TextureFormat,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
for layer in self.layers.iter_mut() {
|
||||
match layer {
|
||||
LayerMut::Live(live) => {
|
||||
if !live.quads.is_empty() {
|
||||
engine.quad_pipeline.prepare_batch(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&live.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
if !live.meshes.is_empty() {
|
||||
engine.triangle_pipeline.prepare_batch(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&live.meshes,
|
||||
transformation
|
||||
* Transformation::scale(scale_factor),
|
||||
);
|
||||
}
|
||||
|
||||
if !live.text.is_empty() {
|
||||
engine.text_pipeline.prepare_batch(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&live.text,
|
||||
live.bounds.unwrap_or(Rectangle::with_size(
|
||||
Size::INFINITY,
|
||||
)),
|
||||
scale_factor,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !live.images.is_empty() {
|
||||
engine.image_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&live.images,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
LayerMut::Cached(mut cached) => {
|
||||
if !cached.quads.is_empty() {
|
||||
engine.quad_pipeline.prepare_cache(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&mut cached.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
if !cached.meshes.is_empty() {
|
||||
engine.triangle_pipeline.prepare_cache(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&mut cached.meshes,
|
||||
transformation
|
||||
* Transformation::scale(scale_factor),
|
||||
);
|
||||
}
|
||||
|
||||
if !cached.text.is_empty() {
|
||||
let bounds = cached
|
||||
.bounds
|
||||
.unwrap_or(Rectangle::with_size(Size::INFINITY));
|
||||
|
||||
engine.text_pipeline.prepare_cache(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut cached.text,
|
||||
bounds,
|
||||
scale_factor,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !cached.images.is_empty() {
|
||||
engine.image_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&cached.images,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
frame: &wgpu::TextureView,
|
||||
clear_color: Option<Color>,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
) {
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: match clear_color {
|
||||
Some(background_color) => wgpu::LoadOp::Clear({
|
||||
let [r, g, b, a] =
|
||||
graphics::color::pack(background_color)
|
||||
.components();
|
||||
|
||||
wgpu::Color {
|
||||
r: f64::from(r),
|
||||
g: f64::from(g),
|
||||
b: f64::from(b),
|
||||
a: f64::from(a),
|
||||
}
|
||||
}),
|
||||
None => wgpu::LoadOp::Load,
|
||||
},
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
|
||||
let mut quad_layer = 0;
|
||||
let mut mesh_layer = 0;
|
||||
let mut text_layer = 0;
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
let mut image_layer = 0;
|
||||
|
||||
// TODO: Can we avoid collecting here?
|
||||
let layers: Vec<_> = self.layers.iter().collect();
|
||||
|
||||
for layer in &layers {
|
||||
match layer {
|
||||
Layer::Live(live) => {
|
||||
let bounds = live
|
||||
.bounds
|
||||
.map(|bounds| bounds * scale_factor)
|
||||
.map(Rectangle::snap)
|
||||
.unwrap_or(Rectangle::with_size(target_size));
|
||||
|
||||
if !live.quads.is_empty() {
|
||||
engine.quad_pipeline.render_batch(
|
||||
quad_layer,
|
||||
bounds,
|
||||
&live.quads,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
quad_layer += 1;
|
||||
}
|
||||
|
||||
if !live.meshes.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
engine.triangle_pipeline.render_batch(
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
mesh_layer,
|
||||
target_size,
|
||||
&live.meshes,
|
||||
bounds,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
mesh_layer += 1;
|
||||
|
||||
render_pass =
|
||||
ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if !live.text.is_empty() {
|
||||
engine.text_pipeline.render_batch(
|
||||
text_layer,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
text_layer += 1;
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !live.images.is_empty() {
|
||||
engine.image_pipeline.render(
|
||||
image_layer,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
image_layer += 1;
|
||||
}
|
||||
}
|
||||
Layer::Cached(cached) => {
|
||||
let bounds = cached
|
||||
.bounds
|
||||
.map(|bounds| bounds * scale_factor)
|
||||
.map(Rectangle::snap)
|
||||
.unwrap_or(Rectangle::with_size(target_size));
|
||||
|
||||
if !cached.quads.is_empty() {
|
||||
engine.quad_pipeline.render_cache(
|
||||
&cached.quads,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
if !cached.meshes.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
engine.triangle_pipeline.render_cache(
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
target_size,
|
||||
&cached.meshes,
|
||||
bounds,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
render_pass =
|
||||
ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if !cached.text.is_empty() {
|
||||
engine.text_pipeline.render_cache(
|
||||
&cached.text,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !cached.images.is_empty() {
|
||||
engine.image_pipeline.render(
|
||||
image_layer,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
image_layer += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
impl core::Renderer for Renderer {
|
||||
fn start_layer(&mut self, bounds: Rectangle) {
|
||||
self.layers.push_clip(Some(bounds));
|
||||
}
|
||||
|
||||
fn end_layer(&mut self, _bounds: Rectangle) {
|
||||
self.layers.pop_clip();
|
||||
}
|
||||
|
||||
fn start_transformation(&mut self, transformation: Transformation) {
|
||||
self.layers.push_transformation(transformation);
|
||||
}
|
||||
|
||||
fn end_transformation(&mut self, _transformation: Transformation) {
|
||||
self.layers.pop_transformation();
|
||||
}
|
||||
|
||||
fn fill_quad(
|
||||
&mut self,
|
||||
quad: core::renderer::Quad,
|
||||
background: impl Into<Background>,
|
||||
) {
|
||||
self.layers.draw_quad(quad, background.into());
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.layers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl core::text::Renderer for Renderer {
|
||||
type Font = Font;
|
||||
type Paragraph = Paragraph;
|
||||
type Editor = Editor;
|
||||
|
||||
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||
|
||||
fn default_font(&self) -> Self::Font {
|
||||
self.default_font
|
||||
}
|
||||
|
||||
fn default_size(&self) -> Pixels {
|
||||
self.default_text_size
|
||||
}
|
||||
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
graphics::text::font_system()
|
||||
.write()
|
||||
.expect("Write font system")
|
||||
.load_font(font);
|
||||
|
||||
// TODO: Invalidate buffer cache
|
||||
}
|
||||
|
||||
fn fill_paragraph(
|
||||
&mut self,
|
||||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
self.layers
|
||||
.draw_paragraph(text, position, color, clip_bounds);
|
||||
}
|
||||
|
||||
fn fill_editor(
|
||||
&mut self,
|
||||
editor: &Self::Editor,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
self.layers
|
||||
.draw_editor(editor, position, color, clip_bounds);
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: core::Text,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
self.layers.draw_text(text, position, color, clip_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
impl core::image::Renderer for Renderer {
|
||||
type Handle = core::image::Handle;
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
|
||||
self.image_cache.lock().measure_image(handle)
|
||||
}
|
||||
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
handle: Self::Handle,
|
||||
filter_method: core::image::FilterMethod,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
self.layers.draw_image(handle, filter_method, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl core::svg::Renderer for Renderer {
|
||||
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
self.image_cache.lock().measure_svg(handle)
|
||||
}
|
||||
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
handle: core::svg::Handle,
|
||||
color_filter: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
self.layers.draw_svg(handle, color_filter, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::mesh::Renderer for Renderer {
|
||||
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
|
||||
self.layers.draw_mesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
impl graphics::geometry::Renderer for Renderer {
|
||||
type Geometry = Geometry;
|
||||
type Frame = geometry::Frame;
|
||||
|
||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||
geometry::Frame::new(size)
|
||||
}
|
||||
|
||||
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||
match geometry {
|
||||
Geometry::Live(layers) => {
|
||||
for layer in layers {
|
||||
self.layers.draw_layer(layer);
|
||||
}
|
||||
}
|
||||
Geometry::Cached(layers) => {
|
||||
for layer in layers.as_ref() {
|
||||
self.layers.draw_cached_layer(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::compositor::Default for crate::Renderer {
|
||||
type Compositor = window::Compositor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ pub mod pipeline;
|
|||
|
||||
pub use pipeline::Pipeline;
|
||||
|
||||
use crate::core::Rectangle;
|
||||
use crate::graphics::{Damage, Mesh};
|
||||
use crate::graphics::Mesh;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
|
@ -19,20 +18,3 @@ pub enum Custom {
|
|||
/// A custom pipeline primitive.
|
||||
Pipeline(Pipeline),
|
||||
}
|
||||
|
||||
impl Damage for Custom {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Self::Mesh(mesh) => mesh.bounds(),
|
||||
Self::Pipeline(pipeline) => pipeline.bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Mesh> for Custom {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
|
||||
Ok(Custom::Mesh(mesh))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
234
wgpu/src/quad.rs
234
wgpu/src/quad.rs
|
|
@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};
|
|||
|
||||
use std::mem;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
||||
const INITIAL_INSTANCES: usize = 2_000;
|
||||
|
||||
/// The properties of a quad.
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
/// The position of the [`Quad`].
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// The size of the [`Quad`].
|
||||
pub size: [f32; 2],
|
||||
|
||||
/// The border color of the [`Quad`], in __linear RGB__.
|
||||
pub border_color: color::Packed,
|
||||
|
||||
/// The border radii of the [`Quad`].
|
||||
pub border_radius: [f32; 4],
|
||||
|
||||
/// The border width of the [`Quad`].
|
||||
pub border_width: f32,
|
||||
|
||||
/// The shadow color of the [`Quad`].
|
||||
pub shadow_color: color::Packed,
|
||||
|
||||
/// The shadow offset of the [`Quad`].
|
||||
pub shadow_offset: [f32; 2],
|
||||
|
||||
/// The shadow blur radius of the [`Quad`].
|
||||
pub shadow_blur_radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
solid: solid::Pipeline,
|
||||
|
|
@ -54,7 +80,7 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
pub fn prepare_batch(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
|
|
@ -73,7 +99,64 @@ impl Pipeline {
|
|||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
pub fn prepare_cache(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
cache: &mut Cache,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
match cache {
|
||||
Cache::Staged(_) => {
|
||||
let Cache::Staged(batch) =
|
||||
std::mem::replace(cache, Cache::Staged(Batch::default()))
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut layer = Layer::new(device, &self.constant_layout);
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&batch,
|
||||
transformation,
|
||||
scale,
|
||||
);
|
||||
|
||||
*cache = Cache::Uploaded {
|
||||
layer,
|
||||
batch,
|
||||
needs_reupload: false,
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Uploaded {
|
||||
batch,
|
||||
layer,
|
||||
needs_reupload,
|
||||
} => {
|
||||
if *needs_reupload {
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
batch,
|
||||
transformation,
|
||||
scale,
|
||||
);
|
||||
|
||||
*needs_reupload = false;
|
||||
} else {
|
||||
layer.update(device, encoder, belt, transformation, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_batch<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
|
|
@ -81,6 +164,28 @@ impl Pipeline {
|
|||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
self.render(bounds, layer, &quads.order, render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_cache<'a>(
|
||||
&'a self,
|
||||
cache: &'a Cache,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Cache::Uploaded { layer, batch, .. } = cache {
|
||||
self.render(bounds, layer, &batch.order, render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(
|
||||
&'a self,
|
||||
bounds: Rectangle<u32>,
|
||||
layer: &'a Layer,
|
||||
order: &Order,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
|
|
@ -91,7 +196,7 @@ impl Pipeline {
|
|||
let mut solid_offset = 0;
|
||||
let mut gradient_offset = 0;
|
||||
|
||||
for (kind, count) in &quads.order {
|
||||
for (kind, count) in order {
|
||||
match kind {
|
||||
Kind::Solid => {
|
||||
self.solid.render(
|
||||
|
|
@ -116,7 +221,6 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
|
|
@ -124,7 +228,49 @@ impl Pipeline {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
pub enum Cache {
|
||||
Staged(Batch),
|
||||
Uploaded {
|
||||
batch: Batch,
|
||||
layer: Layer,
|
||||
needs_reupload: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
|
||||
batch.is_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, new_batch: Batch) {
|
||||
match self {
|
||||
Self::Staged(batch) => {
|
||||
*batch = new_batch;
|
||||
}
|
||||
Self::Uploaded {
|
||||
batch,
|
||||
needs_reupload,
|
||||
..
|
||||
} => {
|
||||
*batch = new_batch;
|
||||
*needs_reupload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cache {
|
||||
fn default() -> Self {
|
||||
Self::Staged(Batch::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
constants: wgpu::BindGroup,
|
||||
constants_buffer: wgpu::Buffer,
|
||||
solid: solid::Layer,
|
||||
|
|
@ -169,9 +315,26 @@ impl Layer {
|
|||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
|
||||
self.update(device, encoder, belt, transformation, scale);
|
||||
|
||||
if !quads.solids.is_empty() {
|
||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||
}
|
||||
|
||||
if !quads.gradients.is_empty() {
|
||||
self.gradient
|
||||
.prepare(device, encoder, belt, &quads.gradients);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
let uniforms = Uniforms::new(transformation, scale);
|
||||
let bytes = bytemuck::bytes_of(&uniforms);
|
||||
|
||||
|
|
@ -183,45 +346,7 @@ impl Layer {
|
|||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
if !quads.solids.is_empty() {
|
||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||
}
|
||||
|
||||
if !quads.gradients.is_empty() {
|
||||
self.gradient
|
||||
.prepare(device, encoder, belt, &quads.gradients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The properties of a quad.
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
/// The position of the [`Quad`].
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// The size of the [`Quad`].
|
||||
pub size: [f32; 2],
|
||||
|
||||
/// The border color of the [`Quad`], in __linear RGB__.
|
||||
pub border_color: color::Packed,
|
||||
|
||||
/// The border radii of the [`Quad`].
|
||||
pub border_radius: [f32; 4],
|
||||
|
||||
/// The border width of the [`Quad`].
|
||||
pub border_width: f32,
|
||||
|
||||
/// The shadow color of the [`Quad`].
|
||||
pub shadow_color: [f32; 4],
|
||||
|
||||
/// The shadow offset of the [`Quad`].
|
||||
pub shadow_offset: [f32; 2],
|
||||
|
||||
/// The shadow blur radius of the [`Quad`].
|
||||
pub shadow_blur_radius: f32,
|
||||
}
|
||||
|
||||
/// A group of [`Quad`]s rendered together.
|
||||
|
|
@ -233,10 +358,13 @@ pub struct Batch {
|
|||
/// The gradient quads of the [`Layer`].
|
||||
gradients: Vec<Gradient>,
|
||||
|
||||
/// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
|
||||
order: Vec<(Kind, usize)>,
|
||||
/// The quad order of the [`Layer`].
|
||||
order: Order,
|
||||
}
|
||||
|
||||
/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
|
||||
type Order = Vec<(Kind, usize)>;
|
||||
|
||||
impl Batch {
|
||||
/// Returns true if there are no quads of any type in [`Quads`].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
|
@ -276,6 +404,12 @@ impl Batch {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.solids.clear();
|
||||
self.gradients.clear();
|
||||
self.order.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
|
|
|||
394
wgpu/src/text.rs
394
wgpu/src/text.rs
|
|
@ -1,20 +1,67 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::text::cache::{self, Cache};
|
||||
use crate::graphics::text::cache::{self, Cache as BufferCache};
|
||||
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
|
||||
use crate::layer::Text;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::graphics::Text;
|
||||
|
||||
pub type Batch = Vec<Text>;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pipeline {
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
format: wgpu::TextureFormat,
|
||||
atlas: glyphon::TextAtlas,
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
prepare_layer: usize,
|
||||
cache: RefCell<Cache>,
|
||||
cache: BufferCache,
|
||||
}
|
||||
|
||||
pub enum Cache {
|
||||
Staged(Batch),
|
||||
Uploaded {
|
||||
batch: Batch,
|
||||
renderer: glyphon::TextRenderer,
|
||||
atlas: Option<glyphon::TextAtlas>,
|
||||
buffer_cache: Option<BufferCache>,
|
||||
scale_factor: f32,
|
||||
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 {
|
||||
|
|
@ -24,6 +71,7 @@ impl Pipeline {
|
|||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
Pipeline {
|
||||
format,
|
||||
renderers: Vec::new(),
|
||||
atlas: glyphon::TextAtlas::with_color_mode(
|
||||
device,
|
||||
|
|
@ -36,25 +84,16 @@ impl Pipeline {
|
|||
},
|
||||
),
|
||||
prepare_layer: 0,
|
||||
cache: RefCell::new(Cache::new()),
|
||||
cache: BufferCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
font_system()
|
||||
.write()
|
||||
.expect("Write font system")
|
||||
.load_font(bytes);
|
||||
|
||||
self.cache = RefCell::new(Cache::new());
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
pub fn prepare_batch(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
sections: &[Text<'_>],
|
||||
sections: &Batch,
|
||||
layer_bounds: Rectangle,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
|
|
@ -68,12 +107,202 @@ impl Pipeline {
|
|||
));
|
||||
}
|
||||
|
||||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
let result = prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
renderer,
|
||||
&mut self.atlas,
|
||||
&mut self.cache,
|
||||
sections,
|
||||
layer_bounds,
|
||||
scale_factor,
|
||||
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_scale_factor: f32,
|
||||
new_target_size: Size<u32>,
|
||||
) {
|
||||
match cache {
|
||||
Cache::Staged(_) => {
|
||||
let Cache::Staged(batch) =
|
||||
std::mem::replace(cache, Cache::Staged(Batch::default()))
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// TODO: Find a better heuristic (?)
|
||||
let (mut atlas, mut buffer_cache) = if batch.len() > 10 {
|
||||
(
|
||||
Some(glyphon::TextAtlas::with_color_mode(
|
||||
device,
|
||||
queue,
|
||||
self.format,
|
||||
if color::GAMMA_CORRECTION {
|
||||
glyphon::ColorMode::Accurate
|
||||
} else {
|
||||
glyphon::ColorMode::Web
|
||||
},
|
||||
)),
|
||||
Some(BufferCache::new()),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let mut renderer = glyphon::TextRenderer::new(
|
||||
atlas.as_mut().unwrap_or(&mut self.atlas),
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut renderer,
|
||||
atlas.as_mut().unwrap_or(&mut self.atlas),
|
||||
buffer_cache.as_mut().unwrap_or(&mut self.cache),
|
||||
&batch,
|
||||
layer_bounds,
|
||||
new_scale_factor,
|
||||
new_target_size,
|
||||
);
|
||||
|
||||
*cache = Cache::Uploaded {
|
||||
batch,
|
||||
needs_reupload: false,
|
||||
renderer,
|
||||
atlas,
|
||||
buffer_cache,
|
||||
scale_factor: new_scale_factor,
|
||||
target_size: new_target_size,
|
||||
}
|
||||
}
|
||||
Cache::Uploaded {
|
||||
batch,
|
||||
needs_reupload,
|
||||
renderer,
|
||||
atlas,
|
||||
buffer_cache,
|
||||
scale_factor,
|
||||
target_size,
|
||||
} => {
|
||||
if *needs_reupload
|
||||
|| atlas.is_none()
|
||||
|| buffer_cache.is_none()
|
||||
|| new_scale_factor != *scale_factor
|
||||
|| new_target_size != *target_size
|
||||
{
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
renderer,
|
||||
atlas.as_mut().unwrap_or(&mut self.atlas),
|
||||
buffer_cache.as_mut().unwrap_or(&mut self.cache),
|
||||
batch,
|
||||
layer_bounds,
|
||||
*scale_factor,
|
||||
*target_size,
|
||||
);
|
||||
|
||||
*scale_factor = new_scale_factor;
|
||||
*target_size = new_target_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_batch<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let renderer = &self.renderers[layer];
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render(&self.atlas, render_pass)
|
||||
.expect("Render text");
|
||||
}
|
||||
|
||||
pub fn render_cache<'a>(
|
||||
&'a self,
|
||||
cache: &'a Cache,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let Cache::Uploaded {
|
||||
renderer, atlas, ..
|
||||
} = cache
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render(atlas.as_ref().unwrap_or(&self.atlas), render_pass)
|
||||
.expect("Render text");
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.atlas.trim();
|
||||
self.cache.trim();
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
renderer: &mut glyphon::TextRenderer,
|
||||
atlas: &mut glyphon::TextAtlas,
|
||||
buffer_cache: &mut BufferCache,
|
||||
sections: &Batch,
|
||||
layer_bounds: Rectangle,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
) -> Result<(), glyphon::PrepareError> {
|
||||
let mut font_system = font_system().write().expect("Write font system");
|
||||
let font_system = font_system.raw();
|
||||
|
||||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
let cache = self.cache.get_mut();
|
||||
|
||||
enum Allocation {
|
||||
Paragraph(Paragraph),
|
||||
Editor(Editor),
|
||||
|
|
@ -90,29 +319,33 @@ impl Pipeline {
|
|||
Text::Editor { editor, .. } => {
|
||||
editor.upgrade().map(Allocation::Editor)
|
||||
}
|
||||
Text::Cached(text) => {
|
||||
let (key, _) = cache.allocate(
|
||||
Text::Cached {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
line_height,
|
||||
font,
|
||||
shaping,
|
||||
..
|
||||
} => {
|
||||
let (key, _) = buffer_cache.allocate(
|
||||
font_system,
|
||||
cache::Key {
|
||||
content: text.content,
|
||||
size: text.size.into(),
|
||||
line_height: f32::from(
|
||||
text.line_height.to_absolute(text.size),
|
||||
),
|
||||
font: text.font,
|
||||
content,
|
||||
size: (*size).into(),
|
||||
line_height: f32::from(line_height.to_absolute(*size)),
|
||||
font: *font,
|
||||
bounds: Size {
|
||||
width: text.bounds.width,
|
||||
height: text.bounds.height,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
shaping: text.shaping,
|
||||
shaping: *shaping,
|
||||
},
|
||||
);
|
||||
|
||||
Some(Allocation::Cache(key))
|
||||
}
|
||||
Text::Raw { raw, .. } => {
|
||||
raw.buffer.upgrade().map(Allocation::Raw)
|
||||
}
|
||||
Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -162,8 +395,7 @@ impl Pipeline {
|
|||
} => {
|
||||
use crate::core::text::Editor as _;
|
||||
|
||||
let Some(Allocation::Editor(editor)) = allocation
|
||||
else {
|
||||
let Some(Allocation::Editor(editor)) = allocation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
|
@ -177,23 +409,28 @@ impl Pipeline {
|
|||
*transformation,
|
||||
)
|
||||
}
|
||||
Text::Cached(text) => {
|
||||
Text::Cached {
|
||||
bounds,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
color,
|
||||
clip_bounds,
|
||||
..
|
||||
} => {
|
||||
let Some(Allocation::Cache(key)) = allocation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let entry = cache.get(key).expect("Get cached buffer");
|
||||
let entry =
|
||||
buffer_cache.get(key).expect("Get cached buffer");
|
||||
|
||||
(
|
||||
&entry.buffer,
|
||||
Rectangle::new(
|
||||
text.bounds.position(),
|
||||
entry.min_bounds,
|
||||
),
|
||||
text.horizontal_alignment,
|
||||
text.vertical_alignment,
|
||||
text.color,
|
||||
text.clip_bounds,
|
||||
Rectangle::new(bounds.position(), entry.min_bounds),
|
||||
*horizontal_alignment,
|
||||
*vertical_alignment,
|
||||
*color,
|
||||
*clip_bounds,
|
||||
Transformation::IDENTITY,
|
||||
)
|
||||
}
|
||||
|
|
@ -209,10 +446,7 @@ impl Pipeline {
|
|||
|
||||
(
|
||||
buffer.as_ref(),
|
||||
Rectangle::new(
|
||||
raw.position,
|
||||
Size::new(width, height),
|
||||
),
|
||||
Rectangle::new(raw.position, Size::new(width, height)),
|
||||
alignment::Horizontal::Left,
|
||||
alignment::Vertical::Top,
|
||||
raw.color,
|
||||
|
|
@ -226,23 +460,18 @@ impl Pipeline {
|
|||
|
||||
let left = match horizontal_alignment {
|
||||
alignment::Horizontal::Left => bounds.x,
|
||||
alignment::Horizontal::Center => {
|
||||
bounds.x - bounds.width / 2.0
|
||||
}
|
||||
alignment::Horizontal::Center => bounds.x - bounds.width / 2.0,
|
||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
||||
};
|
||||
|
||||
let top = match vertical_alignment {
|
||||
alignment::Vertical::Top => bounds.y,
|
||||
alignment::Vertical::Center => {
|
||||
bounds.y - bounds.height / 2.0
|
||||
}
|
||||
alignment::Vertical::Center => bounds.y - bounds.height / 2.0,
|
||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
||||
};
|
||||
|
||||
let clip_bounds = layer_bounds.intersection(
|
||||
&(clip_bounds * transformation * scale_factor),
|
||||
)?;
|
||||
let clip_bounds = layer_bounds
|
||||
.intersection(&(clip_bounds * transformation * scale_factor))?;
|
||||
|
||||
Some(glyphon::TextArea {
|
||||
buffer,
|
||||
|
|
@ -260,56 +489,17 @@ impl Pipeline {
|
|||
},
|
||||
);
|
||||
|
||||
let result = renderer.prepare(
|
||||
renderer.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
font_system,
|
||||
&mut self.atlas,
|
||||
atlas,
|
||||
glyphon::Resolution {
|
||||
width: target_size.width,
|
||||
height: target_size.height,
|
||||
},
|
||||
text_areas,
|
||||
&mut glyphon::SwashCache::new(),
|
||||
);
|
||||
|
||||
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 render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let renderer = &self.renderers[layer];
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render(&self.atlas, render_pass)
|
||||
.expect("Render text");
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.atlas.trim();
|
||||
self.cache.get_mut().trim();
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
//! Draw meshes of triangles.
|
||||
mod msaa;
|
||||
|
||||
use crate::core::{Size, Transformation};
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::layer::mesh::{self, Mesh};
|
||||
use crate::Buffer;
|
||||
|
||||
const INITIAL_INDEX_COUNT: usize = 1_000;
|
||||
const INITIAL_VERTEX_COUNT: usize = 1_000;
|
||||
|
||||
pub type Batch = Vec<Mesh>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
blit: Option<msaa::Blit>,
|
||||
|
|
@ -18,8 +20,270 @@ pub struct Pipeline {
|
|||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> Pipeline {
|
||||
Pipeline {
|
||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_batch(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
meshes: &Batch,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers
|
||||
.push(Layer::new(device, &self.solid, &self.gradient));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
meshes,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn prepare_cache(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
cache: &mut Cache,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
match cache {
|
||||
Cache::Staged(_) => {
|
||||
let Cache::Staged(batch) =
|
||||
std::mem::replace(cache, Cache::Staged(Batch::default()))
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut layer = Layer::new(device, &self.solid, &self.gradient);
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
&batch,
|
||||
transformation,
|
||||
);
|
||||
|
||||
*cache = Cache::Uploaded {
|
||||
layer,
|
||||
batch,
|
||||
needs_reupload: false,
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Uploaded {
|
||||
batch,
|
||||
layer,
|
||||
needs_reupload,
|
||||
} => {
|
||||
if *needs_reupload {
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
batch,
|
||||
transformation,
|
||||
);
|
||||
|
||||
*needs_reupload = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_batch(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
layer: usize,
|
||||
target_size: Size<u32>,
|
||||
meshes: &Batch,
|
||||
bounds: Rectangle<u32>,
|
||||
scale_factor: f32,
|
||||
) {
|
||||
Self::render(
|
||||
device,
|
||||
encoder,
|
||||
target,
|
||||
self.blit.as_mut(),
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
&self.layers[layer],
|
||||
target_size,
|
||||
meshes,
|
||||
bounds,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_cache(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
target_size: Size<u32>,
|
||||
cache: &Cache,
|
||||
bounds: Rectangle<u32>,
|
||||
scale_factor: f32,
|
||||
) {
|
||||
let Cache::Uploaded { batch, layer, .. } = cache else {
|
||||
return;
|
||||
};
|
||||
|
||||
Self::render(
|
||||
device,
|
||||
encoder,
|
||||
target,
|
||||
self.blit.as_mut(),
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
layer,
|
||||
target_size,
|
||||
batch,
|
||||
bounds,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
fn render(
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
mut blit: Option<&mut msaa::Blit>,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
layer: &Layer,
|
||||
target_size: Size<u32>,
|
||||
meshes: &Batch,
|
||||
bounds: Rectangle<u32>,
|
||||
scale_factor: f32,
|
||||
) {
|
||||
{
|
||||
let (attachment, resolve_target, load) = if let Some(blit) =
|
||||
&mut blit
|
||||
{
|
||||
let (attachment, resolve_target) =
|
||||
blit.targets(device, target_size.width, target_size.height);
|
||||
|
||||
(
|
||||
attachment,
|
||||
Some(resolve_target),
|
||||
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
)
|
||||
} else {
|
||||
(target, None, wgpu::LoadOp::Load)
|
||||
};
|
||||
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu.triangle.render_pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: attachment,
|
||||
resolve_target,
|
||||
ops: wgpu::Operations {
|
||||
load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
layer.render(
|
||||
solid,
|
||||
gradient,
|
||||
meshes,
|
||||
bounds,
|
||||
scale_factor,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(blit) = blit {
|
||||
blit.draw(encoder, target);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
pub enum Cache {
|
||||
Staged(Batch),
|
||||
Uploaded {
|
||||
batch: Batch,
|
||||
layer: Layer,
|
||||
needs_reupload: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
|
||||
batch.is_empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, new_batch: Batch) {
|
||||
match self {
|
||||
Self::Staged(batch) => {
|
||||
*batch = new_batch;
|
||||
}
|
||||
Self::Uploaded {
|
||||
batch,
|
||||
needs_reupload,
|
||||
..
|
||||
} => {
|
||||
*batch = new_batch;
|
||||
*needs_reupload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cache {
|
||||
fn default() -> Self {
|
||||
Self::Staged(Batch::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
index_buffer: Buffer<u32>,
|
||||
index_strides: Vec<u32>,
|
||||
solid: solid::Layer,
|
||||
|
|
@ -52,7 +316,7 @@ impl Layer {
|
|||
belt: &mut wgpu::util::StagingBelt,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
meshes: &Batch,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
|
|
@ -100,7 +364,6 @@ impl Layer {
|
|||
|
||||
for mesh in meshes {
|
||||
let indices = mesh.indices();
|
||||
|
||||
let uniforms =
|
||||
Uniforms::new(transformation * mesh.transformation());
|
||||
|
||||
|
|
@ -157,7 +420,8 @@ impl Layer {
|
|||
&'a self,
|
||||
solid: &'a solid::Pipeline,
|
||||
gradient: &'a gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
meshes: &Batch,
|
||||
layer_bounds: Rectangle<u32>,
|
||||
scale_factor: f32,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
|
|
@ -166,7 +430,12 @@ impl Layer {
|
|||
let mut last_is_solid = None;
|
||||
|
||||
for (index, mesh) in meshes.iter().enumerate() {
|
||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||
let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds)
|
||||
.intersection(&(mesh.clip_bounds() * scale_factor))
|
||||
.map(Rectangle::snap)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if clip_bounds.width < 1 || clip_bounds.height < 1 {
|
||||
continue;
|
||||
|
|
@ -234,119 +503,6 @@ impl Layer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> Pipeline {
|
||||
Pipeline {
|
||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
meshes: &[Mesh<'_>],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
|
||||
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers
|
||||
.push(Layer::new(device, &self.solid, &self.gradient));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
meshes,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
layer: usize,
|
||||
target_size: Size<u32>,
|
||||
meshes: &[Mesh<'_>],
|
||||
scale_factor: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
|
||||
|
||||
{
|
||||
let (attachment, resolve_target, load) = if let Some(blit) =
|
||||
&mut self.blit
|
||||
{
|
||||
let (attachment, resolve_target) =
|
||||
blit.targets(device, target_size.width, target_size.height);
|
||||
|
||||
(
|
||||
attachment,
|
||||
Some(resolve_target),
|
||||
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
)
|
||||
} else {
|
||||
(target, None, wgpu::LoadOp::Load)
|
||||
};
|
||||
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu.triangle.render_pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: attachment,
|
||||
resolve_target,
|
||||
ops: wgpu::Operations {
|
||||
load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
let layer = &mut self.layers[layer];
|
||||
|
||||
layer.render(
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
meshes,
|
||||
scale_factor,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(blit) = &mut self.blit {
|
||||
blit.draw(encoder, target);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_target(
|
||||
texture_format: wgpu::TextureFormat,
|
||||
) -> wgpu::ColorTargetState {
|
||||
|
|
|
|||
|
|
@ -4,18 +4,19 @@ use crate::graphics::color;
|
|||
use crate::graphics::compositor;
|
||||
use crate::graphics::error;
|
||||
use crate::graphics::{self, Viewport};
|
||||
use crate::{Backend, Primitive, Renderer, Settings};
|
||||
use crate::{Engine, Renderer, Settings};
|
||||
|
||||
/// A window graphics backend for iced powered by `wgpu`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Compositor {
|
||||
settings: Settings,
|
||||
instance: wgpu::Instance,
|
||||
adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
alpha_mode: wgpu::CompositeAlphaMode,
|
||||
engine: Engine,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
/// A compositor error.
|
||||
|
|
@ -167,15 +168,24 @@ impl Compositor {
|
|||
|
||||
match result {
|
||||
Ok((device, queue)) => {
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
&device,
|
||||
&queue,
|
||||
format,
|
||||
settings.antialiasing,
|
||||
);
|
||||
|
||||
return Ok(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
engine,
|
||||
settings,
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
errors.push((required_limits, error));
|
||||
|
|
@ -185,17 +195,6 @@ impl Compositor {
|
|||
|
||||
Err(Error::RequestDeviceFailed(errors))
|
||||
}
|
||||
|
||||
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
||||
pub fn create_backend(&self) -> Backend {
|
||||
Backend::new(
|
||||
&self.adapter,
|
||||
&self.device,
|
||||
&self.queue,
|
||||
self.settings,
|
||||
self.format,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
|
||||
|
|
@ -210,9 +209,8 @@ pub async fn new<W: compositor::Window>(
|
|||
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
|
||||
pub fn present<T: AsRef<str>>(
|
||||
compositor: &mut Compositor,
|
||||
backend: &mut Backend,
|
||||
renderer: &mut Renderer,
|
||||
surface: &mut wgpu::Surface<'static>,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
|
|
@ -229,21 +227,21 @@ pub fn present<T: AsRef<str>>(
|
|||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
backend.present(
|
||||
renderer.present(
|
||||
&mut compositor.engine,
|
||||
&compositor.device,
|
||||
&compositor.queue,
|
||||
&mut encoder,
|
||||
Some(background_color),
|
||||
frame.texture.format(),
|
||||
view,
|
||||
primitives,
|
||||
viewport,
|
||||
overlay,
|
||||
);
|
||||
|
||||
// Submit work
|
||||
let _submission = compositor.queue.submit(Some(encoder.finish()));
|
||||
backend.recall();
|
||||
let _ = compositor.engine.submit(&compositor.queue, encoder);
|
||||
|
||||
// Present the frame
|
||||
frame.present();
|
||||
|
||||
Ok(())
|
||||
|
|
@ -292,11 +290,7 @@ impl graphics::Compositor for Compositor {
|
|||
}
|
||||
|
||||
fn create_renderer(&self) -> Self::Renderer {
|
||||
Renderer::new(
|
||||
self.create_backend(),
|
||||
self.settings.default_font,
|
||||
self.settings.default_text_size,
|
||||
)
|
||||
Renderer::new(self.settings, &self.engine)
|
||||
}
|
||||
|
||||
fn create_surface<W: compositor::Window>(
|
||||
|
|
@ -328,7 +322,7 @@ impl graphics::Compositor for Compositor {
|
|||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: self.format,
|
||||
present_mode: self.settings.present_mode,
|
||||
present_mode: wgpu::PresentMode::Immediate,
|
||||
width,
|
||||
height,
|
||||
alpha_mode: self.alpha_mode,
|
||||
|
|
@ -355,17 +349,7 @@ impl graphics::Compositor for Compositor {
|
|||
background_color: Color,
|
||||
overlay: &[T],
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
renderer.with_primitives(|backend, primitives| {
|
||||
present(
|
||||
self,
|
||||
backend,
|
||||
surface,
|
||||
primitives,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
)
|
||||
})
|
||||
present(self, renderer, surface, viewport, background_color, overlay)
|
||||
}
|
||||
|
||||
fn screenshot<T: AsRef<str>>(
|
||||
|
|
@ -376,16 +360,7 @@ impl graphics::Compositor for Compositor {
|
|||
background_color: Color,
|
||||
overlay: &[T],
|
||||
) -> Vec<u8> {
|
||||
renderer.with_primitives(|backend, primitives| {
|
||||
screenshot(
|
||||
self,
|
||||
backend,
|
||||
primitives,
|
||||
viewport,
|
||||
background_color,
|
||||
overlay,
|
||||
)
|
||||
})
|
||||
screenshot(self, renderer, viewport, background_color, overlay)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -393,19 +368,12 @@ impl graphics::Compositor for Compositor {
|
|||
///
|
||||
/// Returns RGBA bytes of the texture data.
|
||||
pub fn screenshot<T: AsRef<str>>(
|
||||
compositor: &Compositor,
|
||||
backend: &mut Backend,
|
||||
primitives: &[Primitive],
|
||||
compositor: &mut Compositor,
|
||||
renderer: &mut Renderer,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
overlay: &[T],
|
||||
) -> Vec<u8> {
|
||||
let mut encoder = compositor.device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.encoder"),
|
||||
},
|
||||
);
|
||||
|
||||
let dimensions = BufferDimensions::new(viewport.physical_size());
|
||||
|
||||
let texture_extent = wgpu::Extent3d {
|
||||
|
|
@ -429,14 +397,20 @@ pub fn screenshot<T: AsRef<str>>(
|
|||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
backend.present(
|
||||
let mut encoder = compositor.device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.encoder"),
|
||||
},
|
||||
);
|
||||
|
||||
renderer.present(
|
||||
&mut compositor.engine,
|
||||
&compositor.device,
|
||||
&compositor.queue,
|
||||
&mut encoder,
|
||||
Some(background_color),
|
||||
texture.format(),
|
||||
&view,
|
||||
primitives,
|
||||
viewport,
|
||||
overlay,
|
||||
);
|
||||
|
|
@ -474,7 +448,7 @@ pub fn screenshot<T: AsRef<str>>(
|
|||
texture_extent,
|
||||
);
|
||||
|
||||
let index = compositor.queue.submit(Some(encoder.finish()));
|
||||
let index = compositor.engine.submit(&compositor.queue, encoder);
|
||||
|
||||
let slice = output_buffer.slice(..);
|
||||
slice.map_async(wgpu::MapMode::Read, |_| {});
|
||||
|
|
|
|||
|
|
@ -651,7 +651,7 @@ where
|
|||
defaults: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
|
|
@ -767,8 +767,8 @@ where
|
|||
|
||||
renderer.with_layer(
|
||||
Rectangle {
|
||||
width: bounds.width + 2.0,
|
||||
height: bounds.height + 2.0,
|
||||
width: (bounds.width + 2.0).min(viewport.width),
|
||||
height: (bounds.height + 2.0).min(viewport.height),
|
||||
..bounds
|
||||
},
|
||||
|renderer| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue