From 3762c0590ceb0fc579a1e699702d7d5c2b204348 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 26 Apr 2024 15:17:10 +0200 Subject: Fix panic when scrolling a `TextEditor` inside a `scrollable` --- graphics/src/text/editor.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index c488a51c..4b8f0f2a 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -456,10 +456,14 @@ impl editor::Editor for Editor { } } Action::Scroll { lines } => { - editor.action( - font_system.raw(), - cosmic_text::Action::Scroll { lines }, - ); + let (_, height) = editor.buffer().size(); + + if height < i32::MAX as f32 { + editor.action( + font_system.raw(), + cosmic_text::Action::Scroll { lines }, + ); + } } } -- cgit From b5b78d505e22cafccb4ecbf57dc61f536ca558ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 07:57:54 +0200 Subject: Introduce `canvas::Cache` grouping Caches with the same `Group` will share their text atlas! --- graphics/src/cache.rs | 158 +++++++++++++++++++++++++++++++++++++++++ graphics/src/cached.rs | 24 ------- graphics/src/geometry.rs | 2 +- graphics/src/geometry/cache.rs | 83 ++++++++++------------ graphics/src/lib.rs | 4 +- 5 files changed, 200 insertions(+), 71 deletions(-) create mode 100644 graphics/src/cache.rs delete mode 100644 graphics/src/cached.rs (limited to 'graphics/src') diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs new file mode 100644 index 00000000..7106c392 --- /dev/null +++ b/graphics/src/cache.rs @@ -0,0 +1,158 @@ +//! Cache computations and efficiently reuse them. +use std::cell::RefCell; +use std::fmt; +use std::sync::atomic::{self, AtomicU64}; + +/// A simple cache that stores generated values to avoid recomputation. +/// +/// Keeps track of the last generated value after clearing. +pub struct Cache { + group: Group, + state: RefCell>, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + group: Group::unique(), + state: RefCell::new(State::Empty { previous: None }), + } + } + + /// Creates a new empty [`Cache`] with the given [`Group`]. + /// + /// Caches within the same group may reuse internal rendering storage. + /// + /// You should generally group caches that are likely to change + /// together. + pub fn with_group(group: Group) -> Self { + Cache { + group, + state: RefCell::new(State::Empty { previous: None }), + } + } + + /// Returns the [`Group`] of the [`Cache`]. + pub fn group(&self) -> Group { + self.group + } + + /// Puts the given value in the [`Cache`]. + /// + /// Notice that, given this is a cache, a mutable reference is not + /// necessary to call this method. You can safely update the cache in + /// rendering code. + pub fn put(&self, value: T) { + *self.state.borrow_mut() = State::Filled { current: value }; + } + + /// Returns a reference cell to the internal [`State`] of the [`Cache`]. + pub fn state(&self) -> &RefCell> { + &self.state + } + + /// Clears the [`Cache`]. + pub fn clear(&self) + where + T: Clone, + { + use std::ops::Deref; + + let previous = match self.state.borrow().deref() { + State::Empty { previous } => previous.clone(), + State::Filled { current } => Some(current.clone()), + }; + + *self.state.borrow_mut() = State::Empty { previous }; + } +} + +/// A cache group. +/// +/// Caches that share the same group generally change together. +/// +/// A cache group can be used to implement certain performance +/// optimizations during rendering, like batching or sharing atlases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Group(u64); + +impl Group { + /// Generates a new unique cache [`Group`]. + pub fn unique() -> Self { + static NEXT: AtomicU64 = AtomicU64::new(0); + + Self(NEXT.fetch_add(1, atomic::Ordering::Relaxed)) + } +} + +impl fmt::Debug for Cache +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::ops::Deref; + + let state = self.state.borrow(); + + match state.deref() { + State::Empty { previous } => { + write!(f, "Cache::Empty {{ previous: {previous:?} }}") + } + State::Filled { current } => { + write!(f, "Cache::Filled {{ current: {current:?} }}") + } + } + } +} + +impl Default for Cache { + fn default() -> Self { + Self::new() + } +} + +/// The state of a [`Cache`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum State { + /// The [`Cache`] is empty. + Empty { + /// The previous value of the [`Cache`]. + previous: Option, + }, + /// The [`Cache`] is filled. + Filled { + /// The current value of the [`Cache`] + current: T, + }, +} + +/// A piece of data that can be cached. +pub trait Cached: Sized { + /// The type of cache produced. + type Cache: Clone; + + /// Loads the [`Cache`] into a proper instance. + /// + /// [`Cache`]: Self::Cache + fn load(cache: &Self::Cache) -> Self; + + /// Caches this value, producing its corresponding [`Cache`]. + /// + /// [`Cache`]: Self::Cache + fn cache(self, group: Group, previous: Option) -> Self::Cache; +} + +#[cfg(debug_assertions)] +impl Cached for () { + type Cache = (); + + fn load(_cache: &Self::Cache) -> Self {} + + fn cache( + self, + _group: Group, + _previous: Option, + ) -> Self::Cache { + } +} diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs deleted file mode 100644 index c0e4e029..00000000 --- a/graphics/src/cached.rs +++ /dev/null @@ -1,24 +0,0 @@ -/// A piece of data that can be cached. -pub trait Cached: Sized { - /// The type of cache produced. - type Cache: Clone; - - /// Loads the [`Cache`] into a proper instance. - /// - /// [`Cache`]: Self::Cache - fn load(cache: &Self::Cache) -> Self; - - /// Caches this value, producing its corresponding [`Cache`]. - /// - /// [`Cache`]: Self::Cache - fn cache(self, previous: Option) -> Self::Cache; -} - -#[cfg(debug_assertions)] -impl Cached for () { - type Cache = (); - - fn load(_cache: &Self::Cache) -> Self {} - - fn cache(self, _previous: Option) -> Self::Cache {} -} diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index dd07097f..ab4a7a36 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -18,8 +18,8 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::cache::Cached; use crate::core::{self, Size}; -use crate::Cached; /// A renderer capable of drawing some [`Self::Geometry`]. pub trait Renderer: core::Renderer { diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs index 665e996b..d70cee0b 100644 --- a/graphics/src/geometry/cache.rs +++ b/graphics/src/geometry/cache.rs @@ -1,8 +1,8 @@ +use crate::cache::{self, Cached}; use crate::core::Size; use crate::geometry::{self, Frame}; -use crate::Cached; -use std::cell::RefCell; +pub use cache::Group; /// A simple cache that stores generated geometry to avoid recomputation. /// @@ -12,7 +12,13 @@ pub struct Cache where Renderer: geometry::Renderer, { - state: RefCell>, + raw: crate::Cache::Cache>>, +} + +#[derive(Debug, Clone)] +struct Data { + bounds: Size, + geometry: T, } impl Cache @@ -22,20 +28,25 @@ where /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - state: RefCell::new(State::Empty { previous: None }), + raw: cache::Cache::new(), + } + } + + /// Creates a new empty [`Cache`] with the given [`Group`]. + /// + /// Caches within the same group may reuse internal rendering storage. + /// + /// You should generally group caches that are likely to change + /// together. + pub fn with_group(group: Group) -> Self { + Cache { + raw: crate::Cache::with_group(group), } } /// Clears the [`Cache`], forcing a redraw the next time it is used. pub fn clear(&self) { - use std::ops::Deref; - - let previous = match self.state.borrow().deref() { - State::Empty { previous } => previous.clone(), - State::Filled { geometry, .. } => Some(geometry.clone()), - }; - - *self.state.borrow_mut() = State::Empty { previous }; + self.raw.clear(); } /// Draws geometry using the provided closure and stores it in the @@ -56,27 +67,30 @@ where ) -> Renderer::Geometry { use std::ops::Deref; - let previous = match self.state.borrow().deref() { - State::Empty { previous } => previous.clone(), - State::Filled { - bounds: cached_bounds, - geometry, - } => { - if *cached_bounds == bounds { - return Cached::load(geometry); + let state = self.raw.state(); + + let previous = match state.borrow().deref() { + cache::State::Empty { previous } => { + previous.as_ref().map(|data| data.geometry.clone()) + } + cache::State::Filled { current } => { + if current.bounds == bounds { + return Cached::load(¤t.geometry); } - Some(geometry.clone()) + Some(current.geometry.clone()) } }; let mut frame = Frame::new(renderer, bounds); draw_fn(&mut frame); - let geometry = frame.into_geometry().cache(previous); + let geometry = frame.into_geometry().cache(self.raw.group(), previous); let result = Cached::load(&geometry); - *self.state.borrow_mut() = State::Filled { bounds, geometry }; + *state.borrow_mut() = cache::State::Filled { + current: Data { bounds, geometry }, + }; result } @@ -85,16 +99,10 @@ where impl std::fmt::Debug for Cache where Renderer: geometry::Renderer, + ::Cache: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let state = self.state.borrow(); - - match *state { - State::Empty { .. } => write!(f, "Cache::Empty"), - State::Filled { bounds, .. } => { - write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") - } - } + write!(f, "{:?}", &self.raw) } } @@ -106,16 +114,3 @@ where Self::new() } } - -enum State -where - Geometry: Cached, -{ - Empty { - previous: Option, - }, - Filled { - bounds: Size, - geometry: Geometry::Cache, - }, -} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 865ebd97..b5ef55e7 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,10 +9,10 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; -mod cached; mod settings; mod viewport; +pub mod cache; pub mod color; pub mod compositor; pub mod damage; @@ -27,7 +27,7 @@ pub mod text; pub mod geometry; pub use antialiasing::Antialiasing; -pub use cached::Cached; +pub use cache::Cache; pub use compositor::Compositor; pub use error::Error; pub use gradient::Gradient; -- cgit From b276a603a17eda219b32f207aa53e2b6a1321a9f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 Apr 2024 23:15:04 +0200 Subject: Fix cache trimming loop in `iced_wgpu::text` --- graphics/src/cache.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs index 7106c392..bbba79eb 100644 --- a/graphics/src/cache.rs +++ b/graphics/src/cache.rs @@ -15,7 +15,7 @@ impl Cache { /// Creates a new empty [`Cache`]. pub fn new() -> Self { Cache { - group: Group::unique(), + group: Group::singleton(), state: RefCell::new(State::Empty { previous: None }), } } @@ -27,6 +27,11 @@ impl Cache { /// You should generally group caches that are likely to change /// together. pub fn with_group(group: Group) -> Self { + assert!( + !group.is_singleton(), + "The group {group:?} cannot be shared!" + ); + Cache { group, state: RefCell::new(State::Empty { previous: None }), @@ -75,14 +80,40 @@ impl Cache { /// A cache group can be used to implement certain performance /// optimizations during rendering, like batching or sharing atlases. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Group(u64); +pub struct Group { + id: u64, + is_singleton: bool, +} impl Group { /// Generates a new unique cache [`Group`]. pub fn unique() -> Self { static NEXT: AtomicU64 = AtomicU64::new(0); - Self(NEXT.fetch_add(1, atomic::Ordering::Relaxed)) + Self { + id: NEXT.fetch_add(1, atomic::Ordering::Relaxed), + is_singleton: false, + } + } + + /// Returns `true` if the [`Group`] can only ever have a + /// single [`Cache`] in it. + /// + /// This is the default kind of [`Group`] assigned when using + /// [`Cache::new`]. + /// + /// Knowing that a [`Group`] will never be shared may be + /// useful for rendering backends to perform additional + /// optimizations. + pub fn is_singleton(self) -> bool { + self.is_singleton + } + + fn singleton() -> Self { + Self { + is_singleton: true, + ..Self::unique() + } } } -- cgit From 45254ab88c6ca76759523069c2fb8734de626f02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 00:55:49 +0200 Subject: Use `Bytes` as the `Container` of `ImageBuffer` Since we don't need to mutate images once loaded, we avoid unnecessary extra allocations. --- graphics/src/image.rs | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index c6135e9e..2a630530 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -50,7 +50,8 @@ impl Image { /// [`Handle`]: image::Handle pub fn load( handle: &image::Handle, -) -> ::image::ImageResult<::image::DynamicImage> { +) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba, image::Bytes>> +{ use bitflags::bitflags; bitflags! { @@ -100,8 +101,8 @@ pub fn load( } } - match handle.data() { - image::Data::Path(path) => { + let (width, height, pixels) = match handle { + image::Handle::Path(path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -110,33 +111,43 @@ pub fn load( .and_then(|mut reader| Operation::from_exif(&mut reader).ok()) .unwrap_or_else(Operation::empty); - Ok(operation.perform(image)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Bytes(bytes) => { + image::Handle::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)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Rgba { + image::Handle::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, - ), - )) - } - } + } => (*width, *height, pixels.clone()), + }; + + if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) { + Ok(image) + } else { + Err(::image::error::ImageError::Limits( + ::image::error::LimitError::from_kind( + ::image::error::LimitErrorKind::DimensionError, + ), + )) } } -- cgit From b52c7bb610f593fffc624d461dca17ac50c81626 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 May 2024 01:39:43 +0200 Subject: Use an opaque `Id` type for `image::Handle` Hashing pointers is a terrible idea. --- graphics/src/image.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 2a630530..04c45057 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -102,7 +102,7 @@ pub fn load( } let (width, height, pixels) = match handle { - image::Handle::Path(path) => { + image::Handle::Path(_, path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -119,7 +119,7 @@ pub fn load( image::Bytes::from(rgba.into_raw()), ) } - image::Handle::Bytes(bytes) => { + image::Handle::Bytes(_, bytes) => { let image = ::image::load_from_memory(bytes)?; let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) @@ -138,6 +138,7 @@ pub fn load( width, height, pixels, + .. } => (*width, *height, pixels.clone()), }; -- cgit From 09a6bcfffc24f5abdc8709403bab7ae1e01563f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 13:15:17 +0200 Subject: Add `Image` rotation support Co-authored-by: DKolter <68352124+DKolter@users.noreply.github.com> --- graphics/src/image.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 04c45057..083248bf 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -2,9 +2,7 @@ #[cfg(feature = "image")] pub use ::image as image_rs; -use crate::core::image; -use crate::core::svg; -use crate::core::{Color, Rectangle}; +use crate::core::{image, svg, Color, Rectangle, Size}; /// A raster or vector image. #[derive(Debug, Clone, PartialEq)] @@ -19,6 +17,12 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, + + /// The rotation of the image in radians + rotation: f32, + + /// The scale of the image after rotation + scale: Size, }, /// A vector image. Vector { @@ -30,6 +34,12 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, + + /// The rotation of the image in radians + rotation: f32, + + /// The scale of the image after rotation + scale: Size, }, } -- cgit From a57313b23ecb9843856ca0ea08635b6121fcb2cb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 May 2024 15:21:22 +0200 Subject: Simplify image rotation API and its internals --- graphics/src/image.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 083248bf..4fd6998d 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -2,7 +2,7 @@ #[cfg(feature = "image")] pub use ::image as image_rs; -use crate::core::{image, svg, Color, Rectangle, Size}; +use crate::core::{image, svg, Color, Radians, Rectangle}; /// A raster or vector image. #[derive(Debug, Clone, PartialEq)] @@ -19,10 +19,7 @@ pub enum Image { bounds: Rectangle, /// The rotation of the image in radians - rotation: f32, - - /// The scale of the image after rotation - scale: Size, + rotation: Radians, }, /// A vector image. Vector { @@ -36,10 +33,7 @@ pub enum Image { bounds: Rectangle, /// The rotation of the image in radians - rotation: f32, - - /// The scale of the image after rotation - scale: Size, + rotation: Radians, }, } -- cgit From eac5bcb64f17dfbb52b64ea4f95693462986bb69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 07:04:57 +0200 Subject: Fix `Image::bounds` when rotation present in `iced_graphics` --- graphics/src/image.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 4fd6998d..9d09bf4c 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -41,9 +41,12 @@ impl Image { /// Returns the bounds of the [`Image`]. pub fn bounds(&self) -> Rectangle { match self { - Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => { - *bounds + Image::Raster { + bounds, rotation, .. } + | Image::Vector { + bounds, rotation, .. + } => bounds.rotate(*rotation), } } } -- cgit From fa9e1d96ea1924b51749b775ea0e67e69bc8a305 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 May 2024 13:25:58 +0200 Subject: Introduce dynamic `opacity` support for `Image` and `Svg` --- graphics/src/image.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'graphics/src') diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 9d09bf4c..318592be 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -18,8 +18,11 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, - /// The rotation of the image in radians + /// The rotation of the image. rotation: Radians, + + /// The opacity of the image. + opacity: f32, }, /// A vector image. Vector { @@ -32,8 +35,11 @@ pub enum Image { /// The bounds of the image. bounds: Rectangle, - /// The rotation of the image in radians + /// The rotation of the image. rotation: Radians, + + /// The opacity of the image. + opacity: f32, }, } -- cgit