/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef GrTextBlobCache_DEFINED #define GrTextBlobCache_DEFINED #include "GrAtlasTextContext.h" #include "SkMessageBus.h" #include "SkRefCnt.h" #include "SkTArray.h" #include "SkTextBlobRunIterator.h" #include "SkTHash.h" class GrTextBlobCache { public: /** * The callback function used by the cache when it is still over budget after a purge. The * passed in 'data' is the same 'data' handed to setOverbudgetCallback. */ typedef void (*PFOverBudgetCB)(void* data); GrTextBlobCache(PFOverBudgetCB cb, void* data, uint32_t uniqueID) : fPool(0u, kMinGrowthSize) , fCallback(cb) , fData(data) , fBudget(kDefaultBudget) , fUniqueID(uniqueID) , fPurgeBlobInbox(uniqueID) { SkASSERT(cb && data); } ~GrTextBlobCache(); // creates an uncached blob sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) { return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount); } sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) { int glyphCount = 0; int runCount = 0; BlobGlyphCount(&glyphCount, &runCount, blob); return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount); } sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob, const GrAtlasTextBlob::Key& key, const SkMaskFilterBase::BlurRec& blurRec, const SkPaint& paint) { sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob)); cacheBlob->setupKey(key, blurRec, paint); this->add(cacheBlob); blob->notifyAddedToCache(fUniqueID); return cacheBlob; } sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const { const auto* idEntry = fBlobIDCache.find(key.fUniqueID); return idEntry ? idEntry->find(key) : nullptr; } void remove(GrAtlasTextBlob* blob) { auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID; auto* idEntry = fBlobIDCache.find(id); SkASSERT(idEntry); fBlobList.remove(blob); idEntry->removeBlob(blob); if (idEntry->fBlobs.empty()) { fBlobIDCache.remove(id); } } void makeMRU(GrAtlasTextBlob* blob) { if (fBlobList.head() == blob) { return; } fBlobList.remove(blob); fBlobList.addToHead(blob); } void freeAll(); // TODO move to SkTextBlob static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) { SkTextBlobRunIterator itCounter(blob); for (; !itCounter.done(); itCounter.next(), (*runCount)++) { *glyphCount += itCounter.glyphCount(); } } void setBudget(size_t budget) { fBudget = budget; this->checkPurge(); } struct PurgeBlobMessage { uint32_t fID; }; static void PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID); void purgeStaleBlobs(); private: using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>; struct BlobIDCacheEntry { BlobIDCacheEntry() : fID(SK_InvalidGenID) {} explicit BlobIDCacheEntry(uint32_t id) : fID(id) {} static uint32_t GetKey(const BlobIDCacheEntry& entry) { return entry.fID; } void addBlob(sk_sp<GrAtlasTextBlob> blob) { SkASSERT(blob); SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID); SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob))); fBlobs.emplace_back(std::move(blob)); } void removeBlob(GrAtlasTextBlob* blob) { SkASSERT(blob); SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID); auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob)); SkASSERT(index >= 0); fBlobs.removeShuffle(index); } sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const { auto index = this->findBlobIndex(key); return index < 0 ? nullptr : fBlobs[index]; } int findBlobIndex(const GrAtlasTextBlob::Key& key) const{ for (int i = 0; i < fBlobs.count(); ++i) { if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) { return i; } } return -1; } uint32_t fID; // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/ // linear search is acceptable. If usage changes, we should re-evaluate this structure. SkSTArray<1, sk_sp<GrAtlasTextBlob>, true> fBlobs; }; void add(sk_sp<GrAtlasTextBlob> blob) { auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID; auto* idEntry = fBlobIDCache.find(id); if (!idEntry) { idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id)); } // Safe to retain a raw ptr temporarily here, because the cache will hold a ref. GrAtlasTextBlob* rawBlobPtr = blob.get(); fBlobList.addToHead(rawBlobPtr); idEntry->addBlob(std::move(blob)); this->checkPurge(rawBlobPtr); } void checkPurge(GrAtlasTextBlob* blob = nullptr); static const int kMinGrowthSize = 1 << 16; static const int kDefaultBudget = 1 << 22; GrMemoryPool fPool; BitmapBlobList fBlobList; SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache; PFOverBudgetCB fCallback; void* fData; size_t fBudget; uint32_t fUniqueID; // unique id to use for messaging SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox; }; #endif