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