Merge pull request #1938 from iced-rs/text-cache-modes

Text cache modes
This commit is contained in:
Héctor Ramón 2023-06-30 19:10:41 +02:00 committed by GitHub
commit a057f8811b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 99 additions and 28 deletions

View file

@ -5,13 +5,26 @@ mod null;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub use null::Null; 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. /// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized { pub trait Renderer: Sized {
/// The supported theme of the [`Renderer`]. /// The supported theme of the [`Renderer`].
type Theme; 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<Message>(
&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. /// Draws the primitives recorded in the given closure in a new layer.
/// ///
/// The layer will clip its contents to the provided `bounds`. /// The layer will clip its contents to the provided `bounds`.

View file

@ -12,6 +12,13 @@ use std::borrow::Cow;
pub trait Backend { pub trait Backend {
/// The custom kind of primitives this [`Backend`] supports. /// The custom kind of primitives this [`Backend`] supports.
type Primitive; 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. /// A graphics backend that supports text rendering.

View file

@ -3,10 +3,13 @@ use crate::backend::{self, Backend};
use crate::Primitive; use crate::Primitive;
use iced_core::image; use iced_core::image;
use iced_core::layout;
use iced_core::renderer; use iced_core::renderer;
use iced_core::svg; use iced_core::svg;
use iced_core::text::{self, Text}; 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::borrow::Cow;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -85,6 +88,16 @@ impl<B: Backend, T> Renderer<B, T> {
impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> { impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {
type Theme = T; type Theme = T;
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
self.backend.trim_measurements();
element.as_widget().layout(self, limits)
}
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
let current = self.start_layer(); let current = self.start_layer();

View file

@ -95,9 +95,8 @@ where
let Cache { mut state } = cache; let Cache { mut state } = cache;
state.diff(root.as_widget()); state.diff(root.as_widget());
let base = root let base =
.as_widget() renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
.layout(renderer, &layout::Limits::new(Size::ZERO, bounds));
UserInterface { UserInterface {
root, root,
@ -227,8 +226,8 @@ where
if shell.is_layout_invalid() { if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay); let _ = ManuallyDrop::into_inner(manual_overlay);
self.base = self.root.as_widget().layout( self.base = renderer.layout(
renderer, &self.root,
&layout::Limits::new(Size::ZERO, self.bounds), &layout::Limits::new(Size::ZERO, self.bounds),
); );
@ -323,8 +322,8 @@ where
} }
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
self.base = self.root.as_widget().layout( self.base = renderer.layout(
renderer, &self.root,
&layout::Limits::new(Size::ZERO, self.bounds), &layout::Limits::new(Size::ZERO, self.bounds),
); );

View file

@ -337,6 +337,10 @@ impl Backend {
impl crate::graphics::Backend for Backend { impl crate::graphics::Backend for Backend {
type Primitive = primitive::Custom; type Primitive = primitive::Custom;
fn trim_measurements(&mut self) {
self.text_pipeline.trim_measurements();
}
} }
impl backend::Text for Backend { impl backend::Text for Backend {

View file

@ -80,6 +80,10 @@ impl Pipeline {
let renderer = &mut self.renderers[self.prepare_layer]; let renderer = &mut self.renderers[self.prepare_layer];
let cache = self.cache.get_mut(); let cache = self.cache.get_mut();
if self.prepare_layer == 0 {
cache.trim(Purpose::Drawing);
}
let keys: Vec<_> = sections let keys: Vec<_> = sections
.iter() .iter()
.map(|section| { .map(|section| {
@ -100,6 +104,7 @@ impl Pipeline {
}, },
shaping: section.shaping, shaping: section.shaping,
}, },
Purpose::Drawing,
); );
key key
@ -224,11 +229,14 @@ impl Pipeline {
pub fn end_frame(&mut self) { pub fn end_frame(&mut self) {
self.atlas.trim(); self.atlas.trim();
self.cache.get_mut().trim();
self.prepare_layer = 0; self.prepare_layer = 0;
} }
pub fn trim_measurements(&mut self) {
self.cache.get_mut().trim(Purpose::Measuring);
}
pub fn measure( pub fn measure(
&self, &self,
content: &str, content: &str,
@ -238,11 +246,11 @@ impl Pipeline {
bounds: Size, bounds: Size,
shaping: Shaping, shaping: Shaping,
) -> Size { ) -> 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 line_height = f32::from(line_height.to_absolute(Pixels(size)));
let (_, entry) = measurement_cache.allocate( let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(), &mut self.font_system.borrow_mut(),
Key { Key {
content, content,
@ -252,6 +260,7 @@ impl Pipeline {
bounds, bounds,
shaping, shaping,
}, },
Purpose::Measuring,
); );
entry.bounds entry.bounds
@ -268,11 +277,11 @@ impl Pipeline {
point: Point, point: Point,
_nearest_only: bool, _nearest_only: bool,
) -> Option<Hit> { ) -> Option<Hit> {
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 line_height = f32::from(line_height.to_absolute(Pixels(size)));
let (_, entry) = measurement_cache.allocate( let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(), &mut self.font_system.borrow_mut(),
Key { Key {
content, content,
@ -282,6 +291,7 @@ impl Pipeline {
bounds, bounds,
shaping, shaping,
}, },
Purpose::Measuring,
); );
let cursor = entry.buffer.hit(point.x, point.y)?; let cursor = entry.buffer.hit(point.x, point.y)?;
@ -348,8 +358,9 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping {
struct Cache { struct Cache {
entries: FxHashMap<KeyHash, Entry>, entries: FxHashMap<KeyHash, Entry>,
measurements: FxHashMap<KeyHash, KeyHash>, aliases: FxHashMap<KeyHash, KeyHash>,
recently_used: FxHashSet<KeyHash>, recently_measured: FxHashSet<KeyHash>,
recently_drawn: FxHashSet<KeyHash>,
hasher: HashBuilder, hasher: HashBuilder,
} }
@ -358,6 +369,12 @@ struct Entry {
bounds: Size, bounds: Size,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Purpose {
Measuring,
Drawing,
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = twox_hash::RandomXxHashBuilder64;
@ -368,8 +385,9 @@ impl Cache {
fn new() -> Self { fn new() -> Self {
Self { Self {
entries: FxHashMap::default(), entries: FxHashMap::default(),
measurements: FxHashMap::default(), aliases: FxHashMap::default(),
recently_used: FxHashSet::default(), recently_measured: FxHashSet::default(),
recently_drawn: FxHashSet::default(),
hasher: HashBuilder::default(), hasher: HashBuilder::default(),
} }
} }
@ -382,11 +400,17 @@ impl Cache {
&mut self, &mut self,
font_system: &mut glyphon::FontSystem, font_system: &mut glyphon::FontSystem,
key: Key<'_>, key: Key<'_>,
purpose: Purpose,
) -> (KeyHash, &mut Entry) { ) -> (KeyHash, &mut Entry) {
let hash = key.hash(self.hasher.build_hasher()); let hash = key.hash(self.hasher.build_hasher());
if let Some(hash) = self.measurements.get(&hash) { let recently_used = match purpose {
let _ = self.recently_used.insert(*hash); Purpose::Measuring => &mut self.recently_measured,
Purpose::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()); return (*hash, self.entries.get_mut(hash).unwrap());
} }
@ -421,7 +445,7 @@ impl Cache {
}, },
] { ] {
if key.bounds != bounds { if key.bounds != bounds {
let _ = self.measurements.insert( let _ = self.aliases.insert(
Key { bounds, ..key }.hash(self.hasher.build_hasher()), Key { bounds, ..key }.hash(self.hasher.build_hasher()),
hash, hash,
); );
@ -429,18 +453,29 @@ impl Cache {
} }
} }
let _ = self.recently_used.insert(hash); let _ = recently_used.insert(hash);
(hash, self.entries.get_mut(&hash).unwrap()) (hash, self.entries.get_mut(&hash).unwrap())
} }
fn trim(&mut self) { fn trim(&mut self, purpose: Purpose) {
self.entries self.entries.retain(|key, _| {
.retain(|key, _| self.recently_used.contains(key)); self.recently_measured.contains(key)
self.measurements || self.recently_drawn.contains(key)
.retain(|_, value| self.recently_used.contains(value)); });
self.aliases.retain(|_, value| {
self.recently_measured.contains(value)
|| self.recently_drawn.contains(value)
});
self.recently_used.clear(); match purpose {
Purpose::Measuring => {
self.recently_measured.clear();
}
Purpose::Drawing => {
self.recently_drawn.clear();
}
}
} }
} }