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