• 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 
glyphToUnichar(uint16_t glyphID)131 SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) {
132     return fScalerContext->glyphIDToChar(glyphID);
133 }
134 
135 ///////////////////////////////////////////////////////////////////////////////
136 
getUnicharAdvance(SkUnichar charCode)137 const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) {
138     VALIDATE();
139     uint32_t id = SkGlyph::MakeID(charCode);
140     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
141 
142     if (rec->fID != id) {
143         // this ID is based on the UniChar
144         rec->fID = id;
145         // this ID is based on the glyph index
146         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
147         rec->fGlyph = this->lookupMetrics(id, kJustAdvance_MetricsType);
148     }
149     return *rec->fGlyph;
150 }
151 
getGlyphIDAdvance(uint16_t glyphID)152 const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) {
153     VALIDATE();
154     uint32_t id = SkGlyph::MakeID(glyphID);
155     unsigned index = ID2HashIndex(id);
156     SkGlyph* glyph = fGlyphHash[index];
157 
158     if (NULL == glyph || glyph->fID != id) {
159         glyph = this->lookupMetrics(glyphID, kJustAdvance_MetricsType);
160         fGlyphHash[index] = glyph;
161     }
162     return *glyph;
163 }
164 
165 ///////////////////////////////////////////////////////////////////////////////
166 
getUnicharMetrics(SkUnichar charCode)167 const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) {
168     VALIDATE();
169     uint32_t id = SkGlyph::MakeID(charCode);
170     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
171 
172     if (rec->fID != id) {
173         RecordHashCollisionIf(rec->fGlyph != NULL);
174         // this ID is based on the UniChar
175         rec->fID = id;
176         // this ID is based on the glyph index
177         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
178         rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
179     } else {
180         RecordHashSuccess();
181         if (rec->fGlyph->isJustAdvance()) {
182             fScalerContext->getMetrics(rec->fGlyph);
183         }
184     }
185     SkASSERT(rec->fGlyph->isFullMetrics());
186     return *rec->fGlyph;
187 }
188 
getUnicharMetrics(SkUnichar charCode,SkFixed x,SkFixed y)189 const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode,
190                                                SkFixed x, SkFixed y) {
191     VALIDATE();
192     uint32_t id = SkGlyph::MakeID(charCode, x, y);
193     CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
194 
195     if (rec->fID != id) {
196         RecordHashCollisionIf(rec->fGlyph != NULL);
197         // this ID is based on the UniChar
198         rec->fID = id;
199         // this ID is based on the glyph index
200         id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
201         rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
202     } else {
203         RecordHashSuccess();
204         if (rec->fGlyph->isJustAdvance()) {
205             fScalerContext->getMetrics(rec->fGlyph);
206         }
207     }
208     SkASSERT(rec->fGlyph->isFullMetrics());
209     return *rec->fGlyph;
210 }
211 
getGlyphIDMetrics(uint16_t glyphID)212 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) {
213     VALIDATE();
214     uint32_t id = SkGlyph::MakeID(glyphID);
215     unsigned index = ID2HashIndex(id);
216     SkGlyph* glyph = fGlyphHash[index];
217 
218     if (NULL == glyph || glyph->fID != id) {
219         RecordHashCollisionIf(glyph != NULL);
220         glyph = this->lookupMetrics(glyphID, kFull_MetricsType);
221         fGlyphHash[index] = glyph;
222     } else {
223         RecordHashSuccess();
224         if (glyph->isJustAdvance()) {
225             fScalerContext->getMetrics(glyph);
226         }
227     }
228     SkASSERT(glyph->isFullMetrics());
229     return *glyph;
230 }
231 
getGlyphIDMetrics(uint16_t glyphID,SkFixed x,SkFixed y)232 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID,
233                                                SkFixed x, SkFixed y) {
234     VALIDATE();
235     uint32_t id = SkGlyph::MakeID(glyphID, x, y);
236     unsigned index = ID2HashIndex(id);
237     SkGlyph* glyph = fGlyphHash[index];
238 
239     if (NULL == glyph || glyph->fID != id) {
240         RecordHashCollisionIf(glyph != NULL);
241         glyph = this->lookupMetrics(id, kFull_MetricsType);
242         fGlyphHash[index] = glyph;
243     } else {
244         RecordHashSuccess();
245         if (glyph->isJustAdvance()) {
246             fScalerContext->getMetrics(glyph);
247         }
248     }
249     SkASSERT(glyph->isFullMetrics());
250     return *glyph;
251 }
252 
lookupMetrics(uint32_t id,MetricsType mtype)253 SkGlyph* SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) {
254     SkGlyph* glyph;
255 
256     int     hi = 0;
257     int     count = fGlyphArray.count();
258 
259     if (count) {
260         SkGlyph**   gptr = fGlyphArray.begin();
261         int     lo = 0;
262 
263         hi = count - 1;
264         while (lo < hi) {
265             int mid = (hi + lo) >> 1;
266             if (gptr[mid]->fID < id) {
267                 lo = mid + 1;
268             } else {
269                 hi = mid;
270             }
271         }
272         glyph = gptr[hi];
273         if (glyph->fID == id) {
274             if (kFull_MetricsType == mtype && glyph->isJustAdvance()) {
275                 fScalerContext->getMetrics(glyph);
276             }
277             return glyph;
278         }
279 
280         // check if we need to bump hi before falling though to the allocator
281         if (glyph->fID < id) {
282             hi += 1;
283         }
284     }
285 
286     // not found, but hi tells us where to inser the new glyph
287     fMemoryUsed += sizeof(SkGlyph);
288 
289     glyph = (SkGlyph*)fGlyphAlloc.alloc(sizeof(SkGlyph),
290                                         SkChunkAlloc::kThrow_AllocFailType);
291     glyph->fID = id;
292     glyph->fImage = NULL;
293     glyph->fPath = NULL;
294     *fGlyphArray.insert(hi) = glyph;
295 
296     if (kJustAdvance_MetricsType == mtype) {
297         fScalerContext->getAdvance(glyph);
298         fAdvanceCount += 1;
299     } else {
300         SkASSERT(kFull_MetricsType == mtype);
301         fScalerContext->getMetrics(glyph);
302         fMetricsCount += 1;
303     }
304 
305     return glyph;
306 }
307 
findImage(const SkGlyph & glyph)308 const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
309     if (glyph.fWidth) {
310         if (glyph.fImage == NULL) {
311             size_t  size = glyph.computeImageSize();
312             const_cast<SkGlyph&>(glyph).fImage = fImageAlloc.alloc(size,
313                                         SkChunkAlloc::kReturnNil_AllocFailType);
314             fScalerContext->getImage(glyph);
315             fMemoryUsed += size;
316         }
317     }
318     return glyph.fImage;
319 }
320 
findPath(const SkGlyph & glyph)321 const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) {
322     if (glyph.fWidth) {
323         if (glyph.fPath == NULL) {
324             const_cast<SkGlyph&>(glyph).fPath = SkNEW(SkPath);
325             fScalerContext->getPath(glyph, glyph.fPath);
326             fMemoryUsed += sizeof(SkPath) +
327                     glyph.fPath->getPoints(NULL, 0x7FFFFFFF) * sizeof(SkPoint);
328         }
329     }
330     return glyph.fPath;
331 }
332 
333 ///////////////////////////////////////////////////////////////////////////////
334 
getAuxProcData(void (* proc)(void *),void ** dataPtr) const335 bool SkGlyphCache::getAuxProcData(void (*proc)(void*), void** dataPtr) const {
336     const AuxProcRec* rec = fAuxProcList;
337     while (rec) {
338         if (rec->fProc == proc) {
339             if (dataPtr) {
340                 *dataPtr = rec->fData;
341             }
342             return true;
343         }
344         rec = rec->fNext;
345     }
346     return false;
347 }
348 
setAuxProc(void (* proc)(void *),void * data)349 void SkGlyphCache::setAuxProc(void (*proc)(void*), void* data) {
350     if (proc == NULL) {
351         return;
352     }
353 
354     AuxProcRec* rec = fAuxProcList;
355     while (rec) {
356         if (rec->fProc == proc) {
357             rec->fData = data;
358             return;
359         }
360         rec = rec->fNext;
361     }
362     // not found, create a new rec
363     rec = SkNEW(AuxProcRec);
364     rec->fProc = proc;
365     rec->fData = data;
366     rec->fNext = fAuxProcList;
367     fAuxProcList = rec;
368 }
369 
removeAuxProc(void (* proc)(void *))370 void SkGlyphCache::removeAuxProc(void (*proc)(void*)) {
371     AuxProcRec* rec = fAuxProcList;
372     AuxProcRec* prev = NULL;
373     while (rec) {
374         AuxProcRec* next = rec->fNext;
375         if (rec->fProc == proc) {
376             if (prev) {
377                 prev->fNext = next;
378             } else {
379                 fAuxProcList = next;
380             }
381             SkDELETE(rec);
382             return;
383         }
384         prev = rec;
385         rec = next;
386     }
387 }
388 
invokeAndRemoveAuxProcs()389 void SkGlyphCache::invokeAndRemoveAuxProcs() {
390     AuxProcRec* rec = fAuxProcList;
391     while (rec) {
392         rec->fProc(rec->fData);
393         AuxProcRec* next = rec->fNext;
394         SkDELETE(rec);
395         rec = next;
396     }
397 }
398 
399 ///////////////////////////////////////////////////////////////////////////////
400 ///////////////////////////////////////////////////////////////////////////////
401 
402 #include "SkGlobals.h"
403 #include "SkThread.h"
404 
405 #define SkGlyphCache_GlobalsTag     SkSetFourByteTag('g', 'l', 'f', 'c')
406 
407 #ifdef USE_CACHE_HASH
408     #define HASH_BITCOUNT   6
409     #define HASH_COUNT      (1 << HASH_BITCOUNT)
410     #define HASH_MASK       (HASH_COUNT - 1)
411 
desc_to_hashindex(const SkDescriptor * desc)412     static unsigned desc_to_hashindex(const SkDescriptor* desc)
413     {
414         SkASSERT(HASH_MASK < 256);  // since our munging reduces to 8 bits
415 
416         uint32_t n = *(const uint32_t*)desc;    //desc->getChecksum();
417         SkASSERT(n == desc->getChecksum());
418 
419         // don't trust that the low bits of checksum vary enough, so...
420         n ^= (n >> 24) ^ (n >> 16) ^ (n >> 8) ^ (n >> 30);
421 
422         return n & HASH_MASK;
423     }
424 #endif
425 
426 class SkGlyphCache_Globals : public SkGlobals::Rec {
427 public:
428     SkMutex         fMutex;
429     SkGlyphCache*   fHead;
430     size_t          fTotalMemoryUsed;
431 #ifdef USE_CACHE_HASH
432     SkGlyphCache*   fHash[HASH_COUNT];
433 #endif
434 
435 #ifdef SK_DEBUG
436     void validate() const;
437 #else
validate() const438     void validate() const {}
439 #endif
440 };
441 
442 #ifdef SK_USE_RUNTIME_GLOBALS
create_globals()443     static SkGlobals::Rec* create_globals() {
444         SkGlyphCache_Globals* rec = SkNEW(SkGlyphCache_Globals);
445         rec->fHead = NULL;
446         rec->fTotalMemoryUsed = 0;
447 #ifdef USE_CACHE_HASH
448         memset(rec->fHash, 0, sizeof(rec->fHash));
449 #endif
450         return rec;
451     }
452 
453     #define FIND_GC_GLOBALS()   *(SkGlyphCache_Globals*)SkGlobals::Find(SkGlyphCache_GlobalsTag, create_globals)
454     #define GET_GC_GLOBALS()    *(SkGlyphCache_Globals*)SkGlobals::Get(SkGlyphCache_GlobalsTag)
455 #else
456     static SkGlyphCache_Globals gGCGlobals;
457     #define FIND_GC_GLOBALS()   gGCGlobals
458     #define GET_GC_GLOBALS()    gGCGlobals
459 #endif
460 
VisitAllCaches(bool (* proc)(SkGlyphCache *,void *),void * context)461 void SkGlyphCache::VisitAllCaches(bool (*proc)(SkGlyphCache*, void*),
462                                   void* context) {
463     SkGlyphCache_Globals& globals = FIND_GC_GLOBALS();
464     SkAutoMutexAcquire    ac(globals.fMutex);
465     SkGlyphCache*         cache;
466 
467     globals.validate();
468 
469     for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
470         if (proc(cache, context)) {
471             break;
472         }
473     }
474 
475     globals.validate();
476 }
477 
478 /*  This guy calls the visitor from within the mutext lock, so the visitor
479     cannot:
480     - take too much time
481     - try to acquire the mutext again
482     - call a fontscaler (which might call into the cache)
483 */
VisitCache(const SkDescriptor * desc,bool (* proc)(const SkGlyphCache *,void *),void * context)484 SkGlyphCache* SkGlyphCache::VisitCache(const SkDescriptor* desc,
485                               bool (*proc)(const SkGlyphCache*, void*),
486                               void* context) {
487     SkASSERT(desc);
488 
489     SkGlyphCache_Globals& globals = FIND_GC_GLOBALS();
490     SkAutoMutexAcquire    ac(globals.fMutex);
491     SkGlyphCache*         cache;
492     bool                  insideMutex = true;
493 
494     globals.validate();
495 
496 #ifdef USE_CACHE_HASH
497     SkGlyphCache** hash = globals.fHash;
498     unsigned index = desc_to_hashindex(desc);
499     cache = hash[index];
500     if (cache && *cache->fDesc == *desc) {
501         cache->detach(&globals.fHead);
502         goto FOUND_IT;
503     }
504 #endif
505 
506     for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
507         if (cache->fDesc->equals(*desc)) {
508             cache->detach(&globals.fHead);
509             goto FOUND_IT;
510         }
511     }
512 
513     /* Release the mutex now, before we create a new entry (which might have
514         side-effects like trying to access the cache/mutex (yikes!)
515     */
516     ac.release();           // release the mutex now
517     insideMutex = false;    // can't use globals anymore
518 
519     cache = SkNEW_ARGS(SkGlyphCache, (desc));
520 
521 FOUND_IT:
522     if (proc(cache, context)) {   // stay detached
523         if (insideMutex) {
524             SkASSERT(globals.fTotalMemoryUsed >= cache->fMemoryUsed);
525             globals.fTotalMemoryUsed -= cache->fMemoryUsed;
526 #ifdef USE_CACHE_HASH
527             hash[index] = NULL;
528 #endif
529         }
530     } else {                        // reattach
531         if (insideMutex) {
532             cache->attachToHead(&globals.fHead);
533 #ifdef USE_CACHE_HASH
534             hash[index] = cache;
535 #endif
536         } else {
537             AttachCache(cache);
538         }
539         cache = NULL;
540     }
541     return cache;
542 }
543 
AttachCache(SkGlyphCache * cache)544 void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
545     SkASSERT(cache);
546     SkASSERT(cache->fNext == NULL);
547 
548     SkGlyphCache_Globals& globals = GET_GC_GLOBALS();
549     SkAutoMutexAcquire    ac(globals.fMutex);
550 
551     globals.validate();
552 
553     // if we have a fixed budget for our cache, do a purge here
554     {
555         size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed;
556         size_t amountToFree = SkFontHost::ShouldPurgeFontCache(allocated);
557         if (amountToFree)
558             (void)InternalFreeCache(&globals, amountToFree);
559     }
560 
561     cache->attachToHead(&globals.fHead);
562     globals.fTotalMemoryUsed += cache->fMemoryUsed;
563 
564 #ifdef USE_CACHE_HASH
565     unsigned index = desc_to_hashindex(cache->fDesc);
566     SkASSERT(globals.fHash[index] != cache);
567     globals.fHash[index] = cache;
568 #endif
569 
570     globals.validate();
571 }
572 
GetCacheUsed()573 size_t SkGlyphCache::GetCacheUsed() {
574     SkGlyphCache_Globals& globals = FIND_GC_GLOBALS();
575     SkAutoMutexAcquire  ac(globals.fMutex);
576 
577     return SkGlyphCache::ComputeMemoryUsed(globals.fHead);
578 }
579 
SetCacheUsed(size_t bytesUsed)580 bool SkGlyphCache::SetCacheUsed(size_t bytesUsed) {
581     size_t curr = SkGlyphCache::GetCacheUsed();
582 
583     if (curr > bytesUsed) {
584         SkGlyphCache_Globals& globals = FIND_GC_GLOBALS();
585         SkAutoMutexAcquire  ac(globals.fMutex);
586 
587         return InternalFreeCache(&globals, curr - bytesUsed) > 0;
588     }
589     return false;
590 }
591 
592 ///////////////////////////////////////////////////////////////////////////////
593 
FindTail(SkGlyphCache * cache)594 SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) {
595     if (cache) {
596         while (cache->fNext) {
597             cache = cache->fNext;
598         }
599     }
600     return cache;
601 }
602 
ComputeMemoryUsed(const SkGlyphCache * head)603 size_t SkGlyphCache::ComputeMemoryUsed(const SkGlyphCache* head) {
604     size_t size = 0;
605 
606     while (head != NULL) {
607         size += head->fMemoryUsed;
608         head = head->fNext;
609     }
610     return size;
611 }
612 
613 #ifdef SK_DEBUG
validate() const614 void SkGlyphCache_Globals::validate() const {
615     size_t computed = SkGlyphCache::ComputeMemoryUsed(fHead);
616     if (fTotalMemoryUsed != computed) {
617         printf("total %d, computed %d\n", (int)fTotalMemoryUsed, (int)computed);
618     }
619     SkASSERT(fTotalMemoryUsed == computed);
620 }
621 #endif
622 
InternalFreeCache(SkGlyphCache_Globals * globals,size_t bytesNeeded)623 size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals,
624                                        size_t bytesNeeded) {
625     globals->validate();
626 
627     size_t  bytesFreed = 0;
628     int     count = 0;
629 
630     // don't do any "small" purges
631     size_t minToPurge = globals->fTotalMemoryUsed >> 2;
632     if (bytesNeeded < minToPurge)
633         bytesNeeded = minToPurge;
634 
635     SkGlyphCache* cache = FindTail(globals->fHead);
636     while (cache != NULL && bytesFreed < bytesNeeded) {
637         SkGlyphCache* prev = cache->fPrev;
638         bytesFreed += cache->fMemoryUsed;
639 
640 #ifdef USE_CACHE_HASH
641         unsigned index = desc_to_hashindex(cache->fDesc);
642         if (cache == globals->fHash[index]) {
643             globals->fHash[index] = NULL;
644         }
645 #endif
646 
647         cache->detach(&globals->fHead);
648         SkDELETE(cache);
649         cache = prev;
650         count += 1;
651     }
652 
653     SkASSERT(bytesFreed <= globals->fTotalMemoryUsed);
654     globals->fTotalMemoryUsed -= bytesFreed;
655     globals->validate();
656 
657 #ifdef SPEW_PURGE_STATUS
658     if (count) {
659         SkDebugf("purging %dK from font cache [%d entries]\n",
660                  (int)(bytesFreed >> 10), count);
661     }
662 #endif
663 
664     return bytesFreed;
665 }
666 
667