Line data Source code
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 :
26 0 : GrTextBlobCache(PFOverBudgetCB cb, void* data)
27 0 : : fPool(kPreAllocSize, kMinGrowthSize)
28 : , fCallback(cb)
29 : , fData(data)
30 0 : , fBudget(kDefaultBudget) {
31 0 : SkASSERT(cb && data);
32 0 : }
33 : ~GrTextBlobCache();
34 :
35 : // creates an uncached blob
36 0 : sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) {
37 0 : return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
38 : }
39 :
40 0 : sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) {
41 0 : int glyphCount = 0;
42 0 : int runCount = 0;
43 0 : BlobGlyphCount(&glyphCount, &runCount, blob);
44 0 : return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
45 : }
46 :
47 0 : sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob,
48 : const GrAtlasTextBlob::Key& key,
49 : const SkMaskFilter::BlurRec& blurRec,
50 : const SkPaint& paint) {
51 0 : sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob));
52 0 : cacheBlob->setupKey(key, blurRec, paint);
53 0 : this->add(cacheBlob);
54 0 : blob->notifyAddedToCache();
55 0 : return cacheBlob;
56 : }
57 :
58 0 : sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
59 0 : const auto* idEntry = fBlobIDCache.find(key.fUniqueID);
60 0 : return idEntry ? idEntry->find(key) : nullptr;
61 : }
62 :
63 0 : void remove(GrAtlasTextBlob* blob) {
64 0 : auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
65 0 : auto* idEntry = fBlobIDCache.find(id);
66 0 : SkASSERT(idEntry);
67 :
68 0 : fBlobList.remove(blob);
69 0 : idEntry->removeBlob(blob);
70 0 : if (idEntry->fBlobs.empty()) {
71 0 : fBlobIDCache.remove(id);
72 : }
73 0 : }
74 :
75 0 : void makeMRU(GrAtlasTextBlob* blob) {
76 0 : if (fBlobList.head() == blob) {
77 0 : return;
78 : }
79 :
80 0 : fBlobList.remove(blob);
81 0 : fBlobList.addToHead(blob);
82 : }
83 :
84 : void freeAll();
85 :
86 : // TODO move to SkTextBlob
87 0 : static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
88 0 : SkTextBlobRunIterator itCounter(blob);
89 0 : for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
90 0 : *glyphCount += itCounter.glyphCount();
91 : }
92 0 : }
93 :
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 0 : struct BlobIDCacheEntry {
109 0 : BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
110 0 : explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
111 :
112 : static uint32_t GetKey(const BlobIDCacheEntry& entry) {
113 : return entry.fID;
114 : }
115 :
116 0 : void addBlob(sk_sp<GrAtlasTextBlob> blob) {
117 0 : SkASSERT(blob);
118 0 : SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
119 0 : SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob)));
120 :
121 0 : fBlobs.emplace_back(std::move(blob));
122 0 : }
123 :
124 0 : void removeBlob(GrAtlasTextBlob* blob) {
125 0 : SkASSERT(blob);
126 0 : SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
127 :
128 0 : auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob));
129 0 : SkASSERT(index >= 0);
130 :
131 0 : fBlobs.removeShuffle(index);
132 0 : }
133 :
134 0 : sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
135 0 : auto index = this->findBlobIndex(key);
136 0 : return index < 0 ? nullptr : fBlobs[index];
137 : }
138 :
139 0 : int findBlobIndex(const GrAtlasTextBlob::Key& key) const{
140 0 : for (int i = 0; i < fBlobs.count(); ++i) {
141 0 : if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) {
142 0 : return i;
143 : }
144 : }
145 0 : 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 :
154 0 : void add(sk_sp<GrAtlasTextBlob> blob) {
155 0 : auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
156 0 : auto* idEntry = fBlobIDCache.find(id);
157 0 : if (!idEntry) {
158 0 : 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 0 : GrAtlasTextBlob* rawBlobPtr = blob.get();
163 0 : fBlobList.addToHead(rawBlobPtr);
164 0 : idEntry->addBlob(std::move(blob));
165 :
166 0 : this->checkPurge(rawBlobPtr);
167 0 : }
168 :
169 0 : void checkPurge(GrAtlasTextBlob* blob = nullptr) {
170 : // First, purge all stale blob IDs.
171 : {
172 0 : SkTArray<PurgeBlobMessage> msgs;
173 0 : fPurgeBlobInbox.poll(&msgs);
174 :
175 0 : for (const auto& msg : msgs) {
176 0 : auto* idEntry = fBlobIDCache.find(msg.fID);
177 0 : if (!idEntry) {
178 : // no cache entries for id
179 0 : continue;
180 : }
181 :
182 : // remove all blob entries from the LRU list
183 0 : for (const auto& blob : idEntry->fBlobs) {
184 0 : fBlobList.remove(blob.get());
185 : }
186 :
187 : // drop the idEntry itself (unrefs all blobs)
188 0 : fBlobIDCache.remove(msg.fID);
189 : }
190 : }
191 :
192 : // If we are still overbudget, then unref until we are below budget again
193 0 : if (fPool.size() > fBudget) {
194 0 : BitmapBlobList::Iter iter;
195 0 : iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
196 0 : GrAtlasTextBlob* lruBlob = nullptr;
197 0 : while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) {
198 : // Backup the iterator before removing and unrefing the blob
199 0 : iter.prev();
200 :
201 0 : 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 0 : if (blob && lruBlob == blob) {
208 0 : (*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 0 : }
218 :
219 : // Budget was chosen to be ~4 megabytes. The min alloc and pre alloc sizes in the pool are
220 : // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
221 : static const int kPreAllocSize = 1 << 17;
222 : static const int kMinGrowthSize = 1 << 17;
223 : static const int kDefaultBudget = 1 << 22;
224 : GrMemoryPool fPool;
225 : BitmapBlobList fBlobList;
226 : SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
227 : PFOverBudgetCB fCallback;
228 : void* fData;
229 : size_t fBudget;
230 : SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
231 : };
232 :
233 : #endif
|