Bug 1455848 - implement shared cache of dwrote font files r=jrmuizel
☠☠ backed out by e125bfe1e221 ☠ ☠
authorLee Salzman <lsalzman@mozilla.com>
Wed, 23 Jan 2019 18:22:30 +0000
changeset 515169 424203c9c43005c9e0f7de736d4425436eb55062
parent 515168 b7a0887937f453d7315e99592b2a4f88409ade42
child 515178 6173caa961e07de9ce9cd486a748a204d8d20515
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1455848
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1455848 - implement shared cache of dwrote font files r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D16898
gfx/wr/webrender/src/platform/windows/font.rs
--- a/gfx/wr/webrender/src/platform/windows/font.rs
+++ b/gfx/wr/webrender/src/platform/windows/font.rs
@@ -2,19 +2,22 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation};
 use api::{ColorU, GlyphDimensions, NativeFontHandle};
 use dwrote;
 use gamma_lut::ColorLut;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
-use internal_types::{FastHashMap, ResourceCacheError};
+use internal_types::{FastHashMap, FastHashSet, ResourceCacheError};
+use std::borrow::Borrow;
 use std::collections::hash_map::Entry;
-use std::sync::Arc;
+use std::hash::{Hash, Hasher};
+use std::path::Path;
+use std::sync::{Arc, Mutex};
 
 cfg_if! {
     if #[cfg(feature = "pathfinder")] {
         use pathfinder_font_renderer::{PathfinderComPtr, IDWriteFontFace};
         use glyph_rasterizer::NativeFontHandleWrapper;
     } else if #[cfg(not(feature = "pathfinder"))] {
         use api::FontInstancePlatformOptions;
         use glyph_rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
@@ -26,17 +29,53 @@ lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
         stretch: dwrote::FontStretch::Normal,
         style: dwrote::FontStyle::Normal,
     };
 }
 
+type CachedFontKey = Arc<Path>;
+
+// A cached dwrote font file that is shared among all faces.
+// Each face holds a CachedFontKey to keep track of how many users of the font there are.
+struct CachedFont {
+    key: CachedFontKey,
+    file: dwrote::FontFile,
+}
+
+impl PartialEq for CachedFont {
+    fn eq(&self, other: &CachedFont) -> bool {
+        self.key == other.key
+    }
+}
+impl Eq for CachedFont {}
+
+impl Hash for CachedFont {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.key.hash(state);
+    }
+}
+
+impl Borrow<Path> for CachedFont {
+    fn borrow(&self) -> &Path {
+        &*self.key
+    }
+}
+
+lazy_static! {
+    // This is effectively a weak map of dwrote FontFiles. CachedFonts are entered into the
+    // cache when there are any FontFaces using them. CachedFonts are removed from the cache
+    // when there are no more FontFaces using them at all.
+    static ref FONT_CACHE: Mutex<FastHashSet<CachedFont>> = Mutex::new(FastHashSet::default());
+}
+
 struct FontFace {
+    cached: Option<CachedFontKey>,
     file: dwrote::FontFile,
     index: u32,
     face: dwrote::FontFace,
 }
 
 pub struct FontContext {
     fonts: FastHashMap<FontKey, FontFace>,
     variations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS, Vec<FontVariation>), dwrote::FontFace>,
@@ -118,55 +157,83 @@ impl FontContext {
     }
 
     fn add_font_descriptor(&mut self, font_key: &FontKey, desc: &dwrote::FontDescriptor) {
         let system_fc = dwrote::FontCollection::get_system(false);
         if let Some(font) = system_fc.get_font_from_descriptor(desc) {
             let face = font.create_font_face();
             let file = face.get_files().pop().unwrap();
             let index = face.get_index();
-            self.fonts.insert(*font_key, FontFace { file, index, face });
+            self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
         }
     }
 
     pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
         if self.fonts.contains_key(font_key) {
             return;
         }
 
         if let Some(file) = dwrote::FontFile::new_from_data(data) {
             if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
-                self.fonts.insert(*font_key, FontFace { file, index, face });
+                self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
                 return;
             }
         }
         // XXX add_raw_font needs to have a way to return an error
         debug!("DWrite WR failed to load font from data, using Arial instead");
         self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
     }
 
     pub fn add_native_font(&mut self, font_key: &FontKey, font_handle: NativeFontHandle) {
         if self.fonts.contains_key(font_key) {
             return;
         }
-        if let Some(file) = dwrote::FontFile::new_from_path(font_handle.pathname) {
-            let index = font_handle.index;
-            if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
-                self.fonts.insert(*font_key, FontFace { file, index, face });
+
+        let index = font_handle.index;
+        let mut cache = FONT_CACHE.lock().unwrap();
+        // Check to see if the font is already in the cache. If so, reuse it.
+        if let Some(font) = cache.get(font_handle.path.as_path()) {
+            if let Ok(face) = font.file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
+                self.fonts.insert(
+                    *font_key,
+                    FontFace { cached: Some(font.key.clone()), file: font.file.clone(), index, face },
+                );
                 return;
             }
         }
+        if let Some(file) = dwrote::FontFile::new_from_path(&font_handle.path) {
+            // The font is not in the cache yet, so try to create the font and insert it in the cache.
+            if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
+                let key: CachedFontKey = font_handle.path.into();
+                self.fonts.insert(
+                    *font_key,
+                    FontFace { cached: Some(key.clone()), file: file.clone(), index, face },
+                );
+                cache.insert(CachedFont { key, file });
+                return;
+            }
+        }
+
         // XXX add_native_font needs to have a way to return an error
         debug!("DWrite WR failed to load font from path, using Arial instead");
         self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
-        if let Some(_) = self.fonts.remove(font_key) {
+        if let Some(face) = self.fonts.remove(font_key) {
             self.variations.retain(|k, _| k.0 != *font_key);
+            // Check if this was a cached font.
+            if let Some(key) = face.cached {
+                let mut cache = FONT_CACHE.lock().unwrap();
+                // If there are only two references left, that means only this face and
+                // the cache are using the font. So remove it from the cache.
+                if Arc::strong_count(&key) == 2 {
+                    cache.remove(&*key);
+                }
+            }
         }
     }
 
     pub fn delete_font_instance(&mut self, instance: &FontInstance) {
         // Ensure we don't keep around excessive amounts of stale variations.
         if !instance.variations.is_empty() {
             let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
                 dwrote::DWRITE_FONT_SIMULATIONS_BOLD
@@ -530,17 +597,17 @@ impl FontContext {
             bytes: bgra_pixels,
         })
     }
 }
 
 #[cfg(feature = "pathfinder")]
 impl<'a> From<NativeFontHandleWrapper<'a>> for PathfinderComPtr<IDWriteFontFace> {
     fn from(font_handle: NativeFontHandleWrapper<'a>) -> Self {
-        if let Some(file) = dwrote::FontFile::new_from_path(font_handle.0.pathname) {
+        if let Some(file) = dwrote::FontFile::new_from_path(font_handle.0.path) {
             let index = font_handle.0.index;
             if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
                 return unsafe { PathfinderComPtr::new(face.as_ptr()) };
             }
         }
         panic!("missing font {:?}", font_handle.0)
     }
 }