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 "include/core/SkRefCnt.h"
12#include "include/private/SkTArray.h"
13#include "include/private/SkTHash.h"
14#include "src/core/SkMessageBus.h"
15#include "src/core/SkTextBlobPriv.h"
16#include "src/gpu/text/GrTextBlob.h"
17
18class GrTextBlobCache {
19public:
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 GrTextBlobCache(PFOverBudgetCB cb, void* data, uint32_t uniqueID)
27 : fCallback(cb)
28 , fData(data)
29 , fSizeBudget(kDefaultBudget)
30 , fUniqueID(uniqueID)
31 , fPurgeBlobInbox(uniqueID) {
32 SkASSERT(cb && data);
33 }
34 ~GrTextBlobCache();
35
36 sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList,
37 const SkMatrix& viewMatrix,
38 GrColor color,
39 bool forceW) {
40 return GrTextBlob::Make(glyphRunList, viewMatrix, color, forceW);
41 }
42
43 sk_sp<GrTextBlob> makeCachedBlob(const SkGlyphRunList& glyphRunList,
44 const GrTextBlob::Key& key,
45 const SkMaskFilterBase::BlurRec& blurRec,
46 const SkMatrix& viewMatrix,
47 GrColor color,
48 bool forceW) {
49 sk_sp<GrTextBlob> cacheBlob(this->makeBlob(glyphRunList, viewMatrix, color, forceW));
50 cacheBlob->setupKey(key, blurRec, glyphRunList.paint());
51 this->add(cacheBlob);
52 glyphRunList.temporaryShuntBlobNotifyAddedToCache(fUniqueID);
53 return cacheBlob;
54 }
55
56 sk_sp<GrTextBlob> find(const GrTextBlob::Key& key) const {
57 const auto* idEntry = fBlobIDCache.find(key.fUniqueID);
58 return idEntry ? idEntry->find(key) : nullptr;
59 }
60
61 void remove(GrTextBlob* blob) {
62 auto id = GrTextBlob::GetKey(*blob).fUniqueID;
63 auto* idEntry = fBlobIDCache.find(id);
64 SkASSERT(idEntry);
65
66 fCurrentSize -= blob->size();
67 fBlobList.remove(blob);
68 idEntry->removeBlob(blob);
69 if (idEntry->fBlobs.empty()) {
70 fBlobIDCache.remove(id);
71 }
72 }
73
74 void makeMRU(GrTextBlob* blob) {
75 if (fBlobList.head() == blob) {
76 return;
77 }
78
79 fBlobList.remove(blob);
80 fBlobList.addToHead(blob);
81 }
82
83 void freeAll();
84
85 // TODO move to SkTextBlob
86 static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
87 SkTextBlobRunIterator itCounter(blob);
88 for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
89 *glyphCount += itCounter.glyphCount();
90 }
91 }
92
93 void setBudget(size_t budget) {
94 fSizeBudget = budget;
95 this->checkPurge();
96 }
97
98 struct PurgeBlobMessage {
99 PurgeBlobMessage(uint32_t blobID, uint32_t contextUniqueID)
100 : fBlobID(blobID), fContextID(contextUniqueID) {}
101
102 uint32_t fBlobID;
103 uint32_t fContextID;
104 };
105
106 static void PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID);
107
108 void purgeStaleBlobs();
109
110 size_t usedBytes() const { return fCurrentSize; }
111
112private:
113 using BitmapBlobList = SkTInternalLList<GrTextBlob>;
114
115 struct BlobIDCacheEntry {
116 BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
117 explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
118
119 static uint32_t GetKey(const BlobIDCacheEntry& entry) {
120 return entry.fID;
121 }
122
123 void addBlob(sk_sp<GrTextBlob> blob) {
124 SkASSERT(blob);
125 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID);
126 SkASSERT(!this->find(GrTextBlob::GetKey(*blob)));
127
128 fBlobs.emplace_back(std::move(blob));
129 }
130
131 void removeBlob(GrTextBlob* blob) {
132 SkASSERT(blob);
133 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID);
134
135 auto index = this->findBlobIndex(GrTextBlob::GetKey(*blob));
136 SkASSERT(index >= 0);
137
138 fBlobs.removeShuffle(index);
139 }
140
141 sk_sp<GrTextBlob> find(const GrTextBlob::Key& key) const {
142 auto index = this->findBlobIndex(key);
143 return index < 0 ? nullptr : fBlobs[index];
144 }
145
146 int findBlobIndex(const GrTextBlob::Key& key) const{
147 for (int i = 0; i < fBlobs.count(); ++i) {
148 if (GrTextBlob::GetKey(*fBlobs[i]) == key) {
149 return i;
150 }
151 }
152 return -1;
153 }
154
155 uint32_t fID;
156 // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/
157 // linear search is acceptable. If usage changes, we should re-evaluate this structure.
158 SkSTArray<1, sk_sp<GrTextBlob>> fBlobs;
159 };
160
161 void add(sk_sp<GrTextBlob> blob) {
162 auto id = GrTextBlob::GetKey(*blob).fUniqueID;
163 auto* idEntry = fBlobIDCache.find(id);
164 if (!idEntry) {
165 idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id));
166 }
167
168 // Safe to retain a raw ptr temporarily here, because the cache will hold a ref.
169 GrTextBlob* rawBlobPtr = blob.get();
170 fBlobList.addToHead(rawBlobPtr);
171 fCurrentSize += blob->size();
172 idEntry->addBlob(std::move(blob));
173
174 this->checkPurge(rawBlobPtr);
175 }
176
177 void checkPurge(GrTextBlob* blob = nullptr);
178
179 static const int kMinGrowthSize = 1 << 16;
180 static const int kDefaultBudget = 1 << 22;
181 BitmapBlobList fBlobList;
182 SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
183 PFOverBudgetCB fCallback;
184 void* fData;
185 size_t fSizeBudget;
186 size_t fCurrentSize{0};
187 uint32_t fUniqueID; // unique id to use for messaging
188 SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
189};
190
191#endif
192