Prewarm Linux font match caches (#63158)
Prewarming cosmic-text font cache completely eliminated expensive `get_font_matches` calls and thus reducing the time it takes to call `cosmic_text::shape::ShapeLine::new`. Release Notes: - Improved rendering performance on Linux by prewarming font match caches.
This commit is contained in:
1 parent
cff8de9239
commit
55fed7ab8d
4 files changed
+141
-43
No files matched your search
@@ -1075,6 +1075,8 @@ pub trait PlatformTextSystem: Send + Sync {
|
||||
fn all_font_names(&self) -> Vec<String>;
|
||||
/// Get the font ID for a font descriptor.
|
||||
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
|
||||
/// Prewarm any system font caches needed to shape text.
|
||||
fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
|
||||
/// Get metrics for a font.
|
||||
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
|
||||
/// Get typographic bounds for a glyph.
|
||||
|
||||
@@ -165,6 +165,22 @@ impl TextSystem {
|
||||
);
|
||||
}
|
||||
|
||||
/// Prewarm any system font caches needed to shape text.
|
||||
///
|
||||
/// This may be expensive, so callers should generally invoke it on a
|
||||
/// background executor. Missing entries are still populated on demand by
|
||||
/// the normal shaping path.
|
||||
pub fn prewarm_fonts(&self, fonts: &[Font]) {
|
||||
let mut font_ids = SmallVec::<[FontId; 8]>::new();
|
||||
for font in fonts {
|
||||
let font_id = self.resolve_font(font);
|
||||
if !font_ids.contains(&font_id) {
|
||||
font_ids.push(font_id);
|
||||
}
|
||||
}
|
||||
self.platform_text_system.prewarm_fonts(&font_ids);
|
||||
}
|
||||
|
||||
/// Get the bounding box for the given font and font size.
|
||||
/// A font's bounding box is the smallest rectangle that could enclose all glyphs
|
||||
/// in the font. superimposed over one another.
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::{Context as _, Ok, Result};
|
||||
use collections::HashMap;
|
||||
use cosmic_text::{
|
||||
Attrs, AttrsList, Ellipsize, Family, Font as CosmicTextFont,
|
||||
FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine,
|
||||
FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine, Stretch, Style, Weight,
|
||||
};
|
||||
use gpui::{
|
||||
Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, GlyphId,
|
||||
@@ -61,6 +61,27 @@ struct LoadedFont {
|
||||
user_fallback_chain: Arc<[(FontId, SharedString)]>,
|
||||
}
|
||||
|
||||
struct FontMatchProperties {
|
||||
primary_family_name: SharedString,
|
||||
stretch: Stretch,
|
||||
style: Style,
|
||||
weight: Weight,
|
||||
features: CosmicFontFeatures,
|
||||
fallback_chain: Arc<[(FontId, SharedString)]>,
|
||||
}
|
||||
|
||||
impl FontMatchProperties {
|
||||
fn attributes<'a>(&'a self, font_id: FontId, family_name: &'a str) -> Attrs<'a> {
|
||||
Attrs::new()
|
||||
.metadata(font_id.0)
|
||||
.family(Family::Name(family_name))
|
||||
.stretch(self.stretch)
|
||||
.style(self.style)
|
||||
.weight(self.weight)
|
||||
.font_features(self.features.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl CosmicTextSystem {
|
||||
pub fn new(system_font_fallback: &str) -> Self {
|
||||
let font_system = FontSystem::new();
|
||||
@@ -132,6 +153,10 @@ impl PlatformTextSystem for CosmicTextSystem {
|
||||
Ok(candidates[ix])
|
||||
}
|
||||
|
||||
fn prewarm_fonts(&self, font_ids: &[FontId]) {
|
||||
self.0.write().prewarm_fonts(font_ids);
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
|
||||
let metrics = self
|
||||
.0
|
||||
@@ -208,6 +233,43 @@ impl CosmicTextSystemState {
|
||||
&self.loaded_fonts[font_id.0]
|
||||
}
|
||||
|
||||
fn font_match_properties(&self, font_id: FontId) -> Option<FontMatchProperties> {
|
||||
let loaded_font = self.loaded_font(font_id);
|
||||
let Some(face) = self.font_system.db().face(loaded_font.font.id()) else {
|
||||
log::warn!("font face not found in database for font_id {:?}", font_id);
|
||||
return None;
|
||||
};
|
||||
let Some(first_family) = face.families.first() else {
|
||||
log::warn!("font face has no family names for font_id {:?}", font_id);
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(FontMatchProperties {
|
||||
primary_family_name: first_family.0.clone().into(),
|
||||
stretch: face.stretch,
|
||||
style: face.style,
|
||||
weight: face.weight,
|
||||
features: loaded_font.features.clone(),
|
||||
fallback_chain: Arc::clone(&loaded_font.user_fallback_chain),
|
||||
})
|
||||
}
|
||||
|
||||
fn prewarm_fonts(&mut self, font_ids: &[FontId]) {
|
||||
for &font_id in font_ids {
|
||||
let Some(properties) = self.font_match_properties(font_id) else {
|
||||
continue;
|
||||
};
|
||||
let primary_attributes =
|
||||
properties.attributes(font_id, &properties.primary_family_name);
|
||||
self.font_system.get_font_matches(&primary_attributes);
|
||||
|
||||
for (fallback_id, fallback_name) in &*properties.fallback_chain {
|
||||
let fallback_attributes = properties.attributes(*fallback_id, fallback_name);
|
||||
self.font_system.get_font_matches(&fallback_attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
|
||||
let db = self.font_system.db_mut();
|
||||
@@ -552,54 +614,19 @@ impl CosmicTextSystemState {
|
||||
for run in font_runs {
|
||||
let run_end = offs + run.len;
|
||||
|
||||
let loaded_font = self.loaded_font(run.font_id);
|
||||
let Some(face) = self.font_system.db().face(loaded_font.font.id()) else {
|
||||
log::warn!(
|
||||
"font face not found in database for font_id {:?}",
|
||||
run.font_id
|
||||
);
|
||||
offs = run_end;
|
||||
continue;
|
||||
};
|
||||
let Some(first_family) = face.families.first() else {
|
||||
log::warn!(
|
||||
"font face has no family names for font_id {:?}",
|
||||
run.font_id
|
||||
);
|
||||
let Some(properties) = self.font_match_properties(run.font_id) else {
|
||||
offs = run_end;
|
||||
continue;
|
||||
};
|
||||
|
||||
let primary_family_name: SharedString = first_family.0.clone().into();
|
||||
let primary_stretch = face.stretch;
|
||||
let primary_style = face.style;
|
||||
let primary_weight = face.weight;
|
||||
let primary_features = loaded_font.features.clone();
|
||||
let fallback_chain = Arc::clone(&loaded_font.user_fallback_chain);
|
||||
|
||||
// build one `Attrs` per slot up front. each clone of span attrs
|
||||
// would otherwise re-allocate the `font_features` Vec.
|
||||
let primary_attrs = Attrs::new()
|
||||
.metadata(run.font_id.0)
|
||||
.family(Family::Name(&primary_family_name))
|
||||
.stretch(primary_stretch)
|
||||
.style(primary_style)
|
||||
.weight(primary_weight)
|
||||
.font_features(primary_features.clone());
|
||||
let fallback_attrs: SmallVec<[Attrs<'_>; 4]> = fallback_chain
|
||||
let primary_attrs = properties.attributes(run.font_id, &properties.primary_family_name);
|
||||
let fallback_attrs: SmallVec<[Attrs<'_>; 4]> = properties
|
||||
.fallback_chain
|
||||
.iter()
|
||||
.map(|(fb_id, fb_name)| {
|
||||
Attrs::new()
|
||||
.metadata(fb_id.0)
|
||||
.family(Family::Name(fb_name))
|
||||
.stretch(primary_stretch)
|
||||
.style(primary_style)
|
||||
.weight(primary_weight)
|
||||
.font_features(primary_features.clone())
|
||||
})
|
||||
.map(|(font_id, family_name)| properties.attributes(*font_id, family_name))
|
||||
.collect();
|
||||
|
||||
let spans = if fallback_chain.is_empty() {
|
||||
let spans = if properties.fallback_chain.is_empty() {
|
||||
let mut spans = SmallVec::<[RunSpan; 4]>::new();
|
||||
spans.push(RunSpan {
|
||||
start: offs,
|
||||
@@ -611,7 +638,14 @@ impl CosmicTextSystemState {
|
||||
} else {
|
||||
let loaded_fonts = &self.loaded_fonts;
|
||||
let covers = |id: FontId, ch: char| charmap_covers(loaded_fonts, id, ch);
|
||||
compute_run_spans(text, offs, run.len, run.font_id, &fallback_chain, &covers)
|
||||
compute_run_spans(
|
||||
text,
|
||||
offs,
|
||||
run.len,
|
||||
run.font_id,
|
||||
&properties.fallback_chain,
|
||||
&covers,
|
||||
)
|
||||
};
|
||||
|
||||
for span in spans {
|
||||
|
||||
@@ -732,6 +732,8 @@ fn main() {
|
||||
dev_container::init(cx);
|
||||
|
||||
load_embedded_fonts(cx);
|
||||
#[cfg(target_os = "linux")]
|
||||
prewarm_fonts(cx);
|
||||
|
||||
editor::init(cx);
|
||||
image_viewer::init(cx);
|
||||
@@ -1855,6 +1857,50 @@ fn load_embedded_fonts(cx: &App) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn prewarm_fonts(cx: &mut App) {
|
||||
let theme_settings = theme::theme_settings(cx);
|
||||
let ui_font = theme_settings.ui_font(cx).clone();
|
||||
let buffer_font = theme_settings.buffer_font(cx).clone();
|
||||
let mut fonts = vec![ui_font.clone(), buffer_font.clone()];
|
||||
let mut add_variant = |base_font: &gpui::Font, weight, style| {
|
||||
let mut font = base_font.clone();
|
||||
font.weight = weight;
|
||||
font.style = style;
|
||||
fonts.push(font);
|
||||
};
|
||||
|
||||
for weight in [
|
||||
gpui::FontWeight::MEDIUM,
|
||||
gpui::FontWeight::SEMIBOLD,
|
||||
gpui::FontWeight::BOLD,
|
||||
] {
|
||||
add_variant(&ui_font, weight, gpui::FontStyle::Normal);
|
||||
}
|
||||
add_variant(&ui_font, gpui::FontWeight::NORMAL, gpui::FontStyle::Italic);
|
||||
add_variant(
|
||||
&buffer_font,
|
||||
gpui::FontWeight::BOLD,
|
||||
gpui::FontStyle::Normal,
|
||||
);
|
||||
add_variant(
|
||||
&buffer_font,
|
||||
gpui::FontWeight::NORMAL,
|
||||
gpui::FontStyle::Italic,
|
||||
);
|
||||
add_variant(
|
||||
&buffer_font,
|
||||
gpui::FontWeight::BOLD,
|
||||
gpui::FontStyle::Italic,
|
||||
);
|
||||
|
||||
let text_system = cx.text_system().clone();
|
||||
cx.background_spawn(async move {
|
||||
text_system.prewarm_fonts(&fonts);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Spawns a background task to load the user themes from the themes directory.
|
||||
fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
|
||||
cx.spawn({
|
||||
|
||||
Reference in new issue
Block a user