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 | |
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 | |
34 | auto 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 | |
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 | |
53 | void SkStrikeCache::PurgeAll() { |
54 | GlobalStrikeCache()->purgeAll(); |
55 | } |
56 | |
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 | |
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 | |
122 | sk_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 | |
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 | |
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 | SkAutoSpinlock ac(fLock); |
161 | return this->internalCreateStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner)); |
162 | } |
163 | |
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 | |
175 | void SkStrikeCache::purgeAll() { |
176 | SkAutoSpinlock ac(fLock); |
177 | this->internalPurge(fTotalMemoryUsed); |
178 | } |
179 | |
180 | size_t SkStrikeCache::getTotalMemoryUsed() const { |
181 | SkAutoSpinlock ac(fLock); |
182 | return fTotalMemoryUsed; |
183 | } |
184 | |
185 | int SkStrikeCache::getCacheCountUsed() const { |
186 | SkAutoSpinlock ac(fLock); |
187 | return fCacheCount; |
188 | } |
189 | |
190 | int SkStrikeCache::getCacheCountLimit() const { |
191 | SkAutoSpinlock ac(fLock); |
192 | return fCacheCountLimit; |
193 | } |
194 | |
195 | size_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 | |
204 | size_t SkStrikeCache::getCacheSizeLimit() const { |
205 | SkAutoSpinlock ac(fLock); |
206 | return fCacheSizeLimit; |
207 | } |
208 | |
209 | int 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 | |
222 | int SkStrikeCache::getCachePointSizeLimit() const { |
223 | SkAutoSpinlock ac(fLock); |
224 | return fPointSizeLimit; |
225 | } |
226 | |
227 | int 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 | |
239 | void 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 | |
249 | size_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 | |
302 | void 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 | |
323 | void 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 | |
344 | void 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 | |
368 | void 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 | |