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 "GrAtlasTextContext.h" 12 #include "SkMessageBus.h" 13 #include "SkRefCnt.h" 14 #include "SkTArray.h" 15 #include "SkTextBlobRunIterator.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)26 GrTextBlobCache(PFOverBudgetCB cb, void* data) 27 : fPool(0u, kMinGrowthSize) 28 , fCallback(cb) 29 , fData(data) 30 , fBudget(kDefaultBudget) { 31 SkASSERT(cb && data); 32 } 33 ~GrTextBlobCache(); 34 35 // creates an uncached blob makeBlob(int glyphCount,int runCount)36 sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) { 37 return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount); 38 } 39 makeBlob(const SkTextBlob * blob)40 sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) { 41 int glyphCount = 0; 42 int runCount = 0; 43 BlobGlyphCount(&glyphCount, &runCount, blob); 44 return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount); 45 } 46 makeCachedBlob(const SkTextBlob * blob,const GrAtlasTextBlob::Key & key,const SkMaskFilter::BlurRec & blurRec,const SkPaint & paint)47 sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob, 48 const GrAtlasTextBlob::Key& key, 49 const SkMaskFilter::BlurRec& blurRec, 50 const SkPaint& paint) { 51 sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob)); 52 cacheBlob->setupKey(key, blurRec, paint); 53 this->add(cacheBlob); 54 blob->notifyAddedToCache(); 55 return cacheBlob; 56 } 57 find(const GrAtlasTextBlob::Key & key)58 sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const { 59 const auto* idEntry = fBlobIDCache.find(key.fUniqueID); 60 return idEntry ? idEntry->find(key) : nullptr; 61 } 62 remove(GrAtlasTextBlob * blob)63 void remove(GrAtlasTextBlob* blob) { 64 auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID; 65 auto* idEntry = fBlobIDCache.find(id); 66 SkASSERT(idEntry); 67 68 fBlobList.remove(blob); 69 idEntry->removeBlob(blob); 70 if (idEntry->fBlobs.empty()) { 71 fBlobIDCache.remove(id); 72 } 73 } 74 makeMRU(GrAtlasTextBlob * blob)75 void makeMRU(GrAtlasTextBlob* blob) { 76 if (fBlobList.head() == blob) { 77 return; 78 } 79 80 fBlobList.remove(blob); 81 fBlobList.addToHead(blob); 82 } 83 84 void freeAll(); 85 86 // TODO move to SkTextBlob BlobGlyphCount(int * glyphCount,int * runCount,const SkTextBlob * blob)87 static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) { 88 SkTextBlobRunIterator itCounter(blob); 89 for (; !itCounter.done(); itCounter.next(), (*runCount)++) { 90 *glyphCount += itCounter.glyphCount(); 91 } 92 } 93 setBudget(size_t budget)94 void setBudget(size_t budget) { 95 fBudget = budget; 96 this->checkPurge(); 97 } 98 99 struct PurgeBlobMessage { 100 uint32_t fID; 101 }; 102 103 static void PostPurgeBlobMessage(uint32_t); 104 105 private: 106 using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>; 107 108 struct BlobIDCacheEntry { BlobIDCacheEntryBlobIDCacheEntry109 BlobIDCacheEntry() : fID(SK_InvalidGenID) {} BlobIDCacheEntryBlobIDCacheEntry110 explicit BlobIDCacheEntry(uint32_t id) : fID(id) {} 111 GetKeyBlobIDCacheEntry112 static uint32_t GetKey(const BlobIDCacheEntry& entry) { 113 return entry.fID; 114 } 115 addBlobBlobIDCacheEntry116 void addBlob(sk_sp<GrAtlasTextBlob> blob) { 117 SkASSERT(blob); 118 SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID); 119 SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob))); 120 121 fBlobs.emplace_back(std::move(blob)); 122 } 123 removeBlobBlobIDCacheEntry124 void removeBlob(GrAtlasTextBlob* blob) { 125 SkASSERT(blob); 126 SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID); 127 128 auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob)); 129 SkASSERT(index >= 0); 130 131 fBlobs.removeShuffle(index); 132 } 133 findBlobIDCacheEntry134 sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const { 135 auto index = this->findBlobIndex(key); 136 return index < 0 ? nullptr : fBlobs[index]; 137 } 138 findBlobIndexBlobIDCacheEntry139 int findBlobIndex(const GrAtlasTextBlob::Key& key) const{ 140 for (int i = 0; i < fBlobs.count(); ++i) { 141 if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) { 142 return i; 143 } 144 } 145 return -1; 146 } 147 148 uint32_t fID; 149 // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/ 150 // linear search is acceptable. If usage changes, we should re-evaluate this structure. 151 SkSTArray<1, sk_sp<GrAtlasTextBlob>, true> fBlobs; 152 }; 153 add(sk_sp<GrAtlasTextBlob> blob)154 void add(sk_sp<GrAtlasTextBlob> blob) { 155 auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID; 156 auto* idEntry = fBlobIDCache.find(id); 157 if (!idEntry) { 158 idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id)); 159 } 160 161 // Safe to retain a raw ptr temporarily here, because the cache will hold a ref. 162 GrAtlasTextBlob* rawBlobPtr = blob.get(); 163 fBlobList.addToHead(rawBlobPtr); 164 idEntry->addBlob(std::move(blob)); 165 166 this->checkPurge(rawBlobPtr); 167 } 168 169 void checkPurge(GrAtlasTextBlob* blob = nullptr) { 170 // First, purge all stale blob IDs. 171 { 172 SkTArray<PurgeBlobMessage> msgs; 173 fPurgeBlobInbox.poll(&msgs); 174 175 for (const auto& msg : msgs) { 176 auto* idEntry = fBlobIDCache.find(msg.fID); 177 if (!idEntry) { 178 // no cache entries for id 179 continue; 180 } 181 182 // remove all blob entries from the LRU list 183 for (const auto& blob : idEntry->fBlobs) { 184 fBlobList.remove(blob.get()); 185 } 186 187 // drop the idEntry itself (unrefs all blobs) 188 fBlobIDCache.remove(msg.fID); 189 } 190 } 191 192 // If we are still overbudget, then unref until we are below budget again 193 if (fPool.size() > fBudget) { 194 BitmapBlobList::Iter iter; 195 iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart); 196 GrAtlasTextBlob* lruBlob = nullptr; 197 while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) { 198 // Backup the iterator before removing and unrefing the blob 199 iter.prev(); 200 201 this->remove(lruBlob); 202 } 203 204 // If we break out of the loop with lruBlob == blob, then we haven't purged enough 205 // use the call back and try to free some more. If we are still overbudget after this, 206 // then this single textblob is over our budget 207 if (blob && lruBlob == blob) { 208 (*fCallback)(fData); 209 } 210 211 #ifdef SPEW_BUDGET_MESSAGE 212 if (fPool.size() > fBudget) { 213 SkDebugf("Single textblob is larger than our whole budget"); 214 } 215 #endif 216 } 217 } 218 219 static const int kMinGrowthSize = 1 << 16; 220 static const int kDefaultBudget = 1 << 22; 221 GrMemoryPool fPool; 222 BitmapBlobList fBlobList; 223 SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache; 224 PFOverBudgetCB fCallback; 225 void* fData; 226 size_t fBudget; 227 SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox; 228 }; 229 230 #endif 231