Add shape-run-cache feature, that can significantly improve shaping performance

This commit is contained in:
Jeremy Soller 2024-02-08 11:12:41 -07:00
parent 990d66ed41
commit 1eb3233373
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
7 changed files with 134 additions and 2 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Add `shape-run-cache` feature, that can significantly improve shaping performance
### Removed
- Remove editor-libcosmic, see cosmic-edit instead

View file

@ -36,7 +36,9 @@ features = ["hardcoded-data"]
[features]
default = ["std", "swash", "fontconfig"]
fontconfig = ["fontdb/fontconfig", "std"]
no_std = ["rustybuzz/libm", "hashbrown"]
shape-run-cache = []
std = [
"fontdb/memmap",
"fontdb/std",
@ -47,7 +49,6 @@ std = [
vi = ["modit", "syntect", "cosmic_undo_2"]
wasm-web = ["sys-locale?/js"]
warn_on_missing_glyphs = []
fontconfig = ["fontdb/fontconfig", "std"]
[[bench]]
name = "layout"

View file

@ -252,7 +252,7 @@ impl AttrsOwned {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct AttrsList {
defaults: AttrsOwned,
spans: RangeMap<usize, AttrsOwned>,
pub(crate) spans: RangeMap<usize, AttrsOwned>,
}
impl AttrsList {

View file

@ -32,6 +32,10 @@ pub struct FontSystem {
/// Cache for rustybuzz shape plans.
shape_plan_cache: ShapePlanCache,
/// Cache for shaped runs
#[cfg(feature = "shape-run-cache")]
pub shape_run_cache: crate::ShapeRunCache,
}
impl fmt::Debug for FontSystem {
@ -81,6 +85,8 @@ impl FontSystem {
font_cache: Default::default(),
font_matches_cache: Default::default(),
shape_plan_cache: ShapePlanCache::default(),
#[cfg(feature = "shape-run-cache")]
shape_run_cache: crate::ShapeRunCache::default(),
}
}

View file

@ -129,6 +129,9 @@ mod shape;
use self::shape_plan_cache::*;
mod shape_plan_cache;
pub use self::shape_run_cache::*;
mod shape_run_cache;
#[cfg(feature = "swash")]
pub use self::swash::*;
#[cfg(feature = "swash")]

View file

@ -52,6 +52,7 @@ impl Shaping {
match self {
#[cfg(feature = "swash")]
Self::Basic => shape_skip(font_system, glyphs, line, attrs_list, start_run, end_run),
#[cfg(not(feature = "shape-run-cache"))]
Self::Advanced => shape_run(
scratch,
glyphs,
@ -62,6 +63,17 @@ impl Shaping {
end_run,
span_rtl,
),
#[cfg(feature = "shape-run-cache")]
Self::Advanced => shape_run_cached(
scratch,
glyphs,
font_system,
line,
attrs_list,
start_run,
end_run,
span_rtl,
),
}
}
}
@ -334,6 +346,63 @@ fn shape_run(
scratch.scripts = scripts;
}
#[cfg(feature = "shape-run-cache")]
fn shape_run_cached(
scratch: &mut ShapeBuffer,
glyphs: &mut Vec<ShapeGlyph>,
font_system: &mut FontSystem,
line: &str,
attrs_list: &AttrsList,
start_run: usize,
end_run: usize,
span_rtl: bool,
) {
use crate::{AttrsOwned, ShapeRunKey};
let run_range = start_run..end_run;
let mut key = ShapeRunKey {
text: line[run_range.clone()].to_string(),
default_attrs: AttrsOwned::new(attrs_list.defaults()),
attrs_spans: Vec::new(),
};
for (attrs_range, attrs) in attrs_list.spans.overlapping(&run_range) {
if attrs == &key.default_attrs {
// Skip if attrs matches default attrs
continue;
}
let start = max(attrs_range.start, start_run)
.checked_sub(start_run)
.unwrap_or(0);
let end = min(attrs_range.end, end_run)
.checked_sub(start_run)
.unwrap_or(0);
if end > start {
let range = start..end;
key.attrs_spans.push((range, attrs.clone()));
}
}
if let Some(cache_glyphs) = font_system.shape_run_cache.get(&key) {
// Use cached glyphs
glyphs.extend_from_slice(&cache_glyphs);
return;
}
// Fill in cache if not already set
let mut cache_glyphs = Vec::new();
shape_run(
scratch,
&mut cache_glyphs,
font_system,
line,
attrs_list,
start_run,
end_run,
span_rtl,
);
glyphs.extend_from_slice(&cache_glyphs);
font_system.shape_run_cache.insert(key, cache_glyphs);
}
#[cfg(feature = "swash")]
fn shape_skip(
font_system: &mut FontSystem,

49
src/shape_run_cache.rs Normal file
View file

@ -0,0 +1,49 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::ops::Range;
use crate::{AttrsOwned, HashMap, ShapeGlyph};
/// Key for caching shape runs.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ShapeRunKey {
pub text: String,
pub default_attrs: AttrsOwned,
pub attrs_spans: Vec<(Range<usize>, AttrsOwned)>,
}
/// A helper structure for caching shape runs.
#[derive(Clone, Default)]
pub struct ShapeRunCache {
age: u64,
cache: HashMap<ShapeRunKey, (u64, Vec<ShapeGlyph>)>,
}
impl ShapeRunCache {
/// Get cache item, updating age if found
pub fn get(&mut self, key: &ShapeRunKey) -> Option<&Vec<ShapeGlyph>> {
self.cache.get_mut(key).map(|(age, glyphs)| {
*age = self.age;
&*glyphs
})
}
/// Insert cache item with current age
pub fn insert(&mut self, key: ShapeRunKey, glyphs: Vec<ShapeGlyph>) {
self.cache.insert(key, (self.age, glyphs));
}
/// Remove anything in the cache with an age older than keep_ages
pub fn trim(&mut self, keep_ages: u64) {
self.cache
.retain(|_key, (age, _glyphs)| *age + keep_ages >= self.age);
// Increase age
self.age += 1;
}
}
impl core::fmt::Debug for ShapeRunCache {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("ShapeRunCache").finish()
}
}