Add SkShaper::PurgeCaches.

Allows the user to signal that any global outstanding cached data should
be cleaned up to free resources.

Change-Id: I59d4bb2bbb4356920dea8caf912d9cb5f13014cf
Bug: skia:10763
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/360079
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skparagraph/src/FontCollection.cpp b/modules/skparagraph/src/FontCollection.cpp
index adc21dc..73fffae 100644
--- a/modules/skparagraph/src/FontCollection.cpp
+++ b/modules/skparagraph/src/FontCollection.cpp
@@ -3,6 +3,7 @@
 #include "modules/skparagraph/include/FontCollection.h"
 #include "modules/skparagraph/include/Paragraph.h"
 #include "modules/skparagraph/src/ParagraphImpl.h"
+#include "modules/skshaper/include/SkShaper.h"
 
 namespace skia {
 namespace textlayout {
@@ -150,6 +151,7 @@
 void FontCollection::clearCaches() {
     fParagraphCache.reset();
     fTypefaces.reset();
+    SkShaper::PurgeCaches();
 }
 
 }  // namespace textlayout
diff --git a/modules/skshaper/include/SkShaper.h b/modules/skshaper/include/SkShaper.h
index 6e57ecb..2b6d587 100644
--- a/modules/skshaper/include/SkShaper.h
+++ b/modules/skshaper/include/SkShaper.h
@@ -41,12 +41,6 @@
 class SkFontMgr;
 class SkUnicode;
 
-/**
-   Shapes text using HarfBuzz and places the shaped text into a
-   client-managed buffer.
-
-   If compiled without HarfBuzz, fall back on SkPaint::textToGlyphs.
- */
 class SKSHAPER_API SkShaper {
 public:
     static std::unique_ptr<SkShaper> MakePrimitive();
@@ -54,12 +48,14 @@
     static std::unique_ptr<SkShaper> MakeShaperDrivenWrapper(sk_sp<SkFontMgr> = nullptr);
     static std::unique_ptr<SkShaper> MakeShapeThenWrap(sk_sp<SkFontMgr> = nullptr);
     static std::unique_ptr<SkShaper> MakeShapeDontWrapOrReorder(sk_sp<SkFontMgr> = nullptr);
+    static void PurgeHarfBuzzCache();
     #endif
     #ifdef SK_SHAPER_CORETEXT_AVAILABLE
     static std::unique_ptr<SkShaper> MakeCoreText();
     #endif
 
     static std::unique_ptr<SkShaper> Make(sk_sp<SkFontMgr> = nullptr);
+    static void PurgeCaches();
 
     SkShaper();
     virtual ~SkShaper();
diff --git a/modules/skshaper/src/SkShaper.cpp b/modules/skshaper/src/SkShaper.cpp
index c47e3ad..d52a722 100644
--- a/modules/skshaper/src/SkShaper.cpp
+++ b/modules/skshaper/src/SkShaper.cpp
@@ -36,6 +36,12 @@
     return SkShaper::MakePrimitive();
 }
 
+void SkShaper::PurgeCaches() {
+#ifdef SK_SHAPER_HARFBUZZ_AVAILABLE
+    PurgeHarfBuzzCache();
+#endif
+}
+
 std::unique_ptr<SkShaper::BiDiRunIterator>
 SkShaper::MakeBiDiRunIterator(const char* utf8, size_t utf8Bytes, uint8_t bidiLevel) {
 #ifdef SK_UNICODE_AVAILABLE
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index ca4e298..3f0fb21 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -1256,6 +1256,41 @@
     handler->commitLine();
 }
 
+class HBLockedFaceCache {
+public:
+    HBLockedFaceCache(SkLRUCache<SkFontID, HBFace>& lruCache, SkMutex& mutex)
+        : fLRUCache(lruCache), fMutex(mutex)
+    {
+        fMutex.acquire();
+    }
+    HBLockedFaceCache(const HBLockedFaceCache&) = delete;
+    HBLockedFaceCache& operator=(const HBLockedFaceCache&) = delete;
+    HBLockedFaceCache(HBLockedFaceCache&&) = delete;
+    HBLockedFaceCache& operator=(HBLockedFaceCache&&) = delete;
+
+    ~HBLockedFaceCache() {
+        fMutex.release();
+    }
+
+    HBFace* find(SkFontID fontId) {
+        return fLRUCache.find(fontId);
+    }
+    HBFace* insert(SkFontID fontId, HBFace hbFace) {
+        return fLRUCache.insert(fontId, std::move(hbFace));
+    }
+    void reset() {
+        fLRUCache.reset();
+    }
+private:
+    SkLRUCache<SkFontID, HBFace>& fLRUCache;
+    SkMutex& fMutex;
+};
+static HBLockedFaceCache get_hbFace_cache() {
+    static SkMutex gHBFaceCacheMutex;
+    static SkLRUCache<SkFontID, HBFace> gHBFaceCache(100);
+    return HBLockedFaceCache(gHBFaceCache, gHBFaceCacheMutex);
+}
+
 ShapedRun ShaperHarfBuzz::shape(char const * const utf8,
                                   size_t const utf8Bytes,
                                   char const * const utf8Start,
@@ -1312,16 +1347,14 @@
     // An HBFont is fairly inexpensive.
     // An HBFace is actually tied to the data, not the typeface.
     // The size of 100 here is completely arbitrary and used to match libtxt.
-    static SkLRUCache<SkFontID, HBFace> gHBFaceCache(100);
-    static SkMutex gHBFaceCacheMutex;
     HBFont hbFont;
     {
-        SkAutoMutexExclusive lock(gHBFaceCacheMutex);
+        HBLockedFaceCache cache = get_hbFace_cache();
         SkFontID dataId = font.currentFont().getTypeface()->uniqueID();
-        HBFace* hbFaceCached = gHBFaceCache.find(dataId);
+        HBFace* hbFaceCached = cache.find(dataId);
         if (!hbFaceCached) {
             HBFace hbFace(create_hb_face(*font.currentFont().getTypeface()));
-            hbFaceCached = gHBFaceCache.insert(dataId, std::move(hbFace));
+            hbFaceCached = cache.insert(dataId, std::move(hbFace));
         }
         hbFont = create_hb_font(font.currentFont(), *hbFaceCached);
     }
@@ -1478,3 +1511,8 @@
     return std::make_unique<ShapeDontWrapOrReorder>
         (std::move(unicode), nullptr, nullptr, std::move(buffer), std::move(fontmgr));
 }
+
+void SkShaper::PurgeHarfBuzzCache() {
+    HBLockedFaceCache cache = get_hbFace_cache();
+    cache.reset();
+}
diff --git a/samplecode/SampleTextBox.cpp b/samplecode/SampleTextBox.cpp
index 23cdf30..d929cad 100644
--- a/samplecode/SampleTextBox.cpp
+++ b/samplecode/SampleTextBox.cpp
@@ -56,6 +56,7 @@
         paint.setColor(fg);
 
         for (int i = 9; i < 24; i += 2) {
+            SkShaper::PurgeCaches();
             SkTextBlobBuilderRunHandler builder(gText, { margin, margin });
             SkFont srcFont(nullptr, SkIntToScalar(i));
             srcFont.setEdging(SkFont::Edging::kSubpixelAntiAlias);