Merge pull request #1938 from iced-rs/text-cache-modes
Text cache modes
This commit is contained in:
commit
a057f8811b
6 changed files with 99 additions and 28 deletions
|
|
@ -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`.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue