/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/core/SkStrikeCache.h" #include #include "include/core/SkGraphics.h" #include "include/core/SkRefCnt.h" #include "include/core/SkTraceMemoryDump.h" #include "include/core/SkTypeface.h" #include "include/private/SkMutex.h" #include "include/private/SkTemplates.h" #include "src/core/SkGlyphRunPainter.h" #include "src/core/SkScalerCache.h" #if SK_SUPPORT_GPU #include "src/gpu/text/GrStrikeCache.h" #endif bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false; SkStrikeCache* SkStrikeCache::GlobalStrikeCache() { if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) { static thread_local auto* cache = new SkStrikeCache; return cache; } static auto* cache = new SkStrikeCache; return cache; } auto SkStrikeCache::findOrCreateStrike(const SkStrikeSpec& strikeSpec) -> sk_sp { SkAutoMutexExclusive ac(fLock); sk_sp strike = this->internalFindStrikeOrNull(strikeSpec.descriptor()); if (strike == nullptr) { strike = this->internalCreateStrike(strikeSpec); } this->internalPurge(); return strike; } SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkStrikeSpec& strikeSpec) { return SkScopedStrikeForGPU{this->findOrCreateStrike(strikeSpec).release()}; } void SkStrikeCache::PurgeAll() { GlobalStrikeCache()->purgeAll(); } void SkStrikeCache::Dump() { SkDebugf("GlyphCache [ used budget ]\n"); SkDebugf(" bytes [ %8zu %8zu ]\n", SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit()); SkDebugf(" count [ %8d %8d ]\n", SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit()); int counter = 0; auto visitor = [&counter](const SkStrike& strike) { const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec(); SkDebugf("index %d\n", counter); SkDebugf("%s", rec.dump().c_str()); counter += 1; }; GlobalStrikeCache()->forEachStrike(visitor); } namespace { const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache"; } // namespace void SkStrikeCache::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()); if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) { dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr); return; } auto visitor = [&dump](const SkStrike& strike) { const SkTypeface* face = strike.fScalerCache.getScalerContext()->getTypeface(); const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec(); SkString fontName; face->getFamilyName(&fontName); // Replace all special characters with '_'. for (size_t index = 0; index < fontName.size(); ++index) { if (!std::isalnum(fontName[index])) { fontName[index] = '_'; } } SkString dumpName = SkStringPrintf( "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &strike); dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", strike.fMemoryUsed); dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", strike.fScalerCache.countCachedGlyphs()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); }; GlobalStrikeCache()->forEachStrike(visitor); } sk_sp SkStrikeCache::findStrike(const SkDescriptor& desc) { SkAutoMutexExclusive ac(fLock); sk_sp result = this->internalFindStrikeOrNull(desc); this->internalPurge(); return result; } auto SkStrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) -> sk_sp { // Check head because it is likely the strike we are looking for. if (fHead != nullptr && fHead->getDescriptor() == desc) { return sk_ref_sp(fHead); } // Do the heavy search looking for the strike. sk_sp* strikeHandle = fStrikeLookup.find(desc); if (strikeHandle == nullptr) { return nullptr; } SkStrike* strikePtr = strikeHandle->get(); SkASSERT(strikePtr != nullptr); if (fHead != strikePtr) { // Make most recently used strikePtr->fPrev->fNext = strikePtr->fNext; if (strikePtr->fNext != nullptr) { strikePtr->fNext->fPrev = strikePtr->fPrev; } else { fTail = strikePtr->fPrev; } fHead->fPrev = strikePtr; strikePtr->fNext = fHead; strikePtr->fPrev = nullptr; fHead = strikePtr; } return sk_ref_sp(strikePtr); } sk_sp SkStrikeCache::createStrike( const SkStrikeSpec& strikeSpec, SkFontMetrics* maybeMetrics, std::unique_ptr pinner) { SkAutoMutexExclusive ac(fLock); return this->internalCreateStrike(strikeSpec, maybeMetrics, std::move(pinner)); } auto SkStrikeCache::internalCreateStrike( const SkStrikeSpec& strikeSpec, SkFontMetrics* maybeMetrics, std::unique_ptr pinner) -> sk_sp { std::unique_ptr scaler = strikeSpec.createScalerContext(); auto strike = sk_make_sp(this, strikeSpec, std::move(scaler), maybeMetrics, std::move(pinner)); this->internalAttachToHead(strike); return strike; } void SkStrikeCache::purgeAll() { SkAutoMutexExclusive ac(fLock); this->internalPurge(fTotalMemoryUsed); } size_t SkStrikeCache::getTotalMemoryUsed() const { SkAutoMutexExclusive ac(fLock); return fTotalMemoryUsed; } int SkStrikeCache::getCacheCountUsed() const { SkAutoMutexExclusive ac(fLock); return fCacheCount; } int SkStrikeCache::getCacheCountLimit() const { SkAutoMutexExclusive ac(fLock); return fCacheCountLimit; } size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) { SkAutoMutexExclusive ac(fLock); size_t prevLimit = fCacheSizeLimit; fCacheSizeLimit = newLimit; this->internalPurge(); return prevLimit; } size_t SkStrikeCache::getCacheSizeLimit() const { SkAutoMutexExclusive ac(fLock); return fCacheSizeLimit; } int SkStrikeCache::setCacheCountLimit(int newCount) { if (newCount < 0) { newCount = 0; } SkAutoMutexExclusive ac(fLock); int prevCount = fCacheCountLimit; fCacheCountLimit = newCount; this->internalPurge(); return prevCount; } void SkStrikeCache::forEachStrike(std::function visitor) const { SkAutoMutexExclusive ac(fLock); this->validate(); for (SkStrike* strike = fHead; strike != nullptr; strike = strike->fNext) { visitor(*strike); } } size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) { size_t bytesNeeded = 0; if (fTotalMemoryUsed > fCacheSizeLimit) { bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit; } bytesNeeded = std::max(bytesNeeded, minBytesNeeded); if (bytesNeeded) { // no small purges! bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2); } int countNeeded = 0; if (fCacheCount > fCacheCountLimit) { countNeeded = fCacheCount - fCacheCountLimit; // no small purges! countNeeded = std::max(countNeeded, fCacheCount >> 2); } // early exit if (!countNeeded && !bytesNeeded) { return 0; } size_t bytesFreed = 0; int countFreed = 0; // Start at the tail and proceed backwards deleting; the list is in LRU // order, with unimportant entries at the tail. SkStrike* strike = fTail; while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) { SkStrike* prev = strike->fPrev; // Only delete if the strike is not pinned. if (strike->fPinner == nullptr || strike->fPinner->canDelete()) { bytesFreed += strike->fMemoryUsed; countFreed += 1; this->internalRemoveStrike(strike); } strike = 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 SkStrikeCache::internalAttachToHead(sk_sp strike) { SkASSERT(fStrikeLookup.find(strike->getDescriptor()) == nullptr); SkStrike* strikePtr = strike.get(); fStrikeLookup.set(std::move(strike)); SkASSERT(nullptr == strikePtr->fPrev && nullptr == strikePtr->fNext); fCacheCount += 1; fTotalMemoryUsed += strikePtr->fMemoryUsed; if (fHead != nullptr) { fHead->fPrev = strikePtr; strikePtr->fNext = fHead; } if (fTail == nullptr) { fTail = strikePtr; } fHead = strikePtr; // Transfer ownership of strike to the cache list. } void SkStrikeCache::internalRemoveStrike(SkStrike* strike) { SkASSERT(fCacheCount > 0); fCacheCount -= 1; fTotalMemoryUsed -= strike->fMemoryUsed; if (strike->fPrev) { strike->fPrev->fNext = strike->fNext; } else { fHead = strike->fNext; } if (strike->fNext) { strike->fNext->fPrev = strike->fPrev; } else { fTail = strike->fPrev; } strike->fPrev = strike->fNext = nullptr; strike->fRemoved = true; fStrikeLookup.remove(strike->getDescriptor()); } void SkStrikeCache::validate() const { #ifdef SK_DEBUG size_t computedBytes = 0; int computedCount = 0; const SkStrike* strike = fHead; while (strike != nullptr) { computedBytes += strike->fMemoryUsed; computedCount += 1; SkASSERT(fStrikeLookup.findOrNull(strike->getDescriptor()) != nullptr); strike = strike->fNext; } if (fCacheCount != computedCount) { SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount); SK_ABORT("fCacheCount != computedCount"); } if (fTotalMemoryUsed != computedBytes) { SkDebugf("fTotalMemoryUsed: %zu, computedBytes: %zu", fTotalMemoryUsed, computedBytes); SK_ABORT("fTotalMemoryUsed == computedBytes"); } #endif } #if SK_SUPPORT_GPU sk_sp SkStrike::findOrCreateGrStrike(GrStrikeCache* grStrikeCache) const { return grStrikeCache->findOrCreateStrike(fStrikeSpec); } #endif void SkStrike::updateDelta(size_t increase) { if (increase != 0) { SkAutoMutexExclusive lock{fStrikeCache->fLock}; fMemoryUsed += increase; if (!fRemoved) { fStrikeCache->fTotalMemoryUsed += increase; } } }