1 /* 2 * Copyright 2015 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #ifndef GrTextBlobCache_DEFINED 9 #define GrTextBlobCache_DEFINED 10 11 #include "GrTextBlob.h" 12 #include "SkMessageBus.h" 13 #include "SkRefCnt.h" 14 #include "SkTArray.h" 15 #include "SkTextBlobPriv.h" 16 #include "SkTHash.h" 17 18 class GrTextBlobCache { 19 public: 20 /** 21 * The callback function used by the cache when it is still over budget after a purge. The 22 * passed in 'data' is the same 'data' handed to setOverbudgetCallback. 23 */ 24 typedef void (*PFOverBudgetCB)(void* data); 25 GrTextBlobCache(PFOverBudgetCB cb,void * data,uint32_t uniqueID)26 GrTextBlobCache(PFOverBudgetCB cb, void* data, uint32_t uniqueID) 27 : fCallback(cb) 28 , fData(data) 29 , fSizeBudget(kDefaultBudget) 30 , fUniqueID(uniqueID) 31 , fPurgeBlobInbox(uniqueID) { 32 SkASSERT(cb && data); 33 } 34 ~GrTextBlobCache(); 35 makeBlob(const SkGlyphRunList & glyphRunList,GrColor color)36 sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList, GrColor color) { 37 return GrTextBlob::Make(glyphRunList.totalGlyphCount(), glyphRunList.size(), color); 38 } 39 makeCachedBlob(const SkGlyphRunList & glyphRunList,const GrTextBlob::Key & key,const SkMaskFilterBase::BlurRec & blurRec,const SkPaint & paint,GrColor color)40 sk_sp<GrTextBlob> makeCachedBlob(const SkGlyphRunList& glyphRunList, 41 const GrTextBlob::Key& key, 42 const SkMaskFilterBase::BlurRec& blurRec, 43 const SkPaint& paint, 44 GrColor color) { 45 sk_sp<GrTextBlob> cacheBlob(makeBlob(glyphRunList, color)); 46 cacheBlob->setupKey(key, blurRec, paint); 47 this->add(cacheBlob); 48 glyphRunList.temporaryShuntBlobNotifyAddedToCache(fUniqueID); 49 return cacheBlob; 50 } 51 find(const GrTextBlob::Key & key)52 sk_sp<GrTextBlob> find(const GrTextBlob::Key& key) const { 53 const auto* idEntry = fBlobIDCache.find(key.fUniqueID); 54 return idEntry ? idEntry->find(key) : nullptr; 55 } 56 remove(GrTextBlob * blob)57 void remove(GrTextBlob* blob) { 58 auto id = GrTextBlob::GetKey(*blob).fUniqueID; 59 auto* idEntry = fBlobIDCache.find(id); 60 SkASSERT(idEntry); 61 62 fCurrentSize -= blob->size(); 63 fBlobList.remove(blob); 64 idEntry->removeBlob(blob); 65 if (idEntry->fBlobs.empty()) { 66 fBlobIDCache.remove(id); 67 } 68 } 69 makeMRU(GrTextBlob * blob)70 void makeMRU(GrTextBlob* blob) { 71 if (fBlobList.head() == blob) { 72 return; 73 } 74 75 fBlobList.remove(blob); 76 fBlobList.addToHead(blob); 77 } 78 79 void freeAll(); 80 81 // TODO move to SkTextBlob BlobGlyphCount(int * glyphCount,int * runCount,const SkTextBlob * blob)82 static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) { 83 SkTextBlobRunIterator itCounter(blob); 84 for (; !itCounter.done(); itCounter.next(), (*runCount)++) { 85 *glyphCount += itCounter.glyphCount(); 86 } 87 } 88 setBudget(size_t budget)89 void setBudget(size_t budget) { 90 fSizeBudget = budget; 91 this->checkPurge(); 92 } 93 94 struct PurgeBlobMessage { PurgeBlobMessagePurgeBlobMessage95 PurgeBlobMessage(uint32_t blobID, uint32_t contextUniqueID) 96 : fBlobID(blobID), fContextID(contextUniqueID) {} 97 98 uint32_t fBlobID; 99 uint32_t fContextID; 100 }; 101 102 static void PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID); 103 104 void purgeStaleBlobs(); 105 usedBytes()106 size_t usedBytes() const { return fCurrentSize; } 107 108 private: 109 using BitmapBlobList = SkTInternalLList<GrTextBlob>; 110 111 struct BlobIDCacheEntry { BlobIDCacheEntryBlobIDCacheEntry112 BlobIDCacheEntry() : fID(SK_InvalidGenID) {} BlobIDCacheEntryBlobIDCacheEntry113 explicit BlobIDCacheEntry(uint32_t id) : fID(id) {} 114 GetKeyBlobIDCacheEntry115 static uint32_t GetKey(const BlobIDCacheEntry& entry) { 116 return entry.fID; 117 } 118 addBlobBlobIDCacheEntry119 void addBlob(sk_sp<GrTextBlob> blob) { 120 SkASSERT(blob); 121 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID); 122 SkASSERT(!this->find(GrTextBlob::GetKey(*blob))); 123 124 fBlobs.emplace_back(std::move(blob)); 125 } 126 removeBlobBlobIDCacheEntry127 void removeBlob(GrTextBlob* blob) { 128 SkASSERT(blob); 129 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID); 130 131 auto index = this->findBlobIndex(GrTextBlob::GetKey(*blob)); 132 SkASSERT(index >= 0); 133 134 fBlobs.removeShuffle(index); 135 } 136 findBlobIDCacheEntry137 sk_sp<GrTextBlob> find(const GrTextBlob::Key& key) const { 138 auto index = this->findBlobIndex(key); 139 return index < 0 ? nullptr : fBlobs[index]; 140 } 141 findBlobIndexBlobIDCacheEntry142 int findBlobIndex(const GrTextBlob::Key& key) const{ 143 for (int i = 0; i < fBlobs.count(); ++i) { 144 if (GrTextBlob::GetKey(*fBlobs[i]) == key) { 145 return i; 146 } 147 } 148 return -1; 149 } 150 151 uint32_t fID; 152 // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/ 153 // linear search is acceptable. If usage changes, we should re-evaluate this structure. 154 SkSTArray<1, sk_sp<GrTextBlob>, true> fBlobs; 155 }; 156 add(sk_sp<GrTextBlob> blob)157 void add(sk_sp<GrTextBlob> blob) { 158 auto id = GrTextBlob::GetKey(*blob).fUniqueID; 159 auto* idEntry = fBlobIDCache.find(id); 160 if (!idEntry) { 161 idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id)); 162 } 163 164 // Safe to retain a raw ptr temporarily here, because the cache will hold a ref. 165 GrTextBlob* rawBlobPtr = blob.get(); 166 fBlobList.addToHead(rawBlobPtr); 167 fCurrentSize += blob->size(); 168 idEntry->addBlob(std::move(blob)); 169 170 this->checkPurge(rawBlobPtr); 171 } 172 173 void checkPurge(GrTextBlob* blob = nullptr); 174 175 static const int kMinGrowthSize = 1 << 16; 176 static const int kDefaultBudget = 1 << 22; 177 BitmapBlobList fBlobList; 178 SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache; 179 PFOverBudgetCB fCallback; 180 void* fData; 181 size_t fSizeBudget; 182 size_t fCurrentSize{0}; 183 uint32_t fUniqueID; // unique id to use for messaging 184 SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox; 185 }; 186 187 #endif 188