Redesign iced_wgpu layering architecture

This commit is contained in:
Héctor Ramón Jiménez 2024-04-03 21:07:54 +02:00
parent 99a904112c
commit b05e61f5c8
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
36 changed files with 2781 additions and 2048 deletions

View file

@ -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 {}
}

View file

@ -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 };

View file

@ -1,14 +1,94 @@
//! Load and operate on images.
use crate::core::image::{Data, Handle};
use bitflags::bitflags;
#[cfg(feature = "image")]
pub use ::image as image_rs;
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,
},
}
#[cfg(feature = "image")]
/// Tries to load an image by its [`Handle`].
pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
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 {
// 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>
where
R: std::io::BufRead + std::io::Seek,
{
let exif = exif::Reader::new().read_from_container(reader)?;
Ok(exif
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
.and_then(|field| field.value.get_uint(0))
.and_then(|value| u8::try_from(value).ok())
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
.unwrap_or_else(Self::empty))
}
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);
}
if self.contains(Self::ROTATE_180) {
imageops::rotate180_in_place(&mut image);
}
if self.contains(Self::FLIP_HORIZONTALLY) {
imageops::flip_horizontal_in_place(&mut image);
}
image
}
}
match handle.data() {
Data::Path(path) => {
image::Data::Path(path) => {
let image = ::image::open(path)?;
let operation = std::fs::File::open(path)
@ -19,7 +99,7 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
Data::Bytes(bytes) => {
image::Data::Bytes(bytes) => {
let image = ::image::load_from_memory(bytes)?;
let operation =
Operation::from_exif(&mut std::io::Cursor::new(bytes))
@ -28,68 +108,22 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
Data::Rgba {
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))
if let Some(image) =
::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec())
{
Ok(::image::DynamicImage::ImageRgba8(image))
} else {
Err(image_rs::error::ImageError::Limits(
image_rs::error::LimitError::from_kind(
image_rs::error::LimitErrorKind::DimensionError,
Err(::image::error::ImageError::Limits(
::image::error::LimitError::from_kind(
::image::error::LimitErrorKind::DimensionError,
),
))
}
}
}
}
bitflags! {
struct Operation: u8 {
const FLIP_HORIZONTALLY = 0b001;
const ROTATE_180 = 0b010;
const FLIP_DIAGONALLY = 0b100;
}
}
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>
where
R: std::io::BufRead + std::io::Seek,
{
let exif = exif::Reader::new().read_from_container(reader)?;
Ok(exif
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
.and_then(|field| field.value.get_uint(0))
.and_then(|value| u8::try_from(value).ok())
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
.unwrap_or_else(Self::empty))
}
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);
}
if self.contains(Self::ROTATE_180) {
imageops::rotate180_in_place(&mut image);
}
if self.contains(Self::FLIP_HORIZONTALLY) {
imageops::flip_horizontal_in_place(&mut image);
}
image
}
}

47
graphics/src/layer.rs Normal file
View 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;
}
}

View file

@ -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;

View file

@ -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,19 +24,44 @@ 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`].

View file

@ -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));
}

View file

@ -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.