1 /*
2 * Copyright 2018 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/core/SkStrikeCache.h"
9
10 #include "include/core/SkGraphics.h"
11 #include "include/core/SkRefCnt.h"
12 #include "include/core/SkTraceMemoryDump.h"
13 #include "include/private/base/SkAssert.h"
14 #include "include/private/base/SkDebug.h"
15 #include "include/private/base/SkMutex.h"
16 #include "src/core/SkDescriptor.h"
17 #include "src/core/SkStrike.h"
18 #include "src/core/SkStrikeSpec.h"
19
20 #include <algorithm>
21 #include <utility>
22
23 class SkScalerContext;
24 struct SkFontMetrics;
25
26 using namespace sktext;
27
28 bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false;
29
GlobalStrikeCache()30 SkStrikeCache* SkStrikeCache::GlobalStrikeCache() {
31 if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) {
32 static thread_local auto* cache = new SkStrikeCache;
33 return cache;
34 }
35 static auto* cache = new SkStrikeCache;
36 return cache;
37 }
38
findOrCreateStrike(const SkStrikeSpec & strikeSpec)39 auto SkStrikeCache::findOrCreateStrike(const SkStrikeSpec& strikeSpec) -> sk_sp<SkStrike> {
40 SkAutoMutexExclusive ac(fLock);
41 sk_sp<SkStrike> strike = this->internalFindStrikeOrNull(strikeSpec.descriptor());
42 if (strike == nullptr) {
43 strike = this->internalCreateStrike(strikeSpec);
44 }
45 this->internalPurge();
46 return strike;
47 }
48
findOrCreateScopedStrike(const SkStrikeSpec & strikeSpec)49 sk_sp<StrikeForGPU> SkStrikeCache::findOrCreateScopedStrike(const SkStrikeSpec& strikeSpec) {
50 return this->findOrCreateStrike(strikeSpec);
51 }
52
PurgeAll()53 void SkStrikeCache::PurgeAll() {
54 GlobalStrikeCache()->purgeAll();
55 }
56
Dump()57 void SkStrikeCache::Dump() {
58 SkDebugf("GlyphCache [ used budget ]\n");
59 SkDebugf(" bytes [ %8zu %8zu ]\n",
60 SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit());
61 SkDebugf(" count [ %8d %8d ]\n",
62 SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());
63
64 auto visitor = [](const SkStrike& strike) {
65 strike.dump();
66 };
67
68 GlobalStrikeCache()->forEachStrike(visitor);
69 }
70
DumpMemoryStatistics(SkTraceMemoryDump * dump)71 void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
72 dump->dumpNumericValue(kGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed());
73 dump->dumpNumericValue(kGlyphCacheDumpName, "budget_size", "bytes",
74 SkGraphics::GetFontCacheLimit());
75 dump->dumpNumericValue(kGlyphCacheDumpName, "glyph_count", "objects",
76 SkGraphics::GetFontCacheCountUsed());
77 dump->dumpNumericValue(kGlyphCacheDumpName, "budget_glyph_count", "objects",
78 SkGraphics::GetFontCacheCountLimit());
79
80 if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) {
81 dump->setMemoryBacking(kGlyphCacheDumpName, "malloc", nullptr);
82 return;
83 }
84
85 auto visitor = [&](const SkStrike& strike) {
86 strike.dumpMemoryStatistics(dump);
87 };
88
89 GlobalStrikeCache()->forEachStrike(visitor);
90 }
91
findStrike(const SkDescriptor & desc)92 sk_sp<SkStrike> SkStrikeCache::findStrike(const SkDescriptor& desc) {
93 SkAutoMutexExclusive ac(fLock);
94 sk_sp<SkStrike> result = this->internalFindStrikeOrNull(desc);
95 this->internalPurge();
96 return result;
97 }
98
internalFindStrikeOrNull(const SkDescriptor & desc)99 auto SkStrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) -> sk_sp<SkStrike> {
100
101 // Check head because it is likely the strike we are looking for.
102 if (fHead != nullptr && fHead->getDescriptor() == desc) { return sk_ref_sp(fHead); }
103
104 // Do the heavy search looking for the strike.
105 sk_sp<SkStrike>* strikeHandle = fStrikeLookup.find(desc);
106 if (strikeHandle == nullptr) { return nullptr; }
107 SkStrike* strikePtr = strikeHandle->get();
108 SkASSERT(strikePtr != nullptr);
109 if (fHead != strikePtr) {
110 // Make most recently used
111 strikePtr->fPrev->fNext = strikePtr->fNext;
112 if (strikePtr->fNext != nullptr) {
113 strikePtr->fNext->fPrev = strikePtr->fPrev;
114 } else {
115 fTail = strikePtr->fPrev;
116 }
117 fHead->fPrev = strikePtr;
118 strikePtr->fNext = fHead;
119 strikePtr->fPrev = nullptr;
120 fHead = strikePtr;
121 }
122 return sk_ref_sp(strikePtr);
123 }
124
createStrike(const SkStrikeSpec & strikeSpec,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)125 sk_sp<SkStrike> SkStrikeCache::createStrike(
126 const SkStrikeSpec& strikeSpec,
127 SkFontMetrics* maybeMetrics,
128 std::unique_ptr<SkStrikePinner> pinner) {
129 SkAutoMutexExclusive ac(fLock);
130 return this->internalCreateStrike(strikeSpec, maybeMetrics, std::move(pinner));
131 }
132
internalCreateStrike(const SkStrikeSpec & strikeSpec,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)133 auto SkStrikeCache::internalCreateStrike(
134 const SkStrikeSpec& strikeSpec,
135 SkFontMetrics* maybeMetrics,
136 std::unique_ptr<SkStrikePinner> pinner) -> sk_sp<SkStrike> {
137 std::unique_ptr<SkScalerContext> scaler = strikeSpec.createScalerContext();
138 auto strike =
139 sk_make_sp<SkStrike>(this, strikeSpec, std::move(scaler), maybeMetrics, std::move(pinner));
140 this->internalAttachToHead(strike);
141 return strike;
142 }
143
purgePinned(size_t minBytesNeeded)144 void SkStrikeCache::purgePinned(size_t minBytesNeeded) {
145 SkAutoMutexExclusive ac(fLock);
146 this->internalPurge(minBytesNeeded, /* checkPinners= */ true);
147 }
148
purgeAll()149 void SkStrikeCache::purgeAll() {
150 SkAutoMutexExclusive ac(fLock);
151 this->internalPurge(fTotalMemoryUsed, /* checkPinners= */ true);
152 }
153
getTotalMemoryUsed() const154 size_t SkStrikeCache::getTotalMemoryUsed() const {
155 SkAutoMutexExclusive ac(fLock);
156 return fTotalMemoryUsed;
157 }
158
getCacheCountUsed() const159 int SkStrikeCache::getCacheCountUsed() const {
160 SkAutoMutexExclusive ac(fLock);
161 return fCacheCount;
162 }
163
getCacheCountLimit() const164 int SkStrikeCache::getCacheCountLimit() const {
165 SkAutoMutexExclusive ac(fLock);
166 return fCacheCountLimit;
167 }
168
setCacheSizeLimit(size_t newLimit)169 size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
170 SkAutoMutexExclusive ac(fLock);
171
172 size_t prevLimit = fCacheSizeLimit;
173 fCacheSizeLimit = newLimit;
174 this->internalPurge();
175 return prevLimit;
176 }
177
getCacheSizeLimit() const178 size_t SkStrikeCache::getCacheSizeLimit() const {
179 SkAutoMutexExclusive ac(fLock);
180 return fCacheSizeLimit;
181 }
182
setCacheCountLimit(int newCount)183 int SkStrikeCache::setCacheCountLimit(int newCount) {
184 if (newCount < 0) {
185 newCount = 0;
186 }
187
188 SkAutoMutexExclusive ac(fLock);
189
190 int prevCount = fCacheCountLimit;
191 fCacheCountLimit = newCount;
192 this->internalPurge();
193 return prevCount;
194 }
195
forEachStrike(std::function<void (const SkStrike &)> visitor) const196 void SkStrikeCache::forEachStrike(std::function<void(const SkStrike&)> visitor) const {
197 SkAutoMutexExclusive ac(fLock);
198
199 this->validate();
200
201 for (SkStrike* strike = fHead; strike != nullptr; strike = strike->fNext) {
202 visitor(*strike);
203 }
204 }
205
internalPurge(size_t minBytesNeeded,bool checkPinners)206 size_t SkStrikeCache::internalPurge(size_t minBytesNeeded, bool checkPinners) {
207 #ifndef SK_STRIKE_CACHE_DOESNT_AUTO_CHECK_PINNERS
208 // Temporarily default to checking pinners, for staging.
209 checkPinners = true;
210 #endif
211
212 if (fPinnerCount == fCacheCount && !checkPinners)
213 return 0;
214
215 size_t bytesNeeded = 0;
216 if (fTotalMemoryUsed > fCacheSizeLimit) {
217 bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
218 }
219 bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
220 if (bytesNeeded) {
221 // no small purges!
222 bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
223 }
224
225 int countNeeded = 0;
226 if (fCacheCount > fCacheCountLimit) {
227 countNeeded = fCacheCount - fCacheCountLimit;
228 // no small purges!
229 countNeeded = std::max(countNeeded, fCacheCount >> 2);
230 }
231
232 // early exit
233 if (!countNeeded && !bytesNeeded) {
234 return 0;
235 }
236
237 size_t bytesFreed = 0;
238 int countFreed = 0;
239
240 // Start at the tail and proceed backwards deleting; the list is in LRU
241 // order, with unimportant entries at the tail.
242 SkStrike* strike = fTail;
243 while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
244 SkStrike* prev = strike->fPrev;
245
246 // Only delete if the strike is not pinned.
247 if (strike->fPinner == nullptr || (checkPinners && strike->fPinner->canDelete())) {
248 bytesFreed += strike->fMemoryUsed;
249 countFreed += 1;
250 this->internalRemoveStrike(strike);
251 }
252 strike = prev;
253 }
254
255 this->validate();
256
257 #ifdef SPEW_PURGE_STATUS
258 if (countFreed) {
259 SkDebugf("purging %dK from font cache [%d entries]\n",
260 (int)(bytesFreed >> 10), countFreed);
261 }
262 #endif
263
264 return bytesFreed;
265 }
266
internalAttachToHead(sk_sp<SkStrike> strike)267 void SkStrikeCache::internalAttachToHead(sk_sp<SkStrike> strike) {
268 SkASSERT(fStrikeLookup.find(strike->getDescriptor()) == nullptr);
269 SkStrike* strikePtr = strike.get();
270 fStrikeLookup.set(std::move(strike));
271 SkASSERT(nullptr == strikePtr->fPrev && nullptr == strikePtr->fNext);
272
273 fCacheCount += 1;
274 fPinnerCount += strikePtr->fPinner != nullptr ? 1 : 0;
275 fTotalMemoryUsed += strikePtr->fMemoryUsed;
276
277 if (fHead != nullptr) {
278 fHead->fPrev = strikePtr;
279 strikePtr->fNext = fHead;
280 }
281
282 if (fTail == nullptr) {
283 fTail = strikePtr;
284 }
285
286 fHead = strikePtr; // Transfer ownership of strike to the cache list.
287 }
288
internalRemoveStrike(SkStrike * strike)289 void SkStrikeCache::internalRemoveStrike(SkStrike* strike) {
290 SkASSERT(fCacheCount > 0);
291 fCacheCount -= 1;
292 fPinnerCount -= strike->fPinner != nullptr ? 1 : 0;
293 fTotalMemoryUsed -= strike->fMemoryUsed;
294
295 if (strike->fPrev) {
296 strike->fPrev->fNext = strike->fNext;
297 } else {
298 fHead = strike->fNext;
299 }
300 if (strike->fNext) {
301 strike->fNext->fPrev = strike->fPrev;
302 } else {
303 fTail = strike->fPrev;
304 }
305
306 strike->fPrev = strike->fNext = nullptr;
307 strike->fRemoved = true;
308 fStrikeLookup.remove(strike->getDescriptor());
309 }
310
validate() const311 void SkStrikeCache::validate() const {
312 #ifdef SK_DEBUG
313 size_t computedBytes = 0;
314 int computedCount = 0;
315
316 const SkStrike* strike = fHead;
317 while (strike != nullptr) {
318 computedBytes += strike->fMemoryUsed;
319 computedCount += 1;
320 SkASSERT(fStrikeLookup.findOrNull(strike->getDescriptor()) != nullptr);
321 strike = strike->fNext;
322 }
323
324 if (fCacheCount != computedCount) {
325 SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
326 SK_ABORT("fCacheCount != computedCount");
327 }
328 if (fTotalMemoryUsed != computedBytes) {
329 SkDebugf("fTotalMemoryUsed: %zu, computedBytes: %zu", fTotalMemoryUsed, computedBytes);
330 SK_ABORT("fTotalMemoryUsed == computedBytes");
331 }
332 #endif
333 }
334
GetKey(const sk_sp<SkStrike> & strike)335 const SkDescriptor& SkStrikeCache::StrikeTraits::GetKey(const sk_sp<SkStrike>& strike) {
336 return strike->getDescriptor();
337 }
338
Hash(const SkDescriptor & descriptor)339 uint32_t SkStrikeCache::StrikeTraits::Hash(const SkDescriptor& descriptor) {
340 return descriptor.getChecksum();
341 }
342
343
344