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