blob: 886a0914ca6461595817513ae425fbae47be8a56 [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 Malita33fdb8d2017-03-07 16:51:57 -050012#include "SkTArray.h"
halcanary33779752015-10-27 14:01:05 -070013#include "SkTextBlobRunIterator.h"
Florin Malita33fdb8d2017-03-07 16:51:57 -050014#include "SkTHash.h"
joshualittb7133be2015-04-08 09:08:31 -070015
16class GrTextBlobCache {
17public:
joshualitt0db6dfa2015-04-10 07:01:30 -070018 /**
19 * The callback function used by the cache when it is still over budget after a purge. The
20 * passed in 'data' is the same 'data' handed to setOverbudgetCallback.
21 */
22 typedef void (*PFOverBudgetCB)(void* data);
23
24 GrTextBlobCache(PFOverBudgetCB cb, void* data)
25 : fPool(kPreAllocSize, kMinGrowthSize)
26 , fCallback(cb)
joshualitt17d833b2015-08-03 10:17:44 -070027 , fData(data)
28 , fBudget(kDefaultBudget) {
joshualitt0db6dfa2015-04-10 07:01:30 -070029 SkASSERT(cb && data);
30 }
joshualittb7133be2015-04-08 09:08:31 -070031 ~GrTextBlobCache();
32
33 // creates an uncached blob
joshualitt92303772016-02-10 11:55:52 -080034 GrAtlasTextBlob* createBlob(int glyphCount, int runCount) {
35 return GrAtlasTextBlob::Create(&fPool, glyphCount, runCount);
36 }
37 GrAtlasTextBlob* createBlob(const SkTextBlob* blob) {
joshualitt2a0e9f32015-04-13 06:12:21 -070038 int glyphCount = 0;
39 int runCount = 0;
40 BlobGlyphCount(&glyphCount, &runCount, blob);
joshualitt92303772016-02-10 11:55:52 -080041 GrAtlasTextBlob* cacheBlob = GrAtlasTextBlob::Create(&fPool, glyphCount, runCount);
joshualitt2a0e9f32015-04-13 06:12:21 -070042 return cacheBlob;
43 }
joshualittb7133be2015-04-08 09:08:31 -070044
joshualitt259fbf12015-07-21 11:39:34 -070045 GrAtlasTextBlob* createCachedBlob(const SkTextBlob* blob,
46 const GrAtlasTextBlob::Key& key,
47 const SkMaskFilter::BlurRec& blurRec,
joshualitt92303772016-02-10 11:55:52 -080048 const SkPaint& paint) {
joshualitt259fbf12015-07-21 11:39:34 -070049 int glyphCount = 0;
50 int runCount = 0;
51 BlobGlyphCount(&glyphCount, &runCount, blob);
joshualitt92303772016-02-10 11:55:52 -080052 GrAtlasTextBlob* cacheBlob = GrAtlasTextBlob::Create(&fPool, glyphCount, runCount);
53 cacheBlob->setupKey(key, blurRec, paint);
joshualittb7133be2015-04-08 09:08:31 -070054 this->add(cacheBlob);
55 return cacheBlob;
56 }
57
Florin Malita33fdb8d2017-03-07 16:51:57 -050058 GrAtlasTextBlob* find(const GrAtlasTextBlob::Key& key) const {
59 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
68 idEntry->removeBlob(blob);
69 if (idEntry->fBlobs.empty()) {
70 fBlobIDCache.remove(id);
71 }
72
joshualittb7133be2015-04-08 09:08:31 -070073 fBlobList.remove(blob);
74 blob->unref();
75 }
76
joshualitt374b2f72015-07-21 08:05:03 -070077 void add(GrAtlasTextBlob* blob) {
Florin Malita33fdb8d2017-03-07 16:51:57 -050078 auto id = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
79 auto* idEntry = fBlobIDCache.find(id);
80 if (!idEntry) {
81 idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id));
82 }
83
84 idEntry->addBlob(blob);
joshualittb7133be2015-04-08 09:08:31 -070085 fBlobList.addToHead(blob);
86
joshualitt17d833b2015-08-03 10:17:44 -070087 this->checkPurge(blob);
joshualittb7133be2015-04-08 09:08:31 -070088 }
89
joshualitt374b2f72015-07-21 08:05:03 -070090 void makeMRU(GrAtlasTextBlob* blob) {
joshualittb7133be2015-04-08 09:08:31 -070091 if (fBlobList.head() == blob) {
92 return;
93 }
94
95 fBlobList.remove(blob);
96 fBlobList.addToHead(blob);
97 }
98
joshualitt26ffc002015-04-16 11:24:04 -070099 void freeAll();
100
joshualittb7133be2015-04-08 09:08:31 -0700101 // TODO move to SkTextBlob
joshualitt259fbf12015-07-21 11:39:34 -0700102 static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
halcanary33779752015-10-27 14:01:05 -0700103 SkTextBlobRunIterator itCounter(blob);
joshualittb7133be2015-04-08 09:08:31 -0700104 for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
105 *glyphCount += itCounter.glyphCount();
106 }
107 }
108
joshualitt17d833b2015-08-03 10:17:44 -0700109 void setBudget(size_t budget) {
110 fBudget = budget;
111 this->checkPurge();
112 }
113
joshualitt259fbf12015-07-21 11:39:34 -0700114private:
Florin Malita33fdb8d2017-03-07 16:51:57 -0500115 using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>;
116
117 struct BlobIDCacheEntry {
118 BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
119 explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
120
121 static uint32_t GetKey(const BlobIDCacheEntry& entry) {
122 return entry.fID;
123 }
124
125 void addBlob(GrAtlasTextBlob* blob) {
126 SkASSERT(blob);
127 SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
128 SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob)));
129
130 fBlobs.push_back(blob);
131 }
132
133 void removeBlob(GrAtlasTextBlob* blob) {
134 SkASSERT(blob);
135 SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
136
137 auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob));
138 SkASSERT(index >= 0);
139
140 fBlobs.removeShuffle(index);
141 }
142
143 GrAtlasTextBlob* find(const GrAtlasTextBlob::Key& key) const {
144 auto index = this->findBlobIndex(key);
145 return index < 0 ? nullptr : fBlobs[index];
146 }
147
148 int findBlobIndex(const GrAtlasTextBlob::Key& key) const{
149 for (int i = 0; i < fBlobs.count(); ++i) {
150 if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) {
151 return i;
152 }
153 }
154 return -1;
155 }
156
157 uint32_t fID;
158 // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/
159 // linear search is acceptable. If usage changes, we should re-evaluate this structure.
160 SkSTArray<1, GrAtlasTextBlob*, true> fBlobs;
161 };
joshualittb7133be2015-04-08 09:08:31 -0700162
halcanary96fcdcc2015-08-27 07:41:13 -0700163 void checkPurge(GrAtlasTextBlob* blob = nullptr) {
joshualitt17d833b2015-08-03 10:17:44 -0700164 // If we are overbudget, then unref until we are below budget again
165 if (fPool.size() > fBudget) {
166 BitmapBlobList::Iter iter;
167 iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
halcanary96fcdcc2015-08-27 07:41:13 -0700168 GrAtlasTextBlob* lruBlob = nullptr;
joshualitt17d833b2015-08-03 10:17:44 -0700169 while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) {
joshualitt17d833b2015-08-03 10:17:44 -0700170 // Backup the iterator before removing and unrefing the blob
171 iter.prev();
Florin Malita33fdb8d2017-03-07 16:51:57 -0500172
173 this->remove(lruBlob);
joshualitt17d833b2015-08-03 10:17:44 -0700174 }
175
176 // If we break out of the loop with lruBlob == blob, then we haven't purged enough
177 // use the call back and try to free some more. If we are still overbudget after this,
178 // then this single textblob is over our budget
179 if (blob && lruBlob == blob) {
180 (*fCallback)(fData);
181 }
182
183#ifdef SPEW_BUDGET_MESSAGE
184 if (fPool.size() > fBudget) {
185 SkDebugf("Single textblob is larger than our whole budget");
186 }
187#endif
188 }
189 }
190
joshualittb7133be2015-04-08 09:08:31 -0700191 // Budget was chosen to be ~4 megabytes. The min alloc and pre alloc sizes in the pool are
192 // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
193 static const int kPreAllocSize = 1 << 17;
194 static const int kMinGrowthSize = 1 << 17;
joshualitt17d833b2015-08-03 10:17:44 -0700195 static const int kDefaultBudget = 1 << 22;
joshualittb7133be2015-04-08 09:08:31 -0700196 BitmapBlobList fBlobList;
Florin Malita33fdb8d2017-03-07 16:51:57 -0500197 SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
joshualittb7133be2015-04-08 09:08:31 -0700198 GrMemoryPool fPool;
joshualitt0db6dfa2015-04-10 07:01:30 -0700199 PFOverBudgetCB fCallback;
200 void* fData;
joshualitt17d833b2015-08-03 10:17:44 -0700201 size_t fBudget;
joshualittb7133be2015-04-08 09:08:31 -0700202};
203
204#endif