• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright 2006 The Android Open Source Project
4  *
5  * Use of this source code is governed by a BSD-style license that can be
6  * found in the LICENSE file.
7  */
8 
9 
10 #include "SkGlyphCache.h"
11 #include "SkGraphics.h"
12 #include "SkPaint.h"
13 #include "SkTemplates.h"
14 
15 //#define SPEW_PURGE_STATUS
16 //#define USE_CACHE_HASH
17 //#define RECORD_HASH_EFFICIENCY
18 
19 bool gSkSuppressFontCachePurgeSpew;
20 
21 ///////////////////////////////////////////////////////////////////////////////
22 
23 #ifdef RECORD_HASH_EFFICIENCY
24     static uint32_t gHashSuccess;
25     static uint32_t gHashCollision;
26 
RecordHashSuccess()27     static void RecordHashSuccess() {
28         gHashSuccess += 1;
29     }
30 
RecordHashCollisionIf(bool pred)31     static void RecordHashCollisionIf(bool pred) {
32         if (pred) {
33             gHashCollision += 1;
34 
35             uint32_t total = gHashSuccess + gHashCollision;
36             SkDebugf("Font Cache Hash success rate: %d%%\n",
37                      100 * gHashSuccess / total);
38         }
39     }
40 #else
41     #define RecordHashSuccess() (void)0
42     #define RecordHashCollisionIf(pred) (void)0
43 #endif
44 #define RecordHashCollision() RecordHashCollisionIf(true)
45 
46 ///////////////////////////////////////////////////////////////////////////////
47 
48 #define kMinGlphAlloc       (sizeof(SkGlyph) * 64)
49 #define kMinImageAlloc      (24 * 64)   // should be pointsize-dependent
50 
51 #define METRICS_RESERVE_COUNT  128  // so we don't grow this array a lot
52 
SkGlyphCache(const SkDescriptor * desc)53 SkGlyphCache::SkGlyphCache(const SkDescriptor* desc)
54         : fGlyphAlloc(kMinGlphAlloc), fImageAlloc(kMinImageAlloc) {
55     fPrev = fNext = NULL;
56 
57     fDesc = desc->copy();
58     fScalerContext = SkScalerContext::Create(desc);
59     fScalerContext->getFontMetrics(NULL, &fFontMetricsY);
60 
61     // init to 0 so that all of the pointers will be null
62     memset(fGlyphHash, 0, sizeof(fGlyphHash));
63     // init with 0xFF so that the charCode field will be -1, which is invalid
64     memset(fCharToGlyphHash, 0xFF, sizeof(fCharToGlyphHash));
65 
66     fMemoryUsed = sizeof(*this) + kMinGlphAlloc + kMinImageAlloc;
67 
68     fGlyphArray.setReserve(METRICS_RESERVE_COUNT);
69 
70     fMetricsCount = 0;
71     fAdvanceCount = 0;
72     fAuxProcList = NULL;
73 }
74 
~SkGlyphCache()75 SkGlyphCache::~SkGlyphCache() {
76     SkGlyph**   gptr = fGlyphArray.begin();
77     SkGlyph**   stop = fGlyphArray.end();
78     while (gptr < stop) {
79         SkPath* path = (*gptr)->fPath;
80         if (path) {
81             SkDELETE(path);
82         }
83         gptr += 1;
84     }
85     SkDescriptor::Free(fDesc);
86     SkDELETE(fScalerContext);
87     this->invokeAndRemoveAuxProcs();
88 }
89 
90 ///////////////////////////////////////////////////////////////////////////////
91 
92 #ifdef SK_DEBUG
93 #define VALIDATE()  AutoValidate av(this)
94 #else
95 #define VALIDATE()
96 #endif
97 
unicharToGlyph(SkUnichar charCode)98 uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) {
99     VALIDATE();
100     uint32_t id = SkGlyph::MakeID(charCode);
101     const CharGlyphRec& rec = fCharToGlyphHash[ID2HashIndex(id)];
102 
103     if (rec.fID == id) {
104         return rec.fGlyph->getGlyphID();
105     } else {
106         return fScalerContext->charToGlyphID(charCode);
107     }
108 }
109 
glyphToUnichar(uint16_t glyphID)110 SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) {
111     return fScalerContext->glyphIDToChar(glyphID);
112 }
113 
getGlyphCount()114 unsigned SkGlyphCache::getGlyphCount() {
115     return fScalerContext->getGlyphCount();
116 }
117 
118 ///////////////////////////////////////////////////////////////////////////////
119 
getUnicharAdvance(SkUnichar charCode)120 const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) {
121     VALIDATE();
122     uint32_t id = SkGlyph::MakeID(charCode);
123     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
124 
125     if (rec->fID != id) {
126         // this ID is based on the UniChar
127         rec->fID = id;
128         // this ID is based on the glyph index
129         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
130         rec->fGlyph = this->lookupMetrics(id, kJustAdvance_MetricsType);
131     }
132     return *rec->fGlyph;
133 }
134 
getGlyphIDAdvance(uint16_t glyphID)135 const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) {
136     VALIDATE();
137     uint32_t id = SkGlyph::MakeID(glyphID);
138     unsigned index = ID2HashIndex(id);
139     SkGlyph* glyph = fGlyphHash[index];
140 
141     if (NULL == glyph || glyph->fID != id) {
142         glyph = this->lookupMetrics(glyphID, kJustAdvance_MetricsType);
143         fGlyphHash[index] = glyph;
144     }
145     return *glyph;
146 }
147 
148 ///////////////////////////////////////////////////////////////////////////////
149 
getUnicharMetrics(SkUnichar charCode)150 const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) {
151     VALIDATE();
152     uint32_t id = SkGlyph::MakeID(charCode);
153     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
154 
155     if (rec->fID != id) {
156         RecordHashCollisionIf(rec->fGlyph != NULL);
157         // this ID is based on the UniChar
158         rec->fID = id;
159         // this ID is based on the glyph index
160         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
161         rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
162     } else {
163         RecordHashSuccess();
164         if (rec->fGlyph->isJustAdvance()) {
165             fScalerContext->getMetrics(rec->fGlyph);
166         }
167     }
168     SkASSERT(rec->fGlyph->isFullMetrics());
169     return *rec->fGlyph;
170 }
171 
getUnicharMetrics(SkUnichar charCode,SkFixed x,SkFixed y)172 const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode,
173                                                SkFixed x, SkFixed y) {
174     VALIDATE();
175     uint32_t id = SkGlyph::MakeID(charCode, x, y);
176     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
177 
178     if (rec->fID != id) {
179         RecordHashCollisionIf(rec->fGlyph != NULL);
180         // this ID is based on the UniChar
181         rec->fID = id;
182         // this ID is based on the glyph index
183         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
184         rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
185     } else {
186         RecordHashSuccess();
187         if (rec->fGlyph->isJustAdvance()) {
188             fScalerContext->getMetrics(rec->fGlyph);
189         }
190     }
191     SkASSERT(rec->fGlyph->isFullMetrics());
192     return *rec->fGlyph;
193 }
194 
getGlyphIDMetrics(uint16_t glyphID)195 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) {
196     VALIDATE();
197     uint32_t id = SkGlyph::MakeID(glyphID);
198     unsigned index = ID2HashIndex(id);
199     SkGlyph* glyph = fGlyphHash[index];
200 
201     if (NULL == glyph || glyph->fID != id) {
202         RecordHashCollisionIf(glyph != NULL);
203         glyph = this->lookupMetrics(glyphID, kFull_MetricsType);
204         fGlyphHash[index] = glyph;
205     } else {
206         RecordHashSuccess();
207         if (glyph->isJustAdvance()) {
208             fScalerContext->getMetrics(glyph);
209         }
210     }
211     SkASSERT(glyph->isFullMetrics());
212     return *glyph;
213 }
214 
getGlyphIDMetrics(uint16_t glyphID,SkFixed x,SkFixed y)215 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID,
216                                                SkFixed x, SkFixed y) {
217     VALIDATE();
218     uint32_t id = SkGlyph::MakeID(glyphID, x, y);
219     unsigned index = ID2HashIndex(id);
220     SkGlyph* glyph = fGlyphHash[index];
221 
222     if (NULL == glyph || glyph->fID != id) {
223         RecordHashCollisionIf(glyph != NULL);
224         glyph = this->lookupMetrics(id, kFull_MetricsType);
225         fGlyphHash[index] = glyph;
226     } else {
227         RecordHashSuccess();
228         if (glyph->isJustAdvance()) {
229             fScalerContext->getMetrics(glyph);
230         }
231     }
232     SkASSERT(glyph->isFullMetrics());
233     return *glyph;
234 }
235 
lookupMetrics(uint32_t id,MetricsType mtype)236 SkGlyph* SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) {
237     SkGlyph* glyph;
238 
239     int     hi = 0;
240     int     count = fGlyphArray.count();
241 
242     if (count) {
243         SkGlyph**   gptr = fGlyphArray.begin();
244         int     lo = 0;
245 
246         hi = count - 1;
247         while (lo < hi) {
248             int mid = (hi + lo) >> 1;
249             if (gptr[mid]->fID < id) {
250                 lo = mid + 1;
251             } else {
252                 hi = mid;
253             }
254         }
255         glyph = gptr[hi];
256         if (glyph->fID == id) {
257             if (kFull_MetricsType == mtype && glyph->isJustAdvance()) {
258                 fScalerContext->getMetrics(glyph);
259             }
260             return glyph;
261         }
262 
263         // check if we need to bump hi before falling though to the allocator
264         if (glyph->fID < id) {
265             hi += 1;
266         }
267     }
268 
269     // not found, but hi tells us where to inser the new glyph
270     fMemoryUsed += sizeof(SkGlyph);
271 
272     glyph = (SkGlyph*)fGlyphAlloc.alloc(sizeof(SkGlyph),
273                                         SkChunkAlloc::kThrow_AllocFailType);
274     glyph->init(id);
275     *fGlyphArray.insert(hi) = glyph;
276 
277     if (kJustAdvance_MetricsType == mtype) {
278         fScalerContext->getAdvance(glyph);
279         fAdvanceCount += 1;
280     } else {
281         SkASSERT(kFull_MetricsType == mtype);
282         fScalerContext->getMetrics(glyph);
283         fMetricsCount += 1;
284     }
285 
286     return glyph;
287 }
288 
findImage(const SkGlyph & glyph)289 const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
290     if (glyph.fWidth > 0 && glyph.fWidth < kMaxGlyphWidth) {
291         if (glyph.fImage == NULL) {
292             size_t  size = glyph.computeImageSize();
293             const_cast<SkGlyph&>(glyph).fImage = fImageAlloc.alloc(size,
294                                         SkChunkAlloc::kReturnNil_AllocFailType);
295             // check that alloc() actually succeeded
296             if (glyph.fImage) {
297                 fScalerContext->getImage(glyph);
298                 // TODO: the scaler may have changed the maskformat during
299                 // getImage (e.g. from AA or LCD to BW) which means we may have
300                 // overallocated the buffer. Check if the new computedImageSize
301                 // is smaller, and if so, strink the alloc size in fImageAlloc.
302                 fMemoryUsed += size;
303             }
304         }
305     }
306     return glyph.fImage;
307 }
308 
findPath(const SkGlyph & glyph)309 const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) {
310     if (glyph.fWidth) {
311         if (glyph.fPath == NULL) {
312             const_cast<SkGlyph&>(glyph).fPath = SkNEW(SkPath);
313             fScalerContext->getPath(glyph, glyph.fPath);
314             fMemoryUsed += sizeof(SkPath) +
315                     glyph.fPath->getPoints(NULL, 0x7FFFFFFF) * sizeof(SkPoint);
316         }
317     }
318     return glyph.fPath;
319 }
320 
321 ///////////////////////////////////////////////////////////////////////////////
322 
getAuxProcData(void (* proc)(void *),void ** dataPtr) const323 bool SkGlyphCache::getAuxProcData(void (*proc)(void*), void** dataPtr) const {
324     const AuxProcRec* rec = fAuxProcList;
325     while (rec) {
326         if (rec->fProc == proc) {
327             if (dataPtr) {
328                 *dataPtr = rec->fData;
329             }
330             return true;
331         }
332         rec = rec->fNext;
333     }
334     return false;
335 }
336 
setAuxProc(void (* proc)(void *),void * data)337 void SkGlyphCache::setAuxProc(void (*proc)(void*), void* data) {
338     if (proc == NULL) {
339         return;
340     }
341 
342     AuxProcRec* rec = fAuxProcList;
343     while (rec) {
344         if (rec->fProc == proc) {
345             rec->fData = data;
346             return;
347         }
348         rec = rec->fNext;
349     }
350     // not found, create a new rec
351     rec = SkNEW(AuxProcRec);
352     rec->fProc = proc;
353     rec->fData = data;
354     rec->fNext = fAuxProcList;
355     fAuxProcList = rec;
356 }
357 
removeAuxProc(void (* proc)(void *))358 void SkGlyphCache::removeAuxProc(void (*proc)(void*)) {
359     AuxProcRec* rec = fAuxProcList;
360     AuxProcRec* prev = NULL;
361     while (rec) {
362         AuxProcRec* next = rec->fNext;
363         if (rec->fProc == proc) {
364             if (prev) {
365                 prev->fNext = next;
366             } else {
367                 fAuxProcList = next;
368             }
369             SkDELETE(rec);
370             return;
371         }
372         prev = rec;
373         rec = next;
374     }
375 }
376 
invokeAndRemoveAuxProcs()377 void SkGlyphCache::invokeAndRemoveAuxProcs() {
378     AuxProcRec* rec = fAuxProcList;
379     while (rec) {
380         rec->fProc(rec->fData);
381         AuxProcRec* next = rec->fNext;
382         SkDELETE(rec);
383         rec = next;
384     }
385 }
386 
387 ///////////////////////////////////////////////////////////////////////////////
388 ///////////////////////////////////////////////////////////////////////////////
389 
390 #ifdef USE_CACHE_HASH
391     #define HASH_BITCOUNT   6
392     #define HASH_COUNT      (1 << HASH_BITCOUNT)
393     #define HASH_MASK       (HASH_COUNT - 1)
394 
desc_to_hashindex(const SkDescriptor * desc)395     static unsigned desc_to_hashindex(const SkDescriptor* desc)
396     {
397         SkASSERT(HASH_MASK < 256);  // since our munging reduces to 8 bits
398 
399         uint32_t n = *(const uint32_t*)desc;    //desc->getChecksum();
400         SkASSERT(n == desc->getChecksum());
401 
402         // don't trust that the low bits of checksum vary enough, so...
403         n ^= (n >> 24) ^ (n >> 16) ^ (n >> 8) ^ (n >> 30);
404 
405         return n & HASH_MASK;
406     }
407 #endif
408 
409 #include "SkThread.h"
410 
411 class SkGlyphCache_Globals {
412 public:
SkGlyphCache_Globals()413     SkGlyphCache_Globals() {
414         fHead = NULL;
415         fTotalMemoryUsed = 0;
416 #ifdef USE_CACHE_HASH
417         sk_bzero(fHash, sizeof(fHash));
418 #endif
419     }
420 
421     SkMutex         fMutex;
422     SkGlyphCache*   fHead;
423     size_t          fTotalMemoryUsed;
424 #ifdef USE_CACHE_HASH
425     SkGlyphCache*   fHash[HASH_COUNT];
426 #endif
427 
428 #ifdef SK_DEBUG
429     void validate() const;
430 #else
validate() const431     void validate() const {}
432 #endif
433 };
434 
getGlobals()435 static SkGlyphCache_Globals& getGlobals() {
436     // we leak this, so we don't incur any shutdown cost of the destructor
437     static SkGlyphCache_Globals* gGlobals = new SkGlyphCache_Globals;
438     return *gGlobals;
439 }
440 
VisitAllCaches(bool (* proc)(SkGlyphCache *,void *),void * context)441 void SkGlyphCache::VisitAllCaches(bool (*proc)(SkGlyphCache*, void*),
442                                   void* context) {
443     SkGlyphCache_Globals& globals = getGlobals();
444     SkAutoMutexAcquire    ac(globals.fMutex);
445     SkGlyphCache*         cache;
446 
447     globals.validate();
448 
449     for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
450         if (proc(cache, context)) {
451             break;
452         }
453     }
454 
455     globals.validate();
456 }
457 
458 /*  This guy calls the visitor from within the mutext lock, so the visitor
459     cannot:
460     - take too much time
461     - try to acquire the mutext again
462     - call a fontscaler (which might call into the cache)
463 */
VisitCache(const SkDescriptor * desc,bool (* proc)(const SkGlyphCache *,void *),void * context)464 SkGlyphCache* SkGlyphCache::VisitCache(const SkDescriptor* desc,
465                               bool (*proc)(const SkGlyphCache*, void*),
466                               void* context) {
467     SkASSERT(desc);
468 
469     SkGlyphCache_Globals& globals = getGlobals();
470     SkAutoMutexAcquire    ac(globals.fMutex);
471     SkGlyphCache*         cache;
472     bool                  insideMutex = true;
473 
474     globals.validate();
475 
476 #ifdef USE_CACHE_HASH
477     SkGlyphCache** hash = globals.fHash;
478     unsigned index = desc_to_hashindex(desc);
479     cache = hash[index];
480     if (cache && *cache->fDesc == *desc) {
481         cache->detach(&globals.fHead);
482         goto FOUND_IT;
483     }
484 #endif
485 
486     for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
487         if (cache->fDesc->equals(*desc)) {
488             cache->detach(&globals.fHead);
489             goto FOUND_IT;
490         }
491     }
492 
493     /* Release the mutex now, before we create a new entry (which might have
494         side-effects like trying to access the cache/mutex (yikes!)
495     */
496     ac.release();           // release the mutex now
497     insideMutex = false;    // can't use globals anymore
498 
499     cache = SkNEW_ARGS(SkGlyphCache, (desc));
500 
501 FOUND_IT:
502 
503     AutoValidate av(cache);
504 
505     if (proc(cache, context)) {   // stay detached
506         if (insideMutex) {
507             SkASSERT(globals.fTotalMemoryUsed >= cache->fMemoryUsed);
508             globals.fTotalMemoryUsed -= cache->fMemoryUsed;
509 #ifdef USE_CACHE_HASH
510             hash[index] = NULL;
511 #endif
512         }
513     } else {                        // reattach
514         if (insideMutex) {
515             cache->attachToHead(&globals.fHead);
516 #ifdef USE_CACHE_HASH
517             hash[index] = cache;
518 #endif
519         } else {
520             AttachCache(cache);
521         }
522         cache = NULL;
523     }
524     return cache;
525 }
526 
AttachCache(SkGlyphCache * cache)527 void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
528     SkASSERT(cache);
529     SkASSERT(cache->fNext == NULL);
530 
531     SkGlyphCache_Globals& globals = getGlobals();
532     SkAutoMutexAcquire    ac(globals.fMutex);
533 
534     globals.validate();
535     cache->validate();
536 
537     // if we have a fixed budget for our cache, do a purge here
538     {
539         size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed;
540         size_t budgeted = SkGraphics::GetFontCacheLimit();
541         if (allocated > budgeted) {
542             (void)InternalFreeCache(&globals, allocated - budgeted);
543         }
544     }
545 
546     cache->attachToHead(&globals.fHead);
547     globals.fTotalMemoryUsed += cache->fMemoryUsed;
548 
549 #ifdef USE_CACHE_HASH
550     unsigned index = desc_to_hashindex(cache->fDesc);
551     SkASSERT(globals.fHash[index] != cache);
552     globals.fHash[index] = cache;
553 #endif
554 
555     globals.validate();
556 }
557 
GetCacheUsed()558 size_t SkGlyphCache::GetCacheUsed() {
559     SkGlyphCache_Globals& globals = getGlobals();
560     SkAutoMutexAcquire  ac(globals.fMutex);
561 
562     return SkGlyphCache::ComputeMemoryUsed(globals.fHead);
563 }
564 
SetCacheUsed(size_t bytesUsed)565 bool SkGlyphCache::SetCacheUsed(size_t bytesUsed) {
566     size_t curr = SkGlyphCache::GetCacheUsed();
567 
568     if (curr > bytesUsed) {
569         SkGlyphCache_Globals& globals = getGlobals();
570         SkAutoMutexAcquire  ac(globals.fMutex);
571 
572         return InternalFreeCache(&globals, curr - bytesUsed) > 0;
573     }
574     return false;
575 }
576 
577 ///////////////////////////////////////////////////////////////////////////////
578 
FindTail(SkGlyphCache * cache)579 SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) {
580     if (cache) {
581         while (cache->fNext) {
582             cache = cache->fNext;
583         }
584     }
585     return cache;
586 }
587 
ComputeMemoryUsed(const SkGlyphCache * head)588 size_t SkGlyphCache::ComputeMemoryUsed(const SkGlyphCache* head) {
589     size_t size = 0;
590 
591     while (head != NULL) {
592         size += head->fMemoryUsed;
593         head = head->fNext;
594     }
595     return size;
596 }
597 
598 #ifdef SK_DEBUG
validate() const599 void SkGlyphCache_Globals::validate() const {
600     size_t computed = SkGlyphCache::ComputeMemoryUsed(fHead);
601     if (fTotalMemoryUsed != computed) {
602         printf("total %d, computed %d\n", (int)fTotalMemoryUsed, (int)computed);
603     }
604     SkASSERT(fTotalMemoryUsed == computed);
605 }
606 #endif
607 
InternalFreeCache(SkGlyphCache_Globals * globals,size_t bytesNeeded)608 size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals,
609                                        size_t bytesNeeded) {
610     globals->validate();
611 
612     size_t  bytesFreed = 0;
613     int     count = 0;
614 
615     // don't do any "small" purges
616     size_t minToPurge = globals->fTotalMemoryUsed >> 2;
617     if (bytesNeeded < minToPurge)
618         bytesNeeded = minToPurge;
619 
620     SkGlyphCache* cache = FindTail(globals->fHead);
621     while (cache != NULL && bytesFreed < bytesNeeded) {
622         SkGlyphCache* prev = cache->fPrev;
623         bytesFreed += cache->fMemoryUsed;
624 
625 #ifdef USE_CACHE_HASH
626         unsigned index = desc_to_hashindex(cache->fDesc);
627         if (cache == globals->fHash[index]) {
628             globals->fHash[index] = NULL;
629         }
630 #endif
631 
632         cache->detach(&globals->fHead);
633         SkDELETE(cache);
634         cache = prev;
635         count += 1;
636     }
637 
638     SkASSERT(bytesFreed <= globals->fTotalMemoryUsed);
639     globals->fTotalMemoryUsed -= bytesFreed;
640     globals->validate();
641 
642 #ifdef SPEW_PURGE_STATUS
643     if (count && !gSkSuppressFontCachePurgeSpew) {
644         SkDebugf("purging %dK from font cache [%d entries]\n",
645                  (int)(bytesFreed >> 10), count);
646     }
647 #endif
648 
649     return bytesFreed;
650 }
651 
652 ///////////////////////////////////////////////////////////////////////////////
653 #ifdef SK_DEBUG
654 
validate() const655 void SkGlyphCache::validate() const {
656     int count = fGlyphArray.count();
657     for (int i = 0; i < count; i++) {
658         const SkGlyph* glyph = fGlyphArray[i];
659         SkASSERT(glyph);
660         SkASSERT(fGlyphAlloc.contains(glyph));
661         if (glyph->fImage) {
662             SkASSERT(fImageAlloc.contains(glyph->fImage));
663         }
664     }
665 }
666 
667 #endif
668