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