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