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