diff options
| -rw-r--r-- | examples/clock/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/clock/src/main.rs | 2 | ||||
| -rw-r--r-- | examples/the_matrix/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/the_matrix/src/main.rs | 39 | ||||
| -rw-r--r-- | graphics/src/cache.rs | 189 | ||||
| -rw-r--r-- | graphics/src/cached.rs | 24 | ||||
| -rw-r--r-- | graphics/src/geometry.rs | 2 | ||||
| -rw-r--r-- | graphics/src/geometry/cache.rs | 83 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 4 | ||||
| -rw-r--r-- | renderer/src/fallback.rs | 16 | ||||
| -rw-r--r-- | tiny_skia/src/geometry.rs | 5 | ||||
| -rw-r--r-- | wgpu/src/geometry.rs | 13 | ||||
| -rw-r--r-- | wgpu/src/text.rs | 164 | ||||
| -rw-r--r-- | wgpu/src/triangle.rs | 2 | ||||
| -rw-r--r-- | widget/src/canvas.rs | 1 | 
15 files changed, 398 insertions, 148 deletions
diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 2d3d5908..dc2e5382 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -10,3 +10,4 @@ iced.workspace = true  iced.features = ["canvas", "tokio", "debug"]  time = { version = "0.3", features = ["local-offset"] } +tracing-subscriber = "0.3" diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 897f8f1b..d717db36 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -8,6 +8,8 @@ use iced::{  };  pub fn main() -> iced::Result { +    tracing_subscriber::fmt::init(); +      iced::program("Clock - Iced", Clock::update, Clock::view)          .subscription(Clock::subscription)          .theme(Clock::theme) diff --git a/examples/the_matrix/Cargo.toml b/examples/the_matrix/Cargo.toml index 17cf443b..775e76e0 100644 --- a/examples/the_matrix/Cargo.toml +++ b/examples/the_matrix/Cargo.toml @@ -10,3 +10,4 @@ iced.workspace = true  iced.features = ["canvas", "tokio", "debug"]  rand = "0.8" +tracing-subscriber = "0.3" diff --git a/examples/the_matrix/src/main.rs b/examples/the_matrix/src/main.rs index 55c9da4b..f3a67ac8 100644 --- a/examples/the_matrix/src/main.rs +++ b/examples/the_matrix/src/main.rs @@ -1,22 +1,25 @@  use iced::mouse;  use iced::time::{self, Instant};  use iced::widget::canvas; -use iced::widget::canvas::{Cache, Geometry};  use iced::{      Color, Element, Font, Length, Point, Rectangle, Renderer, Subscription,      Theme,  }; +use std::cell::RefCell; +  pub fn main() -> iced::Result { +    tracing_subscriber::fmt::init(); +      iced::program("The Matrix - Iced", TheMatrix::update, TheMatrix::view)          .subscription(TheMatrix::subscription)          .antialiasing(true)          .run()  } +#[derive(Default)]  struct TheMatrix { -    ticks: usize, -    backgrounds: Vec<Cache>, +    tick: usize,  }  #[derive(Debug, Clone, Copy)] @@ -28,7 +31,7 @@ impl TheMatrix {      fn update(&mut self, message: Message) {          match message {              Message::Tick(_now) => { -                self.ticks += 1; +                self.tick += 1;              }          }      } @@ -45,35 +48,31 @@ impl TheMatrix {      }  } -impl Default for TheMatrix { -    fn default() -> Self { -        let mut backgrounds = Vec::with_capacity(30); -        backgrounds.resize_with(30, Cache::default); - -        Self { -            ticks: 0, -            backgrounds, -        } -    } -} -  impl<Message> canvas::Program<Message> for TheMatrix { -    type State = (); +    type State = RefCell<Vec<canvas::Cache>>;      fn draw(          &self, -        _state: &Self::State, +        state: &Self::State,          renderer: &Renderer,          _theme: &Theme,          bounds: Rectangle,          _cursor: mouse::Cursor, -    ) -> Vec<Geometry> { +    ) -> Vec<canvas::Geometry> {          use rand::distributions::Distribution;          use rand::Rng;          const CELL_SIZE: f32 = 10.0; -        vec![self.backgrounds[self.ticks % self.backgrounds.len()].draw( +        let mut caches = state.borrow_mut(); + +        if caches.is_empty() { +            let group = canvas::Group::unique(); + +            caches.resize_with(30, || canvas::Cache::with_group(group)); +        } + +        vec![caches[self.tick % caches.len()].draw(              renderer,              bounds.size(),              |frame| { diff --git a/graphics/src/cache.rs b/graphics/src/cache.rs new file mode 100644 index 00000000..bbba79eb --- /dev/null +++ b/graphics/src/cache.rs @@ -0,0 +1,189 @@ +//! 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<T> { +    group: Group, +    state: RefCell<State<T>>, +} + +impl<T> Cache<T> { +    /// Creates a new empty [`Cache`]. +    pub fn new() -> Self { +        Cache { +            group: Group::singleton(), +            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 { +        assert!( +            !group.is_singleton(), +            "The group {group:?} cannot be shared!" +        ); + +        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<State<T>> { +        &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 { +    id: u64, +    is_singleton: bool, +} + +impl Group { +    /// Generates a new unique cache [`Group`]. +    pub fn unique() -> Self { +        static NEXT: AtomicU64 = AtomicU64::new(0); + +        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() +        } +    } +} + +impl<T> fmt::Debug for Cache<T> +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<T> Default for Cache<T> { +    fn default() -> Self { +        Self::new() +    } +} + +/// The state of a [`Cache`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum State<T> { +    /// The [`Cache`] is empty. +    Empty { +        /// The previous value of the [`Cache`]. +        previous: Option<T>, +    }, +    /// 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>) -> 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>, +    ) -> 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>) -> Self::Cache; -} - -#[cfg(debug_assertions)] -impl Cached for () { -    type Cache = (); - -    fn load(_cache: &Self::Cache) -> Self {} - -    fn cache(self, _previous: Option<Self::Cache>) -> 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<Renderer>  where      Renderer: geometry::Renderer,  { -    state: RefCell<State<Renderer::Geometry>>, +    raw: crate::Cache<Data<<Renderer::Geometry as Cached>::Cache>>, +} + +#[derive(Debug, Clone)] +struct Data<T> { +    bounds: Size, +    geometry: T,  }  impl<Renderer> Cache<Renderer> @@ -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<Renderer> std::fmt::Debug for Cache<Renderer>  where      Renderer: geometry::Renderer, +    <Renderer::Geometry as Cached>::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<Geometry> -where -    Geometry: Cached, -{ -    Empty { -        previous: Option<Geometry::Cache>, -    }, -    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; diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index c932de00..5f69b420 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -428,8 +428,8 @@ where  mod geometry {      use super::Renderer;      use crate::core::{Point, Radians, Rectangle, Size, Vector}; +    use crate::graphics::cache::{self, Cached};      use crate::graphics::geometry::{self, Fill, Path, Stroke, Text}; -    use crate::graphics::Cached;      impl<A, B> geometry::Renderer for Renderer<A, B>      where @@ -483,21 +483,25 @@ mod geometry {              }          } -        fn cache(self, previous: Option<Self::Cache>) -> Self::Cache { +        fn cache( +            self, +            group: cache::Group, +            previous: Option<Self::Cache>, +        ) -> Self::Cache {              match (self, previous) {                  (                      Self::Primary(geometry),                      Some(Geometry::Primary(previous)), -                ) => Geometry::Primary(geometry.cache(Some(previous))), +                ) => Geometry::Primary(geometry.cache(group, Some(previous))),                  (Self::Primary(geometry), None) => { -                    Geometry::Primary(geometry.cache(None)) +                    Geometry::Primary(geometry.cache(group, None))                  }                  (                      Self::Secondary(geometry),                      Some(Geometry::Secondary(previous)), -                ) => Geometry::Secondary(geometry.cache(Some(previous))), +                ) => Geometry::Secondary(geometry.cache(group, Some(previous))),                  (Self::Secondary(geometry), None) => { -                    Geometry::Secondary(geometry.cache(None)) +                    Geometry::Secondary(geometry.cache(group, None))                  }                  _ => unreachable!(),              } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 117daf41..02b6e1b9 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,10 @@  use crate::core::text::LineHeight;  use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector}; +use crate::graphics::cache::{self, Cached};  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::stroke::{self, Stroke};  use crate::graphics::geometry::{self, Path, Style}; -use crate::graphics::{Cached, Gradient, Text}; +use crate::graphics::{Gradient, Text};  use crate::Primitive;  use std::rc::Rc; @@ -32,7 +33,7 @@ impl Cached for Geometry {          Self::Cache(cache.clone())      } -    fn cache(self, _previous: Option<Cache>) -> Cache { +    fn cache(self, _group: cache::Group, _previous: Option<Cache>) -> Cache {          match self {              Self::Live {                  primitives, diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 60967082..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,6 +3,7 @@ use crate::core::text::LineHeight;  use crate::core::{      Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,  }; +use crate::graphics::cache::{self, Cached};  use crate::graphics::color;  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::{ @@ -10,7 +11,7 @@ use crate::graphics::geometry::{  };  use crate::graphics::gradient::{self, Gradient};  use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Cached, Text}; +use crate::graphics::{self, Text};  use crate::text;  use crate::triangle; @@ -38,7 +39,11 @@ impl Cached for Geometry {          Geometry::Cached(cache.clone())      } -    fn cache(self, previous: Option<Self::Cache>) -> Self::Cache { +    fn cache( +        self, +        group: cache::Group, +        previous: Option<Self::Cache>, +    ) -> Self::Cache {          match self {              Self::Live { meshes, text } => {                  if let Some(mut previous) = previous { @@ -51,14 +56,14 @@ impl Cached for Geometry {                      if let Some(cache) = &mut previous.text {                          cache.update(text);                      } else { -                        previous.text = text::Cache::new(text); +                        previous.text = text::Cache::new(group, text);                      }                      previous                  } else {                      Cache {                          meshes: triangle::Cache::new(meshes), -                        text: text::Cache::new(text), +                        text: text::Cache::new(group, text),                      }                  }              } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 38712660..7e683c77 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,7 +1,8 @@  use crate::core::alignment;  use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::cache;  use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache as BufferCache}; +use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};  use crate::graphics::text::{font_system, to_color, Editor, Paragraph};  use rustc_hash::FxHashMap; @@ -35,6 +36,7 @@ pub enum Item {  #[derive(Debug, Clone)]  pub struct Cache {      id: Id, +    group: cache::Group,      text: Rc<[Text]>,      version: usize,  } @@ -43,7 +45,7 @@ pub struct Cache {  pub struct Id(u64);  impl Cache { -    pub fn new(text: Vec<Text>) -> Option<Self> { +    pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {          static NEXT_ID: AtomicU64 = AtomicU64::new(0);          if text.is_empty() { @@ -52,12 +54,17 @@ impl Cache {          Some(Self {              id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), +            group,              text: Rc::from(text),              version: 0,          })      }      pub fn update(&mut self, text: Vec<Text>) { +        if self.text.is_empty() && text.is_empty() { +            return; +        } +          self.text = Rc::from(text);          self.version += 1;      } @@ -65,29 +72,41 @@ impl Cache {  struct Upload {      renderer: glyphon::TextRenderer, -    atlas: glyphon::TextAtlas,      buffer_cache: BufferCache,      transformation: Transformation,      version: usize, +    group_version: usize,      text: rc::Weak<[Text]>, +    _atlas: rc::Weak<()>,  }  #[derive(Default)]  pub struct Storage { +    groups: FxHashMap<cache::Group, Group>,      uploads: FxHashMap<Id, Upload>,  } +struct Group { +    atlas: glyphon::TextAtlas, +    version: usize, +    should_trim: bool, +    handle: Rc<()>, // Keeps track of active uploads +} +  impl Storage {      pub fn new() -> Self {          Self::default()      } -    fn get(&self, cache: &Cache) -> Option<&Upload> { +    fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> {          if cache.text.is_empty() {              return None;          } -        self.uploads.get(&cache.id) +        self.groups +            .get(&cache.group) +            .map(|group| &group.atlas) +            .zip(self.uploads.get(&cache.id))      }      fn prepare( @@ -101,42 +120,63 @@ impl Storage {          bounds: Rectangle,          target_size: Size<u32>,      ) { +        let group_count = self.groups.len(); + +        let group = self.groups.entry(cache.group).or_insert_with(|| { +            log::debug!( +                "New text atlas: {:?} (total: {})", +                cache.group, +                group_count + 1 +            ); + +            Group { +                atlas: glyphon::TextAtlas::with_color_mode( +                    device, queue, format, COLOR_MODE, +                ), +                version: 0, +                should_trim: false, +                handle: Rc::new(()), +            } +        }); +          match self.uploads.entry(cache.id) {              hash_map::Entry::Occupied(entry) => {                  let upload = entry.into_mut(); -                if !cache.text.is_empty() -                    && (upload.version != cache.version -                        || upload.transformation != new_transformation) +                if upload.version != cache.version +                    || upload.group_version != group.version +                    || upload.transformation != new_transformation                  { -                    let _ = prepare( -                        device, -                        queue, -                        encoder, -                        &mut upload.renderer, -                        &mut upload.atlas, -                        &mut upload.buffer_cache, -                        &cache.text, -                        bounds, -                        new_transformation, -                        target_size, -                    ); +                    if !cache.text.is_empty() { +                        let _ = prepare( +                            device, +                            queue, +                            encoder, +                            &mut upload.renderer, +                            &mut group.atlas, +                            &mut upload.buffer_cache, +                            &cache.text, +                            bounds, +                            new_transformation, +                            target_size, +                        ); +                    } + +                    // Only trim if glyphs have changed +                    group.should_trim = +                        group.should_trim || upload.version != cache.version;                      upload.text = Rc::downgrade(&cache.text);                      upload.version = cache.version; +                    upload.group_version = group.version;                      upload.transformation = new_transformation;                      upload.buffer_cache.trim(); -                    upload.atlas.trim();                  }              }              hash_map::Entry::Vacant(entry) => { -                let mut atlas = glyphon::TextAtlas::with_color_mode( -                    device, queue, format, COLOR_MODE, -                ); -                  let mut renderer = glyphon::TextRenderer::new( -                    &mut atlas, +                    &mut group.atlas,                      device,                      wgpu::MultisampleState::default(),                      None, @@ -144,29 +184,34 @@ impl Storage {                  let mut buffer_cache = BufferCache::new(); -                let _ = prepare( -                    device, -                    queue, -                    encoder, -                    &mut renderer, -                    &mut atlas, -                    &mut buffer_cache, -                    &cache.text, -                    bounds, -                    new_transformation, -                    target_size, -                ); +                if !cache.text.is_empty() { +                    let _ = prepare( +                        device, +                        queue, +                        encoder, +                        &mut renderer, +                        &mut group.atlas, +                        &mut buffer_cache, +                        &cache.text, +                        bounds, +                        new_transformation, +                        target_size, +                    ); +                }                  let _ = entry.insert(Upload {                      renderer, -                    atlas,                      buffer_cache,                      transformation: new_transformation,                      version: 0, +                    group_version: group.version,                      text: Rc::downgrade(&cache.text), +                    _atlas: Rc::downgrade(&group.handle),                  }); -                log::info!( +                group.should_trim = cache.group.is_singleton(); + +                log::debug!(                      "New text upload: {} (total: {})",                      cache.id.0,                      self.uploads.len() @@ -178,6 +223,37 @@ impl Storage {      pub fn trim(&mut self) {          self.uploads              .retain(|_id, upload| upload.text.strong_count() > 0); + +        self.groups.retain(|id, group| { +            let active_uploads = Rc::weak_count(&group.handle); + +            if active_uploads == 0 { +                log::debug!("Dropping text atlas: {id:?}"); + +                return false; +            } + +            if group.should_trim { +                log::trace!("Trimming text atlas: {id:?}"); + +                group.atlas.trim(); +                group.should_trim = false; + +                // We only need to worry about glyph fighting +                // when the atlas may be shared by multiple +                // uploads. +                if !id.is_singleton() { +                    log::debug!( +                        "Invalidating text atlas: {id:?} \ +                        (uploads: {active_uploads})" +                    ); + +                    group.version += 1; +                } +            } + +            true +        });      }  } @@ -306,10 +382,10 @@ impl Pipeline {                      layer_count += 1;                  }                  Item::Cached { cache, .. } => { -                    if let Some(upload) = storage.get(cache) { +                    if let Some((atlas, upload)) = storage.get(cache) {                          upload                              .renderer -                            .render(&upload.atlas, render_pass) +                            .render(atlas, render_pass)                              .expect("Render cached text");                      }                  } @@ -345,7 +421,7 @@ fn prepare(      enum Allocation {          Paragraph(Paragraph),          Editor(Editor), -        Cache(cache::KeyHash), +        Cache(text_cache::KeyHash),          Raw(Arc<glyphon::Buffer>),      } @@ -369,7 +445,7 @@ fn prepare(              } => {                  let (key, _) = buffer_cache.allocate(                      font_system, -                    cache::Key { +                    text_cache::Key {                          content,                          size: f32::from(*size),                          line_height: f32::from(*line_height), diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index ca36de82..b0551f55 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -138,7 +138,7 @@ impl Storage {                      batch: Rc::downgrade(&cache.batch),                  }); -                log::info!( +                log::debug!(                      "New mesh upload: {} (total: {})",                      cache.id.0,                      self.uploads.len() diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 42f92de0..be09f163 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -6,6 +6,7 @@ mod program;  pub use event::Event;  pub use program::Program; +pub use crate::graphics::cache::Group;  pub use crate::graphics::geometry::{      fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,      Path, Stroke, Style, Text,  | 
