1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 13:50:04 -04:00

Remove more Arc<Mutex<...>> from font code

By making `Font` a view type and indexing by font ID, we can avoid
wrapping `FontImpl` and `TextureAtlas` in an `Arc<Mutex<...>>`.
This commit is contained in:
valadaptive
2025-07-04 00:03:57 -04:00
parent 02d45d70bc
commit 837fc7fef7
5 changed files with 208 additions and 156 deletions

View File

@@ -1588,7 +1588,7 @@ impl Context {
let font_id = TextStyle::Body.resolve(&self.style()); let font_id = TextStyle::Body.resolve(&self.style());
self.fonts_mut(|f| { self.fonts_mut(|f| {
let font = f.fonts.font(&font_id.family); let mut font = f.fonts.font(&font_id.family);
font.has_glyphs(alt) font.has_glyphs(alt)
&& font.has_glyphs(ctrl) && font.has_glyphs(ctrl)
&& font.has_glyphs(shift) && font.has_glyphs(shift)
@@ -2466,14 +2466,15 @@ impl ContextImpl {
self.memory.end_pass(&viewport.this_pass.used_ids); self.memory.end_pass(&viewport.this_pass.used_ids);
if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { let num_font_envs = self.fonts.len();
if let Some(fonts) = self.fonts.get_mut(&pixels_per_point.into()) {
let tex_mngr = &mut self.tex_manager.0.write(); let tex_mngr = &mut self.tex_manager.0.write();
if let Some(font_image_delta) = fonts.font_image_delta() { if let Some(font_image_delta) = fonts.font_image_delta() {
// A partial font atlas update, e.g. a new glyph has been entered. // A partial font atlas update, e.g. a new glyph has been entered.
tex_mngr.set(TextureId::default(), font_image_delta); tex_mngr.set(TextureId::default(), font_image_delta);
} }
if 1 < self.fonts.len() { if 1 < num_font_envs {
// We have multiple different `pixels_per_point`, // We have multiple different `pixels_per_point`,
// e.g. because we have many viewports spread across // e.g. because we have many viewports spread across
// monitors with different DPI scaling. // monitors with different DPI scaling.
@@ -2693,10 +2694,6 @@ impl Context {
.1 .1
.texture_atlas() .texture_atlas()
}; };
let (font_tex_size, prepared_discs) = {
let atlas = texture_atlas.lock();
(atlas.size(), atlas.prepared_discs())
};
let paint_stats = PaintStats::from_shapes(&shapes); let paint_stats = PaintStats::from_shapes(&shapes);
let clipped_primitives = { let clipped_primitives = {
@@ -2704,8 +2701,8 @@ impl Context {
tessellator::Tessellator::new( tessellator::Tessellator::new(
pixels_per_point, pixels_per_point,
tessellation_options, tessellation_options,
font_tex_size, texture_atlas.size(),
prepared_discs, texture_atlas.prepared_discs(),
) )
.tessellate_shapes(shapes) .tessellate_shapes(shapes)
}; };

View File

@@ -227,7 +227,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width); let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width);
let font_image_size = fonts.font_image_size(); let font_image_size = fonts.font_image_size();
let prepared_discs = fonts.texture_atlas().lock().prepared_discs(); let prepared_discs = fonts.texture_atlas().prepared_discs();
let mut tessellator = egui::epaint::Tessellator::new( let mut tessellator = egui::epaint::Tessellator::new(
1.0, 1.0,
Default::default(), Default::default(),

View File

@@ -1,13 +1,14 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc;
use ab_glyph::{Font as _, PxScaleFont, ScaleFont as _}; use ab_glyph::{Font as _, PxScaleFont, ScaleFont as _};
use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2}; use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
use crate::{ use crate::{
TextureAtlas, TextureAtlas,
mutex::{Mutex, RwLock}, text::{
text::FontTweak, FontTweak,
fonts::{CachedFamily, FontFaceKey},
},
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -84,9 +85,8 @@ pub struct FontImpl {
name: String, name: String,
ab_glyph_font: ab_glyph::FontArc, ab_glyph_font: ab_glyph::FontArc,
tweak: FontTweak, tweak: FontTweak,
glyph_info_cache: RwLock<ahash::HashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
glyph_alloc_cache: RwLock<ahash::HashMap<(GlyphInfo, OrderedFloat<f32>), GlyphAllocation>>, // TODO(emilk): standard Mutex glyph_alloc_cache: ahash::HashMap<(GlyphInfo, OrderedFloat<f32>), GlyphAllocation>,
atlas: Arc<Mutex<TextureAtlas>>,
} }
trait FontExt { trait FontExt {
@@ -116,19 +116,13 @@ where
} }
impl FontImpl { impl FontImpl {
pub fn new( pub fn new(name: String, ab_glyph_font: ab_glyph::FontArc, tweak: FontTweak) -> Self {
atlas: Arc<Mutex<TextureAtlas>>,
name: String,
ab_glyph_font: ab_glyph::FontArc,
tweak: FontTweak,
) -> Self {
Self { Self {
name, name,
ab_glyph_font, ab_glyph_font,
tweak, tweak,
glyph_info_cache: Default::default(), glyph_info_cache: Default::default(),
glyph_alloc_cache: Default::default(), glyph_alloc_cache: Default::default(),
atlas,
} }
} }
@@ -161,9 +155,9 @@ impl FontImpl {
} }
/// `\n` will result in `None` /// `\n` will result in `None`
fn glyph_info(&self, c: char) -> Option<GlyphInfo> { fn glyph_info(&mut self, c: char) -> Option<GlyphInfo> {
{ {
if let Some(glyph_info) = self.glyph_info_cache.read().get(&c) { if let Some(glyph_info) = self.glyph_info_cache.get(&c) {
return Some(*glyph_info); return Some(*glyph_info);
} }
} }
@@ -180,7 +174,7 @@ impl FontImpl {
.into(), .into(),
..space ..space
}; };
self.glyph_info_cache.write().insert(c, glyph_info); self.glyph_info_cache.insert(c, glyph_info);
return Some(glyph_info); return Some(glyph_info);
} }
} }
@@ -197,14 +191,14 @@ impl FontImpl {
advance_width_unscaled: advance_width.into(), advance_width_unscaled: advance_width.into(),
..space ..space
}; };
self.glyph_info_cache.write().insert(c, glyph_info); self.glyph_info_cache.insert(c, glyph_info);
return Some(glyph_info); return Some(glyph_info);
} }
} }
if invisible_char(c) { if invisible_char(c) {
let glyph_info = GlyphInfo::default(); let glyph_info = GlyphInfo::default();
self.glyph_info_cache.write().insert(c, glyph_info); self.glyph_info_cache.insert(c, glyph_info);
return Some(glyph_info); return Some(glyph_info);
} }
@@ -219,7 +213,7 @@ impl FontImpl {
advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(), advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(),
visible: true, visible: true,
}; };
self.glyph_info_cache.write().insert(c, glyph_info); self.glyph_info_cache.insert(c, glyph_info);
Some(glyph_info) Some(glyph_info)
} }
} }
@@ -259,8 +253,9 @@ impl FontImpl {
} }
pub fn allocate_glyph( pub fn allocate_glyph(
&self, &mut self,
glyph_info: GlyphInfo, glyph_info: GlyphInfo,
atlas: &mut TextureAtlas,
font_size: f32, font_size: f32,
pixels_per_point: f32, pixels_per_point: f32,
) -> GlyphAllocation { ) -> GlyphAllocation {
@@ -273,8 +268,7 @@ impl FontImpl {
.ab_glyph_font .ab_glyph_font
.pt_scale_factor(font_size * self.tweak.scale * pixels_per_point) .pt_scale_factor(font_size * self.tweak.scale * pixels_per_point)
.round(); .round();
let mut cache = self.glyph_alloc_cache.write(); let entry = match self.glyph_alloc_cache.entry((glyph_info, scale.into())) {
let entry = match cache.entry((glyph_info, scale.into())) {
std::collections::hash_map::Entry::Occupied(glyph_alloc) => { std::collections::hash_map::Entry::Occupied(glyph_alloc) => {
return *glyph_alloc.get(); return *glyph_alloc.get();
} }
@@ -312,7 +306,6 @@ impl FontImpl {
UvRect::default() UvRect::default()
} else { } else {
let glyph_pos = { let glyph_pos = {
let atlas = &mut self.atlas.lock();
let text_alpha_from_coverage = atlas.text_alpha_from_coverage; let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height)); let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
glyph.draw(|x, y, v| { glyph.draw(|x, y, v| {
@@ -352,56 +345,15 @@ impl FontImpl {
} }
} }
type FontIndex = usize;
// TODO(emilk): rename? // TODO(emilk): rename?
/// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis) /// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis)
pub struct Font { pub struct Font<'a> {
fonts: Vec<Arc<FontImpl>>, pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
pub(super) cached_family: &'a mut CachedFamily,
/// Lazily calculated. pub(super) atlas: &'a mut TextureAtlas,
characters: Option<BTreeMap<char, Vec<String>>>,
replacement_glyph: (FontIndex, GlyphInfo),
glyph_info_cache: ahash::HashMap<char, (FontIndex, GlyphInfo)>,
} }
impl Font { impl Font<'_> {
pub fn new(fonts: Vec<Arc<FontImpl>>) -> Self {
if fonts.is_empty() {
return Self {
fonts,
characters: None,
replacement_glyph: Default::default(),
glyph_info_cache: Default::default(),
};
}
let mut slf = Self {
fonts,
characters: None,
replacement_glyph: Default::default(),
glyph_info_cache: Default::default(),
};
const PRIMARY_REPLACEMENT_CHAR: char = '◻'; // white medium square
const FALLBACK_REPLACEMENT_CHAR: char = '?'; // fallback for the fallback
let replacement_glyph = slf
.glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR)
.or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR))
.unwrap_or_else(|| {
#[cfg(feature = "log")]
log::warn!(
"Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph."
);
(0, GlyphInfo::default())
});
slf.replacement_glyph = replacement_glyph;
slf
}
pub fn preload_characters(&mut self, s: &str) { pub fn preload_characters(&mut self, s: &str) {
for c in s.chars() { for c in s.chars() {
self.glyph_info(c); self.glyph_info(c);
@@ -421,9 +373,10 @@ impl Font {
/// All supported characters, and in which font they are available in. /// All supported characters, and in which font they are available in.
pub fn characters(&mut self) -> &BTreeMap<char, Vec<String>> { pub fn characters(&mut self) -> &BTreeMap<char, Vec<String>> {
self.characters.get_or_insert_with(|| { self.cached_family.characters.get_or_insert_with(|| {
let mut characters: BTreeMap<char, Vec<String>> = Default::default(); let mut characters: BTreeMap<char, Vec<String>> = Default::default();
for font in &self.fonts { for font_id in &self.cached_family.fonts {
let font = self.fonts_by_id.get(font_id).expect("Nonexistent font ID");
for chr in font.characters() { for chr in font.characters() {
characters.entry(chr).or_default().push(font.name.clone()); characters.entry(chr).or_default().push(font.name.clone());
} }
@@ -437,20 +390,27 @@ impl Font {
/// Returns a value rounded to [`emath::GUI_ROUNDING`]. /// Returns a value rounded to [`emath::GUI_ROUNDING`].
#[inline(always)] #[inline(always)]
pub fn row_height(&self, font_size: f32) -> f32 { pub fn row_height(&self, font_size: f32) -> f32 {
self.fonts[0].row_height(font_size) let Some(first_font) = self.fonts_by_id.get(&self.cached_family.fonts[0]) else {
return 0.0;
};
first_font.row_height(font_size)
} }
/// Width of this character in points. /// Width of this character in points.
pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 { pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 {
let (font_index, glyph_info) = self.glyph_info(c); let (key, glyph_info) = self.glyph_info(c);
let font = &self.fonts[font_index].ab_glyph_font; let font = &self
.fonts_by_id
.get(&key)
.expect("Nonexistent font ID")
.ab_glyph_font;
glyph_info.advance_width_unscaled.0 * font.pt_scale_factor(font_size) glyph_info.advance_width_unscaled.0 * font.pt_scale_factor(font_size)
/ font.height_unscaled() / font.height_unscaled()
} }
/// Can we display this glyph? /// Can we display this glyph?
pub fn has_glyph(&mut self, c: char) -> bool { pub fn has_glyph(&mut self, c: char) -> bool {
self.glyph_info(c) != self.replacement_glyph // TODO(emilk): this is a false negative if the user asks about the replacement character itself 🤦‍♂️ self.glyph_info(c) != self.cached_family.replacement_glyph // TODO(emilk): this is a false negative if the user asks about the replacement character itself 🤦‍♂️
} }
/// Can we display all the glyphs in this text? /// Can we display all the glyphs in this text?
@@ -459,24 +419,30 @@ impl Font {
} }
/// `\n` will (intentionally) show up as the replacement character. /// `\n` will (intentionally) show up as the replacement character.
fn glyph_info(&mut self, c: char) -> (FontIndex, GlyphInfo) { fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) {
if let Some(font_index_glyph_info) = self.glyph_info_cache.get(&c) { if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) {
return *font_index_glyph_info; return *font_index_glyph_info;
} }
let font_index_glyph_info = self.glyph_info_no_cache_or_fallback(c); let font_index_glyph_info = self.glyph_info_no_cache_or_fallback(c);
let font_index_glyph_info = font_index_glyph_info.unwrap_or(self.replacement_glyph); let font_index_glyph_info =
self.glyph_info_cache.insert(c, font_index_glyph_info); font_index_glyph_info.unwrap_or(self.cached_family.replacement_glyph);
self.cached_family
.glyph_info_cache
.insert(c, font_index_glyph_info);
font_index_glyph_info font_index_glyph_info
} }
#[inline] #[inline]
pub(crate) fn font_impl_and_glyph_info(&mut self, c: char) -> (Option<&FontImpl>, GlyphInfo) { pub(crate) fn font_impl_and_glyph_info(
if self.fonts.is_empty() { &mut self,
return (None, self.replacement_glyph.1); c: char,
) -> (Option<&mut FontImpl>, GlyphInfo) {
if self.cached_family.fonts.is_empty() {
return (None, self.cached_family.replacement_glyph.1);
} }
let (font_index, glyph_info) = self.glyph_info(c); let (key, glyph_info) = self.glyph_info(c);
let font_impl = &self.fonts[font_index]; let font_impl = self.fonts_by_id.get_mut(&key).expect("Nonexistent font ID");
(Some(font_impl), glyph_info) (Some(font_impl), glyph_info)
} }
@@ -487,28 +453,39 @@ impl Font {
font_size: f32, font_size: f32,
pixels_per_point: f32, pixels_per_point: f32,
) -> (Option<&FontImpl>, GlyphAllocation) { ) -> (Option<&FontImpl>, GlyphAllocation) {
if self.fonts.is_empty() { if self.cached_family.fonts.is_empty() {
return (None, Default::default()); return (None, Default::default());
} }
let (font_index, glyph_info) = self.glyph_info(c); let (key, glyph_info) = self.glyph_info(c);
let font_impl = &self.fonts[font_index]; let font_impl = self.fonts_by_id.get_mut(&key).expect("Nonexistent font ID");
let allocated_glyph = font_impl.allocate_glyph(glyph_info, font_size, pixels_per_point); let allocated_glyph =
font_impl.allocate_glyph(glyph_info, self.atlas, font_size, pixels_per_point);
(Some(font_impl), allocated_glyph) (Some(font_impl), allocated_glyph)
} }
pub(crate) fn ascent(&self, font_size: f32) -> f32 { pub(crate) fn ascent(&self, font_size: f32) -> f32 {
if let Some(first) = self.fonts.first() { if let Some(first) = self.cached_family.fonts.first() {
let first = self.fonts_by_id.get(first).expect("Nonexistent font ID");
first.ascent(font_size) first.ascent(font_size)
} else { } else {
self.row_height(font_size) self.row_height(font_size)
} }
} }
fn glyph_info_no_cache_or_fallback(&mut self, c: char) -> Option<(FontIndex, GlyphInfo)> { pub(crate) fn glyph_info_no_cache_or_fallback(
for (font_index, font_impl) in self.fonts.iter().enumerate() { &mut self,
c: char,
) -> Option<(FontFaceKey, GlyphInfo)> {
for font_key in &self.cached_family.fonts {
let font_impl = self
.fonts_by_id
.get_mut(font_key)
.expect("Nonexistent font ID");
if let Some(glyph_info) = font_impl.glyph_info(c) { if let Some(glyph_info) = font_impl.glyph_info(c) {
self.glyph_info_cache.insert(c, (font_index, glyph_info)); self.cached_family
return Some((font_index, glyph_info)); .glyph_info_cache
.insert(c, (*font_key, glyph_info));
return Some((*font_key, glyph_info));
} }
} }
None None

View File

@@ -1,11 +1,16 @@
use std::{collections::BTreeMap, sync::Arc}; use std::{
collections::BTreeMap,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use crate::{ use crate::{
AlphaFromCoverage, TextureAtlas, AlphaFromCoverage, TextureAtlas,
mutex::Mutex,
text::{ text::{
Galley, LayoutJob, LayoutSection, Galley, LayoutJob, LayoutSection,
font::{Font, FontImpl}, font::{Font, FontImpl, GlyphInfo},
}, },
}; };
use emath::NumExt as _; use emath::NumExt as _;
@@ -399,6 +404,79 @@ impl FontDefinitions {
} }
} }
/// Unique ID for looking up a single font face/file.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub(crate) struct FontFaceKey(pub u64);
static KEY_COUNTER: AtomicU64 = AtomicU64::new(1);
impl FontFaceKey {
fn new() -> Self {
Self(KEY_COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
impl nohash_hasher::IsEnabled for FontFaceKey {}
/// Cached data for working with a font family (e.g. doing character lookups).
#[derive(Debug)]
pub(super) struct CachedFamily {
pub fonts: Vec<FontFaceKey>,
/// Lazily calculated.
pub characters: Option<BTreeMap<char, Vec<String>>>,
pub replacement_glyph: (FontFaceKey, GlyphInfo),
pub glyph_info_cache: ahash::HashMap<char, (FontFaceKey, GlyphInfo)>,
}
impl CachedFamily {
fn new(
fonts: Vec<FontFaceKey>,
fonts_by_id: &mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
atlas: &mut TextureAtlas,
) -> Self {
if fonts.is_empty() {
return Self {
fonts,
characters: None,
replacement_glyph: Default::default(),
glyph_info_cache: Default::default(),
};
}
let mut slf = Self {
fonts,
characters: None,
replacement_glyph: Default::default(),
glyph_info_cache: Default::default(),
};
const PRIMARY_REPLACEMENT_CHAR: char = '◻'; // white medium square
const FALLBACK_REPLACEMENT_CHAR: char = '?'; // fallback for the fallback
let mut font = Font {
fonts_by_id,
cached_family: &mut slf,
atlas,
};
let replacement_glyph = font
.glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR)
.or_else(|| font.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR))
.unwrap_or_else(|| {
#[cfg(feature = "log")]
log::warn!(
"Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph."
);
(FontFaceKey::default(), GlyphInfo::default())
});
slf.replacement_glyph = replacement_glyph;
slf
}
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// The collection of fonts used by `epaint`. /// The collection of fonts used by `epaint`.
@@ -454,8 +532,8 @@ impl Fonts {
let pixels_per_point_changed = self.fonts.pixels_per_point != pixels_per_point; let pixels_per_point_changed = self.fonts.pixels_per_point != pixels_per_point;
let max_texture_side_changed = self.fonts.max_texture_side != max_texture_side; let max_texture_side_changed = self.fonts.max_texture_side != max_texture_side;
let text_alpha_from_coverage_changed = let text_alpha_from_coverage_changed =
self.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage; self.fonts.atlas.text_alpha_from_coverage != text_alpha_from_coverage;
let font_atlas_almost_full = self.fonts.atlas.lock().fill_ratio() > 0.8; let font_atlas_almost_full = self.fonts.atlas.fill_ratio() > 0.8;
let needs_recreate = pixels_per_point_changed let needs_recreate = pixels_per_point_changed
|| max_texture_side_changed || max_texture_side_changed
|| text_alpha_from_coverage_changed || text_alpha_from_coverage_changed
@@ -479,8 +557,8 @@ impl Fonts {
} }
/// Call at the end of each frame (before painting) to get the change to the font texture since last call. /// Call at the end of each frame (before painting) to get the change to the font texture since last call.
pub fn font_image_delta(&self) -> Option<crate::ImageDelta> { pub fn font_image_delta(&mut self) -> Option<crate::ImageDelta> {
self.fonts.atlas.lock().take_delta() self.fonts.atlas.take_delta()
} }
#[inline] #[inline]
@@ -495,20 +573,20 @@ impl Fonts {
/// The font atlas. /// The font atlas.
/// Pass this to [`crate::Tessellator`]. /// Pass this to [`crate::Tessellator`].
pub fn texture_atlas(&self) -> Arc<Mutex<TextureAtlas>> { pub fn texture_atlas(&self) -> &TextureAtlas {
self.fonts.atlas.clone() &self.fonts.atlas
} }
/// The full font atlas image. /// The full font atlas image.
#[inline] #[inline]
pub fn image(&self) -> crate::ColorImage { pub fn image(&self) -> crate::ColorImage {
self.fonts.atlas.lock().image().clone() self.fonts.atlas.image().clone()
} }
/// Current size of the font image. /// Current size of the font image.
/// Pass this to [`crate::Tessellator`]. /// Pass this to [`crate::Tessellator`].
pub fn font_image_size(&self) -> [usize; 2] { pub fn font_image_size(&self) -> [usize; 2] {
self.fonts.atlas.lock().size() self.fonts.atlas.size()
} }
/// Width of this character in points. /// Width of this character in points.
@@ -564,7 +642,7 @@ impl Fonts {
/// This increases as new fonts and/or glyphs are used, /// This increases as new fonts and/or glyphs are used,
/// but can also decrease in a call to [`Self::begin_pass`]. /// but can also decrease in a call to [`Self::begin_pass`].
pub fn font_atlas_fill_ratio(&self) -> f32 { pub fn font_atlas_fill_ratio(&self) -> f32 {
self.fonts.atlas.lock().fill_ratio() self.fonts.atlas.fill_ratio()
} }
/// Will wrap text at the given width and line break at `\n`. /// Will wrap text at the given width and line break at `\n`.
@@ -618,10 +696,10 @@ pub struct FontsImpl {
pixels_per_point: f32, pixels_per_point: f32,
max_texture_side: usize, max_texture_side: usize,
definitions: FontDefinitions, definitions: FontDefinitions,
atlas: Arc<Mutex<TextureAtlas>>, atlas: TextureAtlas,
//font_impl_cache: FontImplCache, fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontImpl>,
font_impls: ahash::HashMap<String, Arc<FontImpl>>, font_impls: ahash::HashMap<String, FontFaceKey>,
family_cache: ahash::HashMap<FontFamily, Font>, family_cache: ahash::HashMap<FontFamily, CachedFamily>,
} }
impl FontsImpl { impl FontsImpl {
@@ -642,24 +720,23 @@ impl FontsImpl {
let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways. let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage); let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage);
let atlas = Arc::new(Mutex::new(atlas)); let mut fonts_by_id: nohash_hasher::IntMap<FontFaceKey, FontImpl> = Default::default();
let mut font_impls: ahash::HashMap<String, FontFaceKey> = Default::default();
let font_impls = definitions for (name, font_data) in &definitions.font_data {
.font_data let tweak = font_data.tweak;
.iter() let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
.map(|(name, font_data)| { let font_impl = FontImpl::new(name.clone(), ab_glyph, tweak);
let tweak = font_data.tweak; let key = FontFaceKey::new();
let ab_glyph = ab_glyph_font_from_font_data(name, font_data); fonts_by_id.insert(key, font_impl);
let font_impl = FontImpl::new(atlas.clone(), name.clone(), ab_glyph, tweak); font_impls.insert(name.clone(), key);
(name.clone(), Arc::new(font_impl)) }
})
.collect();
Self { Self {
pixels_per_point, pixels_per_point,
max_texture_side, max_texture_side,
definitions, definitions,
atlas, atlas,
fonts_by_id,
font_impls, font_impls,
family_cache: Default::default(), family_cache: Default::default(),
} }
@@ -676,24 +753,29 @@ impl FontsImpl {
} }
/// Get the right font implementation from [`FontFamily`]. /// Get the right font implementation from [`FontFamily`].
pub fn font(&mut self, family: &FontFamily) -> &mut Font { pub fn font(&mut self, family: &FontFamily) -> Font<'_> {
self.family_cache.entry(family.clone()).or_insert_with(|| { let cached_family = self.family_cache.entry(family.clone()).or_insert_with(|| {
let fonts = &self.definitions.families.get(family); let fonts = &self.definitions.families.get(family);
let fonts = let fonts =
fonts.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); fonts.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
let fonts: Vec<Arc<FontImpl>> = fonts let fonts: Vec<FontFaceKey> = fonts
.iter() .iter()
.map(|font_name| { .map(|font_name| {
self.font_impls *self
.font_impls
.get(font_name) .get(font_name)
.unwrap_or_else(|| panic!("No font data found for {font_name:?}")) .unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
.clone()
}) })
.collect(); .collect();
Font::new(fonts) CachedFamily::new(fonts, &mut self.fonts_by_id, &mut self.atlas)
}) });
Font {
fonts_by_id: &mut self.fonts_by_id,
cached_family,
atlas: &mut self.atlas,
}
} }
/// Width of this character in points. /// Width of this character in points.

View File

@@ -144,7 +144,7 @@ fn layout_section(
format, format,
} = section; } = section;
let pixels_per_point = fonts.pixels_per_point(); let pixels_per_point = fonts.pixels_per_point();
let font = fonts.font(&format.font_id.family); let mut font = fonts.font(&format.font_id.family);
let font_size = format.font_id.size; let font_size = format.font_id.size;
let line_height = section let line_height = section
.format .format
@@ -416,7 +416,7 @@ fn replace_last_glyph_with_overflow_character(
} }
} }
fn row_height(section: &LayoutSection, font: &Font, font_size: f32) -> f32 { fn row_height(section: &LayoutSection, font: &Font<'_>, font_size: f32) -> f32 {
section section
.format .format
.line_height .line_height
@@ -433,9 +433,9 @@ fn replace_last_glyph_with_overflow_character(
if let Some(last_glyph) = row.glyphs.last() { if let Some(last_glyph) = row.glyphs.last() {
let section_index = last_glyph.section_index; let section_index = last_glyph.section_index;
let section = &job.sections[section_index as usize]; let section = &job.sections[section_index as usize];
let font = fonts.font(&section.format.font_id.family); let mut font = fonts.font(&section.format.font_id.family);
let font_size = section.format.font_id.size; let font_size = section.format.font_id.size;
let line_height = row_height(section, font, font_size); let line_height = row_height(section, &font, font_size);
let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr); let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
@@ -470,29 +470,25 @@ fn replace_last_glyph_with_overflow_character(
} else { } else {
let section_index = row.section_index_at_start; let section_index = row.section_index_at_start;
let section = &job.sections[section_index as usize]; let section = &job.sections[section_index as usize];
let font = fonts.font(&section.format.font_id.family); let mut font = fonts.font(&section.format.font_id.family);
let font_size = section.format.font_id.size; let font_size = section.format.font_id.size;
let line_height = row_height(section, font, font_size); let line_height = row_height(section, &font, font_size);
let x = 0.0; // TODO(emilk): heed paragraph leading_space 😬 let x = 0.0; // TODO(emilk): heed paragraph leading_space 😬
let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character); let (mut font_impl, replacement_glyph_alloc) =
let glyph_alloc = if let Some(font_impl) = font_impl { font.font_impl_and_glyph_alloc(overflow_character, font_size, pixels_per_point);
font_impl.allocate_glyph(replacement_glyph_info, font_size, pixels_per_point)
} else {
Default::default()
};
row.glyphs.push(Glyph { row.glyphs.push(Glyph {
chr: overflow_character, chr: overflow_character,
pos: pos2(x, f32::NAN), pos: pos2(x, f32::NAN),
advance_width: glyph_alloc.advance_width, advance_width: replacement_glyph_alloc.advance_width,
line_height, line_height,
font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)), font_impl_height: font_impl.as_mut().map_or(0.0, |f| f.row_height(font_size)),
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)), font_impl_ascent: font_impl.as_mut().map_or(0.0, |f| f.ascent(font_size)),
font_height: font.row_height(font_size), font_height: font.row_height(font_size),
font_ascent: font.ascent(font_size), font_ascent: font.ascent(font_size),
uv_rect: glyph_alloc.uv_rect, uv_rect: replacement_glyph_alloc.uv_rect,
section_index, section_index,
}); });
} }
@@ -519,7 +515,7 @@ fn replace_last_glyph_with_overflow_character(
let section = &job.sections[last_glyph.section_index as usize]; let section = &job.sections[last_glyph.section_index as usize];
let extra_letter_spacing = section.format.extra_letter_spacing; let extra_letter_spacing = section.format.extra_letter_spacing;
let pixels_per_point = fonts.pixels_per_point(); let pixels_per_point = fonts.pixels_per_point();
let font = fonts.font(&section.format.font_id.family); let mut font = fonts.font(&section.format.font_id.family);
let font_size = section.format.font_id.size; let font_size = section.format.font_id.size;
if let Some(prev_glyph) = prev_glyph { if let Some(prev_glyph) = prev_glyph {
@@ -573,7 +569,7 @@ fn replace_last_glyph_with_overflow_character(
return; return;
}; };
let pixels_per_point = fonts.pixels_per_point(); let pixels_per_point = fonts.pixels_per_point();
let font = fonts.font(&section.format.font_id.family); let mut font = fonts.font(&section.format.font_id.family);
let font_size = section.format.font_id.size; let font_size = section.format.font_id.size;
// Just replace and be done with it. // Just replace and be done with it.
last_glyph.chr = overflow_character; last_glyph.chr = overflow_character;