Cache rustybuzz shape plans.

This commit is contained in:
David Stern 2023-12-18 18:10:09 -05:00
parent 94e6cdefda
commit 73acfb0962
7 changed files with 98 additions and 17 deletions

View file

@ -18,7 +18,7 @@ log = "0.4.20"
modit = { version = "0.1.3", optional = true }
rangemap = "1.4.0"
rustc-hash = { version = "1.1.0", default-features = false }
rustybuzz = { version = "0.11.0", default-features = false, features = ["libm"] }
rustybuzz = { version = "0.12.0", default-features = false, features = ["libm"] }
self_cell = "1.0.1"
swash = { version = "0.1.8", optional = true }
syntect = { version = "5.1.0", optional = true }

View file

@ -6,7 +6,7 @@ use alloc::vec::Vec;
use fontdb::Family;
use unicode_script::Script;
use crate::{Font, FontSystem};
use crate::{Font, FontSystem, ShapePlanCache};
use self::platform::*;
@ -103,6 +103,10 @@ impl<'a> FontFallbackIter<'a> {
}
}
pub fn shape_plan_cache(&mut self) -> &mut ShapePlanCache {
self.font_system.shape_plan_cache()
}
fn face_contains_family(&self, id: fontdb::ID, family_name: &str) -> bool {
if let Some(face) = self.font_system.db().face(id) {
face.families.iter().any(|(name, _)| name == family_name)

View file

@ -1,17 +1,10 @@
use crate::{Attrs, AttrsOwned, Font};
use crate::{Attrs, AttrsOwned, Font, HashMap, ShapePlanCache};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use core::ops::{Deref, DerefMut};
type BuildHasher = core::hash::BuildHasherDefault<rustc_hash::FxHasher>;
#[cfg(feature = "std")]
type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasher>;
#[cfg(not(feature = "std"))]
type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasher>;
// re-export fontdb and rustybuzz
pub use fontdb;
pub use rustybuzz;
@ -29,6 +22,9 @@ pub struct FontSystem {
/// Cache for font matches.
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<fontdb::ID>>>,
/// Cache for rustybuzz shape plans.
shape_plan_cache: ShapePlanCache,
}
impl fmt::Debug for FontSystem {
@ -74,8 +70,9 @@ impl FontSystem {
Self {
locale,
db,
font_cache: HashMap::default(),
font_matches_cache: HashMap::default(),
font_cache: Default::default(),
font_matches_cache: Default::default(),
shape_plan_cache: ShapePlanCache::default(),
}
}
@ -89,6 +86,11 @@ impl FontSystem {
&self.db
}
/// Get the shape plan cache.
pub(crate) fn shape_plan_cache(&mut self) -> &mut ShapePlanCache {
&mut self.shape_plan_cache
}
/// Get a mutable reference to the database.
pub fn db_mut(&mut self) -> &mut fontdb::Database {
self.font_matches_cache.clear();

View file

@ -108,8 +108,8 @@ mod buffer;
pub use self::buffer_line::*;
mod buffer_line;
pub use self::cache::*;
mod cache;
pub use self::glyph_cache::*;
mod glyph_cache;
pub use self::edit::*;
mod edit;
@ -123,7 +123,17 @@ mod layout;
pub use self::shape::*;
mod shape;
use self::shape_plan_cache::*;
mod shape_plan_cache;
#[cfg(feature = "swash")]
pub use self::swash::*;
#[cfg(feature = "swash")]
mod swash;
type BuildHasher = core::hash::BuildHasherDefault<rustc_hash::FxHasher>;
#[cfg(feature = "std")]
type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasher>;
#[cfg(not(feature = "std"))]
type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasher>;

View file

@ -12,7 +12,9 @@ use unicode_script::{Script, UnicodeScript};
use unicode_segmentation::UnicodeSegmentation;
use crate::fallback::FontFallbackIter;
use crate::{Align, AttrsList, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
use crate::{
Align, AttrsList, Color, Font, FontSystem, LayoutGlyph, LayoutLine, ShapePlanCache, Wrap,
};
/// The shaping strategy of some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -85,6 +87,7 @@ impl fmt::Debug for ShapeBuffer {
fn shape_fallback(
scratch: &mut ShapeBuffer,
glyphs: &mut Vec<ShapeGlyph>,
shape_plan_cache: &mut ShapePlanCache,
font: &Font,
line: &str,
attrs_list: &AttrsList,
@ -110,7 +113,8 @@ fn shape_fallback(
let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft);
assert_eq!(rtl, span_rtl);
let glyph_buffer = rustybuzz::shape(font.rustybuzz(), &[], buffer);
let shape_plan = shape_plan_cache.get(font, &buffer);
let glyph_buffer = rustybuzz::shape_with_plan(font.rustybuzz(), shape_plan, buffer);
let glyph_infos = glyph_buffer.glyph_infos();
let glyph_positions = glyph_buffer.glyph_positions();
@ -218,7 +222,15 @@ fn shape_run(
let glyph_start = glyphs.len();
let mut missing = shape_fallback(
scratch, glyphs, &font, line, attrs_list, start_run, end_run, span_rtl,
scratch,
glyphs,
font_iter.shape_plan_cache(),
&font,
line,
attrs_list,
start_run,
end_run,
span_rtl,
);
//TODO: improve performance!
@ -236,6 +248,7 @@ fn shape_run(
let fb_missing = shape_fallback(
scratch,
&mut fb_glyphs,
font_iter.shape_plan_cache(),
&font,
line,
attrs_list,

52
src/shape_plan_cache.rs Normal file
View file

@ -0,0 +1,52 @@
use std::collections::hash_map::Entry;
use crate::{Font, HashMap};
/// Key for caching shape plans.
#[derive(Debug, Hash, PartialEq, Eq)]
struct ShapePlanKey {
font_id: fontdb::ID,
direction: rustybuzz::Direction,
script: rustybuzz::Script,
language: Option<rustybuzz::Language>,
}
/// A helper structure for caching rustybuzz shape plans.
#[derive(Default)]
pub struct ShapePlanCache(HashMap<ShapePlanKey, rustybuzz::ShapePlan>);
impl ShapePlanCache {
pub fn get(&mut self, font: &Font, buffer: &rustybuzz::UnicodeBuffer) -> &rustybuzz::ShapePlan {
let key = ShapePlanKey {
font_id: font.id(),
direction: buffer.direction(),
script: buffer.script(),
language: buffer.language(),
};
match self.0.entry(key) {
Entry::Occupied(occ) => occ.into_mut(),
Entry::Vacant(vac) => {
let ShapePlanKey {
direction,
script,
language,
..
} = vac.key();
let plan = rustybuzz::ShapePlan::new(
font.rustybuzz(),
*direction,
Some(*script),
language.as_ref(),
&[],
);
vac.insert(plan)
}
}
}
}
impl std::fmt::Debug for ShapePlanCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("ShapePlanCache").finish()
}
}