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#include "src/gpu/text/GrTextBlobCache.h"
9
10DECLARE_SKMESSAGEBUS_MESSAGE(GrTextBlobCache::PurgeBlobMessage)
11
12// This function is captured by the above macro using implementations from SkMessageBus.h
13static inline bool SkShouldPostMessageToBus(
14 const GrTextBlobCache::PurgeBlobMessage& msg, uint32_t msgBusUniqueID) {
15 return msg.fContextID == msgBusUniqueID;
16}
17
18GrTextBlobCache::GrTextBlobCache(uint32_t messageBusID)
19 : fSizeBudget(kDefaultBudget)
20 , fMessageBusID(messageBusID)
21 , fPurgeBlobInbox(messageBusID) { }
22
23sk_sp<GrTextBlob>
24GrTextBlobCache::makeCachedBlob(const SkGlyphRunList& glyphRunList, const GrTextBlob::Key& key,
25 const SkMaskFilterBase::BlurRec& blurRec,
26 const SkMatrix& viewMatrix) {
27 sk_sp<GrTextBlob> cacheBlob(GrTextBlob::Make(glyphRunList, viewMatrix));
28 cacheBlob->setupKey(key, blurRec, glyphRunList.paint());
29 SkAutoSpinlock lock{fSpinLock};
30 this->internalAdd(cacheBlob);
31 glyphRunList.temporaryShuntBlobNotifyAddedToCache(fMessageBusID);
32 return cacheBlob;
33}
34
35sk_sp<GrTextBlob> GrTextBlobCache::find(const GrTextBlob::Key& key) {
36 SkAutoSpinlock lock{fSpinLock};
37 const BlobIDCacheEntry* idEntry = fBlobIDCache.find(key.fUniqueID);
38 if (idEntry == nullptr) {
39 return nullptr;
40 }
41
42 sk_sp<GrTextBlob> blob = idEntry->find(key);
43 GrTextBlob* blobPtr = blob.get();
44 if (blobPtr != nullptr && blobPtr != fBlobList.head()) {
45 fBlobList.remove(blobPtr);
46 fBlobList.addToHead(blobPtr);
47 }
48 return blob;
49}
50
51void GrTextBlobCache::remove(GrTextBlob* blob) {
52 SkAutoSpinlock lock{fSpinLock};
53 this->internalRemove(blob);
54}
55
56void GrTextBlobCache::internalRemove(GrTextBlob* blob) {
57 auto id = GrTextBlob::GetKey(*blob).fUniqueID;
58 auto* idEntry = fBlobIDCache.find(id);
59 SkASSERT(idEntry);
60
61 fCurrentSize -= blob->size();
62 fBlobList.remove(blob);
63 idEntry->removeBlob(blob);
64 if (idEntry->fBlobs.empty()) {
65 fBlobIDCache.remove(id);
66 }
67}
68
69void GrTextBlobCache::freeAll() {
70 SkAutoSpinlock lock{fSpinLock};
71 fBlobIDCache.reset();
72 fBlobList.reset();
73 fCurrentSize = 0;
74}
75
76void GrTextBlobCache::PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID) {
77 SkASSERT(blobID != SK_InvalidGenID);
78 SkMessageBus<PurgeBlobMessage>::Post(PurgeBlobMessage(blobID, cacheID));
79}
80
81void GrTextBlobCache::purgeStaleBlobs() {
82 SkAutoSpinlock lock{fSpinLock};
83 this->internalPurgeStaleBlobs();
84}
85
86void GrTextBlobCache::internalPurgeStaleBlobs() {
87 SkTArray<PurgeBlobMessage> msgs;
88 fPurgeBlobInbox.poll(&msgs);
89
90 for (const auto& msg : msgs) {
91 auto* idEntry = fBlobIDCache.find(msg.fBlobID);
92 if (!idEntry) {
93 // no cache entries for id
94 continue;
95 }
96
97 // remove all blob entries from the LRU list
98 for (const auto& blob : idEntry->fBlobs) {
99 fCurrentSize -= blob->size();
100 fBlobList.remove(blob.get());
101 }
102
103 // drop the idEntry itself (unrefs all blobs)
104 fBlobIDCache.remove(msg.fBlobID);
105 }
106}
107
108size_t GrTextBlobCache::usedBytes() const {
109 SkAutoSpinlock lock{fSpinLock};
110 return fCurrentSize;
111}
112
113bool GrTextBlobCache::isOverBudget() const {
114 SkAutoSpinlock lock{fSpinLock};
115 return fCurrentSize > fSizeBudget;
116}
117
118void GrTextBlobCache::internalCheckPurge(GrTextBlob* blob) {
119 // First, purge all stale blob IDs.
120 this->internalPurgeStaleBlobs();
121
122 // If we are still over budget, then unref until we are below budget again
123 if (fCurrentSize > fSizeBudget) {
124 TextBlobList::Iter iter;
125 iter.init(fBlobList, TextBlobList::Iter::kTail_IterStart);
126 GrTextBlob* lruBlob = nullptr;
127 while (fCurrentSize > fSizeBudget && (lruBlob = iter.get()) && lruBlob != blob) {
128 // Backup the iterator before removing and unrefing the blob
129 iter.prev();
130
131 this->internalRemove(lruBlob);
132 }
133
134 #ifdef SPEW_BUDGET_MESSAGE
135 if (fCurrentSize > fSizeBudget) {
136 SkDebugf("Single textblob is larger than our whole budget");
137 }
138 #endif
139 }
140}
141
142void GrTextBlobCache::internalAdd(sk_sp<GrTextBlob> blob) {
143 auto id = GrTextBlob::GetKey(*blob).fUniqueID;
144 auto* idEntry = fBlobIDCache.find(id);
145 if (!idEntry) {
146 idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id));
147 }
148
149 // Safe to retain a raw ptr temporarily here, because the cache will hold a ref.
150 GrTextBlob* rawBlobPtr = blob.get();
151 fBlobList.addToHead(rawBlobPtr);
152 fCurrentSize += blob->size();
153 idEntry->addBlob(std::move(blob));
154
155 this->internalCheckPurge(rawBlobPtr);
156}
157
158GrTextBlobCache::BlobIDCacheEntry::BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
159
160GrTextBlobCache::BlobIDCacheEntry::BlobIDCacheEntry(uint32_t id) : fID(id) {}
161
162uint32_t GrTextBlobCache::BlobIDCacheEntry::GetKey(const GrTextBlobCache::BlobIDCacheEntry& entry) {
163 return entry.fID;
164}
165
166void GrTextBlobCache::BlobIDCacheEntry::addBlob(sk_sp<GrTextBlob> blob) {
167 SkASSERT(blob);
168 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID);
169 SkASSERT(!this->find(GrTextBlob::GetKey(*blob)));
170
171 fBlobs.emplace_back(std::move(blob));
172}
173
174void GrTextBlobCache::BlobIDCacheEntry::removeBlob(GrTextBlob* blob) {
175 SkASSERT(blob);
176 SkASSERT(GrTextBlob::GetKey(*blob).fUniqueID == fID);
177
178 auto index = this->findBlobIndex(GrTextBlob::GetKey(*blob));
179 SkASSERT(index >= 0);
180
181 fBlobs.removeShuffle(index);
182}
183
184sk_sp<GrTextBlob> GrTextBlobCache::BlobIDCacheEntry::find(const GrTextBlob::Key& key) const {
185 auto index = this->findBlobIndex(key);
186 return index < 0 ? nullptr : fBlobs[index];
187}
188
189int GrTextBlobCache::BlobIDCacheEntry::findBlobIndex(const GrTextBlob::Key& key) const {
190 for (int i = 0; i < fBlobs.count(); ++i) {
191 if (GrTextBlob::GetKey(*fBlobs[i]) == key) {
192 return i;
193 }
194 }
195 return -1;
196}
197