blob: 87a3751bf86fe96b038f2f81e4b11b84240bb9b4 [file] [log] [blame]
joshualittb7133be2015-04-08 09:08:31 -07001/*
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"
Florin Malita4a01ac92017-03-13 16:45:28 -040012#include "SkMessageBus.h"
Florin Malitac337c9e2017-03-10 18:02:29 +000013#include "SkRefCnt.h"
Florin Malita33fdb8d2017-03-07 16:51:57 -050014#include "SkTArray.h"
halcanary33779752015-10-27 14:01:05 -070015#include "SkTextBlobRunIterator.h"
Florin Malita33fdb8d2017-03-07 16:51:57 -050016#include "SkTHash.h"
joshualittb7133be2015-04-08 09:08:31 -070017
18class GrTextBlobCache {
19public:
joshualitt0db6dfa2015-04-10 07:01:30 -070020 /**
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 GrTextBlobCache(PFOverBudgetCB cb, void* data)
27 : fPool(kPreAllocSize, kMinGrowthSize)
28 , fCallback(cb)
joshualitt17d833b2015-08-03 10:17:44 -070029 , fData(data)
30 , fBudget(kDefaultBudget) {
joshualitt0db6dfa2015-04-10 07:01:30 -070031 SkASSERT(cb && data);
32 }
joshualittb7133be2015-04-08 09:08:31 -070033 ~GrTextBlobCache();
34
35 // creates an uncached blob
Florin Malitac337c9e2017-03-10 18:02:29 +000036 sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) {
37 return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
Florin Malitadb3ceb82017-03-09 14:21:44 -050038 }
39
Florin Malitac337c9e2017-03-10 18:02:29 +000040 sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) {
Florin Malita3304c442017-03-09 22:42:58 +000041 int glyphCount = 0;
42 int runCount = 0;
43 BlobGlyphCount(&glyphCount, &runCount, blob);
Florin Malitac337c9e2017-03-10 18:02:29 +000044 return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
45 }
46
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));
joshualitt92303772016-02-10 11:55:52 -080052 cacheBlob->setupKey(key, blurRec, paint);
joshualittb7133be2015-04-08 09:08:31 -070053 this->add(cacheBlob);
Florin Malita4a01ac92017-03-13 16:45:28 -040054 blob->notifyAddedToCache();
joshualittb7133be2015-04-08 09:08:31 -070055 return cacheBlob;
56 }
57
Florin Malitac337c9e2017-03-10 18:02:29 +000058 sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
Florin Malita33fdb8d2017-03-07 16:51:57 -050059 const auto* idEntry = fBlobIDCache.find(key.fUniqueID);
60 return idEntry ? idEntry->find(key) : nullptr;
joshualittb7133be2015-04-08 09:08:31 -070061 }
62
joshualitt374b2f72015-07-21 08:05:03 -070063 void remove(GrAtlasTextBlob* blob) {
Florin Malita33fdb8d2017-03-07 16:51:57 -050064 auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
65 auto* idEntry = fBlobIDCache.find(id);
66 SkASSERT(idEntry);
67
Florin Malitac337c9e2017-03-10 18:02:29 +000068 fBlobList.remove(blob);
Florin Malita33fdb8d2017-03-07 16:51:57 -050069 idEntry->removeBlob(blob);
70 if (idEntry->fBlobs.empty()) {
71 fBlobIDCache.remove(id);
72 }
joshualittb7133be2015-04-08 09:08:31 -070073 }
74
joshualitt374b2f72015-07-21 08:05:03 -070075 void makeMRU(GrAtlasTextBlob* blob) {
joshualittb7133be2015-04-08 09:08:31 -070076 if (fBlobList.head() == blob) {
77 return;
78 }
79
80 fBlobList.remove(blob);
81 fBlobList.addToHead(blob);
82 }
83
joshualitt26ffc002015-04-16 11:24:04 -070084 void freeAll();
85
joshualittb7133be2015-04-08 09:08:31 -070086 // TODO move to SkTextBlob
joshualitt259fbf12015-07-21 11:39:34 -070087 static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
halcanary33779752015-10-27 14:01:05 -070088 SkTextBlobRunIterator itCounter(blob);
joshualittb7133be2015-04-08 09:08:31 -070089 for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
90 *glyphCount += itCounter.glyphCount();
91 }
92 }
93
joshualitt17d833b2015-08-03 10:17:44 -070094 void setBudget(size_t budget) {
95 fBudget = budget;
96 this->checkPurge();
97 }
98
Florin Malita4a01ac92017-03-13 16:45:28 -040099 struct PurgeBlobMessage {
100 uint32_t fID;
101 };
102
103 static void PostPurgeBlobMessage(uint32_t);
104
joshualitt259fbf12015-07-21 11:39:34 -0700105private:
Florin Malita33fdb8d2017-03-07 16:51:57 -0500106 using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>;
107
108 struct BlobIDCacheEntry {
109 BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
110 explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
111
112 static uint32_t GetKey(const BlobIDCacheEntry& entry) {
113 return entry.fID;
114 }
115
Florin Malitac337c9e2017-03-10 18:02:29 +0000116 void addBlob(sk_sp<GrAtlasTextBlob> blob) {
Florin Malita33fdb8d2017-03-07 16:51:57 -0500117 SkASSERT(blob);
118 SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
119 SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob)));
120
Florin Malitac337c9e2017-03-10 18:02:29 +0000121 fBlobs.emplace_back(std::move(blob));
Florin Malita33fdb8d2017-03-07 16:51:57 -0500122 }
123
124 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
Florin Malitac337c9e2017-03-10 18:02:29 +0000134 sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
Florin Malita33fdb8d2017-03-07 16:51:57 -0500135 auto index = this->findBlobIndex(key);
136 return index < 0 ? nullptr : fBlobs[index];
137 }
138
139 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.
Florin Malitac337c9e2017-03-10 18:02:29 +0000151 SkSTArray<1, sk_sp<GrAtlasTextBlob>, true> fBlobs;
Florin Malita33fdb8d2017-03-07 16:51:57 -0500152 };
joshualittb7133be2015-04-08 09:08:31 -0700153
Florin Malitac337c9e2017-03-10 18:02:29 +0000154 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
halcanary96fcdcc2015-08-27 07:41:13 -0700169 void checkPurge(GrAtlasTextBlob* blob = nullptr) {
Florin Malita4a01ac92017-03-13 16:45:28 -0400170 // First, purge all stale blob IDs.
171 {
Florin Malitac500d9b2017-03-14 10:08:33 -0400172 SkTArray<PurgeBlobMessage> msgs;
Florin Malita4a01ac92017-03-13 16:45:28 -0400173 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
joshualitt17d833b2015-08-03 10:17:44 -0700193 if (fPool.size() > fBudget) {
194 BitmapBlobList::Iter iter;
195 iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
halcanary96fcdcc2015-08-27 07:41:13 -0700196 GrAtlasTextBlob* lruBlob = nullptr;
joshualitt17d833b2015-08-03 10:17:44 -0700197 while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) {
joshualitt17d833b2015-08-03 10:17:44 -0700198 // Backup the iterator before removing and unrefing the blob
199 iter.prev();
Florin Malita33fdb8d2017-03-07 16:51:57 -0500200
201 this->remove(lruBlob);
joshualitt17d833b2015-08-03 10:17:44 -0700202 }
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
joshualittb7133be2015-04-08 09:08:31 -0700219 // 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;
joshualitt17d833b2015-08-03 10:17:44 -0700223 static const int kDefaultBudget = 1 << 22;
Florin Malitac337c9e2017-03-10 18:02:29 +0000224 GrMemoryPool fPool;
joshualittb7133be2015-04-08 09:08:31 -0700225 BitmapBlobList fBlobList;
Florin Malita33fdb8d2017-03-07 16:51:57 -0500226 SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
joshualitt0db6dfa2015-04-10 07:01:30 -0700227 PFOverBudgetCB fCallback;
228 void* fData;
joshualitt17d833b2015-08-03 10:17:44 -0700229 size_t fBudget;
Florin Malita4a01ac92017-03-13 16:45:28 -0400230 SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
joshualittb7133be2015-04-08 09:08:31 -0700231};
232
233#endif