Bug 1455848 - implement shared cache of dwrote font files. r=jrmuizel
authorLee Salzman <lsalzman@mozilla.com>
Thu, 17 Jan 2019 15:45:36 -0500
changeset 515230 3c0c1dc9ae8b271e9b23482ec239ecbc9279d675
parent 515229 4a57b65dc4f16350e7d51b80f5535ecc81e24952
child 515231 3e5f4a8daaee1b9ccad2fe88666a3cfadffb2a78
child 515245 616df32dc42d3cbeb9b592e989063527c336a0d7
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
gfx/wr/wrench/src/wrench.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)
     }
 }
--- a/gfx/wr/wrench/src/wrench.rs
+++ b/gfx/wr/wrench/src/wrench.rs
@@ -389,18 +389,18 @@ impl Wrench {
         key
     }
 
     #[cfg(target_os = "windows")]
     pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
         self.font_key_from_properties(
             font_name,
             dwrote::FontWeight::Regular.to_u32(),
+            dwrote::FontStyle::Normal.to_u32(),
             dwrote::FontStretch::Normal.to_u32(),
-            dwrote::FontStyle::Normal.to_u32(),
         )
     }
 
     #[cfg(target_os = "windows")]
     pub fn font_key_from_properties(
         &mut self,
         family: &str,
         weight: u32,