Cache hb_face.
Creating an hb_face can be quite expensive, cache them.
This implementation is similar to the super simple caching strategy used
by libtxt. It uses a simple global LRU cache from SkFontID to hb_hbface
of size 100.
Change-Id: I364a4548699cece50073e829a065c0a303245873
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/289442
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index 29d9fe9..d2e3bd4 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -20,11 +20,13 @@
#include "include/core/SkTypes.h"
#include "include/private/SkBitmaskEnum.h"
#include "include/private/SkMalloc.h"
+#include "include/private/SkMutex.h"
#include "include/private/SkTArray.h"
#include "include/private/SkTFitsIn.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.h"
#include "modules/skshaper/include/SkShaper.h"
+#include "src/core/SkLRUCache.h"
#include "src/core/SkSpan.h"
#include "src/core/SkTDPQueue.h"
#include "src/utils/SkUTF.h"
@@ -73,25 +75,6 @@
using ICUBrk = resource<UBreakIterator, decltype(ubrk_close) , ubrk_close >;
using ICUUText = resource<UText , decltype(utext_close) , utext_close >;
-HBBlob stream_to_blob(std::unique_ptr<SkStreamAsset> asset) {
- size_t size = asset->getLength();
- HBBlob blob;
- if (const void* base = asset->getMemoryBase()) {
- blob.reset(hb_blob_create((char*)base, SkToUInt(size),
- HB_MEMORY_MODE_READONLY, asset.release(),
- [](void* p) { delete (SkStreamAsset*)p; }));
- } else {
- // SkDebugf("Extra SkStreamAsset copy\n");
- void* ptr = size ? sk_malloc_throw(size) : nullptr;
- asset->read(ptr, size);
- blob.reset(hb_blob_create((char*)ptr, SkToUInt(size),
- HB_MEMORY_MODE_READONLY, ptr, sk_free));
- }
- SkASSERT(blob);
- hb_blob_make_immutable(blob.get());
- return blob;
-}
-
hb_position_t skhb_position(SkScalar value) {
// Treat HarfBuzz hb_position_t as 16.16 fixed-point.
constexpr int kHbPosition1 = 1 << 16;
@@ -266,31 +249,65 @@
}
SkData* rawData = data.release();
return hb_blob_create(reinterpret_cast<char*>(rawData->writable_data()), rawData->size(),
- HB_MEMORY_MODE_WRITABLE, rawData, [](void* ctx) {
+ HB_MEMORY_MODE_READONLY, rawData, [](void* ctx) {
SkSafeUnref(((SkData*)ctx));
});
}
-HBFont create_hb_font(const SkFont& font) {
- SkASSERT(font.getTypeface());
- int index;
- std::unique_ptr<SkStreamAsset> typefaceAsset = font.getTypeface()->openStream(&index);
- HBFace face;
- if (!typefaceAsset) {
- face.reset(hb_face_create_for_tables(
- skhb_get_table,
- reinterpret_cast<void *>(font.refTypeface().release()),
- [](void* user_data){ SkSafeUnref(reinterpret_cast<SkTypeface*>(user_data)); }));
+HBBlob stream_to_blob(std::unique_ptr<SkStreamAsset> asset) {
+ size_t size = asset->getLength();
+ HBBlob blob;
+ if (const void* base = asset->getMemoryBase()) {
+ blob.reset(hb_blob_create((char*)base, SkToUInt(size),
+ HB_MEMORY_MODE_READONLY, asset.release(),
+ [](void* p) { delete (SkStreamAsset*)p; }));
} else {
+ // SkDebugf("Extra SkStreamAsset copy\n");
+ void* ptr = size ? sk_malloc_throw(size) : nullptr;
+ asset->read(ptr, size);
+ blob.reset(hb_blob_create((char*)ptr, SkToUInt(size),
+ HB_MEMORY_MODE_READONLY, ptr, sk_free));
+ }
+ SkASSERT(blob);
+ hb_blob_make_immutable(blob.get());
+ return blob;
+}
+
+SkDEBUGCODE(static hb_user_data_key_t gDataIdKey;)
+
+HBFace create_hb_face(const SkTypeface& typeface) {
+ int index;
+ std::unique_ptr<SkStreamAsset> typefaceAsset = typeface.openStream(&index);
+ HBFace face;
+ if (typefaceAsset && typefaceAsset->getMemoryBase()) {
HBBlob blob(stream_to_blob(std::move(typefaceAsset)));
face.reset(hb_face_create(blob.get(), (unsigned)index));
+ } else {
+ face.reset(hb_face_create_for_tables(
+ skhb_get_table,
+ const_cast<SkTypeface*>(SkRef(&typeface)),
+ [](void* user_data){ SkSafeUnref(reinterpret_cast<SkTypeface*>(user_data)); }));
}
SkASSERT(face);
if (!face) {
return nullptr;
}
hb_face_set_index(face.get(), (unsigned)index);
- hb_face_set_upem(face.get(), font.getTypeface()->getUnitsPerEm());
+ hb_face_set_upem(face.get(), typeface.getUnitsPerEm());
+
+ SkDEBUGCODE(
+ hb_face_set_user_data(face.get(), &gDataIdKey, const_cast<SkTypeface*>(&typeface),
+ nullptr, false);
+ )
+
+ return face;
+}
+
+HBFont create_hb_font(const SkFont& font, const HBFace& face) {
+ SkDEBUGCODE(
+ void* dataId = hb_face_get_user_data(face.get(), &gDataIdKey);
+ SkASSERT(dataId == font.getTypeface());
+ )
HBFont otFont(hb_font_create(face.get()));
SkASSERT(otFont);
@@ -1318,8 +1335,24 @@
hb_buffer_set_language(buffer, hb_language_from_string(language.currentLanguage(), -1));
hb_buffer_guess_segment_properties(buffer);
- // TODO: how to cache hbface (typeface) / hbfont (font)
- HBFont hbFont(create_hb_font(font.currentFont()));
+ // TODO: better cache HBFace (data) / hbfont (typeface)
+ // An HBFace is expensive (it sanitizes the bits).
+ // 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);
+ SkFontID dataId = font.currentFont().getTypeface()->uniqueID();
+ HBFace* hbFaceCached = gHBFaceCache.find(dataId);
+ if (!hbFaceCached) {
+ HBFace hbFace(create_hb_face(*font.currentFont().getTypeface()));
+ hbFaceCached = gHBFaceCache.insert(dataId, std::move(hbFace));
+ }
+ hbFont = create_hb_font(font.currentFont(), *hbFaceCached);
+ }
if (!hbFont) {
return run;
}