Home | History | Annotate | Download | only in core
      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 
     33     static void RecordHashSuccess() {
     34         gHashSuccess += 1;
     35     }
     36 
     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 
     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 
     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:
    101     AutoCheckForNull(const SkTDArray<SkGlyph*>& array) : fArray(array) {
    102         for (int i = 0; i < array.count(); i++)
    103             SkASSERT(array[i]);
    104     }
    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 
    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 SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) {
    132     return fScalerContext->glyphIDToChar(glyphID);
    133 }
    134 
    135 ///////////////////////////////////////////////////////////////////////////////
    136 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    335 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 
    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 
    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 
    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 
    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
    438     void validate() const {}
    439 #endif
    440 };
    441 
    442 #ifdef SK_USE_RUNTIME_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 
    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 */
    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 
    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 
    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 
    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 
    594 SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) {
    595     if (cache) {
    596         while (cache->fNext) {
    597             cache = cache->fNext;
    598         }
    599     }
    600     return cache;
    601 }
    602 
    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
    614 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 
    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