/* * Copyright 2006 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkGlyphCache.h" #include "SkGlyphCache_Globals.h" #include "SkGraphics.h" #include "SkOnce.h" #include "SkOncePtr.h" #include "SkPath.h" #include "SkTemplates.h" #include "SkTraceMemoryDump.h" #include "SkTypeface.h" //#define SPEW_PURGE_STATUS namespace { const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache"; // Used to pass context to the sk_trace_dump_visitor. struct SkGlyphCacheDumpContext { int* counter; SkTraceMemoryDump* dump; }; } // namespace // Returns the shared globals SK_DECLARE_STATIC_ONCE_PTR(SkGlyphCache_Globals, globals); static SkGlyphCache_Globals& get_globals() { return *globals.get([]{ return new SkGlyphCache_Globals; }); } /////////////////////////////////////////////////////////////////////////////// // so we don't grow our arrays a lot #define kMinGlyphCount 16 #define kMinGlyphImageSize (16*2) #define kMinAllocAmount ((sizeof(SkGlyph) + kMinGlyphImageSize) * kMinGlyphCount) SkGlyphCache::SkGlyphCache(SkTypeface* typeface, const SkDescriptor* desc, SkScalerContext* ctx) : fNext(nullptr) , fPrev(nullptr) , fDesc(desc->copy()) , fRefCount(0) , fGlyphAlloc(kMinAllocAmount) , fMemoryUsed(sizeof(*this)) , fScalerContext(ctx) , fAuxProcList(nullptr) { SkASSERT(typeface); SkASSERT(desc); SkASSERT(ctx); fScalerContext->getFontMetrics(&fFontMetrics); } SkGlyphCache::~SkGlyphCache() { fGlyphMap.foreach ([](SkGlyph* g) { delete g->fPath; }); SkDescriptor::Free(fDesc); delete fScalerContext; AuxProcRec* rec = fAuxProcList; while (rec) { rec->fProc(rec->fData); AuxProcRec* next = rec->fNext; delete rec; rec = next; } } void SkGlyphCache::increaseMemoryUsed(size_t used) { fMemoryUsed += used; get_globals().increaseTotalMemoryUsed(used); } SkGlyphCache::CharGlyphRec SkGlyphCache::PackedUnicharIDtoCharGlyphRec(PackedUnicharID packedUnicharID) { SkFixed x = SkGlyph::SubToFixed(SkGlyph::ID2SubX(packedUnicharID)); SkFixed y = SkGlyph::SubToFixed(SkGlyph::ID2SubY(packedUnicharID)); SkUnichar unichar = SkGlyph::ID2Code(packedUnicharID); SkAutoMutexAcquire lock(fScalerMutex); PackedGlyphID packedGlyphID = SkGlyph::MakeID(fScalerContext->charToGlyphID(unichar), x, y); return {packedUnicharID, packedGlyphID}; } SkGlyphCache::CharGlyphRec* SkGlyphCache::getCharGlyphRec(PackedUnicharID packedUnicharID) { if (nullptr == fPackedUnicharIDToPackedGlyphID.get()) { fMapMutex.releaseShared(); // Add the map only if there is a call for char -> glyph mapping. { SkAutoTAcquire lock(fMapMutex); // Now that the cache is locked exclusively, make sure no one added this array // while unlocked. if (nullptr == fPackedUnicharIDToPackedGlyphID.get()) { // Allocate the array. fPackedUnicharIDToPackedGlyphID.reset(new PackedUnicharIDToPackedGlyphIDMap); } fPackedUnicharIDToPackedGlyphID->set(PackedUnicharIDtoCharGlyphRec(packedUnicharID)); } fMapMutex.acquireShared(); return fPackedUnicharIDToPackedGlyphID->find(packedUnicharID); } CharGlyphRec* answer = fPackedUnicharIDToPackedGlyphID->find(packedUnicharID); if (nullptr == answer) { fMapMutex.releaseShared(); // Add a new char -> glyph mapping. { SkAutoTAcquire lock(fMapMutex); answer = fPackedUnicharIDToPackedGlyphID->find(packedUnicharID); if (nullptr == answer) { fPackedUnicharIDToPackedGlyphID->set( PackedUnicharIDtoCharGlyphRec(packedUnicharID)); } } fMapMutex.acquireShared(); return fPackedUnicharIDToPackedGlyphID->find(packedUnicharID); } return answer; } /////////////////////////////////////////////////////////////////////////////// #ifdef SK_DEBUG #define VALIDATE() AutoValidate av(this) #else #define VALIDATE() #endif uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) { VALIDATE(); PackedUnicharID packedUnicharID = SkGlyph::MakeID(charCode); const CharGlyphRec& rec = *this->getCharGlyphRec(packedUnicharID); return SkGlyph::ID2Code(rec.fPackedGlyphID); } SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) { SkAutoMutexAcquire lock(fScalerMutex); return fScalerContext->glyphIDToChar(glyphID); } unsigned SkGlyphCache::getGlyphCount() const { return fScalerContext->getGlyphCount(); } int SkGlyphCache::countCachedGlyphs() const { return fGlyphMap.count(); } /////////////////////////////////////////////////////////////////////////////// SkGlyph* SkGlyphCache::lookupByChar(SkUnichar charCode, SkFixed x, SkFixed y) { PackedUnicharID targetUnicharID = SkGlyph::MakeID(charCode, x, y); CharGlyphRec* rec = this->getCharGlyphRec(targetUnicharID); PackedGlyphID packedGlyphID = rec->fPackedGlyphID; return this->lookupByPackedGlyphID(packedGlyphID); } const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) { VALIDATE(); return *this->lookupByChar(charCode); } const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) { VALIDATE(); return *this->lookupByChar(charCode); } const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode, SkFixed x, SkFixed y) { VALIDATE(); return *this->lookupByChar(charCode, x, y); } /////////////////////////////////////////////////////////////////////////////// SkGlyph* SkGlyphCache::allocateNewGlyph(PackedGlyphID packedGlyphID) { SkGlyph* glyphPtr; { fMapMutex.releaseShared(); { SkAutoTAcquire mapLock(fMapMutex); glyphPtr = fGlyphMap.find(packedGlyphID); if (nullptr == glyphPtr) { SkGlyph glyph; glyph.initGlyphFromCombinedID(packedGlyphID); { SkAutoMutexAcquire lock(fScalerMutex); fScalerContext->getMetrics(&glyph); this->increaseMemoryUsed(sizeof(SkGlyph)); glyphPtr = fGlyphMap.set(glyph); } // drop scaler lock } } // drop map lock fMapMutex.acquireShared(); glyphPtr = fGlyphMap.find(packedGlyphID); } SkASSERT(glyphPtr->fID != SkGlyph::kImpossibleID); return glyphPtr; } SkGlyph* SkGlyphCache::lookupByPackedGlyphID(PackedGlyphID packedGlyphID) { SkGlyph* glyph = fGlyphMap.find(packedGlyphID); if (nullptr == glyph) { glyph = this->allocateNewGlyph(packedGlyphID); } return glyph; } const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) { VALIDATE(); PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID); return *this->lookupByPackedGlyphID(packedGlyphID); } const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) { VALIDATE(); PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID); return *this->lookupByPackedGlyphID(packedGlyphID); } const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID, SkFixed x, SkFixed y) { VALIDATE(); PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID, x, y); return *this->lookupByPackedGlyphID(packedGlyphID); } /////////////////////////////////////////////////////////////////////////////// void SkGlyphCache::OnceFillInImage(GlyphAndCache gc) { SkGlyphCache* cache = gc.cache; const SkGlyph* glyph = gc.glyph; cache->fScalerMutex.assertHeld(); if (glyph->fWidth > 0 && glyph->fWidth < kMaxGlyphWidth) { size_t size = glyph->computeImageSize(); sk_atomic_store(&const_cast(glyph)->fImage, cache->fGlyphAlloc.alloc(size, SkChunkAlloc::kReturnNil_AllocFailType), sk_memory_order_relaxed); if (glyph->fImage != nullptr) { cache->fScalerContext->getImage(*glyph); cache->increaseMemoryUsed(size); } } } const void* SkGlyphCache::findImage(const SkGlyph& glyph) { SkOnce( &glyph.fImageIsSet, &fScalerMutex, &SkGlyphCache::OnceFillInImage, {this, &glyph}); return sk_atomic_load(&glyph.fImage, sk_memory_order_seq_cst); } void SkGlyphCache::OnceFillInPath(GlyphAndCache gc) { SkGlyphCache* cache = gc.cache; const SkGlyph* glyph = gc.glyph; cache->fScalerMutex.assertHeld(); if (glyph->fWidth > 0) { sk_atomic_store(&const_cast(glyph)->fPath, new SkPath, sk_memory_order_relaxed); cache->fScalerContext->getPath(*glyph, glyph->fPath); size_t size = sizeof(SkPath) + glyph->fPath->countPoints() * sizeof(SkPoint); cache->increaseMemoryUsed(size); } } const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) { SkOnce( &glyph.fPathIsSet, &fScalerMutex, &SkGlyphCache::OnceFillInPath, {this, &glyph}); return sk_atomic_load(&glyph.fPath, sk_memory_order_seq_cst); } void SkGlyphCache::dump() const { const SkTypeface* face = fScalerContext->getTypeface(); const SkScalerContextRec& rec = fScalerContext->getRec(); SkMatrix matrix; rec.getSingleMatrix(&matrix); matrix.preScale(SkScalarInvert(rec.fTextSize), SkScalarInvert(rec.fTextSize)); SkString name; face->getFamilyName(&name); SkString msg; msg.printf( "cache typeface:%x %25s:%d size:%2g [%g %g %g %g] " "lum:%02X devG:%d pntG:%d cntr:%d glyphs:%3d", face->uniqueID(), name.c_str(), face->style(), rec.fTextSize, matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMSkewY], matrix[SkMatrix::kMScaleY], rec.fLumBits & 0xFF, rec.fDeviceGamma, rec.fPaintGamma, rec.fContrast, fGlyphMap.count()); SkDebugf("%s\n", msg.c_str()); } /////////////////////////////////////////////////////////////////////////////// bool SkGlyphCache::getAuxProcData(void (*proc)(void*), void** dataPtr) const { // Borrow the fScalerMutex to protect the AuxProc list. SkAutoMutexAcquire lock(fScalerMutex); const AuxProcRec* rec = fAuxProcList; while (rec) { if (rec->fProc == proc) { if (dataPtr) { *dataPtr = rec->fData; } return true; } rec = rec->fNext; } return false; } void SkGlyphCache::setAuxProc(void (*proc)(void*), void* data) { if (proc == nullptr) { return; } // Borrow the fScalerMutex to protect the AuxProc linked list. SkAutoMutexAcquire lock(fScalerMutex); AuxProcRec* rec = fAuxProcList; while (rec) { if (rec->fProc == proc) { rec->fData = data; return; } rec = rec->fNext; } // not found, create a new rec rec = new AuxProcRec; rec->fProc = proc; rec->fData = data; rec->fNext = fAuxProcList; fAuxProcList = rec; } /////////////////////////////////////////////////////////////////////////////// typedef SkAutoTAcquire AutoAcquire; size_t SkGlyphCache_Globals::setCacheSizeLimit(size_t newLimit) { static const size_t minLimit = 256 * 1024; if (newLimit < minLimit) { newLimit = minLimit; } AutoAcquire ac(fLock); size_t prevLimit = fCacheSizeLimit; fCacheSizeLimit = newLimit; this->internalPurge(); return prevLimit; } int SkGlyphCache_Globals::setCacheCountLimit(int newCount) { if (newCount < 0) { newCount = 0; } AutoAcquire ac(fLock); int prevCount = fCacheCountLimit; fCacheCountLimit = newCount; this->internalPurge(); return prevCount; } void SkGlyphCache_Globals::purgeAll() { AutoAcquire ac(fLock); this->internalPurge(fTotalMemoryUsed.load()); } /* This guy calls the visitor from within the mutext lock, so the visitor cannot: - take too much time - try to acquire the mutext again - call a fontscaler (which might call into the cache) */ SkGlyphCache* SkGlyphCache::VisitCache( SkTypeface* typeface, const SkDescriptor* desc, VisitProc proc, void* context) { if (!typeface) { typeface = SkTypeface::GetDefaultTypeface(); } SkASSERT(desc); SkGlyphCache_Globals& globals = get_globals(); SkGlyphCache* cache; { AutoAcquire ac(globals.fLock); globals.validate(); for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) { if (cache->fDesc->equals(*desc)) { globals.internalMoveToHead(cache); cache->fMapMutex.acquireShared(); if (!proc(cache, context)) { cache->fMapMutex.releaseShared(); return nullptr; } // The caller will take reference on this SkGlyphCache, and the corresponding // Attach call will decrement the reference. cache->fRefCount += 1; return cache; } } } // Check if we can create a scaler-context before creating the glyphcache. // If not, we may have exhausted OS/font resources, so try purging the // cache once and try again. { // pass true the first time, to notice if the scalercontext failed, // so we can try the purge. SkScalerContext* ctx = typeface->createScalerContext(desc, true); if (nullptr == ctx) { get_globals().purgeAll(); ctx = typeface->createScalerContext(desc, false); SkASSERT(ctx); } cache = new SkGlyphCache(typeface, desc, ctx); globals.attachCacheToHead(cache); } AutoValidate av(cache); AutoAcquire ac(globals.fLock); cache->fMapMutex.acquireShared(); if (!proc(cache, context)) { // need to reattach cache->fMapMutex.releaseShared(); return nullptr; } // The caller will take reference on this SkGlyphCache, and the corresponding // Attach call will decrement the reference. cache->fRefCount += 1; return cache; } void SkGlyphCache::AttachCache(SkGlyphCache* cache) { SkASSERT(cache); cache->fMapMutex.releaseShared(); SkGlyphCache_Globals& globals = get_globals(); AutoAcquire ac(globals.fLock); globals.validate(); cache->validate(); // Unref and delete if no longer in the LRU list. cache->fRefCount -= 1; if (cache->fRefCount == 0) { delete cache; } globals.internalPurge(); } static void dump_visitor(const SkGlyphCache& cache, void* context) { int* counter = (int*)context; int index = *counter; *counter += 1; const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); SkDebugf("[%3d] ID %3d, glyphs %3d, size %g, scale %g, skew %g, [%g %g %g %g]\n", index, rec.fFontID, cache.countCachedGlyphs(), rec.fTextSize, rec.fPreScaleX, rec.fPreSkewX, rec.fPost2x2[0][0], rec.fPost2x2[0][1], rec.fPost2x2[1][0], rec.fPost2x2[1][1]); } void SkGlyphCache::Dump() { SkDebugf("GlyphCache [ used budget ]\n"); SkDebugf(" bytes [ %8zu %8zu ]\n", SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit()); SkDebugf(" count [ %8zu %8zu ]\n", SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit()); int counter = 0; SkGlyphCache::VisitAll(dump_visitor, &counter); } static void sk_trace_dump_visitor(const SkGlyphCache& cache, void* context) { SkGlyphCacheDumpContext* dumpContext = static_cast(context); SkTraceMemoryDump* dump = dumpContext->dump; int* counter = dumpContext->counter; int index = *counter; *counter += 1; const SkTypeface* face = cache.getScalerContext()->getTypeface(); SkString font_name; face->getFamilyName(&font_name); const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); SkString dump_name = SkStringPrintf("%s/%s_%3d/index_%d", gGlyphCacheDumpName, font_name.c_str(), rec.fFontID, index); dump->dumpNumericValue(dump_name.c_str(), "size", "bytes", cache.getMemoryUsed()); dump->dumpNumericValue(dump_name.c_str(), "glyph_count", "objects", cache.countCachedGlyphs()); dump->setMemoryBacking(dump_name.c_str(), "malloc", nullptr); } void SkGlyphCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) { dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed()); dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes", SkGraphics::GetFontCacheLimit()); dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects", SkGraphics::GetFontCacheCountUsed()); dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects", SkGraphics::GetFontCacheCountLimit()); int counter = 0; SkGlyphCacheDumpContext context = { &counter, dump }; SkGlyphCache::VisitAll(sk_trace_dump_visitor, &context); } void SkGlyphCache::VisitAll(Visitor visitor, void* context) { SkGlyphCache_Globals& globals = get_globals(); AutoAcquire ac(globals.fLock); SkGlyphCache* cache; globals.validate(); for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) { visitor(*cache, context); } } /////////////////////////////////////////////////////////////////////////////// void SkGlyphCache_Globals::attachCacheToHead(SkGlyphCache* cache) { AutoAcquire ac(fLock); fCacheCount += 1; cache->fRefCount += 1; // Access to cache->fMemoryUsed is single threaded until internalMoveToHead. fTotalMemoryUsed.fetch_add(cache->fMemoryUsed); this->internalMoveToHead(cache); this->validate(); cache->validate(); this->internalPurge(); } SkGlyphCache* SkGlyphCache_Globals::internalGetTail() const { SkGlyphCache* cache = fHead; if (cache) { while (cache->fNext) { cache = cache->fNext; } } return cache; } size_t SkGlyphCache_Globals::internalPurge(size_t minBytesNeeded) { this->validate(); size_t bytesNeeded = 0; if (fTotalMemoryUsed.load() > fCacheSizeLimit) { bytesNeeded = fTotalMemoryUsed.load() - fCacheSizeLimit; } bytesNeeded = SkTMax(bytesNeeded, minBytesNeeded); if (bytesNeeded) { // no small purges! bytesNeeded = SkTMax(bytesNeeded, fTotalMemoryUsed.load() >> 2); } int countNeeded = 0; if (fCacheCount > fCacheCountLimit) { countNeeded = fCacheCount - fCacheCountLimit; // no small purges! countNeeded = SkMax32(countNeeded, fCacheCount >> 2); } // early exit if (!countNeeded && !bytesNeeded) { return 0; } size_t bytesFreed = 0; int countFreed = 0; // we start at the tail and proceed backwards, as the linklist is in LRU // order, with unimportant entries at the tail. SkGlyphCache* cache = this->internalGetTail(); while (cache != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) { SkGlyphCache* prev = cache->fPrev; bytesFreed += cache->fMemoryUsed; countFreed += 1; this->internalDetachCache(cache); if (0 == cache->fRefCount) { delete cache; } cache = prev; } this->validate(); #ifdef SPEW_PURGE_STATUS if (countFreed) { SkDebugf("purging %dK from font cache [%d entries]\n", (int)(bytesFreed >> 10), countFreed); } #endif return bytesFreed; } void SkGlyphCache_Globals::internalMoveToHead(SkGlyphCache *cache) { if (cache != fHead) { if (cache->fPrev) { cache->fPrev->fNext = cache->fNext; } if (cache->fNext) { cache->fNext->fPrev = cache->fPrev; } cache->fNext = nullptr; cache->fPrev = nullptr; if (fHead) { fHead->fPrev = cache; cache->fNext = fHead; } fHead = cache; } } void SkGlyphCache_Globals::internalDetachCache(SkGlyphCache* cache) { fCacheCount -= 1; fTotalMemoryUsed.fetch_sub(cache->fMemoryUsed); if (cache->fPrev) { cache->fPrev->fNext = cache->fNext; } else { // If cache->fPrev == nullptr then this is the head node. fHead = cache->fNext; if (fHead != nullptr) { fHead->fPrev = nullptr; } } if (cache->fNext) { cache->fNext->fPrev = cache->fPrev; } else { // If cache->fNext == nullptr then this is the last node. if (cache->fPrev != nullptr) { cache->fPrev->fNext = nullptr; } } cache->fPrev = cache->fNext = nullptr; cache->fRefCount -= 1; } /////////////////////////////////////////////////////////////////////////////// #ifdef SK_DEBUG void SkGlyphCache::validate() const { #ifdef SK_DEBUG_GLYPH_CACHE int count = fGlyphArray.count(); for (int i = 0; i < count; i++) { const SkGlyph* glyph = &fGlyphArray[i]; SkASSERT(glyph); if (glyph->fImage) { SkASSERT(fGlyphAlloc.contains(glyph->fImage)); } } #endif } void SkGlyphCache_Globals::validate() const { int computedCount = 0; SkGlyphCache* head = fHead; while (head != nullptr) { computedCount += 1; head = head->fNext; } SkASSERTF(fCacheCount == computedCount, "fCacheCount: %d, computedCount: %d", fCacheCount, computedCount); } #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #include "SkTypefaceCache.h" size_t SkGraphics::GetFontCacheLimit() { return get_globals().getCacheSizeLimit(); } size_t SkGraphics::SetFontCacheLimit(size_t bytes) { return get_globals().setCacheSizeLimit(bytes); } size_t SkGraphics::GetFontCacheUsed() { return get_globals().getTotalMemoryUsed(); } int SkGraphics::GetFontCacheCountLimit() { return get_globals().getCacheCountLimit(); } int SkGraphics::SetFontCacheCountLimit(int count) { return get_globals().setCacheCountLimit(count); } int SkGraphics::GetFontCacheCountUsed() { return get_globals().getCacheCountUsed(); } void SkGraphics::PurgeFontCache() { get_globals().purgeAll(); SkTypefaceCache::PurgeAll(); } // TODO(herb): clean up TLS apis. size_t SkGraphics::GetTLSFontCacheLimit() { return 0; } void SkGraphics::SetTLSFontCacheLimit(size_t bytes) { }