diff options
Diffstat (limited to 'wgpu/src/text.rs')
-rw-r--r-- | wgpu/src/text.rs | 173 |
1 files changed, 117 insertions, 56 deletions
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6a552270..65d3b818 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -18,8 +18,7 @@ pub struct Pipeline { renderers: Vec<glyphon::TextRenderer>, atlas: glyphon::TextAtlas, prepare_layer: usize, - measurement_cache: RefCell<Cache>, - render_cache: Cache, + cache: RefCell<Cache>, } impl Pipeline { @@ -47,8 +46,7 @@ impl Pipeline { }, ), prepare_layer: 0, - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), + cache: RefCell::new(Cache::new()), } } @@ -56,6 +54,8 @@ impl Pipeline { let _ = self.font_system.get_mut().db_mut().load_font_source( glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); + + self.cache = RefCell::new(Cache::new()); } pub fn prepare( @@ -78,28 +78,33 @@ impl Pipeline { let font_system = self.font_system.get_mut(); let renderer = &mut self.renderers[self.prepare_layer]; + let cache = self.cache.get_mut(); + + if self.prepare_layer == 0 { + cache.trim(Purpose::Drawing); + } let keys: Vec<_> = sections .iter() .map(|section| { - let (key, _) = self.render_cache.allocate( + let (key, _) = cache.allocate( font_system, Key { content: section.content, - size: section.size * scale_factor, + size: section.size, line_height: f32::from( section .line_height .to_absolute(Pixels(section.size)), - ) * scale_factor, + ), font: section.font, bounds: Size { - width: (section.bounds.width * scale_factor).ceil(), - height: (section.bounds.height * scale_factor) - .ceil(), + width: section.bounds.width, + height: section.bounds.height, }, shaping: section.shaping, }, + Purpose::Drawing, ); key @@ -113,14 +118,14 @@ impl Pipeline { .iter() .zip(keys.iter()) .filter_map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let (max_width, total_height) = measure(buffer); + let entry = cache.get(key).expect("Get cached buffer"); let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; + let max_width = entry.bounds.width * scale_factor; + let total_height = entry.bounds.height * scale_factor; + let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, alignment::Horizontal::Center => x - max_width / 2.0, @@ -142,14 +147,11 @@ impl Pipeline { let clip_bounds = bounds.intersection(§ion_bounds)?; - // TODO: Subpixel glyph positioning - let left = left.round() as i32; - let top = top.round() as i32; - Some(glyphon::TextArea { - buffer, + buffer: &entry.buffer, left, top, + scale: scale_factor, bounds: glyphon::TextBounds { left: clip_bounds.x as i32, top: clip_bounds.y as i32, @@ -227,11 +229,14 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); - self.render_cache.trim(); self.prepare_layer = 0; } + pub fn trim_measurements(&mut self) { + self.cache.get_mut().trim(Purpose::Measuring); + } + pub fn measure( &self, content: &str, @@ -240,12 +245,12 @@ impl Pipeline { font: Font, bounds: Size, shaping: Shaping, - ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + ) -> Size { + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, paragraph) = measurement_cache.allocate( + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -255,9 +260,10 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); - measure(paragraph) + entry.bounds } pub fn hit_test( @@ -271,11 +277,11 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option<Hit> { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, paragraph) = measurement_cache.allocate( + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -285,26 +291,23 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); - let cursor = paragraph.hit(point.x, point.y)?; + let cursor = entry.buffer.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) } - - pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); - } } -fn measure(buffer: &glyphon::Buffer) -> (f32, f32) { +fn measure(buffer: &glyphon::Buffer) -> Size { let (width, total_lines) = buffer .layout_runs() .fold((0.0, 0usize), |(width, total_lines), run| { (run.line_w.max(width), total_lines + 1) }); - (width, total_lines as f32 * buffer.metrics().line_height) + Size::new(width, total_lines as f32 * buffer.metrics().line_height) } fn to_family(family: font::Family) -> glyphon::Family<'static> { @@ -354,11 +357,24 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping { } struct Cache { - entries: FxHashMap<KeyHash, glyphon::Buffer>, - recently_used: FxHashSet<KeyHash>, + entries: FxHashMap<KeyHash, Entry>, + aliases: FxHashMap<KeyHash, KeyHash>, + recently_measured: FxHashSet<KeyHash>, + recently_drawn: FxHashSet<KeyHash>, hasher: HashBuilder, } +struct Entry { + buffer: glyphon::Buffer, + bounds: Size, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Purpose { + Measuring, + Drawing, +} + #[cfg(not(target_arch = "wasm32"))] type HashBuilder = twox_hash::RandomXxHashBuilder64; @@ -369,12 +385,14 @@ impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), - recently_used: FxHashSet::default(), + aliases: FxHashMap::default(), + recently_measured: FxHashSet::default(), + recently_drawn: FxHashSet::default(), hasher: HashBuilder::default(), } } - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> { + fn get(&self, key: &KeyHash) -> Option<&Entry> { self.entries.get(key) } @@ -382,21 +400,21 @@ impl Cache { &mut self, font_system: &mut glyphon::FontSystem, key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer) { - let hash = { - let mut hasher = self.hasher.build_hasher(); - - key.content.hash(&mut hasher); - key.size.to_bits().hash(&mut hasher); - key.line_height.to_bits().hash(&mut hasher); - key.font.hash(&mut hasher); - key.bounds.width.to_bits().hash(&mut hasher); - key.bounds.height.to_bits().hash(&mut hasher); - key.shaping.hash(&mut hasher); - - hasher.finish() + purpose: Purpose, + ) -> (KeyHash, &mut Entry) { + let hash = key.hash(self.hasher.build_hasher()); + + 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) { + let _ = recently_used.insert(*hash); + + return (*hash, self.entries.get_mut(hash).unwrap()); + } + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = glyphon::Metrics::new(key.size, key.line_height); let mut buffer = glyphon::Buffer::new(font_system, metrics); @@ -416,19 +434,48 @@ impl Cache { to_shaping(key.shaping), ); - let _ = entry.insert(buffer); + let bounds = measure(&buffer); + let _ = entry.insert(Entry { buffer, bounds }); + + for bounds in [ + bounds, + Size { + width: key.bounds.width, + ..bounds + }, + ] { + if key.bounds != bounds { + let _ = self.aliases.insert( + Key { bounds, ..key }.hash(self.hasher.build_hasher()), + hash, + ); + } + } } - 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)); + fn trim(&mut self, purpose: Purpose) { + 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 purpose { + Purpose::Measuring => { + self.recently_measured.clear(); + } + Purpose::Drawing => { + self.recently_drawn.clear(); + } + } } } @@ -442,4 +489,18 @@ struct Key<'a> { shaping: Shaping, } +impl Key<'_> { + fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash { + self.content.hash(&mut hasher); + self.size.to_bits().hash(&mut hasher); + self.line_height.to_bits().hash(&mut hasher); + self.font.hash(&mut hasher); + self.bounds.width.to_bits().hash(&mut hasher); + self.bounds.height.to_bits().hash(&mut hasher); + self.shaping.hash(&mut hasher); + + hasher.finish() + } +} + type KeyHash = u64; |