/* * 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/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/SkStrike.h" class SkStrikeCache::Node final : public SkStrikeInterface { public: Node(SkStrikeCache* strikeCache, const SkDescriptor& desc, std::unique_ptr scaler, const SkFontMetrics& metrics, std::unique_ptr pinner) : fStrikeCache{strikeCache} , fStrike{desc, std::move(scaler), metrics} , fPinner{std::move(pinner)} {} SkVector rounding() const override { return fStrike.rounding(); } SkIPoint subpixelMask() const override { return fStrike.subpixelMask(); } SkSpan prepareForDrawing(const SkPackedGlyphID packedGlyphIDs[], const SkPoint positions[], size_t n, int maxDimension, PreparationDetail detail, SkGlyphPos results[]) override { return fStrike.prepareForDrawing(packedGlyphIDs, positions, n, maxDimension, detail, results); } const SkDescriptor& getDescriptor() const override { return fStrike.getDescriptor(); } void onAboutToExitScope() override { fStrikeCache->attachNode(this); } SkStrikeCache* const fStrikeCache; Node* fNext{nullptr}; Node* fPrev{nullptr}; SkStrike fStrike; std::unique_ptr fPinner; }; SkStrikeCache* SkStrikeCache::GlobalStrikeCache() { static auto* cache = new SkStrikeCache; return cache; } SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(SkStrikeCache::Node* node) : fNode{node} {} SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr() : fNode{nullptr} {} SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(ExclusiveStrikePtr&& o) : fNode{o.fNode} { o.fNode = nullptr; } SkStrikeCache::ExclusiveStrikePtr& SkStrikeCache::ExclusiveStrikePtr::operator = (ExclusiveStrikePtr&& o) { if (fNode != nullptr) { fNode->fStrikeCache->attachNode(fNode); } fNode = o.fNode; o.fNode = nullptr; return *this; } SkStrikeCache::ExclusiveStrikePtr::~ExclusiveStrikePtr() { if (fNode != nullptr) { fNode->fStrikeCache->attachNode(fNode); } } SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const { return &fNode->fStrike; } SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const { return this->get(); } SkStrike& SkStrikeCache::ExclusiveStrikePtr::operator * () const { return *this->get(); } SkStrikeCache::ExclusiveStrikePtr::operator bool () const { return fNode != nullptr; } bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, const SkStrikeCache::ExclusiveStrikePtr& rhs) { return lhs.fNode == rhs.fNode; } bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, decltype(nullptr)) { return lhs.fNode == nullptr; } bool operator == (decltype(nullptr), const SkStrikeCache::ExclusiveStrikePtr& rhs) { return nullptr == rhs.fNode; } SkStrikeCache::~SkStrikeCache() { Node* node = fHead; while (node) { Node* next = node->fNext; delete node; node = next; } } std::unique_ptr SkStrikeCache::CreateScalerContext( const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) { auto scaler = typeface.createScalerContext(effects, &desc, true /* can fail */); // 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, if (scaler == nullptr) { PurgeAll(); scaler = typeface.createScalerContext(effects, &desc, false /* must succeed */); } return scaler; } SkExclusiveStrikePtr SkStrikeCache::findOrCreateStrikeExclusive( const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) { return SkExclusiveStrikePtr(this->findOrCreateStrike(desc, effects, typeface)); } auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) -> Node* { Node* node = this->findAndDetachStrike(desc); if (node == nullptr) { auto scaler = CreateScalerContext(desc, effects, typeface); node = this->createStrike(desc, std::move(scaler)); } return node; } SkScopedStrike SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) { return SkScopedStrike{this->findOrCreateStrike(desc, effects, typeface)}; } void SkStrikeCache::PurgeAll() { GlobalStrikeCache()->purgeAll(); } void SkStrikeCache::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; auto visitor = [&counter](const SkStrike& cache) { const SkScalerContextRec& rec = cache.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& cache) { const SkTypeface* face = cache.getScalerContext()->getTypeface(); const SkScalerContextRec& rec = cache.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, &cache); dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", cache.getMemoryUsed()); dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", cache.countCachedGlyphs()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); }; GlobalStrikeCache()->forEachStrike(visitor); } void SkStrikeCache::attachNode(Node* node) { if (node == nullptr) { return; } SkAutoSpinlock ac(fLock); this->validate(); node->fStrike.validate(); this->internalAttachToHead(node); this->internalPurge(); } SkExclusiveStrikePtr SkStrikeCache::findStrikeExclusive(const SkDescriptor& desc) { return SkExclusiveStrikePtr(this->findAndDetachStrike(desc)); } auto SkStrikeCache::findAndDetachStrike(const SkDescriptor& desc) -> Node* { SkAutoSpinlock ac(fLock); for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { if (node->fStrike.getDescriptor() == desc) { this->internalDetachCache(node); return node; } } return nullptr; } static bool loose_compare(const SkDescriptor& lhs, const SkDescriptor& rhs) { uint32_t size; auto ptr = lhs.findEntry(kRec_SkDescriptorTag, &size); SkScalerContextRec lhsRec; std::memcpy(&lhsRec, ptr, size); ptr = rhs.findEntry(kRec_SkDescriptorTag, &size); SkScalerContextRec rhsRec; std::memcpy(&rhsRec, ptr, size); // If these don't match, there's no way we can use these strikes interchangeably. // Note that a typeface from each renderer maps to a unique proxy typeface on the GPU, // keyed in the glyph cache using fontID in the SkDescriptor. By limiting this search // to descriptors with the same fontID, we ensure that a renderer never uses glyphs // generated by a different renderer. return lhsRec.fFontID == rhsRec.fFontID && lhsRec.fTextSize == rhsRec.fTextSize && lhsRec.fPreScaleX == rhsRec.fPreScaleX && lhsRec.fPreSkewX == rhsRec.fPreSkewX && lhsRec.fPost2x2[0][0] == rhsRec.fPost2x2[0][0] && lhsRec.fPost2x2[0][1] == rhsRec.fPost2x2[0][1] && lhsRec.fPost2x2[1][0] == rhsRec.fPost2x2[1][0] && lhsRec.fPost2x2[1][1] == rhsRec.fPost2x2[1][1]; } bool SkStrikeCache::desperationSearchForImage(const SkDescriptor& desc, SkGlyph* glyph, SkStrike* targetCache) { SkAutoSpinlock ac(fLock); SkGlyphID glyphID = glyph->getGlyphID(); for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { if (loose_compare(node->fStrike.getDescriptor(), desc)) { if (SkGlyph *fallback = node->fStrike.glyphOrNull(glyph->getPackedID())) { // This desperate-match node may disappear as soon as we drop fLock, so we // need to copy the glyph from node into this strike, including a // deep copy of the mask. targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback); return true; } // Look for any sub-pixel pos for this glyph, in case there is a pos mismatch. if (const auto* fallback = node->fStrike.getCachedGlyphAnySubPix(glyphID)) { targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback); return true; } } } return false; } bool SkStrikeCache::desperationSearchForPath( const SkDescriptor& desc, SkGlyphID glyphID, SkPath* path) { SkAutoSpinlock ac(fLock); // The following is wrong there is subpixel positioning with paths... // Paths are only ever at sub-pixel position (0,0), so we can just try that directly rather // than try our packed position first then search all others on failure like for masks. // // This will have to search the sub-pixel positions too. // There is also a problem with accounting for cache size with shared path data. for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) { if (loose_compare(node->fStrike.getDescriptor(), desc)) { if (SkGlyph *from = node->fStrike.glyphOrNull(SkPackedGlyphID{glyphID})) { if (from->setPathHasBeenCalled() && from->path() != nullptr) { // We can just copy the path out by value here, so no need to worry // about the lifetime of this desperate-match node. *path = *from->path(); return true; } } } } return false; } SkExclusiveStrikePtr SkStrikeCache::createStrikeExclusive( const SkDescriptor& desc, std::unique_ptr scaler, SkFontMetrics* maybeMetrics, std::unique_ptr pinner) { return SkExclusiveStrikePtr( this->createStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner))); } auto SkStrikeCache::createStrike( const SkDescriptor& desc, std::unique_ptr scaler, SkFontMetrics* maybeMetrics, std::unique_ptr pinner) -> Node* { SkFontMetrics fontMetrics; if (maybeMetrics != nullptr) { fontMetrics = *maybeMetrics; } else { scaler->getFontMetrics(&fontMetrics); } return new Node{this, desc, std::move(scaler), fontMetrics, std::move(pinner)}; } void SkStrikeCache::purgeAll() { SkAutoSpinlock ac(fLock); this->internalPurge(fTotalMemoryUsed); } size_t SkStrikeCache::getTotalMemoryUsed() const { SkAutoSpinlock ac(fLock); return fTotalMemoryUsed; } int SkStrikeCache::getCacheCountUsed() const { SkAutoSpinlock ac(fLock); return fCacheCount; } int SkStrikeCache::getCacheCountLimit() const { SkAutoSpinlock ac(fLock); return fCacheCountLimit; } size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) { static const size_t minLimit = 256 * 1024; if (newLimit < minLimit) { newLimit = minLimit; } SkAutoSpinlock ac(fLock); size_t prevLimit = fCacheSizeLimit; fCacheSizeLimit = newLimit; this->internalPurge(); return prevLimit; } size_t SkStrikeCache::getCacheSizeLimit() const { SkAutoSpinlock ac(fLock); return fCacheSizeLimit; } int SkStrikeCache::setCacheCountLimit(int newCount) { if (newCount < 0) { newCount = 0; } SkAutoSpinlock ac(fLock); int prevCount = fCacheCountLimit; fCacheCountLimit = newCount; this->internalPurge(); return prevCount; } int SkStrikeCache::getCachePointSizeLimit() const { SkAutoSpinlock ac(fLock); return fPointSizeLimit; } int SkStrikeCache::setCachePointSizeLimit(int newLimit) { if (newLimit < 0) { newLimit = 0; } SkAutoSpinlock ac(fLock); int prevLimit = fPointSizeLimit; fPointSizeLimit = newLimit; return prevLimit; } void SkStrikeCache::forEachStrike(std::function visitor) const { SkAutoSpinlock ac(fLock); this->validate(); for (Node* node = this->internalGetHead(); node != nullptr; node = node->fNext) { visitor(node->fStrike); } } size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) { this->validate(); size_t bytesNeeded = 0; if (fTotalMemoryUsed > fCacheSizeLimit) { bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit; } bytesNeeded = SkTMax(bytesNeeded, minBytesNeeded); if (bytesNeeded) { // no small purges! bytesNeeded = SkTMax(bytesNeeded, fTotalMemoryUsed >> 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; // Start at the tail and proceed backwards deleting; the list is in LRU // order, with unimportant entries at the tail. Node* node = this->internalGetTail(); while (node != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) { Node* prev = node->fPrev; // Only delete if the strike is not pinned. if (node->fPinner == nullptr || node->fPinner->canDelete()) { bytesFreed += node->fStrike.getMemoryUsed(); countFreed += 1; this->internalDetachCache(node); delete node; } node = 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(Node* node) { SkASSERT(nullptr == node->fPrev && nullptr == node->fNext); if (fHead) { fHead->fPrev = node; node->fNext = fHead; } fHead = node; if (fTail == nullptr) { fTail = node; } fCacheCount += 1; fTotalMemoryUsed += node->fStrike.getMemoryUsed(); } void SkStrikeCache::internalDetachCache(Node* node) { SkASSERT(fCacheCount > 0); fCacheCount -= 1; fTotalMemoryUsed -= node->fStrike.getMemoryUsed(); if (node->fPrev) { node->fPrev->fNext = node->fNext; } else { fHead = node->fNext; } if (node->fNext) { node->fNext->fPrev = node->fPrev; } else { fTail = node->fPrev; } node->fPrev = node->fNext = nullptr; } void SkStrikeCache::ValidateGlyphCacheDataSize() { #ifdef SK_DEBUG GlobalStrikeCache()->validateGlyphCacheDataSize(); #endif } #ifdef SK_DEBUG void SkStrikeCache::validateGlyphCacheDataSize() const { this->forEachStrike( [](const SkStrike& cache) { cache.forceValidate(); }); } #endif #ifdef SK_DEBUG void SkStrikeCache::validate() const { size_t computedBytes = 0; int computedCount = 0; const Node* node = fHead; while (node != nullptr) { computedBytes += node->fStrike.getMemoryUsed(); computedCount += 1; node = node->fNext; } SkASSERTF(fCacheCount == computedCount, "fCacheCount: %d, computedCount: %d", fCacheCount, computedCount); SkASSERTF(fTotalMemoryUsed == computedBytes, "fTotalMemoryUsed: %d, computedBytes: %d", fTotalMemoryUsed, computedBytes); } #endif ////////////////////////////////////////////////////////////////////////////////////////////////////