From 98febd9a428f56e28112257adb72ef603fd2ddc5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 17:54:54 +0200 Subject: Introduce `Mode` for `text::Cache` and trim only when switching modes --- wgpu/src/text.rs | 82 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1b94edf6..c87b94a8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -80,6 +80,11 @@ impl Pipeline { let renderer = &mut self.renderers[self.prepare_layer]; let cache = self.cache.get_mut(); + if self.prepare_layer == 0 { + let _ = cache.switch(Mode::Drawing); + cache.trim(); + } + let keys: Vec<_> = sections .iter() .map(|section| { @@ -224,7 +229,6 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); - self.cache.get_mut().trim(); self.prepare_layer = 0; } @@ -238,11 +242,15 @@ impl Pipeline { bounds: Size, shaping: Shaping, ) -> Size { - let mut measurement_cache = self.cache.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, entry) = measurement_cache.allocate( + if cache.switch(Mode::Measuring) { + cache.trim(); + } + + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -268,11 +276,15 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option { - let mut measurement_cache = self.cache.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, entry) = measurement_cache.allocate( + if cache.switch(Mode::Measuring) { + cache.trim(); + } + + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -348,9 +360,11 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping { struct Cache { entries: FxHashMap, - measurements: FxHashMap, - recently_used: FxHashSet, + aliases: FxHashMap, + recently_measured: FxHashSet, + recently_drawn: FxHashSet, hasher: HashBuilder, + mode: Mode, } struct Entry { @@ -358,6 +372,12 @@ struct Entry { bounds: Size, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Measuring, + Drawing, +} + #[cfg(not(target_arch = "wasm32"))] type HashBuilder = twox_hash::RandomXxHashBuilder64; @@ -368,9 +388,11 @@ impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), - measurements: FxHashMap::default(), - recently_used: FxHashSet::default(), + aliases: FxHashMap::default(), + recently_measured: FxHashSet::default(), + recently_drawn: FxHashSet::default(), hasher: HashBuilder::default(), + mode: Mode::Measuring, } } @@ -378,6 +400,14 @@ impl Cache { self.entries.get(key) } + fn switch(&mut self, mode: Mode) -> bool { + let has_changed = self.mode != mode; + + self.mode = mode; + + has_changed + } + fn allocate( &mut self, font_system: &mut glyphon::FontSystem, @@ -385,8 +415,13 @@ impl Cache { ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - if let Some(hash) = self.measurements.get(&hash) { - let _ = self.recently_used.insert(*hash); + let recently_used = match self.mode { + Mode::Measuring => &mut self.recently_measured, + Mode::Drawing => &mut self.recently_drawn, + }; + + if let Some(hash) = self.aliases.get(&hash) { + let _ = recently_used.insert(*hash); return (*hash, self.entries.get_mut(hash).unwrap()); } @@ -421,7 +456,7 @@ impl Cache { }, ] { if key.bounds != bounds { - let _ = self.measurements.insert( + let _ = self.aliases.insert( Key { bounds, ..key }.hash(self.hasher.build_hasher()), hash, ); @@ -429,18 +464,29 @@ impl Cache { } } - let _ = self.recently_used.insert(hash); + let _ = recently_used.insert(hash); (hash, self.entries.get_mut(&hash).unwrap()) } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - self.measurements - .retain(|_, value| self.recently_used.contains(value)); + self.entries.retain(|key, _| { + self.recently_measured.contains(key) + || self.recently_drawn.contains(key) + }); + self.aliases.retain(|_, value| { + self.recently_measured.contains(value) + || self.recently_drawn.contains(value) + }); - self.recently_used.clear(); + match self.mode { + Mode::Measuring => { + self.recently_measured.clear(); + } + Mode::Drawing => { + self.recently_drawn.clear(); + } + } } } -- cgit From cdce03cf7f520ef0227aaec4eaed19332197f53b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 18:17:18 +0200 Subject: Revert "Remove `layout` method from `core::Renderer` trait" This reverts commit 2128472c2a8afcb59927712497c4f613612e9dcc. --- core/src/renderer.rs | 15 ++++++++++++++- graphics/src/renderer.rs | 13 ++++++++++++- runtime/src/user_interface.rs | 13 ++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 1b327e56..7c73d2e4 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -5,13 +5,26 @@ mod null; #[cfg(debug_assertions)] pub use null::Null; -use crate::{Background, BorderRadius, Color, Rectangle, Vector}; +use crate::layout; +use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector}; /// A component that can be used by widgets to draw themselves on a screen. pub trait Renderer: Sized { /// The supported theme of the [`Renderer`]. type Theme; + /// Lays out the elements of a user interface. + /// + /// You should override this if you need to perform any operations before or + /// after layouting. For instance, trimming the measurements cache. + fn layout( + &mut self, + element: &Element<'_, Message, Self>, + limits: &layout::Limits, + ) -> layout::Node { + element.as_widget().layout(self, limits) + } + /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index d80dea34..476aa407 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -3,10 +3,13 @@ use crate::backend::{self, Backend}; use crate::Primitive; use iced_core::image; +use iced_core::layout; use iced_core::renderer; use iced_core::svg; use iced_core::text::{self, Text}; -use iced_core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use iced_core::{ + Background, Color, Element, Font, Point, Rectangle, Size, Vector, +}; use std::borrow::Cow; use std::marker::PhantomData; @@ -85,6 +88,14 @@ impl Renderer { impl iced_core::Renderer for Renderer { type Theme = T; + fn layout( + &mut self, + element: &Element<'_, Message, Self>, + limits: &layout::Limits, + ) -> layout::Node { + element.as_widget().layout(self, limits) + } + fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { let current = self.start_layer(); diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 34b2ada0..619423fd 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -95,9 +95,8 @@ where let Cache { mut state } = cache; state.diff(root.as_widget()); - let base = root - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, bounds)); + let base = + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); UserInterface { root, @@ -227,8 +226,8 @@ where if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); - self.base = self.root.as_widget().layout( - renderer, + self.base = renderer.layout( + &self.root, &layout::Limits::new(Size::ZERO, self.bounds), ); @@ -323,8 +322,8 @@ where } shell.revalidate_layout(|| { - self.base = self.root.as_widget().layout( - renderer, + self.base = renderer.layout( + &self.root, &layout::Limits::new(Size::ZERO, self.bounds), ); -- cgit From d666e739cdcc2084c14593888867d40066c232fe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 18:23:11 +0200 Subject: Trim text measurements only before `layout` --- graphics/src/backend.rs | 7 +++++++ graphics/src/renderer.rs | 2 ++ wgpu/src/backend.rs | 4 ++++ wgpu/src/text.rs | 45 +++++++++++++++++---------------------------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 77bb650b..59e95bf8 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -12,6 +12,13 @@ use std::borrow::Cow; pub trait Backend { /// The custom kind of primitives this [`Backend`] supports. type Primitive; + + /// Trims the measurements cache. + /// + /// This method is currently necessary to properly trim the text cache in + /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering + /// pipeline. It will be removed in the future. + fn trim_measurements(&mut self) {} } /// A graphics backend that supports text rendering. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 476aa407..c0cec60a 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -93,6 +93,8 @@ impl iced_core::Renderer for Renderer { element: &Element<'_, Message, Self>, limits: &layout::Limits, ) -> layout::Node { + self.backend.trim_measurements(); + element.as_widget().layout(self, limits) } diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 596d43c5..4a0c54f0 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -337,6 +337,10 @@ impl Backend { impl crate::graphics::Backend for Backend { type Primitive = primitive::Custom; + + fn trim_measurements(&mut self) { + self.text_pipeline.trim_measurements(); + } } impl backend::Text for Backend { diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c87b94a8..65d3b818 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -81,8 +81,7 @@ impl Pipeline { let cache = self.cache.get_mut(); if self.prepare_layer == 0 { - let _ = cache.switch(Mode::Drawing); - cache.trim(); + cache.trim(Purpose::Drawing); } let keys: Vec<_> = sections @@ -105,6 +104,7 @@ impl Pipeline { }, shaping: section.shaping, }, + Purpose::Drawing, ); key @@ -233,6 +233,10 @@ impl Pipeline { self.prepare_layer = 0; } + pub fn trim_measurements(&mut self) { + self.cache.get_mut().trim(Purpose::Measuring); + } + pub fn measure( &self, content: &str, @@ -246,10 +250,6 @@ impl Pipeline { let line_height = f32::from(line_height.to_absolute(Pixels(size))); - if cache.switch(Mode::Measuring) { - cache.trim(); - } - let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { @@ -260,6 +260,7 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); entry.bounds @@ -280,10 +281,6 @@ impl Pipeline { let line_height = f32::from(line_height.to_absolute(Pixels(size))); - if cache.switch(Mode::Measuring) { - cache.trim(); - } - let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { @@ -294,6 +291,7 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); let cursor = entry.buffer.hit(point.x, point.y)?; @@ -364,7 +362,6 @@ struct Cache { recently_measured: FxHashSet, recently_drawn: FxHashSet, hasher: HashBuilder, - mode: Mode, } struct Entry { @@ -373,7 +370,7 @@ struct Entry { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Mode { +enum Purpose { Measuring, Drawing, } @@ -392,7 +389,6 @@ impl Cache { recently_measured: FxHashSet::default(), recently_drawn: FxHashSet::default(), hasher: HashBuilder::default(), - mode: Mode::Measuring, } } @@ -400,24 +396,17 @@ impl Cache { self.entries.get(key) } - fn switch(&mut self, mode: Mode) -> bool { - let has_changed = self.mode != mode; - - self.mode = mode; - - has_changed - } - fn allocate( &mut self, font_system: &mut glyphon::FontSystem, key: Key<'_>, + purpose: Purpose, ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - let recently_used = match self.mode { - Mode::Measuring => &mut self.recently_measured, - Mode::Drawing => &mut self.recently_drawn, + let recently_used = match purpose { + Purpose::Measuring => &mut self.recently_measured, + Purpose::Drawing => &mut self.recently_drawn, }; if let Some(hash) = self.aliases.get(&hash) { @@ -469,7 +458,7 @@ impl Cache { (hash, self.entries.get_mut(&hash).unwrap()) } - fn trim(&mut self) { + fn trim(&mut self, purpose: Purpose) { self.entries.retain(|key, _| { self.recently_measured.contains(key) || self.recently_drawn.contains(key) @@ -479,11 +468,11 @@ impl Cache { || self.recently_drawn.contains(value) }); - match self.mode { - Mode::Measuring => { + match purpose { + Purpose::Measuring => { self.recently_measured.clear(); } - Mode::Drawing => { + Purpose::Drawing => { self.recently_drawn.clear(); } } -- cgit