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
21bool gSkUseThreadLocalStrikeCaches_IAcknowledgeThisIsIncrediblyExperimental = false;
22
23SkStrikeCache* 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
34auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc,
35 const SkScalerContextEffects& effects,
36 const SkTypeface& typeface) -> sk_sp<Strike> {
37 SkAutoSpinlock 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
47SkScopedStrikeForGPU SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
48 const SkScalerContextEffects& effects,
49 const SkTypeface& typeface) {
50 return SkScopedStrikeForGPU{this->findOrCreateStrike(desc, effects, typeface).release()};
51}
52
53void SkStrikeCache::PurgeAll() {
54 GlobalStrikeCache()->purgeAll();
55}
56
57void 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
77namespace {
78 const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache";
79} // namespace
80
81void 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
122sk_sp<SkStrike> SkStrikeCache::findStrike(const SkDescriptor& desc) {
123 SkAutoSpinlock ac(fLock);
124 sk_sp<SkStrike> result = this->internalFindStrikeOrNull(desc);
125 this->internalPurge();
126 return result;
127}
128
129auto 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
155sk_sp<SkStrike> SkStrikeCache::createStrike(
156 const SkDescriptor& desc,
157 std::unique_ptr<SkScalerContext> scaler,
158 SkFontMetrics* maybeMetrics,
159 std::unique_ptr<SkStrikePinner> pinner) {
160 SkAutoSpinlock ac(fLock);
161 return this->internalCreateStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner));
162}
163
164auto 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
175void SkStrikeCache::purgeAll() {
176 SkAutoSpinlock ac(fLock);
177 this->internalPurge(fTotalMemoryUsed);
178}
179
180size_t SkStrikeCache::getTotalMemoryUsed() const {
181 SkAutoSpinlock ac(fLock);
182 return fTotalMemoryUsed;
183}
184
185int SkStrikeCache::getCacheCountUsed() const {
186 SkAutoSpinlock ac(fLock);
187 return fCacheCount;
188}
189
190int SkStrikeCache::getCacheCountLimit() const {
191 SkAutoSpinlock ac(fLock);
192 return fCacheCountLimit;
193}
194
195size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
196 SkAutoSpinlock ac(fLock);
197
198 size_t prevLimit = fCacheSizeLimit;
199 fCacheSizeLimit = newLimit;
200 this->internalPurge();
201 return prevLimit;
202}
203
204size_t SkStrikeCache::getCacheSizeLimit() const {
205 SkAutoSpinlock ac(fLock);
206 return fCacheSizeLimit;
207}
208
209int SkStrikeCache::setCacheCountLimit(int newCount) {
210 if (newCount < 0) {
211 newCount = 0;
212 }
213
214 SkAutoSpinlock ac(fLock);
215
216 int prevCount = fCacheCountLimit;
217 fCacheCountLimit = newCount;
218 this->internalPurge();
219 return prevCount;
220}
221
222int SkStrikeCache::getCachePointSizeLimit() const {
223 SkAutoSpinlock ac(fLock);
224 return fPointSizeLimit;
225}
226
227int SkStrikeCache::setCachePointSizeLimit(int newLimit) {
228 if (newLimit < 0) {
229 newLimit = 0;
230 }
231
232 SkAutoSpinlock ac(fLock);
233
234 int prevLimit = fPointSizeLimit;
235 fPointSizeLimit = newLimit;
236 return prevLimit;
237}
238
239void SkStrikeCache::forEachStrike(std::function<void(const Strike&)> visitor) const {
240 SkAutoSpinlock ac(fLock);
241
242 this->validate();
243
244 for (Strike* strike = fHead; strike != nullptr; strike = strike->fNext) {
245 visitor(*strike);
246 }
247}
248
249size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) {
250 size_t bytesNeeded = 0;
251 if (fTotalMemoryUsed > fCacheSizeLimit) {
252 bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
253 }
254 bytesNeeded = std::max(bytesNeeded, minBytesNeeded);
255 if (bytesNeeded) {
256 // no small purges!
257 bytesNeeded = std::max(bytesNeeded, fTotalMemoryUsed >> 2);
258 }
259
260 int countNeeded = 0;
261 if (fCacheCount > fCacheCountLimit) {
262 countNeeded = fCacheCount - fCacheCountLimit;
263 // no small purges!
264 countNeeded = std::max(countNeeded, fCacheCount >> 2);
265 }
266
267 // early exit
268 if (!countNeeded && !bytesNeeded) {
269 return 0;
270 }
271
272 size_t bytesFreed = 0;
273 int countFreed = 0;
274
275 // Start at the tail and proceed backwards deleting; the list is in LRU
276 // order, with unimportant entries at the tail.
277 Strike* strike = fTail;
278 while (strike != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
279 Strike* prev = strike->fPrev;
280
281 // Only delete if the strike is not pinned.
282 if (strike->fPinner == nullptr || strike->fPinner->canDelete()) {
283 bytesFreed += strike->fMemoryUsed;
284 countFreed += 1;
285 this->internalRemoveStrike(strike);
286 }
287 strike = prev;
288 }
289
290 this->validate();
291
292#ifdef SPEW_PURGE_STATUS
293 if (countFreed) {
294 SkDebugf("purging %dK from font cache [%d entries]\n",
295 (int)(bytesFreed >> 10), countFreed);
296 }
297#endif
298
299 return bytesFreed;
300}
301
302void SkStrikeCache::internalAttachToHead(sk_sp<Strike> strike) {
303 SkASSERT(fStrikeLookup.find(strike->getDescriptor()) == nullptr);
304 Strike* strikePtr = strike.get();
305 fStrikeLookup.set(std::move(strike));
306 SkASSERT(nullptr == strikePtr->fPrev && nullptr == strikePtr->fNext);
307
308 fCacheCount += 1;
309 fTotalMemoryUsed += strikePtr->fMemoryUsed;
310
311 if (fHead != nullptr) {
312 fHead->fPrev = strikePtr;
313 strikePtr->fNext = fHead;
314 }
315
316 if (fTail == nullptr) {
317 fTail = strikePtr;
318 }
319
320 fHead = strikePtr; // Transfer ownership of strike to the cache list.
321}
322
323void SkStrikeCache::internalRemoveStrike(Strike* strike) {
324 SkASSERT(fCacheCount > 0);
325 fCacheCount -= 1;
326 fTotalMemoryUsed -= strike->fMemoryUsed;
327
328 if (strike->fPrev) {
329 strike->fPrev->fNext = strike->fNext;
330 } else {
331 fHead = strike->fNext;
332 }
333 if (strike->fNext) {
334 strike->fNext->fPrev = strike->fPrev;
335 } else {
336 fTail = strike->fPrev;
337 }
338
339 strike->fPrev = strike->fNext = nullptr;
340 strike->fRemoved = true;
341 fStrikeLookup.remove(strike->getDescriptor());
342}
343
344void SkStrikeCache::validate() const {
345#ifdef SK_DEBUG
346 size_t computedBytes = 0;
347 int computedCount = 0;
348
349 const Strike* strike = fHead;
350 while (strike != nullptr) {
351 computedBytes += strike->fMemoryUsed;
352 computedCount += 1;
353 SkASSERT(fStrikeLookup.findOrNull(strike->getDescriptor()) != nullptr);
354 strike = strike->fNext;
355 }
356
357 if (fCacheCount != computedCount) {
358 SkDebugf("fCacheCount: %d, computedCount: %d", fCacheCount, computedCount);
359 SK_ABORT("fCacheCount != computedCount");
360 }
361 if (fTotalMemoryUsed != computedBytes) {
362 SkDebugf("fTotalMemoryUsed: %d, computedBytes: %d", fTotalMemoryUsed, computedBytes);
363 SK_ABORT("fTotalMemoryUsed == computedBytes");
364 }
365#endif
366}
367
368void SkStrikeCache::Strike::updateDelta(size_t increase) {
369 if (increase != 0) {
370 SkAutoSpinlock lock{fStrikeCache->fLock};
371 fMemoryUsed += increase;
372 if (!fRemoved) {
373 fStrikeCache->fTotalMemoryUsed += increase;
374 }
375 }
376}
377