• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/SkTraceMemoryDump.h"
14 #include "include/core/SkTypeface.h"
15 #include "include/private/SkMutex.h"
16 #include "include/private/SkTemplates.h"
17 #include "src/core/SkGlyphRunPainter.h"
18 #include "src/core/SkStrike.h"
19 
20 class SkStrikeCache::Node final : public SkStrikeInterface {
21 public:
Node(SkStrikeCache * strikeCache,const SkDescriptor & desc,std::unique_ptr<SkScalerContext> scaler,const SkFontMetrics & metrics,std::unique_ptr<SkStrikePinner> pinner)22     Node(SkStrikeCache* strikeCache,
23          const SkDescriptor& desc,
24          std::unique_ptr<SkScalerContext> scaler,
25          const SkFontMetrics& metrics,
26          std::unique_ptr<SkStrikePinner> pinner)
27             : fStrikeCache{strikeCache}
28             , fStrike{desc, std::move(scaler), metrics}
29             , fPinner{std::move(pinner)} {}
30 
rounding() const31     SkVector rounding() const override {
32         return fStrike.rounding();
33     }
34 
subpixelMask() const35     SkIPoint subpixelMask() const override {
36         return fStrike.subpixelMask();
37     }
38 
39     SkSpan<const SkGlyphPos>
prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[],const SkPoint positions[],size_t n,int maxDimension,PreparationDetail detail,SkGlyphPos results[])40     prepareForDrawingRemoveEmpty(const SkPackedGlyphID packedGlyphIDs[],
41                                  const SkPoint positions[],
42                                  size_t n,
43                                  int maxDimension,
44                                  PreparationDetail detail,
45                                  SkGlyphPos results[]) override {
46         return fStrike.prepareForDrawingRemoveEmpty(packedGlyphIDs,
47                                                     positions,
48                                                     n,
49                                                     maxDimension,
50                                                     detail,
51                                                     results);
52     }
53 
getDescriptor() const54     const SkDescriptor& getDescriptor() const override {
55         return fStrike.getDescriptor();
56     }
57 
onAboutToExitScope()58     void onAboutToExitScope() override {
59         fStrikeCache->attachNode(this);
60     }
61 
62     SkStrikeCache* const            fStrikeCache;
63     Node*                           fNext{nullptr};
64     Node*                           fPrev{nullptr};
65     SkStrike                        fStrike;
66     std::unique_ptr<SkStrikePinner> fPinner;
67 };
68 
GlobalStrikeCache()69 SkStrikeCache* SkStrikeCache::GlobalStrikeCache() {
70     static auto* cache = new SkStrikeCache;
71     return cache;
72 }
73 
ExclusiveStrikePtr(SkStrikeCache::Node * node)74 SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(SkStrikeCache::Node* node)
75     : fNode{node} {}
76 
ExclusiveStrikePtr()77 SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr()
78     : fNode{nullptr} {}
79 
ExclusiveStrikePtr(ExclusiveStrikePtr && o)80 SkStrikeCache::ExclusiveStrikePtr::ExclusiveStrikePtr(ExclusiveStrikePtr&& o)
81     : fNode{o.fNode} {
82     o.fNode = nullptr;
83 }
84 
85 SkStrikeCache::ExclusiveStrikePtr&
operator =(ExclusiveStrikePtr && o)86 SkStrikeCache::ExclusiveStrikePtr::operator = (ExclusiveStrikePtr&& o) {
87     if (fNode != nullptr) {
88         fNode->fStrikeCache->attachNode(fNode);
89     }
90     fNode = o.fNode;
91     o.fNode = nullptr;
92     return *this;
93 }
94 
~ExclusiveStrikePtr()95 SkStrikeCache::ExclusiveStrikePtr::~ExclusiveStrikePtr() {
96     if (fNode != nullptr) {
97         fNode->fStrikeCache->attachNode(fNode);
98     }
99 }
100 
get() const101 SkStrike* SkStrikeCache::ExclusiveStrikePtr::get() const {
102     return &fNode->fStrike;
103 }
104 
operator ->() const105 SkStrike* SkStrikeCache::ExclusiveStrikePtr::operator -> () const {
106     return this->get();
107 }
108 
operator *() const109 SkStrike& SkStrikeCache::ExclusiveStrikePtr::operator *  () const {
110     return *this->get();
111 }
112 
operator bool() const113 SkStrikeCache::ExclusiveStrikePtr::operator bool () const {
114     return fNode != nullptr;
115 }
116 
operator ==(const SkStrikeCache::ExclusiveStrikePtr & lhs,const SkStrikeCache::ExclusiveStrikePtr & rhs)117 bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs,
118                   const SkStrikeCache::ExclusiveStrikePtr& rhs) {
119     return lhs.fNode == rhs.fNode;
120 }
121 
operator ==(const SkStrikeCache::ExclusiveStrikePtr & lhs,decltype(nullptr) )122 bool operator == (const SkStrikeCache::ExclusiveStrikePtr& lhs, decltype(nullptr)) {
123     return lhs.fNode == nullptr;
124 }
125 
operator ==(decltype(nullptr) ,const SkStrikeCache::ExclusiveStrikePtr & rhs)126 bool operator == (decltype(nullptr), const SkStrikeCache::ExclusiveStrikePtr& rhs) {
127     return nullptr == rhs.fNode;
128 }
129 
~SkStrikeCache()130 SkStrikeCache::~SkStrikeCache() {
131     Node* node = fHead;
132     while (node) {
133         Node* next = node->fNext;
134         delete node;
135         node = next;
136     }
137 }
138 
CreateScalerContext(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)139 std::unique_ptr<SkScalerContext> SkStrikeCache::CreateScalerContext(
140         const SkDescriptor& desc,
141         const SkScalerContextEffects& effects,
142         const SkTypeface& typeface) {
143     auto scaler = typeface.createScalerContext(effects, &desc, true /* can fail */);
144 
145     // Check if we can create a scaler-context before creating the glyphcache.
146     // If not, we may have exhausted OS/font resources, so try purging the
147     // cache once and try again
148     // pass true the first time, to notice if the scalercontext failed,
149     if (scaler == nullptr) {
150         PurgeAll();
151         scaler = typeface.createScalerContext(effects, &desc, false /* must succeed */);
152     }
153     return scaler;
154 }
155 
findOrCreateStrikeExclusive(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)156 SkExclusiveStrikePtr SkStrikeCache::findOrCreateStrikeExclusive(
157         const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface)
158 {
159     return SkExclusiveStrikePtr(this->findOrCreateStrike(desc, effects, typeface));
160 }
161 
findOrCreateStrike(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)162 auto SkStrikeCache::findOrCreateStrike(const SkDescriptor& desc,
163                                        const SkScalerContextEffects& effects,
164                                        const SkTypeface& typeface) -> Node* {
165     Node* node = this->findAndDetachStrike(desc);
166     if (node == nullptr) {
167         auto scaler = CreateScalerContext(desc, effects, typeface);
168         node = this->createStrike(desc, std::move(scaler));
169     }
170     return node;
171 }
172 
findOrCreateScopedStrike(const SkDescriptor & desc,const SkScalerContextEffects & effects,const SkTypeface & typeface)173 SkScopedStrike SkStrikeCache::findOrCreateScopedStrike(const SkDescriptor& desc,
174                                                        const SkScalerContextEffects& effects,
175                                                        const SkTypeface& typeface) {
176     return SkScopedStrike{this->findOrCreateStrike(desc, effects, typeface)};
177 }
178 
PurgeAll()179 void SkStrikeCache::PurgeAll() {
180     GlobalStrikeCache()->purgeAll();
181 }
182 
Dump()183 void SkStrikeCache::Dump() {
184     SkDebugf("GlyphCache [     used    budget ]\n");
185     SkDebugf("    bytes  [ %8zu  %8zu ]\n",
186              SkGraphics::GetFontCacheUsed(), SkGraphics::GetFontCacheLimit());
187     SkDebugf("    count  [ %8zu  %8zu ]\n",
188              SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());
189 
190     int counter = 0;
191 
192     auto visitor = [&counter](const SkStrike& cache) {
193         const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
194 
195         SkDebugf("index %d\n", counter);
196         SkDebugf("%s", rec.dump().c_str());
197         counter += 1;
198     };
199 
200     GlobalStrikeCache()->forEachStrike(visitor);
201 }
202 
203 namespace {
204     const char gGlyphCacheDumpName[] = "skia/sk_glyph_cache";
205 }  // namespace
206 
DumpMemoryStatistics(SkTraceMemoryDump * dump)207 void SkStrikeCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
208     dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed());
209     dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes",
210                            SkGraphics::GetFontCacheLimit());
211     dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects",
212                            SkGraphics::GetFontCacheCountUsed());
213     dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects",
214                            SkGraphics::GetFontCacheCountLimit());
215 
216     if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) {
217         dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr);
218         return;
219     }
220 
221     auto visitor = [&dump](const SkStrike& cache) {
222         const SkTypeface* face = cache.getScalerContext()->getTypeface();
223         const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
224 
225         SkString fontName;
226         face->getFamilyName(&fontName);
227         // Replace all special characters with '_'.
228         for (size_t index = 0; index < fontName.size(); ++index) {
229             if (!std::isalnum(fontName[index])) {
230                 fontName[index] = '_';
231             }
232         }
233 
234         SkString dumpName = SkStringPrintf(
235                 "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache);
236 
237         dump->dumpNumericValue(dumpName.c_str(),
238                                "size", "bytes", cache.getMemoryUsed());
239         dump->dumpNumericValue(dumpName.c_str(),
240                                "glyph_count", "objects", cache.countCachedGlyphs());
241         dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
242     };
243 
244     GlobalStrikeCache()->forEachStrike(visitor);
245 }
246 
247 
attachNode(Node * node)248 void SkStrikeCache::attachNode(Node* node) {
249     if (node == nullptr) {
250         return;
251     }
252     SkAutoSpinlock ac(fLock);
253 
254     this->validate();
255     node->fStrike.validate();
256 
257     this->internalAttachToHead(node);
258     this->internalPurge();
259 }
260 
findStrikeExclusive(const SkDescriptor & desc)261 SkExclusiveStrikePtr SkStrikeCache::findStrikeExclusive(const SkDescriptor& desc) {
262     return SkExclusiveStrikePtr(this->findAndDetachStrike(desc));
263 }
264 
findAndDetachStrike(const SkDescriptor & desc)265 auto SkStrikeCache::findAndDetachStrike(const SkDescriptor& desc) -> Node* {
266     SkAutoSpinlock ac(fLock);
267 
268     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
269         if (node->fStrike.getDescriptor() == desc) {
270             this->internalDetachCache(node);
271             return node;
272         }
273     }
274 
275     return nullptr;
276 }
277 
278 
loose_compare(const SkDescriptor & lhs,const SkDescriptor & rhs)279 static bool loose_compare(const SkDescriptor& lhs, const SkDescriptor& rhs) {
280     uint32_t size;
281     auto ptr = lhs.findEntry(kRec_SkDescriptorTag, &size);
282     SkScalerContextRec lhsRec;
283     std::memcpy(&lhsRec, ptr, size);
284 
285     ptr = rhs.findEntry(kRec_SkDescriptorTag, &size);
286     SkScalerContextRec rhsRec;
287     std::memcpy(&rhsRec, ptr, size);
288 
289     // If these don't match, there's no way we can use these strikes interchangeably.
290     // Note that a typeface from each renderer maps to a unique proxy typeface on the GPU,
291     // keyed in the glyph cache using fontID in the SkDescriptor. By limiting this search
292     // to descriptors with the same fontID, we ensure that a renderer never uses glyphs
293     // generated by a different renderer.
294     return
295         lhsRec.fFontID == rhsRec.fFontID &&
296         lhsRec.fTextSize == rhsRec.fTextSize &&
297         lhsRec.fPreScaleX == rhsRec.fPreScaleX &&
298         lhsRec.fPreSkewX == rhsRec.fPreSkewX &&
299         lhsRec.fPost2x2[0][0] == rhsRec.fPost2x2[0][0] &&
300         lhsRec.fPost2x2[0][1] == rhsRec.fPost2x2[0][1] &&
301         lhsRec.fPost2x2[1][0] == rhsRec.fPost2x2[1][0] &&
302         lhsRec.fPost2x2[1][1] == rhsRec.fPost2x2[1][1];
303 }
304 
desperationSearchForImage(const SkDescriptor & desc,SkGlyph * glyph,SkStrike * targetCache)305 bool SkStrikeCache::desperationSearchForImage(const SkDescriptor& desc, SkGlyph* glyph,
306                                               SkStrike* targetCache) {
307     SkAutoSpinlock ac(fLock);
308 
309     SkGlyphID glyphID = glyph->getGlyphID();
310     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
311         if (loose_compare(node->fStrike.getDescriptor(), desc)) {
312             if (SkGlyph *fallback = node->fStrike.glyphOrNull(glyph->getPackedID())) {
313                 // This desperate-match node may disappear as soon as we drop fLock, so we
314                 // need to copy the glyph from node into this strike, including a
315                 // deep copy of the mask.
316                 targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback);
317                 return true;
318             }
319 
320             // Look for any sub-pixel pos for this glyph, in case there is a pos mismatch.
321             if (const auto* fallback = node->fStrike.getCachedGlyphAnySubPix(glyphID)) {
322                 targetCache->mergeGlyphAndImage(glyph->getPackedID(), *fallback);
323                 return true;
324             }
325         }
326     }
327 
328     return false;
329 }
330 
desperationSearchForPath(const SkDescriptor & desc,SkGlyphID glyphID,SkPath * path)331 bool SkStrikeCache::desperationSearchForPath(
332         const SkDescriptor& desc, SkGlyphID glyphID, SkPath* path) {
333     SkAutoSpinlock ac(fLock);
334 
335     // The following is wrong there is subpixel positioning with paths...
336     // Paths are only ever at sub-pixel position (0,0), so we can just try that directly rather
337     // than try our packed position first then search all others on failure like for masks.
338     //
339     // This will have to search the sub-pixel positions too.
340     // There is also a problem with accounting for cache size with shared path data.
341     for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
342         if (loose_compare(node->fStrike.getDescriptor(), desc)) {
343             if (SkGlyph *from = node->fStrike.glyphOrNull(SkPackedGlyphID{glyphID})) {
344                 if (from->setPathHasBeenCalled() && from->path() != nullptr) {
345                     // We can just copy the path out by value here, so no need to worry
346                     // about the lifetime of this desperate-match node.
347                     *path = *from->path();
348                     return true;
349                 }
350             }
351         }
352     }
353     return false;
354 }
355 
createStrikeExclusive(const SkDescriptor & desc,std::unique_ptr<SkScalerContext> scaler,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)356 SkExclusiveStrikePtr SkStrikeCache::createStrikeExclusive(
357         const SkDescriptor& desc,
358         std::unique_ptr<SkScalerContext> scaler,
359         SkFontMetrics* maybeMetrics,
360         std::unique_ptr<SkStrikePinner> pinner)
361 {
362     return SkExclusiveStrikePtr(
363             this->createStrike(desc, std::move(scaler), maybeMetrics, std::move(pinner)));
364 }
365 
createStrike(const SkDescriptor & desc,std::unique_ptr<SkScalerContext> scaler,SkFontMetrics * maybeMetrics,std::unique_ptr<SkStrikePinner> pinner)366 auto SkStrikeCache::createStrike(
367         const SkDescriptor& desc,
368         std::unique_ptr<SkScalerContext> scaler,
369         SkFontMetrics* maybeMetrics,
370         std::unique_ptr<SkStrikePinner> pinner) -> Node* {
371     SkFontMetrics fontMetrics;
372     if (maybeMetrics != nullptr) {
373         fontMetrics = *maybeMetrics;
374     } else {
375         scaler->getFontMetrics(&fontMetrics);
376     }
377 
378     return new Node{this, desc, std::move(scaler), fontMetrics, std::move(pinner)};
379 }
380 
purgeAll()381 void SkStrikeCache::purgeAll() {
382     SkAutoSpinlock ac(fLock);
383     this->internalPurge(fTotalMemoryUsed);
384 }
385 
getTotalMemoryUsed() const386 size_t SkStrikeCache::getTotalMemoryUsed() const {
387     SkAutoSpinlock ac(fLock);
388     return fTotalMemoryUsed;
389 }
390 
getCacheCountUsed() const391 int SkStrikeCache::getCacheCountUsed() const {
392     SkAutoSpinlock ac(fLock);
393     return fCacheCount;
394 }
395 
getCacheCountLimit() const396 int SkStrikeCache::getCacheCountLimit() const {
397     SkAutoSpinlock ac(fLock);
398     return fCacheCountLimit;
399 }
400 
setCacheSizeLimit(size_t newLimit)401 size_t SkStrikeCache::setCacheSizeLimit(size_t newLimit) {
402     static const size_t minLimit = 256 * 1024;
403     if (newLimit < minLimit) {
404         newLimit = minLimit;
405     }
406 
407     SkAutoSpinlock ac(fLock);
408 
409     size_t prevLimit = fCacheSizeLimit;
410     fCacheSizeLimit = newLimit;
411     this->internalPurge();
412     return prevLimit;
413 }
414 
getCacheSizeLimit() const415 size_t  SkStrikeCache::getCacheSizeLimit() const {
416     SkAutoSpinlock ac(fLock);
417     return fCacheSizeLimit;
418 }
419 
setCacheCountLimit(int newCount)420 int SkStrikeCache::setCacheCountLimit(int newCount) {
421     if (newCount < 0) {
422         newCount = 0;
423     }
424 
425     SkAutoSpinlock ac(fLock);
426 
427     int prevCount = fCacheCountLimit;
428     fCacheCountLimit = newCount;
429     this->internalPurge();
430     return prevCount;
431 }
432 
getCachePointSizeLimit() const433 int SkStrikeCache::getCachePointSizeLimit() const {
434     SkAutoSpinlock ac(fLock);
435     return fPointSizeLimit;
436 }
437 
setCachePointSizeLimit(int newLimit)438 int SkStrikeCache::setCachePointSizeLimit(int newLimit) {
439     if (newLimit < 0) {
440         newLimit = 0;
441     }
442 
443     SkAutoSpinlock ac(fLock);
444 
445     int prevLimit = fPointSizeLimit;
446     fPointSizeLimit = newLimit;
447     return prevLimit;
448 }
449 
forEachStrike(std::function<void (const SkStrike &)> visitor) const450 void SkStrikeCache::forEachStrike(std::function<void(const SkStrike&)> visitor) const {
451     SkAutoSpinlock ac(fLock);
452 
453     this->validate();
454 
455     for (Node* node = this->internalGetHead(); node != nullptr; node = node->fNext) {
456         visitor(node->fStrike);
457     }
458 }
459 
internalPurge(size_t minBytesNeeded)460 size_t SkStrikeCache::internalPurge(size_t minBytesNeeded) {
461     this->validate();
462 
463     size_t bytesNeeded = 0;
464     if (fTotalMemoryUsed > fCacheSizeLimit) {
465         bytesNeeded = fTotalMemoryUsed - fCacheSizeLimit;
466     }
467     bytesNeeded = SkTMax(bytesNeeded, minBytesNeeded);
468     if (bytesNeeded) {
469         // no small purges!
470         bytesNeeded = SkTMax(bytesNeeded, fTotalMemoryUsed >> 2);
471     }
472 
473     int countNeeded = 0;
474     if (fCacheCount > fCacheCountLimit) {
475         countNeeded = fCacheCount - fCacheCountLimit;
476         // no small purges!
477         countNeeded = SkMax32(countNeeded, fCacheCount >> 2);
478     }
479 
480     // early exit
481     if (!countNeeded && !bytesNeeded) {
482         return 0;
483     }
484 
485     size_t  bytesFreed = 0;
486     int     countFreed = 0;
487 
488     // Start at the tail and proceed backwards deleting; the list is in LRU
489     // order, with unimportant entries at the tail.
490     Node* node = this->internalGetTail();
491     while (node != nullptr && (bytesFreed < bytesNeeded || countFreed < countNeeded)) {
492         Node* prev = node->fPrev;
493 
494         // Only delete if the strike is not pinned.
495         if (node->fPinner == nullptr || node->fPinner->canDelete()) {
496             bytesFreed += node->fStrike.getMemoryUsed();
497             countFreed += 1;
498             this->internalDetachCache(node);
499             delete node;
500         }
501         node = prev;
502     }
503 
504     this->validate();
505 
506 #ifdef SPEW_PURGE_STATUS
507     if (countFreed) {
508         SkDebugf("purging %dK from font cache [%d entries]\n",
509                  (int)(bytesFreed >> 10), countFreed);
510     }
511 #endif
512 
513     return bytesFreed;
514 }
515 
internalAttachToHead(Node * node)516 void SkStrikeCache::internalAttachToHead(Node* node) {
517     SkASSERT(nullptr == node->fPrev && nullptr == node->fNext);
518     if (fHead) {
519         fHead->fPrev = node;
520         node->fNext = fHead;
521     }
522     fHead = node;
523 
524     if (fTail == nullptr) {
525         fTail = node;
526     }
527 
528     fCacheCount += 1;
529     fTotalMemoryUsed += node->fStrike.getMemoryUsed();
530 }
531 
internalDetachCache(Node * node)532 void SkStrikeCache::internalDetachCache(Node* node) {
533     SkASSERT(fCacheCount > 0);
534     fCacheCount -= 1;
535     fTotalMemoryUsed -= node->fStrike.getMemoryUsed();
536 
537     if (node->fPrev) {
538         node->fPrev->fNext = node->fNext;
539     } else {
540         fHead = node->fNext;
541     }
542     if (node->fNext) {
543         node->fNext->fPrev = node->fPrev;
544     } else {
545         fTail = node->fPrev;
546     }
547     node->fPrev = node->fNext = nullptr;
548 }
549 
ValidateGlyphCacheDataSize()550 void SkStrikeCache::ValidateGlyphCacheDataSize() {
551 #ifdef SK_DEBUG
552     GlobalStrikeCache()->validateGlyphCacheDataSize();
553 #endif
554 }
555 
556 #ifdef SK_DEBUG
validateGlyphCacheDataSize() const557 void SkStrikeCache::validateGlyphCacheDataSize() const {
558     this->forEachStrike(
559             [](const SkStrike& cache) { cache.forceValidate();
560     });
561 }
562 #endif
563 
564 #ifdef SK_DEBUG
validate() const565 void SkStrikeCache::validate() const {
566     size_t computedBytes = 0;
567     int computedCount = 0;
568 
569     const Node* node = fHead;
570     while (node != nullptr) {
571         computedBytes += node->fStrike.getMemoryUsed();
572         computedCount += 1;
573         node = node->fNext;
574     }
575 
576     SkASSERTF(fCacheCount == computedCount, "fCacheCount: %d, computedCount: %d", fCacheCount,
577               computedCount);
578     SkASSERTF(fTotalMemoryUsed == computedBytes, "fTotalMemoryUsed: %d, computedBytes: %d",
579               fTotalMemoryUsed, computedBytes);
580 }
581 #endif
582 
583 ////////////////////////////////////////////////////////////////////////////////////////////////////
584