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 <cctype>
11
12 #include "include/core/SkGraphics.h"
13 #include "include/core/SkRefCnt.h"
14 #include "include/core/SkTraceMemoryDump.h"
15 #include "include/core/SkTypeface.h"
16 #include "include/private/SkMutex.h"
17 #include "include/private/SkTemplates.h"
18 #include "src/core/SkGlyphRunPainter.h"
19 #include "src/core/SkScalerCache.h"
20
21 bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false;
22
GlobalStrikeCache()23 SkStrikeCache* SkStrikeCache::GlobalStrikeCache() {
24 #if !defined(SK_BUILD_FOR_IOS)
25 if (gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental) {
26 static thread_local auto* cache = new SkStrikeCache;
27 return cache;
28 }
29 #endif
30 static auto* cache = new SkStrikeCache;
31 return cache;
32 }
33
findOrCreateStrike(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)34 auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc,
35 const SkScalerContextEffects& effects,
36 const SkTypeface& typeface) -> sk_sp<Strike> {
37 SkAutoMutexExclusive ac(fLock);
38 sk_sp<Strike> strike = this->internalFindStrikeOrNull(desc);
39 if (strike == nullptr) {
40 auto scaler = typeface.createScalerContext(effects, &desc);
41 strike = this->internalCreateStrike(desc, std::move(scaler));
42 }
43 this->internalPurge();
44 return strike;
45 }
46
findOrCreateScopedStrike(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)47 SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
48 const SkScalerContextEffects& effects,
49 const SkTypeface& typeface) {
50 return SkScopedStrikeForGPU{this->findOrCreateStrike(desc, effects, typeface).release()};
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 [ %8zu %8zu ]\n",
62 SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());
63
64 int counter = 0;
65
66 auto visitor = [&counter](const Strike& strike) {
67 const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();
68
69 SkDebugf("index %d\n", counter);
70 SkDebugf("%s", rec.dump().c_str());
71 counter += 1;
72 };
73
74 GlobalStrikeCache()->forEachStrike(visitor);
75 }
76
77 namespace {
78 const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache";
79 } // namespace
80
DumpMemoryStatistics(SkTraceMemoryDump * dump)81 void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
82 dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed());
83 dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes",
84 SkGraphics::GetFontCacheLimit());
85 dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects",
86 SkGraphics::GetFontCacheCountUsed());
87 dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects",
88 SkGraphics::GetFontCacheCountLimit());
89
90 if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) {
91 dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr);
92 return;
93 }
94
95 auto visitor = [&dump](const Strike& strike) {
96 const SkTypeface* face = strike.fScalerCache.getScalerContext()->getTypeface();
97 const SkScalerContextRec& rec = strike.fScalerCache.getScalerContext()->getRec();
98
99 SkString fontName;
100 face->getFamilyName(&fontName);
101 // Replace all special characters with '_'.
102 for (size_t index = 0; index < fontName.size(); ++index) {
103 if (!std::isalnum(fontName[index])) {
104 fontName[index] = '_';
105 }
106 }
107
108 SkString dumpName = SkStringPrintf(
109 "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &strike);
110
111 dump->dumpNumericValue(dumpName.c_str(),
112 "size", "bytes", strike.fMemoryUsed);
113 dump->dumpNumericValue(dumpName.c_str(),
114 "glyph_count", "objects",
115 strike.fScalerCache.countCachedGlyphs());
116 dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
117 };
118
119 GlobalStrikeCache()->forEachStrike(visitor);
120 }
121
findStrike(const SkDescriptor & desc)122 sk_sp<SkStrike> SkStrikeCache::findStrike(const SkDescriptor& desc) {
123 SkAutoMutexExclusive ac(fLock);
124 sk_sp<SkStrike> result = this->internalFindStrikeOrNull(desc);
125 this->internalPurge();
126 return result;
127 }
128
internalFindStrikeOrNull(const SkDescriptor & desc)129 auto SkStrikeCache::internalFindStrikeOrNull(const SkDescriptor& desc) -> sk_sp<Strike> {
130
131 // Check head because it is likely the strike we are looking for.
132 if (fHead != nullptr && fHead->getDescriptor() == desc) { return sk_ref_sp(fHead); }
133
134 // Do the heavy search looking for the strike.
135 sk_sp<Strike>* strikeHandle = fStrikeLookup.find(desc);
136 if (strikeHandle == nullptr) { return nullptr; }
137 Strike* strikePtr = strikeHandle->get();
138 SkASSERT(strikePtr != nullptr);
139 if (fHead != strikePtr) {
140 // Make most recently used
141 strikePtr->fPrev->fNext = strikePtr->fNext;
142 if (strikePtr->fNext != nullptr) {
143 strikePtr->fNext->fPrev = strikePtr->fPrev;
144 } else {
145 fTail = strikePtr->fPrev;
146 }
147 fHead->fPrev = strikePtr;
148 strikePtr->fNext = fHead;
149 strikePtr->fPrev = nullptr;
150 fHead = strikePtr;
151 }
152 return sk_ref_sp(strikePtr);
153 }
154
createStrike(const SkDescriptor & desc,std::unique_ptr<SkScalerContext> scaler,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)155 sk_sp<SkStrike> SkStrikeCache::createStrike(
156 const SkDescriptor& desc,
157 std::unique_ptr<SkScalerContext> scaler,
158 SkFontMetrics* maybeMetrics,
159 std::unique_ptr<SkStrikePinner> pinner) {
160 SkAutoMutexExclusive ac(fLock);
161 return this->internalCreateStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner));
162 }
163
internalCreateStrike(const SkDescriptor & desc,std::unique_ptr<SkScalerContext> scaler,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)164 auto SkStrikeCache::internalCreateStrike(
165 const SkDescriptor& desc,
166 std::unique_ptr<SkScalerContext> scaler,
167 SkFontMetrics* maybeMetrics,
168 std::unique_ptr<SkStrikePinner> pinner) -> sk_sp<Strike> {
169 auto strike =
170 sk_make_sp<Strike>(this, desc, std::move(scaler), maybeMetrics, std::move(pinner));
171 this->internalAttachToHead(strike);
172 return strike;
173 }
174
purgeAll()175 void SkStrikeCache::purgeAll() {
176 SkAutoMutexExclusive ac(fLock);
177 this->internalPurge(fTotalMemoryUsed);
178 }
179
getTotalMemoryUsed() const180 size_t SkStrikeCache::getTotalMemoryUsed() const {
181 SkAutoMutexExclusive ac(fLock);
182 return fTotalMemoryUsed;
183 }
184
getCacheCountUsed() const185 int SkStrikeCache::getCacheCountUsed() const {
186 SkAutoMutexExclusive ac(fLock);
187 return fCacheCount;
188 }
189
getCacheCountLimit() const190 int SkStrikeCache::getCacheCountLimit() const {
191 SkAutoMutexExclusive ac(fLock);
192 return fCacheCountLimit;
193 }
194
setCacheSizeLimit(size_t newLimit)195 size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
196 SkAutoMutexExclusive ac(fLock);
197
198 size_t prevLimit = fCacheSizeLimit;
199 fCacheSizeLimit = newLimit;
200 this->internalPurge();
201 return prevLimit;
202 }
203
getCacheSizeLimit() const204 size_t SkStrikeCache::getCacheSizeLimit() const {
205 SkAutoMutexExclusive ac(fLock);
206 return fCacheSizeLimit;
207 }
208
setCacheCountLimit(int newCount)209 int SkStrikeCache::setCacheCountLimit(int newCount) {
210 if (newCount < 0) {
211 newCount = 0;
212 }
213
214 SkAutoMutexExclusive ac(fLock);
215
216 int prevCount = fCacheCountLimit;
217 fCacheCountLimit = newCount;
218 this->internalPurge();
219 return prevCount;
220 }
221
forEachStrike(std::function<void (const Strike &)> visitor) const222 void SkStrikeCache::forEachStrike(std::function<void(const Strike&)> visitor) const {
223 SkAutoMutexExclusive ac(fLock);
224
225 this->validate();
226
227 for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
228 visitor(*strike);
229 }
230 }
231
internalPurge(size_t minBytesNeeded)232 size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) {
233 size_t bytesNeeded = 0;
234 if (fTotalMemoryUsed > fCacheSizeLimit) {
235 bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
236 }
237 bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
238 if (bytesNeeded) {
239 // no small purges!
240 bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
241 }
242
243 int countNeeded = 0;
244 if (fCacheCount > fCacheCountLimit) {
245 countNeeded = fCacheCount - fCacheCountLimit;
246 // no small purges!
247 countNeeded = std::max(countNeeded, fCacheCount >> 2);
248 }
249
250 // early exit
251 if (!countNeeded && !bytesNeeded) {
252 return 0;
253 }
254
255 size_t bytesFreed = 0;
256 int countFreed = 0;
257
258 // Start at the tail and proceed backwards deleting; the list is in LRU
259 // order, with unimportant entries at the tail.
260 Strike* strike = fTail;
261 while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
262 Strike* prev = strike->fPrev;
263
264 // Only delete if the strike is not pinned.
265 if (strike->fPinner == nullptr || strike->fPinner->canDelete()) {
266 bytesFreed += strike->fMemoryUsed;
267 countFreed += 1;
268 this->internalRemoveStrike(strike);
269 }
270 strike = prev;
271 }
272
273 this->validate();
274
275 #ifdef SPEW_PURGE_STATUS
276 if (countFreed) {
277 SkDebugf("purging %dK from font cache [%d entries]\n",
278 (int)(bytesFreed >> 10), countFreed);
279 }
280 #endif
281
282 return bytesFreed;
283 }
284
internalAttachToHead(sk_sp<Strike> strike)285 void SkStrikeCache::internalAttachToHead(sk_sp<Strike> strike) {
286 SkASSERT(fStrikeLookup.find(strike->getDescriptor()) == nullptr);
287 Strike* strikePtr = strike.get();
288 fStrikeLookup.set(std::move(strike));
289 SkASSERT(nullptr == strikePtr->fPrev && nullptr == strikePtr->fNext);
290
291 fCacheCount += 1;
292 fTotalMemoryUsed += strikePtr->fMemoryUsed;
293
294 if (fHead != nullptr) {
295 fHead->fPrev = strikePtr;
296 strikePtr->fNext = fHead;
297 }
298
299 if (fTail == nullptr) {
300 fTail = strikePtr;
301 }
302
303 fHead = strikePtr; // Transfer ownership of strike to the cache list.
304 }
305
internalRemoveStrike(Strike * strike)306 void SkStrikeCache::internalRemoveStrike(Strike* strike) {
307 SkASSERT(fCacheCount > 0);
308 fCacheCount -= 1;
309 fTotalMemoryUsed -= strike->fMemoryUsed;
310
311 if (strike->fPrev) {
312 strike->fPrev->fNext = strike->fNext;
313 } else {
314 fHead = strike->fNext;
315 }
316 if (strike->fNext) {
317 strike->fNext->fPrev = strike->fPrev;
318 } else {
319 fTail = strike->fPrev;
320 }
321
322 strike->fPrev = strike->fNext = nullptr;
323 strike->fRemoved = true;
324 fStrikeLookup.remove(strike->getDescriptor());
325 }
326
validate() const327 void SkStrikeCache::validate() const {
328 #ifdef SK_DEBUG
329 size_t computedBytes = 0;
330 int computedCount = 0;
331
332 const Strike* strike = fHead;
333 while (strike != nullptr) {
334 computedBytes += strike->fMemoryUsed;
335 computedCount += 1;
336 SkASSERT(fStrikeLookup.findOrNull(strike->getDescriptor()) != nullptr);
337 strike = strike->fNext;
338 }
339
340 if (fCacheCount != computedCount) {
341 SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
342 SK_ABORT("fCacheCount != computedCount");
343 }
344 if (fTotalMemoryUsed != computedBytes) {
345 SkDebugf("fTotalMemoryUsed: %d, computedBytes: %d", fTotalMemoryUsed, computedBytes);
346 SK_ABORT("fTotalMemoryUsed == computedBytes");
347 }
348 #endif
349 }
350
updateDelta(size_t increase)351 void SkStrikeCache::Strike::updateDelta(size_t increase) {
352 if (increase != 0) {
353 SkAutoMutexExclusive lock{fStrikeCache->fLock};
354 fMemoryUsed += increase;
355 if (!fRemoved) {
356 fStrikeCache->fTotalMemoryUsed += increase;
357 }
358 }
359 }
360