summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-02-02 01:24:27 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-02-24 13:22:57 +0100
commit1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e (patch)
tree8cc41f70f935ab332864d3c280f864f0dfc4f505
parent032e860f13a562719faf128238abe7ffb7f2a610 (diff)
downloadiced-1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e.tar.gz
iced-1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e.tar.bz2
iced-1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e.zip
Implement basic text caching in `iced_wgpu`
-rw-r--r--core/src/font.rs16
-rw-r--r--wgpu/Cargo.toml2
-rw-r--r--wgpu/src/text.rs201
3 files changed, 159 insertions, 60 deletions
diff --git a/core/src/font.rs b/core/src/font.rs
index 3f9ad2b5..130e378e 100644
--- a/core/src/font.rs
+++ b/core/src/font.rs
@@ -1,3 +1,5 @@
+use std::hash::{Hash, Hasher};
+
/// A font.
#[derive(Debug, Clone, Copy)]
pub enum Font {
@@ -22,3 +24,17 @@ impl Default for Font {
Font::Default
}
}
+
+impl Hash for Font {
+ fn hash<H: Hasher>(&self, hasher: &mut H) {
+ match self {
+ Self::Default => {
+ 0.hash(hasher);
+ }
+ Self::External { name, .. } => {
+ 1.hash(hasher);
+ name.hash(hasher);
+ }
+ }
+ }
+}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index d29c1129..1a94c6a3 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -34,6 +34,8 @@ guillotiere = "0.6"
futures = "0.3"
bitflags = "1.2"
once_cell = "1.0"
+rustc-hash = "1.1"
+twox-hash = "1.6"
[dependencies.bytemuck]
version = "1.9"
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 3f828da1..a1d621d4 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -2,16 +2,107 @@ pub use iced_native::text::Hit;
use iced_graphics::layer::Text;
use iced_native::alignment;
-use iced_native::{Font, Rectangle, Size};
+use iced_native::{Color, Font, Rectangle, Size};
+
+use rustc_hash::{FxHashMap, FxHashSet};
+use std::cell::RefCell;
+use std::hash::{BuildHasher, Hash, Hasher};
+use twox_hash::RandomXxHashBuilder64;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
cache: glyphon::SwashCache<'static>,
+ measurement_cache: RefCell<Cache>,
+ render_cache: Cache,
layer: usize,
}
+struct Cache {
+ entries: FxHashMap<KeyHash, glyphon::Buffer<'static>>,
+ recently_used: FxHashSet<KeyHash>,
+ hasher: RandomXxHashBuilder64,
+}
+
+impl Cache {
+ fn new() -> Self {
+ Self {
+ entries: FxHashMap::default(),
+ recently_used: FxHashSet::default(),
+ hasher: RandomXxHashBuilder64::default(),
+ }
+ }
+
+ fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> {
+ self.entries.get(key)
+ }
+
+ fn allocate(
+ &mut self,
+ key: Key<'_>,
+ ) -> (KeyHash, &mut glyphon::Buffer<'static>) {
+ let hash = {
+ let mut hasher = self.hasher.build_hasher();
+
+ key.content.hash(&mut hasher);
+ (key.size as i32).hash(&mut hasher);
+ key.font.hash(&mut hasher);
+ (key.bounds.width as i32).hash(&mut hasher);
+ (key.bounds.height as i32).hash(&mut hasher);
+ key.color.into_rgba8().hash(&mut hasher);
+
+ hasher.finish()
+ };
+
+ if !self.entries.contains_key(&hash) {
+ let metrics =
+ glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32);
+
+ let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics);
+
+ buffer.set_size(key.bounds.width as i32, key.bounds.height as i32);
+ buffer.set_text(
+ key.content,
+ glyphon::Attrs::new().family(to_family(key.font)).color({
+ let [r, g, b, a] = key.color.into_linear();
+
+ glyphon::Color::rgba(
+ (r * 255.0) as u8,
+ (g * 255.0) as u8,
+ (b * 255.0) as u8,
+ (a * 255.0) as u8,
+ )
+ }),
+ );
+
+ let _ = self.entries.insert(hash, buffer);
+ }
+
+ let _ = self.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.recently_used.clear();
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Key<'a> {
+ content: &'a str,
+ size: f32,
+ font: Font,
+ bounds: Size,
+ color: Color,
+}
+
+type KeyHash = u64;
+
// TODO: Share with `iced_graphics`
static FONT_SYSTEM: once_cell::sync::Lazy<glyphon::FontSystem> =
once_cell::sync::Lazy::new(glyphon::FontSystem::new);
@@ -28,6 +119,8 @@ impl Pipeline {
renderers: Vec::new(),
atlas: glyphon::TextAtlas::new(device, queue, format),
cache: glyphon::SwashCache::new(&FONT_SYSTEM),
+ measurement_cache: RefCell::new(Cache::new()),
+ render_cache: Cache::new(),
layer: 0,
}
}
@@ -48,48 +141,29 @@ impl Pipeline {
let renderer = &mut self.renderers[self.layer];
- let buffers: Vec<_> = sections
+ let keys: Vec<_> = sections
.iter()
.map(|section| {
- let metrics = glyphon::Metrics::new(
- (section.size * scale_factor) as i32,
- (section.size * 1.2 * scale_factor) as i32,
- );
-
- let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics);
-
- buffer.set_size(
- (section.bounds.width * scale_factor).ceil() as i32,
- (section.bounds.height * scale_factor).ceil() as i32,
- );
-
- buffer.set_text(
- section.content,
- glyphon::Attrs::new()
- .color({
- let [r, g, b, a] = section.color.into_linear();
-
- glyphon::Color::rgba(
- (r * 255.0) as u8,
- (g * 255.0) as u8,
- (b * 255.0) as u8,
- (a * 255.0) as u8,
- )
- })
- .family(match section.font {
- Font::Default => glyphon::Family::SansSerif,
- Font::External { name, .. } => {
- glyphon::Family::Name(name)
- }
- }),
- );
-
- buffer.shape_until_scroll();
-
- buffer
+ let (key, _) = self.render_cache.allocate(Key {
+ content: section.content,
+ size: section.size * scale_factor,
+ font: section.font,
+ bounds: Size {
+ width: section.bounds.width * scale_factor,
+ height: section.bounds.height * scale_factor,
+ },
+ color: section.color,
+ });
+
+ key
})
.collect();
+ let buffers: Vec<_> = keys
+ .iter()
+ .map(|key| self.render_cache.get(key).expect("Get cached buffer"))
+ .collect();
+
let bounds = glyphon::TextBounds {
left: (bounds.x * scale_factor) as i32,
top: (bounds.y * scale_factor) as i32,
@@ -190,29 +264,27 @@ impl Pipeline {
font: Font,
bounds: Size,
) -> (f32, f32) {
- let attrs = match font {
- Font::Default => glyphon::Attrs::new(),
- Font::External { name, .. } => glyphon::Attrs {
- family: glyphon::Family::Name(name),
- ..glyphon::Attrs::new()
+ let mut measurement_cache = self.measurement_cache.borrow_mut();
+
+ let (_, paragraph) = measurement_cache.allocate(Key {
+ content,
+ size: size,
+ font,
+ bounds: Size {
+ width: bounds.width,
+ height: f32::INFINITY,
},
- };
+ color: Color::BLACK,
+ });
+
+ let (total_lines, max_width) = paragraph
+ .layout_runs()
+ .enumerate()
+ .fold((0, 0.0), |(_, max), (i, buffer)| {
+ (i + 1, buffer.line_w.max(max))
+ });
- let mut paragraph =
- glyphon::BufferLine::new(content, glyphon::AttrsList::new(attrs));
-
- // TODO: Cache layout
- let layout = paragraph.layout(
- &FONT_SYSTEM,
- size as i32,
- bounds.width as i32,
- glyphon::Wrap::Word,
- );
-
- (
- layout.iter().fold(0.0, |max, line| line.w.max(max)),
- size * 1.2 * layout.len() as f32,
- )
+ (max_width, size * 1.2 * total_lines as f32)
}
pub fn hit_test(
@@ -227,5 +299,14 @@ impl Pipeline {
None
}
- pub fn trim_measurement_cache(&mut self) {}
+ pub fn trim_measurement_cache(&mut self) {
+ self.measurement_cache.borrow_mut().trim();
+ }
+}
+
+fn to_family(font: Font) -> glyphon::Family<'static> {
+ match font {
+ Font::Default => glyphon::Family::SansSerif,
+ Font::External { name, .. } => glyphon::Family::Name(name),
+ }
}