• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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