diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-05-02 12:34:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-02 10:34:10 +0000 |
commit | 556bfb7dff48f64e9e02872dba29fbdabc8c6ad0 (patch) | |
tree | 0c9e1e80582fee2a64aa5904df3230e8a3d2befd | |
parent | 8ec5344f70dd1d556cacd72d778924048b0b1154 (diff) | |
download | servo-556bfb7dff48f64e9e02872dba29fbdabc8c6ad0.tar.gz servo-556bfb7dff48f64e9e02872dba29fbdabc8c6ad0.zip |
fonts: Make `FontContext` thread-safe and share it per-Layout (#32205)
This allows sharing font templates, fonts, and platform fonts across
layout threads. It's the first step toward storing web fonts in the
layout versus the shared `FontCacheThread`. Now fonts and font groups
have some locking (especially on FreeType), which will probably affect
performance. On the other hand, we measured memory usage and this saves
roughly 40 megabytes of memory when loading servo.org based on data from
the memory profiler.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
27 files changed, 444 insertions, 507 deletions
diff --git a/Cargo.lock b/Cargo.lock index 6b1530a7de0..20c65956206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1952,6 +1952,7 @@ name = "gfx" version = "0.0.1" dependencies = [ "app_units", + "atomic_refcell", "bitflags 2.5.0", "byteorder", "core-foundation", diff --git a/Cargo.toml b/Cargo.toml index 42123438215..f3809e5c641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ to_shmem = { git = "https://github.com/servo/stylo", branch = "2024-04-16" } tokio = "1" tokio-rustls = "0.24" tungstenite = "0.20" +uluru = "3.0" unicode-bidi = "0.3.15" unicode-script = "0.5" unicode-segmentation = "1.1.0" diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 5bb4640dfd1..0175242521e 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -2,9 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::RefCell; use std::mem; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use canvas_traits::canvas::*; use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D}; @@ -359,21 +358,6 @@ pub enum Filter { Nearest, } -pub(crate) type CanvasFontContext = FontContext<FontCacheThread>; - -thread_local!(static FONT_CONTEXT: RefCell<Option<CanvasFontContext>> = RefCell::new(None)); - -pub(crate) fn with_thread_local_font_context<F, R>(canvas_data: &CanvasData, f: F) -> R -where - F: FnOnce(&mut CanvasFontContext) -> R, -{ - FONT_CONTEXT.with(|font_context| { - f(font_context.borrow_mut().get_or_insert_with(|| { - FontContext::new(canvas_data.font_cache_thread.lock().unwrap().clone()) - })) - }) -} - pub struct CanvasData<'a> { backend: Box<dyn Backend>, drawtarget: Box<dyn GenericDrawTarget>, @@ -386,7 +370,7 @@ pub struct CanvasData<'a> { old_image_key: Option<ImageKey>, /// An old webrender image key that can be deleted when the current epoch ends. very_old_image_key: Option<ImageKey>, - font_cache_thread: Mutex<FontCacheThread>, + font_context: Arc<FontContext<FontCacheThread>>, } fn create_backend() -> Box<dyn Backend> { @@ -398,7 +382,7 @@ impl<'a> CanvasData<'a> { size: Size2D<u64>, webrender_api: Box<dyn WebrenderApi>, antialias: AntialiasMode, - font_cache_thread: FontCacheThread, + font_context: Arc<FontContext<FontCacheThread>>, ) -> CanvasData<'a> { let backend = create_backend(); let draw_target = backend.create_drawtarget(size); @@ -412,7 +396,7 @@ impl<'a> CanvasData<'a> { image_key: None, old_image_key: None, very_old_image_key: None, - font_cache_thread: Mutex::new(font_cache_thread), + font_context, } } @@ -494,17 +478,14 @@ impl<'a> CanvasData<'a> { let font = font_style.map_or_else( || load_system_font_from_style(None), |style| { - with_thread_local_font_context(self, |font_context| { - let font_group = font_context.font_group(ServoArc::new(style.clone())); - let font = font_group - .borrow_mut() - .first(font_context) - .expect("couldn't find font"); - let font = font.borrow_mut(); - Font::from_bytes(font.template.data(), 0) - .ok() - .or_else(|| load_system_font_from_style(Some(style))) - }) + let font_group = self.font_context.font_group(ServoArc::new(style.clone())); + let font = font_group + .write() + .first(&self.font_context) + .expect("couldn't find font"); + Font::from_bytes(font.template.data(), 0) + .ok() + .or_else(|| load_system_font_from_style(Some(style))) }, ); let font = match font { diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index ff9fcfaa313..2176ad8d636 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -4,6 +4,7 @@ use std::borrow::ToOwned; use std::collections::HashMap; +use std::sync::Arc; use std::thread; use canvas_traits::canvas::*; @@ -11,6 +12,7 @@ use canvas_traits::ConstellationCanvasMsg; use crossbeam_channel::{select, unbounded, Sender}; use euclid::default::Size2D; use gfx::font_cache_thread::FontCacheThread; +use gfx::font_context::FontContext; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use log::warn; @@ -40,7 +42,7 @@ pub struct CanvasPaintThread<'a> { canvases: HashMap<CanvasId, CanvasData<'a>>, next_canvas_id: CanvasId, webrender_api: Box<dyn WebrenderApi>, - font_cache_thread: FontCacheThread, + font_context: Arc<FontContext<FontCacheThread>>, } impl<'a> CanvasPaintThread<'a> { @@ -52,7 +54,7 @@ impl<'a> CanvasPaintThread<'a> { canvases: HashMap::new(), next_canvas_id: CanvasId(0), webrender_api, - font_cache_thread, + font_context: Arc::new(FontContext::new(font_cache_thread)), } } @@ -129,8 +131,6 @@ impl<'a> CanvasPaintThread<'a> { AntialiasMode::None }; - let font_cache_thread = self.font_cache_thread.clone(); - let canvas_id = self.next_canvas_id; self.next_canvas_id.0 += 1; @@ -138,7 +138,7 @@ impl<'a> CanvasPaintThread<'a> { size, self.webrender_api.clone(), antialias, - font_cache_thread, + self.font_context.clone(), ); self.canvases.insert(canvas_id, canvas_data); diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index c9c0d388fcb..bec2264c44c 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [dependencies] app_units = { workspace = true } +atomic_refcell = { workspace = true } bitflags = { workspace = true } cssparser = { workspace = true } euclid = { workspace = true } diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 1921edc1d12..de42f6c321c 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -3,11 +3,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::ToOwned; -use std::cell::RefCell; use std::collections::HashMap; -use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use std::time::Instant; use std::{iter, str}; @@ -15,6 +13,7 @@ use app_units::Au; use bitflags::bitflags; use euclid::default::{Point2D, Rect, Size2D}; use log::debug; +use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use servo_atoms::{atom, Atom}; use smallvec::SmallVec; @@ -185,15 +184,21 @@ impl<'a> From<&'a FontStyleStruct> for FontDescriptor { } } +#[derive(Debug, Default)] +struct CachedShapeData { + glyph_advances: HashMap<GlyphId, FractionalPixel>, + glyph_indices: HashMap<char, Option<GlyphId>>, + shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>, +} + #[derive(Debug)] pub struct Font { pub handle: PlatformFont, pub template: FontTemplateRef, pub metrics: FontMetrics, pub descriptor: FontDescriptor, - shaper: Option<Shaper>, - shape_cache: RefCell<HashMap<ShapeCacheEntry, Arc<GlyphStore>>>, - glyph_advance_cache: RefCell<HashMap<u32, FractionalPixel>>, + shaper: OnceLock<Shaper>, + cached_shape_data: RwLock<CachedShapeData>, pub font_key: FontInstanceKey, /// If this is a synthesized small caps font, then this font reference is for @@ -214,11 +219,10 @@ impl Font { Ok(Font { handle, template, - shaper: None, + shaper: OnceLock::new(), descriptor, metrics, - shape_cache: RefCell::new(HashMap::new()), - glyph_advance_cache: RefCell::new(HashMap::new()), + cached_shape_data: Default::default(), font_key: FontInstanceKey::default(), synthesized_small_caps, }) @@ -272,52 +276,49 @@ struct ShapeCacheEntry { } impl Font { - pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> { + pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> { let this = self as *const Font; - let mut shaper = self.shaper.take(); - let lookup_key = ShapeCacheEntry { text: text.to_owned(), options: *options, }; - let result = self - .shape_cache - .borrow_mut() - .entry(lookup_key) - .or_insert_with(|| { - let start_time = Instant::now(); - let mut glyphs = GlyphStore::new( - text.len(), - options - .flags - .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG), - options.flags.contains(ShapingFlags::RTL_FLAG), - ); - - if self.can_do_fast_shaping(text, options) { - debug!("shape_text: Using ASCII fast path."); - self.shape_text_fast(text, options, &mut glyphs); - } else { - debug!("shape_text: Using Harfbuzz."); - if shaper.is_none() { - shaper = Some(Shaper::new(this)); - } - shaper - .as_ref() - .unwrap() - .shape_text(text, options, &mut glyphs); - } + { + let cache = self.cached_shape_data.read(); + if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) { + return shaped_text.clone(); + } + } - let end_time = Instant::now(); - TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add( - (end_time.duration_since(start_time).as_nanos()) as usize, - Ordering::Relaxed, - ); - Arc::new(glyphs) - }) - .clone(); - self.shaper = shaper; - result + let start_time = Instant::now(); + let mut glyphs = GlyphStore::new( + text.len(), + options + .flags + .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG), + options.flags.contains(ShapingFlags::RTL_FLAG), + ); + + if self.can_do_fast_shaping(text, options) { + debug!("shape_text: Using ASCII fast path."); + self.shape_text_fast(text, options, &mut glyphs); + } else { + debug!("shape_text: Using Harfbuzz."); + self.shaper + .get_or_init(|| Shaper::new(this)) + .shape_text(text, options, &mut glyphs); + } + + let shaped_text = Arc::new(glyphs); + let mut cache = self.cached_shape_data.write(); + cache.shaped_text.insert(lookup_key, shaped_text.clone()); + + let end_time = Instant::now(); + TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add( + (end_time.duration_since(start_time).as_nanos()) as usize, + Ordering::Relaxed, + ); + + shaped_text } fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool { @@ -377,11 +378,21 @@ impl Font { #[inline] pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { + { + let cache = self.cached_shape_data.read(); + if let Some(glyph) = cache.glyph_indices.get(&codepoint) { + return *glyph; + } + } let codepoint = match self.descriptor.variant { font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(), font_variant_caps::T::Normal => codepoint, }; - self.handle.glyph_index(codepoint) + let glyph_index = self.handle.glyph_index(codepoint); + + let mut cache = self.cached_shape_data.write(); + cache.glyph_indices.insert(codepoint, glyph_index); + glyph_index } pub fn has_glyph_for(&self, codepoint: char) -> bool { @@ -392,21 +403,27 @@ impl Font { self.handle.glyph_h_kerning(first_glyph, second_glyph) } - pub fn glyph_h_advance(&self, glyph: GlyphId) -> FractionalPixel { - *self - .glyph_advance_cache - .borrow_mut() - .entry(glyph) - .or_insert_with(|| { - match self.handle.glyph_h_advance(glyph) { - Some(adv) => adv, - None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel, // FIXME: Need fallback strategy - } - }) + pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel { + { + let cache = self.cached_shape_data.read(); + if let Some(width) = cache.glyph_advances.get(&glyph_id) { + return *width; + } + } + + // TODO: Need a fallback strategy. + let new_width = match self.handle.glyph_h_advance(glyph_id) { + Some(adv) => adv, + None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel, + }; + + let mut cache = self.cached_shape_data.write(); + cache.glyph_advances.insert(glyph_id, new_width); + new_width } } -pub type FontRef = Rc<RefCell<Font>>; +pub type FontRef = Arc<Font>; /// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by /// `TextRun` to decide which font to render a character with. If none of the fonts listed in the @@ -442,14 +459,13 @@ impl FontGroup { /// found, returns None. pub fn find_by_codepoint<S: FontSource>( &mut self, - font_context: &mut FontContext<S>, + font_context: &FontContext<S>, codepoint: char, ) -> Option<FontRef> { let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps && codepoint.is_ascii_lowercase(); let font_or_synthesized_small_caps = |font: FontRef| { if should_look_for_small_caps { - let font = font.borrow(); if font.synthesized_small_caps.is_some() { return font.synthesized_small_caps.clone(); } @@ -457,7 +473,7 @@ impl FontGroup { Some(font) }; - let glyph_in_font = |font: &FontRef| font.borrow().has_glyph_for(codepoint); + let glyph_in_font = |font: &FontRef| font.has_glyph_for(codepoint); let char_in_template = |template: FontTemplateRef| template.char_in_unicode_range(codepoint); @@ -466,7 +482,7 @@ impl FontGroup { } if let Some(ref last_matching_fallback) = self.last_matching_fallback { - if char_in_template(last_matching_fallback.borrow().template.clone()) && + if char_in_template(last_matching_fallback.template.clone()) && glyph_in_font(last_matching_fallback) { return font_or_synthesized_small_caps(last_matching_fallback.clone()); @@ -487,7 +503,7 @@ impl FontGroup { } /// Find the first available font in the group, or the first available fallback font. - pub fn first<S: FontSource>(&mut self, font_context: &mut FontContext<S>) -> Option<FontRef> { + pub fn first<S: FontSource>(&mut self, font_context: &FontContext<S>) -> Option<FontRef> { // From https://drafts.csswg.org/css-fonts/#first-available-font: // > The first available font, used for example in the definition of font-relative lengths // > such as ex or in the definition of the line-height property, is defined to be the first @@ -506,7 +522,7 @@ impl FontGroup { /// a suitable font. fn find<S, TemplatePredicate, FontPredicate>( &mut self, - font_context: &mut FontContext<S>, + font_context: &FontContext<S>, template_predicate: TemplatePredicate, font_predicate: FontPredicate, ) -> Option<FontRef> @@ -535,7 +551,7 @@ impl FontGroup { /// used to refine the list of family names which will be tried. fn find_fallback<S, TemplatePredicate, FontPredicate>( &mut self, - font_context: &mut FontContext<S>, + font_context: &FontContext<S>, codepoint: Option<char>, template_predicate: TemplatePredicate, font_predicate: FontPredicate, @@ -602,7 +618,7 @@ impl FontGroupFamily { fn find<S, TemplatePredicate, FontPredicate>( &mut self, font_descriptor: &FontDescriptor, - font_context: &mut FontContext<S>, + font_context: &FontContext<S>, template_predicate: &TemplatePredicate, font_predicate: &FontPredicate, ) -> Option<FontRef> @@ -633,7 +649,7 @@ impl FontGroupFamily { fn members<'a, S: FontSource>( &'a mut self, font_descriptor: &FontDescriptor, - font_context: &mut FontContext<S>, + font_context: &FontContext<S>, ) -> impl Iterator<Item = &mut FontGroupFamilyMember> + 'a { let family_descriptor = &self.family_descriptor; let members = self.members.get_or_insert_with(|| { diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 5d7d160ca65..3c1790edb6f 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -3,14 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::ToOwned; -use std::cell::RefCell; use std::collections::HashMap; use std::ops::{Deref, RangeInclusive}; -use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::{f32, fmt, mem, thread}; use app_units::Au; +use atomic_refcell::AtomicRefCell; use gfx_traits::WebrenderApi; use ipc_channel::ipc::{self, IpcBytesSender, IpcReceiver, IpcSender}; use log::{debug, trace}; @@ -125,7 +124,8 @@ impl FontTemplates { return; } } - self.templates.push(Rc::new(RefCell::new(new_template))); + self.templates + .push(Arc::new(AtomicRefCell::new(new_template))); } } @@ -730,7 +730,7 @@ impl FontSource for FontCacheThread { .into_iter() .map(|serialized_font_template| { let font_data = serialized_font_template.bytes_receiver.recv().ok(); - Rc::new(RefCell::new(FontTemplate { + Arc::new(AtomicRefCell::new(FontTemplate { identifier: serialized_font_template.identifier, descriptor: serialized_font_template.descriptor.clone(), data: font_data.map(Arc::new), diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs index b831fc916cd..c08764f7a70 100644 --- a/components/gfx/font_context.rs +++ b/components/gfx/font_context.rs @@ -2,17 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::RefCell; use std::collections::HashMap; use std::default::Default; use std::hash::{BuildHasherDefault, Hash, Hasher}; -use std::rc::Rc; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use app_units::Au; use fnv::FnvHasher; use log::debug; -use servo_arc::Arc; +use parking_lot::{Mutex, RwLock}; +use servo_arc::Arc as ServoArc; use style::computed_values::font_variant_caps::T as FontVariantCaps; use style::properties::style_structs::Font as FontStyleStruct; use webrender_api::{FontInstanceFlags, FontInstanceKey}; @@ -25,10 +24,6 @@ use crate::platform::core_text_font_cache::CoreTextFontCache; static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) -/// An epoch for the font context cache. The cache is flushed if the current epoch does not match -/// this one. -static FONT_CACHE_EPOCH: AtomicUsize = AtomicUsize::new(0); - pub trait FontSource { fn get_font_instance( &mut self, @@ -49,48 +44,48 @@ pub trait FontSource { /// required. #[derive(Debug)] pub struct FontContext<S: FontSource> { - font_source: S, + font_source: Mutex<S>, // TODO: The font context holds a strong ref to the cached fonts // so they will never be released. Find out a good time to drop them. // See bug https://github.com/servo/servo/issues/3300 - font_cache: HashMap<FontCacheKey, Option<FontRef>>, - font_template_cache: HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>, - + font_cache: RwLock<HashMap<FontCacheKey, Option<FontRef>>>, + font_template_cache: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>, font_group_cache: - HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>, - - epoch: usize, + RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>, } impl<S: FontSource> FontContext<S> { pub fn new(font_source: S) -> FontContext<S> { #[allow(clippy::default_constructed_unit_structs)] FontContext { - font_source, - font_cache: HashMap::new(), - font_template_cache: HashMap::new(), - font_group_cache: HashMap::with_hasher(Default::default()), - epoch: 0, + font_source: Mutex::new(font_source), + font_cache: RwLock::default(), + font_template_cache: RwLock::default(), + font_group_cache: RwLock::default(), } } - fn expire_font_caches_if_necessary(&mut self) { - let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst); - if current_epoch == self.epoch { - return; - } - - self.font_cache.clear(); - self.font_template_cache.clear(); - self.font_group_cache.clear(); - self.epoch = current_epoch + /// Invalidate all caches that this [`FontContext`] holds and any in-process platform-specific + /// caches. + /// + /// # Safety + /// + /// This should never be called when more than one thread is using the [`FontContext`] or it + /// may leave the context in an inconsistent state. + pub fn invalidate_caches(&self) { + #[cfg(target_os = "macos")] + CoreTextFontCache::clear_core_text_font_cache(); + + self.font_cache.write().clear(); + self.font_template_cache.write().clear(); + self.font_group_cache.write().clear(); } /// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`. /// Font groups are cached, so subsequent calls with the same `style` will return a reference /// to an existing `FontGroup`. - pub fn font_group(&mut self, style: Arc<FontStyleStruct>) -> Rc<RefCell<FontGroup>> { + pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> Arc<RwLock<FontGroup>> { let font_size = style.font_size.computed_size().into(); self.font_group_with_size(style, font_size) } @@ -98,27 +93,27 @@ impl<S: FontSource> FontContext<S> { /// Like [`Self::font_group`], but overriding the size found in the [`FontStyleStruct`] with the given size /// in pixels. pub fn font_group_with_size( - &mut self, - style: Arc<FontStyleStruct>, + &self, + style: ServoArc<FontStyleStruct>, size: Au, - ) -> Rc<RefCell<FontGroup>> { - self.expire_font_caches_if_necessary(); - + ) -> Arc<RwLock<FontGroup>> { let cache_key = FontGroupCacheKey { size, style }; - if let Some(font_group) = self.font_group_cache.get(&cache_key) { + if let Some(font_group) = self.font_group_cache.read().get(&cache_key) { return font_group.clone(); } - let font_group = Rc::new(RefCell::new(FontGroup::new(&cache_key.style))); - self.font_group_cache.insert(cache_key, font_group.clone()); + let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style))); + self.font_group_cache + .write() + .insert(cache_key, font_group.clone()); font_group } /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a /// reference to the same underlying `Font`. pub fn font( - &mut self, + &self, font_template: FontTemplateRef, font_descriptor: &FontDescriptor, ) -> Option<FontRef> { @@ -130,7 +125,7 @@ impl<S: FontSource> FontContext<S> { } fn get_font_maybe_synthesizing_small_caps( - &mut self, + &self, font_template: FontTemplateRef, font_descriptor: &FontDescriptor, synthesize_small_caps: bool, @@ -157,27 +152,28 @@ impl<S: FontSource> FontContext<S> { font_descriptor: font_descriptor.clone(), }; - self.font_cache.get(&cache_key).cloned().unwrap_or_else(|| { - debug!( - "FontContext::font cache miss for font_template={:?} font_descriptor={:?}", - font_template, font_descriptor - ); - - let font = self - .create_font( - font_template, - font_descriptor.to_owned(), - synthesized_small_caps_font, - ) - .ok(); - self.font_cache.insert(cache_key, font.clone()); + if let Some(font) = self.font_cache.read().get(&cache_key).cloned() { + return font; + } - font - }) + debug!( + "FontContext::font cache miss for font_template={:?} font_descriptor={:?}", + font_template, font_descriptor + ); + + let font = self + .create_font( + font_template, + font_descriptor.to_owned(), + synthesized_small_caps_font, + ) + .ok(); + self.font_cache.write().insert(cache_key, font.clone()); + font } pub fn matching_templates( - &mut self, + &self, descriptor_to_match: &FontDescriptor, family_descriptor: &FontFamilyDescriptor, ) -> Vec<FontTemplateRef> { @@ -186,27 +182,31 @@ impl<S: FontSource> FontContext<S> { family_descriptor: family_descriptor.clone(), }; - self.font_template_cache.get(&cache_key).cloned().unwrap_or_else(|| { - debug!( - "FontContext::font_template cache miss for template_descriptor={:?} family_descriptor={:?}", - descriptor_to_match, - family_descriptor - ); - - let template_info = self.font_source.find_matching_font_templates( - descriptor_to_match, - family_descriptor.clone(), - ); - - self.font_template_cache.insert(cache_key, template_info.clone()); - template_info - }) + if let Some(templates) = self.font_template_cache.read().get(&cache_key).cloned() { + return templates; + } + + debug!( + "FontContext::font_template cache miss for template_descriptor={:?} family_descriptor={:?}", + descriptor_to_match, + family_descriptor + ); + + let templates = self + .font_source + .lock() + .find_matching_font_templates(descriptor_to_match, family_descriptor.clone()); + + self.font_template_cache + .write() + .insert(cache_key, templates.clone()); + templates } /// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the /// cache thread and a `FontDescriptor` which contains the styling parameters. fn create_font( - &mut self, + &self, font_template: FontTemplateRef, font_descriptor: FontDescriptor, synthesized_small_caps: Option<FontRef>, @@ -216,13 +216,13 @@ impl<S: FontSource> FontContext<S> { font_descriptor.clone(), synthesized_small_caps, )?; - font.font_key = self.font_source.get_font_instance( + font.font_key = self.font_source.lock().get_font_instance( font_template.identifier(), font_descriptor.pt_size, font.webrender_font_instance_flags(), ); - Ok(Rc::new(RefCell::new(font))) + Ok(Arc::new(font)) } } @@ -240,7 +240,7 @@ struct FontTemplateCacheKey { #[derive(Debug)] struct FontGroupCacheKey { - style: Arc<FontStyleStruct>, + style: ServoArc<FontStyleStruct>, size: Au, } @@ -260,11 +260,3 @@ impl Hash for FontGroupCacheKey { self.style.hash.hash(hasher) } } - -#[inline] -pub fn invalidate_font_caches() { - FONT_CACHE_EPOCH.fetch_add(1, Ordering::SeqCst); - - #[cfg(target_os = "macos")] - CoreTextFontCache::clear_core_text_font_cache(); -} diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs index 30a40608df5..0af8aa75443 100644 --- a/components/gfx/font_template.rs +++ b/components/gfx/font_template.rs @@ -2,12 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::RefCell; use std::fmt::{Debug, Error, Formatter}; use std::ops::RangeInclusive; -use std::rc::Rc; use std::sync::Arc; +use atomic_refcell::AtomicRefCell; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; use style::computed_values::font_stretch::T as FontStretch; @@ -22,7 +21,7 @@ use crate::platform::font::PlatformFont; use crate::platform::font_list::LocalFontIdentifier; /// A reference to a [`FontTemplate`] with shared ownership and mutability. -pub type FontTemplateRef = Rc<RefCell<FontTemplate>>; +pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>; /// Describes how to select a font from a given family. This is very basic at the moment and needs /// to be expanded or refactored when we support more of the font styling parameters. diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs index f437dbed8c0..f68e370e6d3 100644 --- a/components/gfx/platform/freetype/font.rs +++ b/components/gfx/platform/freetype/font.rs @@ -9,14 +9,15 @@ use std::{mem, ptr}; use app_units::Au; use freetype::freetype::{ - FT_Done_Face, FT_F26Dot6, FT_Face, FT_FaceRec, FT_Get_Char_Index, FT_Get_Kerning, - FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, FT_Load_Glyph, FT_Load_Sfnt_Table, - FT_Long, FT_New_Memory_Face, FT_Set_Char_Size, FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics, - FT_UInt, FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC, + FT_Done_Face, FT_F26Dot6, FT_Face, FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table, + FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, FT_Load_Glyph, FT_Load_Sfnt_Table, FT_Long, + FT_New_Memory_Face, FT_Set_Char_Size, FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics, FT_UInt, + FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC, }; use freetype::succeeded; use freetype::tt_os2::TT_OS2; use log::debug; +use parking_lot::ReentrantMutex; use style::computed_values::font_stretch::T as FontStretch; use style::computed_values::font_weight::T as FontWeight; use style::values::computed::font::FontStyle; @@ -75,19 +76,26 @@ pub struct PlatformFont { /// The font data itself, which must stay valid for the lifetime of the /// platform [`FT_Face`]. font_data: Arc<Vec<u8>>, - face: FT_Face, + face: ReentrantMutex<FT_Face>, can_do_fast_shaping: bool, } +// FT_Face can be used in multiple threads, but from only one thread at a time. +// It's protected with a ReentrantMutex for PlatformFont. +// See https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face. +unsafe impl Sync for PlatformFont {} +unsafe impl Send for PlatformFont {} + impl Drop for PlatformFont { fn drop(&mut self) { - assert!(!self.face.is_null()); + let face = self.face.lock(); + assert!(!face.is_null()); unsafe { // The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face` // should be protected by a mutex. // See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html. let _guard = FreeTypeLibraryHandle::get().lock(); - if !succeeded(FT_Done_Face(self.face)) { + if !succeeded(FT_Done_Face(*face)) { panic!("FT_Done_Face failed"); } } @@ -134,7 +142,7 @@ impl PlatformFontMethods for PlatformFont { ) -> Result<PlatformFont, &'static str> { let face = create_face(data.clone(), face_index, pt_size)?; let mut handle = PlatformFont { - face, + face: ReentrantMutex::new(face), font_data: data, can_do_fast_shaping: false, }; @@ -147,7 +155,8 @@ impl PlatformFontMethods for PlatformFont { } fn descriptor(&self) -> FontTemplateDescriptor { - let style = if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 } { + let face = self.face.lock(); + let style = if unsafe { (**face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 } { FontStyle::ITALIC } else { FontStyle::NORMAL @@ -178,9 +187,11 @@ impl PlatformFontMethods for PlatformFont { } fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { - assert!(!self.face.is_null()); + let face = self.face.lock(); + assert!(!face.is_null()); + unsafe { - let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong); + let idx = FT_Get_Char_Index(*face, codepoint as FT_ULong); if idx != 0 as FT_UInt { Some(idx as GlyphId) } else { @@ -194,11 +205,13 @@ impl PlatformFontMethods for PlatformFont { } fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel { - assert!(!self.face.is_null()); + let face = self.face.lock(); + assert!(!face.is_null()); + let mut delta = FT_Vector { x: 0, y: 0 }; unsafe { FT_Get_Kerning( - self.face, + *face, first_glyph, second_glyph, FT_Kerning_Mode::FT_KERNING_DEFAULT as FT_UInt, @@ -213,11 +226,13 @@ impl PlatformFontMethods for PlatformFont { } fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> { - assert!(!self.face.is_null()); + let face = self.face.lock(); + assert!(!face.is_null()); + unsafe { - let res = FT_Load_Glyph(self.face, glyph as FT_UInt, GLYPH_LOAD_FLAGS); + let res = FT_Load_Glyph(*face, glyph as FT_UInt, GLYPH_LOAD_FLAGS); if succeeded(res) { - let void_glyph = (*self.face).glyph; + let void_glyph = (**face).glyph; let slot: FT_GlyphSlot = void_glyph; assert!(!slot.is_null()); let advance = (*slot).metrics.horiAdvance; @@ -232,8 +247,8 @@ impl PlatformFontMethods for PlatformFont { } fn metrics(&self) -> FontMetrics { - /* TODO(Issue #76): complete me */ - let face = self.face_rec_mut(); + let face = self.face.lock(); + let face = unsafe { **face }; let underline_size = self.font_units_to_au(face.underline_thickness as f64); let underline_offset = self.font_units_to_au(face.underline_position as f64); @@ -294,24 +309,19 @@ impl PlatformFontMethods for PlatformFont { } fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> { + let face = self.face.lock(); let tag = tag as FT_ULong; unsafe { // Get the length let mut len = 0; - if !succeeded(FT_Load_Sfnt_Table( - self.face, - tag, - 0, - ptr::null_mut(), - &mut len, - )) { + if !succeeded(FT_Load_Sfnt_Table(*face, tag, 0, ptr::null_mut(), &mut len)) { return None; } // Get the bytes let mut buf = vec![0u8; len as usize]; if !succeeded(FT_Load_Sfnt_Table( - self.face, + *face, tag, 0, buf.as_mut_ptr(), @@ -343,9 +353,10 @@ impl<'a> PlatformFont { } fn has_table(&self, tag: FontTableTag) -> bool { + let face = self.face.lock(); unsafe { succeeded(FT_Load_Sfnt_Table( - self.face, + *face, tag as FT_ULong, 0, ptr::null_mut(), @@ -354,13 +365,9 @@ impl<'a> PlatformFont { } } - #[allow(clippy::mut_from_ref)] // Intended for this function - fn face_rec_mut(&'a self) -> &'a mut FT_FaceRec { - unsafe { &mut (*self.face) } - } - fn font_units_to_au(&self, value: f64) -> Au { - let face = self.face_rec_mut(); + let face = self.face.lock(); + let face = unsafe { **face }; // face.size is a *c_void in the bindings, presumably to avoid // recursive structural types @@ -377,9 +384,10 @@ impl<'a> PlatformFont { } fn os2_table(&self) -> Option<OS2Table> { + let face = self.face.lock(); + unsafe { - let os2 = - FT_Get_Sfnt_Table(self.face_rec_mut(), FT_Sfnt_Tag::FT_SFNT_OS2) as *mut TT_OS2; + let os2 = FT_Get_Sfnt_Table(*face, FT_Sfnt_Tag::FT_SFNT_OS2) as *mut TT_OS2; let valid = !os2.is_null() && (*os2).version != 0xffff; if !valid { diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs index c6e009ffae8..7ab00b51cd0 100644 --- a/components/gfx/platform/macos/font.rs +++ b/components/gfx/platform/macos/font.rs @@ -63,6 +63,17 @@ pub struct PlatformFont { can_do_fast_shaping: bool, } +// From https://developer.apple.com/documentation/coretext: +// > All individual functions in Core Text are thread-safe. Font objects (CTFont, +// > CTFontDescriptor, and associated objects) can be used simultaneously by multiple +// > operations, work queues, or threads. However, the layout objects (CTTypesetter, +// > CTFramesetter, CTRun, CTLine, CTFrame, and associated objects) should be used in a +// > single operation, work queue, or thread. +// +// The other element is a read-only CachedKernTable which is stored in a CFData. +unsafe impl Sync for PlatformFont {} +unsafe impl Send for PlatformFont {} + impl PlatformFont { /// Cache all the data needed for basic horizontal kerning. This is used only as a fallback or /// fast path (when the GPOS table is missing or unnecessary) so it needn't handle every case. diff --git a/components/gfx/platform/windows/font.rs b/components/gfx/platform/windows/font.rs index 5debd0c64fa..5ec0a4c658a 100644 --- a/components/gfx/platform/windows/font.rs +++ b/components/gfx/platform/windows/font.rs @@ -82,6 +82,13 @@ pub struct PlatformFont { scaled_du_to_px: f32, } +// Based on information from the Skia codebase, it seems that DirectWrite APIs from +// Windows 10 and beyond are thread safe. If problems arise from this, we can protect the +// platform font with a Mutex. +// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/ports/SkScalerContext_win_dw.cpp;l=56;bpv=0;bpt=1. +unsafe impl Sync for PlatformFont {} +unsafe impl Send for PlatformFont {} + struct Nondebug<T>(T); impl<T> fmt::Debug for Nondebug<T> { diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index f502b7d97fe..54babf3ef83 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -150,22 +150,26 @@ fn font_family(names: Vec<&str>) -> FontFamily { #[test] fn test_font_group_is_cached_by_style() { let source = TestFontSource::new(); - let mut context = FontContext::new(source); + let context = FontContext::new(source); let style1 = style(); let mut style2 = style(); style2.set_font_style(FontStyle::ITALIC); - assert_eq!( - context.font_group(Arc::new(style1.clone())).as_ptr(), - context.font_group(Arc::new(style1.clone())).as_ptr(), + assert!( + std::ptr::eq( + &*context.font_group(Arc::new(style1.clone())).read(), + &*context.font_group(Arc::new(style1.clone())).read() + ), "the same font group should be returned for two styles with the same hash" ); - assert_ne!( - context.font_group(Arc::new(style1.clone())).as_ptr(), - context.font_group(Arc::new(style2.clone())).as_ptr(), + assert!( + !std::ptr::eq( + &*context.font_group(Arc::new(style1.clone())).read(), + &*context.font_group(Arc::new(style2.clone())).read() + ), "different font groups should be returned for two styles with different hashes" ) } @@ -181,12 +185,9 @@ fn test_font_group_find_by_codepoint() { let group = context.font_group(Arc::new(style)); - let font = group - .borrow_mut() - .find_by_codepoint(&mut context, 'a') - .unwrap(); + let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); assert_eq!( - font.borrow().identifier(), + font.identifier(), TestFontSource::identifier_for_font_name("csstest-ascii") ); assert_eq!( @@ -195,12 +196,9 @@ fn test_font_group_find_by_codepoint() { "only the first font in the list should have been loaded" ); - let font = group - .borrow_mut() - .find_by_codepoint(&mut context, 'a') - .unwrap(); + let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); assert_eq!( - font.borrow().identifier(), + font.identifier(), TestFontSource::identifier_for_font_name("csstest-ascii") ); assert_eq!( @@ -209,12 +207,9 @@ fn test_font_group_find_by_codepoint() { "we shouldn't load the same font a second time" ); - let font = group - .borrow_mut() - .find_by_codepoint(&mut context, 'á') - .unwrap(); + let font = group.write().find_by_codepoint(&mut context, 'á').unwrap(); assert_eq!( - font.borrow().identifier(), + font.identifier(), TestFontSource::identifier_for_font_name("csstest-basic-regular") ); assert_eq!(count.get(), 2, "both fonts should now have been loaded"); @@ -230,22 +225,16 @@ fn test_font_fallback() { let group = context.font_group(Arc::new(style)); - let font = group - .borrow_mut() - .find_by_codepoint(&mut context, 'a') - .unwrap(); + let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); assert_eq!( - font.borrow().identifier(), + font.identifier(), TestFontSource::identifier_for_font_name("csstest-ascii"), "a family in the group should be used if there is a matching glyph" ); - let font = group - .borrow_mut() - .find_by_codepoint(&mut context, 'á') - .unwrap(); + let font = group.write().find_by_codepoint(&mut context, 'á').unwrap(); assert_eq!( - font.borrow().identifier(), + font.identifier(), TestFontSource::identifier_for_font_name("csstest-basic-regular"), "a fallback font should be used if there is no matching glyph in the group" ); @@ -255,7 +244,7 @@ fn test_font_fallback() { fn test_font_template_is_cached() { let source = TestFontSource::new(); let count = source.find_font_count.clone(); - let mut context = FontContext::new(source); + let context = FontContext::new(source); let mut font_descriptor = FontDescriptor { weight: FontWeight::normal(), @@ -280,8 +269,7 @@ fn test_font_template_is_cached() { .unwrap(); assert_ne!( - font1.borrow().descriptor.pt_size, - font2.borrow().descriptor.pt_size, + font1.descriptor.pt_size, font2.descriptor.pt_size, "the same font should not have been returned" ); diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index 5f2e801f40a..ce0ff44be8f 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -130,6 +130,12 @@ pub struct Shaper { font: *const Font, } +// The HarfBuzz API is thread safe as well as our `Font`, so we can make the data +// structures here as thread-safe as well. This doesn't seem to be documented, +// but was expressed as one of the original goals of the HarfBuzz API. +unsafe impl Sync for Shaper {} +unsafe impl Send for Shaper {} + impl Drop for Shaper { fn drop(&mut self) { unsafe { diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs index 3973702a026..d4bec79564a 100644 --- a/components/gfx/text/text_run.rs +++ b/components/gfx/text/text_run.rs @@ -16,7 +16,7 @@ use unicode_bidi as bidi; use webrender_api::FontInstanceKey; use xi_unicode::LineBreakLeafIter; -use crate::font::{Font, FontMetrics, RunMetrics, ShapingFlags, ShapingOptions}; +use crate::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions}; use crate::text::glyph::{ByteIndex, GlyphStore}; thread_local! { @@ -180,13 +180,14 @@ impl<'a> Iterator for CharacterSliceIterator<'a> { impl<'a> TextRun { /// Constructs a new text run. Also returns if there is a line break at the beginning pub fn new( - font: &mut Font, + font: FontRef, text: String, options: &ShapingOptions, bidi_level: bidi::Level, breaker: &mut Option<LineBreakLeafIter>, ) -> (TextRun, bool) { - let (glyphs, break_at_zero) = TextRun::break_and_shape(font, &text, options, breaker); + let (glyphs, break_at_zero) = + TextRun::break_and_shape(font.clone(), &text, options, breaker); ( TextRun { text: Arc::new(text), @@ -202,7 +203,7 @@ impl<'a> TextRun { } pub fn break_and_shape( - font: &mut Font, + font: FontRef, text: &str, options: &ShapingOptions, breaker: &mut Option<LineBreakLeafIter>, diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 522635543fd..1c4eff55898 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -42,7 +42,7 @@ use style::values::generics::counters::ContentItem; use style::LocalName; use crate::block::BlockFlow; -use crate::context::{with_thread_local_font_context, LayoutContext}; +use crate::context::LayoutContext; use crate::data::{InnerLayoutData, LayoutDataFlags}; use crate::display_list::items::OpaqueNode; use crate::flex::FlexFlow; @@ -517,11 +517,10 @@ where // We must scan for runs before computing minimum ascent and descent because scanning // for runs might collapse so much whitespace away that only hypothetical fragments // remain. In that case the inline flow will compute its ascent and descent to be zero. - let scanned_fragments = - with_thread_local_font_context(self.layout_context, |font_context| { - TextRunScanner::new() - .scan_for_runs(font_context, mem::take(&mut fragments.fragments)) - }); + let scanned_fragments = TextRunScanner::new().scan_for_runs( + &self.layout_context.font_context, + mem::take(&mut fragments.fragments), + ); let mut inline_flow_ref = FlowRef::new(Arc::new(InlineFlow::from_fragments( scanned_fragments, node.style(self.style_context()).writing_mode, @@ -550,11 +549,10 @@ where { // FIXME(#6503): Use Arc::get_mut().unwrap() here. let inline_flow = FlowRef::deref_mut(&mut inline_flow_ref).as_mut_inline(); - inline_flow.minimum_line_metrics = - with_thread_local_font_context(self.layout_context, |font_context| { - inline_flow - .minimum_line_metrics(font_context, &node.style(self.style_context())) - }); + inline_flow.minimum_line_metrics = inline_flow.minimum_line_metrics( + &self.layout_context.font_context, + &node.style(self.style_context()), + ); } inline_flow_ref.finish(); @@ -1545,11 +1543,10 @@ where )), self.layout_context, )); - let marker_fragments = - with_thread_local_font_context(self.layout_context, |font_context| { - TextRunScanner::new() - .scan_for_runs(font_context, unscanned_marker_fragments) - }); + let marker_fragments = TextRunScanner::new().scan_for_runs( + &self.layout_context.font_context, + unscanned_marker_fragments, + ); marker_fragments.fragments }, ListStyleTypeContent::GeneratedContent(info) => vec![Fragment::new( diff --git a/components/layout/context.rs b/components/layout/context.rs index d43210c141b..5cedf5d38aa 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -4,7 +4,6 @@ //! Data needed by layout. -use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::sync::{Arc, Mutex}; @@ -13,7 +12,6 @@ use std::thread; use fnv::FnvHasher; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context::FontContext; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use msg::constellation_msg::PipelineId; use net_traits::image_cache::{ ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder, @@ -29,32 +27,6 @@ use crate::display_list::items::{OpaqueNode, WebRenderImageInfo}; pub type LayoutFontContext = FontContext<FontCacheThread>; -thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None)); - -pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R -where - F: FnOnce(&mut LayoutFontContext) -> R, -{ - FONT_CONTEXT_KEY.with(|k| { - let mut font_context = k.borrow_mut(); - if font_context.is_none() { - let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone(); - *font_context = Some(FontContext::new(font_cache_thread)); - } - f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap())) - }) -} - -pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize { - FONT_CONTEXT_KEY.with(|r| { - if let Some(ref context) = *r.borrow() { - context.size_of(ops) - } else { - 0 - } - }) -} - type WebrenderImageCache = HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>; @@ -72,8 +44,8 @@ pub struct LayoutContext<'a> { /// Reference to the script thread image cache. pub image_cache: Arc<dyn ImageCache>, - /// Interface to the font cache thread. - pub font_cache_thread: Mutex<FontCacheThread>, + /// A FontContext to be used during layout. + pub font_context: Arc<FontContext<FontCacheThread>>, /// A cache of WebRender image info. pub webrender_image_cache: Arc<RwLock<WebrenderImageCache>>, diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 8e50d1c499e..45843627493 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -57,7 +57,7 @@ use style::values::generics::transform; use webrender_api::units::LayoutTransform; use webrender_api::{self, ImageKey}; -use crate::context::{with_thread_local_font_context, LayoutContext}; +use crate::context::LayoutContext; use crate::display_list::items::{ClipScrollNodeIndex, OpaqueNode, BLUR_INFLATION_FACTOR}; use crate::display_list::ToLayout; use crate::floats::ClearType; @@ -852,9 +852,8 @@ impl Fragment { ))), ); unscanned_ellipsis_fragments.push_back(ellipsis_fragment); - let ellipsis_fragments = with_thread_local_font_context(layout_context, |font_context| { - TextRunScanner::new().scan_for_runs(font_context, unscanned_ellipsis_fragments) - }); + let ellipsis_fragments = TextRunScanner::new() + .scan_for_runs(&layout_context.font_context, unscanned_ellipsis_fragments); debug_assert_eq!(ellipsis_fragments.len(), 1); ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap(); ellipsis_fragment.flags |= FragmentFlags::IS_ELLIPSIS; @@ -2346,9 +2345,10 @@ impl Fragment { return InlineMetrics::new(Au(0), Au(0), Au(0)); } // See CSS 2.1 § 10.8.1. - let font_metrics = with_thread_local_font_context(layout_context, |font_context| { - text::font_metrics_for_style(font_context, self_.style.clone_font()) - }); + let font_metrics = text::font_metrics_for_style( + &layout_context.font_context, + self_.style.clone_font(), + ); let line_height = text::line_height_from_style(&self_.style, &font_metrics); InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height) } @@ -2426,10 +2426,10 @@ impl Fragment { VerticalAlign::Keyword(kw) => match kw { VerticalAlignKeyword::Baseline => {}, VerticalAlignKeyword::Middle => { - let font_metrics = - with_thread_local_font_context(layout_context, |font_context| { - text::font_metrics_for_style(font_context, self.style.clone_font()) - }); + let font_metrics = text::font_metrics_for_style( + &layout_context.font_context, + self.style.clone_font(), + ); offset += (content_inline_metrics.ascent - content_inline_metrics.space_below_baseline - font_metrics.x_height) diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index 862e8c88191..3a50024d522 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -20,7 +20,7 @@ use style::servo::restyle_damage::ServoRestyleDamage; use style::values::generics::counters::ContentItem; use style::values::specified::list::{QuotePair, Quotes}; -use crate::context::{with_thread_local_font_context, LayoutContext}; +use crate::context::LayoutContext; use crate::display_list::items::OpaqueNode; use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils}; use crate::fragment::{ @@ -493,9 +493,7 @@ fn render_text( )); // FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen // due to text run splitting. - let fragments = with_thread_local_font_context(layout_context, |font_context| { - TextRunScanner::new().scan_for_runs(font_context, fragments) - }); + let fragments = TextRunScanner::new().scan_for_runs(&layout_context.font_context, fragments); if fragments.is_empty() { None } else { diff --git a/components/layout/inline.rs b/components/layout/inline.rs index fa2ebb01efb..5926e4b2e03 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -1239,7 +1239,7 @@ impl InlineFlow { /// `style` is the style of the block. pub fn minimum_line_metrics( &self, - font_context: &mut LayoutFontContext, + font_context: &LayoutFontContext, style: &ComputedValues, ) -> LineMetrics { InlineFlow::minimum_line_metrics_for_fragments( @@ -1255,7 +1255,7 @@ impl InlineFlow { /// `style` is the style of the block that these fragments belong to. pub fn minimum_line_metrics_for_fragments( fragments: &[Fragment], - font_context: &mut LayoutFontContext, + font_context: &LayoutFontContext, style: &ComputedValues, ) -> LineMetrics { // As a special case, if this flow contains only hypothetical fragments, then the entire diff --git a/components/layout/list_item.rs b/components/layout/list_item.rs index 601046fc0c8..a7c44164ac1 100644 --- a/components/layout/list_item.rs +++ b/components/layout/list_item.rs @@ -14,7 +14,7 @@ use style::properties::ComputedValues; use style::servo::restyle_damage::ServoRestyleDamage; use crate::block::BlockFlow; -use crate::context::{with_thread_local_font_context, LayoutContext}; +use crate::context::LayoutContext; use crate::display_list::items::DisplayListSection; use crate::display_list::{ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState, @@ -114,13 +114,11 @@ impl ListItemFlow { fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) { // FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does? - let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| { - InlineFlow::minimum_line_metrics_for_fragments( - &self.marker_fragments, - font_context, - &self.block_flow.fragment.style, - ) - }); + let marker_line_metrics = InlineFlow::minimum_line_metrics_for_fragments( + &self.marker_fragments, + &layout_context.font_context, + &self.block_flow.fragment.style, + ); for marker in &mut self.marker_fragments { marker.assign_replaced_block_size_if_necessary(); diff --git a/components/layout/text.rs b/components/layout/text.rs index b11a25f8dee..2875ccd0d77 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -70,7 +70,7 @@ impl TextRunScanner { pub fn scan_for_runs( &mut self, - font_context: &mut LayoutFontContext, + font_context: &LayoutFontContext, mut fragments: LinkedList<Fragment>, ) -> InlineFragments { debug!( @@ -150,7 +150,7 @@ impl TextRunScanner { /// be adjusted. fn flush_clump_to_list( &mut self, - font_context: &mut LayoutFontContext, + font_context: &LayoutFontContext, out_fragments: &mut Vec<Fragment>, paragraph_bytes_processed: &mut usize, bidi_levels: Option<&[bidi::Level]>, @@ -203,10 +203,9 @@ impl TextRunScanner { .map(|l| l.into()) .unwrap_or_else(|| { let space_width = font_group - .borrow_mut() + .write() .find_by_codepoint(font_context, ' ') .and_then(|font| { - let font = font.borrow(); font.glyph_index(' ') .map(|glyph_id| font.glyph_h_advance(glyph_id)) }) @@ -248,7 +247,7 @@ impl TextRunScanner { for (byte_index, character) in text.char_indices() { if !character.is_control() { let font = font_group - .borrow_mut() + .write() .find_by_codepoint(font_context, character); let bidi_level = match bidi_levels { @@ -367,7 +366,7 @@ impl TextRunScanner { // If no font is found (including fallbacks), there's no way we can render. let font = match run_info .font - .or_else(|| font_group.borrow_mut().first(font_context)) + .or_else(|| font_group.write().first(font_context)) { Some(font) => font, None => { @@ -377,7 +376,7 @@ impl TextRunScanner { }; let (run, break_at_zero) = TextRun::new( - &mut font.borrow_mut(), + font, run_info.text, &options, run_info.bidi_level, @@ -535,14 +534,12 @@ fn bounding_box_for_run_metrics( /// Panics if no font can be found for the given font style. #[inline] pub fn font_metrics_for_style( - font_context: &mut LayoutFontContext, + font_context: &LayoutFontContext, style: crate::ServoArc<FontStyleStruct>, ) -> FontMetrics { let font_group = font_context.font_group(style); - let font = font_group.borrow_mut().first(font_context); - let font = font.as_ref().unwrap().borrow(); - - font.metrics.clone() + let font = font_group.write().first(font_context); + font.as_ref().unwrap().metrics.clone() } /// Returns the line block-size needed by the given computed style and font size. @@ -664,10 +661,8 @@ impl RunInfo { fn has_font(&self, font: &Option<FontRef>) -> bool { fn identifier_and_pt_size(font: &Option<FontRef>) -> Option<(FontIdentifier, Au)> { - font.as_ref().map(|font| { - let font = font.borrow(); - (font.identifier().clone(), font.descriptor.pt_size) - }) + font.as_ref() + .map(|font| (font.identifier().clone(), font.descriptor.pt_size)) } identifier_and_pt_size(&self.font) == identifier_and_pt_size(font) diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 8ffc3386e3e..500f16093b1 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::RefCell; use std::sync::{Arc, Mutex}; use fnv::FnvHashMap; @@ -12,7 +11,7 @@ use msg::constellation_msg::PipelineId; use net_traits::image_cache::{ ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder, }; -use parking_lot::{ReentrantMutex, RwLock}; +use parking_lot::RwLock; use script_layout_interface::{PendingImage, PendingImageState}; use servo_url::{ImmutableOrigin, ServoUrl}; use style::context::SharedStyleContext; @@ -20,8 +19,6 @@ use style::dom::OpaqueNode; use crate::display_list::WebRenderImageInfo; -thread_local!(static FONT_CONTEXT: RefCell<Option<FontContext<FontCacheThread>>> = RefCell::new(None)); - pub struct LayoutContext<'a> { pub id: PipelineId, pub use_rayon: bool, @@ -31,7 +28,7 @@ pub struct LayoutContext<'a> { pub style_context: SharedStyleContext<'a>, /// A FontContext to be used during layout. - pub font_cache_thread: Arc<ReentrantMutex<FontCacheThread>>, + pub font_context: Arc<FontContext<FontCacheThread>>, /// Reference to the script thread image cache. pub image_cache: Arc<dyn ImageCache>, @@ -133,27 +130,4 @@ impl<'a> LayoutContext<'a> { None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None, } } - - pub fn with_font_context<F, R>(&self, callback: F) -> R - where - F: FnOnce(&mut FontContext<FontCacheThread>) -> R, - { - with_thread_local_font_context(&self.font_cache_thread, callback) - } -} - -pub fn with_thread_local_font_context<F, R>( - font_cache_thread: &ReentrantMutex<FontCacheThread>, - callback: F, -) -> R -where - F: FnOnce(&mut FontContext<FontCacheThread>) -> R, -{ - FONT_CONTEXT.with(|font_context| { - callback( - font_context - .borrow_mut() - .get_or_insert_with(|| FontContext::new(font_cache_thread.lock().clone())), - ) - }) } diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index cadf5840e32..047cfc7087d 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -1638,10 +1638,9 @@ impl InlineFormattingContext { // It's unfortunate that it isn't possible to get this during IFC text processing, but in // that situation the style of the containing block is unknown. - let default_font_metrics = layout_context.with_font_context(|font_context| { - get_font_for_first_font_for_style(style, font_context) - .map(|font| font.borrow().metrics.clone()) - }); + let default_font_metrics = + get_font_for_first_font_for_style(style, &layout_context.font_context) + .map(|font| font.metrics.clone()); let style_text = containing_block.style.get_inherited_text(); let mut ifc = InlineFormattingContextState { @@ -1768,34 +1767,30 @@ impl InlineFormattingContext { // For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary. let mut on_word_boundary = true; - layout_context.with_font_context(|font_context| { - let mut linebreaker = None; - self.foreach(|iter_item| match iter_item { - InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun( - ref mut text_run, - )) => { - text_run.break_and_shape( - font_context, - &mut linebreaker, - &mut ifc_fonts, - &mut last_inline_box_ended_with_white_space, - &mut on_word_boundary, - ); - }, - InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => { - if let Some(font) = - get_font_for_first_font_for_style(&inline_box.style, font_context) - { - inline_box.default_font_index = - Some(add_or_get_font(&font, &mut ifc_fonts)); - } - }, - InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(_)) => { - last_inline_box_ended_with_white_space = false; - on_word_boundary = true; - }, - _ => {}, - }); + let mut linebreaker = None; + self.foreach(|iter_item| match iter_item { + InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(ref mut text_run)) => { + text_run.break_and_shape( + &layout_context.font_context, + &mut linebreaker, + &mut ifc_fonts, + &mut last_inline_box_ended_with_white_space, + &mut on_word_boundary, + ); + }, + InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => { + if let Some(font) = get_font_for_first_font_for_style( + &inline_box.style, + &layout_context.font_context, + ) { + inline_box.default_font_index = Some(add_or_get_font(&font, &mut ifc_fonts)); + } + }, + InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(_)) => { + last_inline_box_ended_with_white_space = false; + on_word_boundary = true; + }, + _ => {}, }); self.font_metrics = ifc_fonts; diff --git a/components/layout_2020/flow/text_run.rs b/components/layout_2020/flow/text_run.rs index 86884565d57..e82d1a6159b 100644 --- a/components/layout_2020/flow/text_run.rs +++ b/components/layout_2020/flow/text_run.rs @@ -111,7 +111,7 @@ impl TextRunSegment { /// compatible with this segment or false otherwise. fn update_if_compatible( &mut self, - font: &FontRef, + new_font: &FontRef, script: Script, fonts: &[FontKeyAndMetrics], ) -> bool { @@ -120,7 +120,6 @@ impl TextRunSegment { } let current_font_key_and_metrics = &fonts[self.font_index]; - let new_font = font.borrow(); if new_font.font_key != current_font_key_and_metrics.key || new_font.descriptor.pt_size != current_font_key_and_metrics.pt_size { @@ -208,7 +207,7 @@ impl TextRun { pub(super) fn break_and_shape( &mut self, - font_context: &mut FontContext<FontCacheThread>, + font_context: &FontContext<FontCacheThread>, linebreaker: &mut Option<LineBreakLeafIter>, font_cache: &mut Vec<FontKeyAndMetrics>, last_inline_box_ended_with_collapsible_white_space: &mut bool, @@ -244,7 +243,6 @@ impl TextRun { let segments = segment_results .into_iter() .map(|(mut segment, font)| { - let mut font = font.borrow_mut(); let word_spacing = style_word_spacing.unwrap_or_else(|| { let space_width = font .glyph_index(' ') @@ -260,7 +258,7 @@ impl TextRun { }; (segment.runs, segment.break_at_start) = gfx::text::text_run::TextRun::break_and_shape( - &mut font, + font, &self.text [segment.range.begin().0 as usize..segment.range.end().0 as usize], &shaping_options, @@ -280,7 +278,7 @@ impl TextRun { /// [`super::InlineFormattingContext`]. fn segment_text( &mut self, - font_context: &mut FontContext<FontCacheThread>, + font_context: &FontContext<FontCacheThread>, font_cache: &mut Vec<FontKeyAndMetrics>, last_inline_box_ended_with_collapsible_white_space: &mut bool, on_word_boundary: &mut bool, @@ -341,7 +339,7 @@ impl TextRun { } let font = match font_group - .borrow_mut() + .write() .find_by_codepoint(font_context, character) { Some(font) => font, @@ -383,7 +381,7 @@ impl TextRun { // Either we have a current segment or we only had control character and whitespace. In both // of those cases, just use the first font. if current.is_none() { - current = font_group.borrow_mut().first(font_context).map(|font| { + current = font_group.write().first(font_context).map(|font| { let font_index = add_or_get_font(&font, font_cache); ( TextRunSegment::new(font_index, Script::Common, ByteIndex(0)), @@ -489,7 +487,6 @@ fn char_does_not_change_font(character: char) -> bool { } pub(super) fn add_or_get_font(font: &FontRef, ifc_fonts: &mut Vec<FontKeyAndMetrics>) -> usize { - let font = font.borrow(); for (index, ifc_font_info) in ifc_fonts.iter().enumerate() { if ifc_font_info.key == font.font_key && ifc_font_info.pt_size == font.descriptor.pt_size { return index; @@ -505,11 +502,11 @@ pub(super) fn add_or_get_font(font: &FontRef, ifc_fonts: &mut Vec<FontKeyAndMetr pub(super) fn get_font_for_first_font_for_style( style: &ComputedValues, - font_context: &mut FontContext<FontCacheThread>, + font_context: &FontContext<FontCacheThread>, ) -> Option<FontRef> { let font = font_context .font_group(style.clone_font()) - .borrow_mut() + .write() .first(font_context); if font.is_none() { warn!("Could not find font for style: {:?}", style.clone_font()); diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index ccd0f37d829..589a2744364 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -22,16 +22,15 @@ use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as use euclid::{Point2D, Rect, Scale, Size2D}; use fnv::FnvHashMap; use fxhash::{FxHashMap, FxHashSet}; +use gfx::font; use gfx::font_cache_thread::FontCacheThread; -use gfx::{font, font_context}; +use gfx::font_context::FontContext; use gfx_traits::{node_id_from_scroll_id, Epoch}; use histogram::Histogram; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use layout::construct::ConstructionResult; -use layout::context::{ - malloc_size_of_persistent_local_context, LayoutContext, RegisteredPainter, RegisteredPainters, -}; +use layout::context::{LayoutContext, RegisteredPainter, RegisteredPainters}; use layout::display_list::items::{DisplayList, ScrollOffsetMap, WebRenderImageInfo}; use layout::display_list::{IndexableText, ToLayout}; use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils}; @@ -136,6 +135,9 @@ pub struct LayoutThread { /// Public interface to the font cache thread. font_cache_thread: FontCacheThread, + /// A FontContext to be used during layout. + font_context: Arc<FontContext<FontCacheThread>>, + /// Is this the first reflow in this LayoutThread? first_reflow: Cell<bool>, @@ -513,7 +515,7 @@ impl Layout for LayoutThread { // malloc_enclosing_size_of function. let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None); - // FIXME(njn): Just measuring the display tree for now. + // TODO: Measure more than just display list, stylist, and font context. let display_list = self.display_list.borrow(); let display_list_ref = display_list.as_ref(); let formatted_url = &format!("url({})", self.url); @@ -528,13 +530,6 @@ impl Layout for LayoutThread { kind: ReportKind::ExplicitJemallocHeapSize, size: self.stylist.size_of(&mut ops), }); - - // The LayoutThread has data in Persistent TLS... - reports.push(Report { - path: path![formatted_url, "layout-thread", "local-context"], - kind: ReportKind::ExplicitJemallocHeapSize, - size: malloc_size_of_persistent_local_context(&mut ops), - }); } fn reflow(&mut self, script_reflow: script_layout_interface::ScriptReflow) { @@ -573,6 +568,7 @@ impl LayoutThread { // Let webrender know about this pipeline by sending an empty display list. webrender_api.send_initial_transaction(id.into()); + let font_context = Arc::new(FontContext::new(font_cache_thread.clone())); let device = Device::new( MediaType::screen(), QuirksMode::NoQuirks, @@ -602,6 +598,7 @@ impl LayoutThread { registered_painters: RegisteredPaintersImpl(Default::default()), image_cache, font_cache_thread, + font_context, first_reflow: Cell::new(true), font_cache_sender: ipc_font_cache_sender, parallel_flag: true, @@ -675,7 +672,7 @@ impl LayoutThread { traversal_flags, ), image_cache: self.image_cache.clone(), - font_cache_thread: Mutex::new(self.font_cache_thread.clone()), + font_context: self.font_context.clone(), webrender_image_cache: self.webrender_image_cache.clone(), pending_images: Mutex::new(vec![]), registered_painters: &self.registered_painters, @@ -726,7 +723,7 @@ impl LayoutThread { } fn handle_web_font_loaded(&self) { - font_context::invalidate_font_caches(); + self.font_context.invalidate_caches(); self.script_chan .send(ConstellationControlMsg::WebFontLoaded(self.id)) .unwrap(); diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index d0a9eccfef9..c23e6f55fd4 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -22,7 +22,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fxhash::FxHashMap; use gfx::font_cache_thread::FontCacheThread; -use gfx::font_context; +use gfx::font_context::FontContext; use gfx_traits::{node_id_from_scroll_id, Epoch}; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; @@ -41,7 +41,7 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use metrics::{PaintTimeMetrics, ProfilerMetadataFactory}; use msg::constellation_msg::{BrowsingContextId, PipelineId}; use net_traits::image_cache::{ImageCache, UsePlaceholder}; -use parking_lot::{ReentrantMutex, RwLock}; +use parking_lot::RwLock; use profile_traits::mem::{Report, ReportKind}; use profile_traits::path; use profile_traits::time::{ @@ -121,7 +121,10 @@ pub struct LayoutThread { /// Public interface to the font cache thread. This needs to be behind a [`ReentrantMutex`], /// because some font cache operations can trigger others. - font_cache_thread: Arc<ReentrantMutex<FontCacheThread>>, + font_cache_thread: FontCacheThread, + + /// A FontContext to be used during layout. + font_context: Arc<FontContext<FontCacheThread>>, /// Is this the first reflow in this LayoutThread? first_reflow: Cell<bool>, @@ -484,13 +487,13 @@ impl LayoutThread { // The device pixel ratio is incorrect (it does not have the hidpi value), // but it will be set correctly when the initial reflow takes place. - let font_cache_thread = Arc::new(ReentrantMutex::new(font_cache_thread)); + let font_context = Arc::new(FontContext::new(font_cache_thread.clone())); let device = Device::new( MediaType::screen(), QuirksMode::NoQuirks, window_size.initial_viewport, window_size.device_pixel_ratio, - Box::new(LayoutFontMetricsProvider(font_cache_thread.clone())), + Box::new(LayoutFontMetricsProvider(font_context.clone())), ); // Ask the router to proxy IPC messages from the font cache thread to layout. @@ -514,6 +517,7 @@ impl LayoutThread { registered_painters: RegisteredPaintersImpl(Default::default()), image_cache, font_cache_thread, + font_context, first_reflow: Cell::new(true), font_cache_sender: ipc_font_cache_sender, generation: Cell::new(0), @@ -584,7 +588,7 @@ impl LayoutThread { traversal_flags, ), image_cache: self.image_cache.clone(), - font_cache_thread: self.font_cache_thread.clone(), + font_context: self.font_context.clone(), webrender_image_cache: self.webrender_image_cache.clone(), pending_images: Mutex::new(vec![]), use_rayon, @@ -616,10 +620,8 @@ impl LayoutThread { // Find all font-face rules and notify the font cache of them. // GWTODO: Need to handle unloading web fonts. if stylesheet.is_effective_for_device(self.stylist.device(), guard) { - let newly_loading_font_count = self - .font_cache_thread - .lock() - .add_all_web_fonts_from_stylesheet( + let newly_loading_font_count = + self.font_cache_thread.add_all_web_fonts_from_stylesheet( stylesheet, guard, self.stylist.device(), @@ -637,7 +639,7 @@ impl LayoutThread { } fn handle_web_font_loaded(&self) { - font_context::invalidate_font_caches(); + self.font_context.invalidate_caches(); self.script_chan .send(ConstellationControlMsg::WebFontLoaded(self.id)) .unwrap(); @@ -1076,7 +1078,7 @@ impl LayoutThread { self.stylist.quirks_mode(), window_size_data.initial_viewport, window_size_data.device_pixel_ratio, - Box::new(LayoutFontMetricsProvider(self.font_cache_thread.clone())), + Box::new(LayoutFontMetricsProvider(self.font_context.clone())), ); // Preserve any previously computed root font size. @@ -1235,7 +1237,7 @@ impl RegisteredSpeculativePainters for RegisteredPaintersImpl { } #[derive(Debug)] -struct LayoutFontMetricsProvider(Arc<ReentrantMutex<FontCacheThread>>); +struct LayoutFontMetricsProvider(Arc<FontContext<FontCacheThread>>); impl FontMetricsProvider for LayoutFontMetricsProvider { fn query_font_metrics( @@ -1246,55 +1248,55 @@ impl FontMetricsProvider for LayoutFontMetricsProvider { _in_media_query: bool, _retrieve_math_scales: bool, ) -> FontMetrics { - layout::context::with_thread_local_font_context(&self.0, move |font_context| { - let font_group = - font_context.font_group_with_size(ServoArc::new(font.clone()), base_size.into()); - let Some(first_font_metrics) = font_group - .borrow_mut() - .first(font_context) - .map(|font| font.borrow().metrics.clone()) - else { - return Default::default(); - }; + let font_context = &self.0; + let font_group = self + .0 + .font_group_with_size(ServoArc::new(font.clone()), base_size.into()); + + let Some(first_font_metrics) = font_group + .write() + .first(font_context) + .map(|font| font.metrics.clone()) + else { + return Default::default(); + }; - // Only use the x-height of this font if it is non-zero. Some fonts return - // inaccurate metrics, which shouldn't be used. - let x_height = Some(first_font_metrics.x_height) - .filter(|x_height| !x_height.is_zero()) - .map(CSSPixelLength::from); - - let zero_advance_measure = first_font_metrics - .zero_horizontal_advance - .or_else(|| { - font_group - .borrow_mut() - .find_by_codepoint(font_context, '0')? - .borrow() - .metrics - .zero_horizontal_advance - }) - .map(CSSPixelLength::from); - let ic_width = first_font_metrics - .ic_horizontal_advance - .or_else(|| { - font_group - .borrow_mut() - .find_by_codepoint(font_context, '\u{6C34}')? - .borrow() - .metrics - .ic_horizontal_advance - }) - .map(CSSPixelLength::from); - - FontMetrics { - x_height, - zero_advance_measure, - cap_height: None, - ic_width, - ascent: first_font_metrics.ascent.into(), - script_percent_scale_down: None, - script_script_percent_scale_down: None, - } - }) + // Only use the x-height of this font if it is non-zero. Some fonts return + // inaccurate metrics, which shouldn't be used. + let x_height = Some(first_font_metrics.x_height) + .filter(|x_height| !x_height.is_zero()) + .map(CSSPixelLength::from); + + let zero_advance_measure = first_font_metrics + .zero_horizontal_advance + .or_else(|| { + font_group + .write() + .find_by_codepoint(font_context, '0')? + .metrics + .zero_horizontal_advance + }) + .map(CSSPixelLength::from); + + let ic_width = first_font_metrics + .ic_horizontal_advance + .or_else(|| { + font_group + .write() + .find_by_codepoint(font_context, '\u{6C34}')? + .metrics + .ic_horizontal_advance + }) + .map(CSSPixelLength::from); + + FontMetrics { + x_height, + zero_advance_measure, + cap_height: None, + ic_width, + ascent: first_font_metrics.ascent.into(), + script_percent_scale_down: None, + script_script_percent_scale_down: None, + } } } |