SkGlyphCache.cpp revision e16efc1882ab34a0bb3ae361a2d37f840044cf87
1
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkGlyphCache.h"
11#include "SkGraphics.h"
12#include "SkPaint.h"
13#include "SkPath.h"
14#include "SkTemplates.h"
15#include "SkTLS.h"
16
17//#define SPEW_PURGE_STATUS
18//#define USE_CACHE_HASH
19//#define RECORD_HASH_EFFICIENCY
20
21bool gSkSuppressFontCachePurgeSpew;
22
23///////////////////////////////////////////////////////////////////////////////
24
25#ifdef RECORD_HASH_EFFICIENCY
26    static uint32_t gHashSuccess;
27    static uint32_t gHashCollision;
28
29    static void RecordHashSuccess() {
30        gHashSuccess += 1;
31    }
32
33    static void RecordHashCollisionIf(bool pred) {
34        if (pred) {
35            gHashCollision += 1;
36
37            uint32_t total = gHashSuccess + gHashCollision;
38            SkDebugf("Font Cache Hash success rate: %d%%\n",
39                     100 * gHashSuccess / total);
40        }
41    }
42#else
43    #define RecordHashSuccess() (void)0
44    #define RecordHashCollisionIf(pred) (void)0
45#endif
46#define RecordHashCollision() RecordHashCollisionIf(true)
47
48///////////////////////////////////////////////////////////////////////////////
49
50#define kMinGlphAlloc       (sizeof(SkGlyph) * 64)
51#define kMinImageAlloc      (24 * 64)   // should be pointsize-dependent
52
53#define METRICS_RESERVE_COUNT  128  // so we don't grow this array a lot
54
55SkGlyphCache::SkGlyphCache(const SkDescriptor* desc)
56        : fGlyphAlloc(kMinGlphAlloc), fImageAlloc(kMinImageAlloc) {
57    fPrev = fNext = NULL;
58
59    fDesc = desc->copy();
60    fScalerContext = SkScalerContext::Create(desc);
61    fScalerContext->getFontMetrics(NULL, &fFontMetricsY);
62
63    // init to 0 so that all of the pointers will be null
64    memset(fGlyphHash, 0, sizeof(fGlyphHash));
65    // init with 0xFF so that the charCode field will be -1, which is invalid
66    memset(fCharToGlyphHash, 0xFF, sizeof(fCharToGlyphHash));
67
68    fMemoryUsed = sizeof(*this) + kMinGlphAlloc + kMinImageAlloc;
69
70    fGlyphArray.setReserve(METRICS_RESERVE_COUNT);
71
72    fMetricsCount = 0;
73    fAdvanceCount = 0;
74    fAuxProcList = NULL;
75}
76
77SkGlyphCache::~SkGlyphCache() {
78    SkGlyph**   gptr = fGlyphArray.begin();
79    SkGlyph**   stop = fGlyphArray.end();
80    while (gptr < stop) {
81        SkPath* path = (*gptr)->fPath;
82        if (path) {
83            SkDELETE(path);
84        }
85        gptr += 1;
86    }
87    SkDescriptor::Free(fDesc);
88    SkDELETE(fScalerContext);
89    this->invokeAndRemoveAuxProcs();
90}
91
92///////////////////////////////////////////////////////////////////////////////
93
94#ifdef SK_DEBUG
95#define VALIDATE()  AutoValidate av(this)
96#else
97#define VALIDATE()
98#endif
99
100uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) {
101    VALIDATE();
102    uint32_t id = SkGlyph::MakeID(charCode);
103    const CharGlyphRec& rec = fCharToGlyphHash[ID2HashIndex(id)];
104
105    if (rec.fID == id) {
106        return rec.fGlyph->getGlyphID();
107    } else {
108        return fScalerContext->charToGlyphID(charCode);
109    }
110}
111
112SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) {
113    return fScalerContext->glyphIDToChar(glyphID);
114}
115
116unsigned SkGlyphCache::getGlyphCount() {
117    return fScalerContext->getGlyphCount();
118}
119
120///////////////////////////////////////////////////////////////////////////////
121
122const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) {
123    VALIDATE();
124    uint32_t id = SkGlyph::MakeID(charCode);
125    CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
126
127    if (rec->fID != id) {
128        // this ID is based on the UniChar
129        rec->fID = id;
130        // this ID is based on the glyph index
131        id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
132        rec->fGlyph = this->lookupMetrics(id, kJustAdvance_MetricsType);
133    }
134    return *rec->fGlyph;
135}
136
137const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) {
138    VALIDATE();
139    uint32_t id = SkGlyph::MakeID(glyphID);
140    unsigned index = ID2HashIndex(id);
141    SkGlyph* glyph = fGlyphHash[index];
142
143    if (NULL == glyph || glyph->fID != id) {
144        glyph = this->lookupMetrics(glyphID, kJustAdvance_MetricsType);
145        fGlyphHash[index] = glyph;
146    }
147    return *glyph;
148}
149
150///////////////////////////////////////////////////////////////////////////////
151
152const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) {
153    VALIDATE();
154    uint32_t id = SkGlyph::MakeID(charCode);
155    CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
156
157    if (rec->fID != id) {
158        RecordHashCollisionIf(rec->fGlyph != NULL);
159        // this ID is based on the UniChar
160        rec->fID = id;
161        // this ID is based on the glyph index
162        id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode));
163        rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
164    } else {
165        RecordHashSuccess();
166        if (rec->fGlyph->isJustAdvance()) {
167            fScalerContext->getMetrics(rec->fGlyph);
168        }
169    }
170    SkASSERT(rec->fGlyph->isFullMetrics());
171    return *rec->fGlyph;
172}
173
174const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode,
175                                               SkFixed x, SkFixed y) {
176    VALIDATE();
177    uint32_t id = SkGlyph::MakeID(charCode, x, y);
178    CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)];
179
180    if (rec->fID != id) {
181        RecordHashCollisionIf(rec->fGlyph != NULL);
182        // this ID is based on the UniChar
183        rec->fID = id;
184        // this ID is based on the glyph index
185        id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
186        rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType);
187    } else {
188        RecordHashSuccess();
189        if (rec->fGlyph->isJustAdvance()) {
190            fScalerContext->getMetrics(rec->fGlyph);
191        }
192    }
193    SkASSERT(rec->fGlyph->isFullMetrics());
194    return *rec->fGlyph;
195}
196
197const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) {
198    VALIDATE();
199    uint32_t id = SkGlyph::MakeID(glyphID);
200    unsigned index = ID2HashIndex(id);
201    SkGlyph* glyph = fGlyphHash[index];
202
203    if (NULL == glyph || glyph->fID != id) {
204        RecordHashCollisionIf(glyph != NULL);
205        glyph = this->lookupMetrics(glyphID, kFull_MetricsType);
206        fGlyphHash[index] = glyph;
207    } else {
208        RecordHashSuccess();
209        if (glyph->isJustAdvance()) {
210            fScalerContext->getMetrics(glyph);
211        }
212    }
213    SkASSERT(glyph->isFullMetrics());
214    return *glyph;
215}
216
217const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID,
218                                               SkFixed x, SkFixed y) {
219    VALIDATE();
220    uint32_t id = SkGlyph::MakeID(glyphID, x, y);
221    unsigned index = ID2HashIndex(id);
222    SkGlyph* glyph = fGlyphHash[index];
223
224    if (NULL == glyph || glyph->fID != id) {
225        RecordHashCollisionIf(glyph != NULL);
226        glyph = this->lookupMetrics(id, kFull_MetricsType);
227        fGlyphHash[index] = glyph;
228    } else {
229        RecordHashSuccess();
230        if (glyph->isJustAdvance()) {
231            fScalerContext->getMetrics(glyph);
232        }
233    }
234    SkASSERT(glyph->isFullMetrics());
235    return *glyph;
236}
237
238SkGlyph* SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) {
239    SkGlyph* glyph;
240
241    int     hi = 0;
242    int     count = fGlyphArray.count();
243
244    if (count) {
245        SkGlyph**   gptr = fGlyphArray.begin();
246        int     lo = 0;
247
248        hi = count - 1;
249        while (lo < hi) {
250            int mid = (hi + lo) >> 1;
251            if (gptr[mid]->fID < id) {
252                lo = mid + 1;
253            } else {
254                hi = mid;
255            }
256        }
257        glyph = gptr[hi];
258        if (glyph->fID == id) {
259            if (kFull_MetricsType == mtype && glyph->isJustAdvance()) {
260                fScalerContext->getMetrics(glyph);
261            }
262            return glyph;
263        }
264
265        // check if we need to bump hi before falling though to the allocator
266        if (glyph->fID < id) {
267            hi += 1;
268        }
269    }
270
271    // not found, but hi tells us where to inser the new glyph
272    fMemoryUsed += sizeof(SkGlyph);
273
274    glyph = (SkGlyph*)fGlyphAlloc.alloc(sizeof(SkGlyph),
275                                        SkChunkAlloc::kThrow_AllocFailType);
276    glyph->init(id);
277    *fGlyphArray.insert(hi) = glyph;
278
279    if (kJustAdvance_MetricsType == mtype) {
280        fScalerContext->getAdvance(glyph);
281        fAdvanceCount += 1;
282    } else {
283        SkASSERT(kFull_MetricsType == mtype);
284        fScalerContext->getMetrics(glyph);
285        fMetricsCount += 1;
286    }
287
288    return glyph;
289}
290
291const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
292    if (glyph.fWidth > 0 && glyph.fWidth < kMaxGlyphWidth) {
293        if (glyph.fImage == NULL) {
294            size_t  size = glyph.computeImageSize();
295            const_cast<SkGlyph&>(glyph).fImage = fImageAlloc.alloc(size,
296                                        SkChunkAlloc::kReturnNil_AllocFailType);
297            // check that alloc() actually succeeded
298            if (glyph.fImage) {
299                fScalerContext->getImage(glyph);
300                // TODO: the scaler may have changed the maskformat during
301                // getImage (e.g. from AA or LCD to BW) which means we may have
302                // overallocated the buffer. Check if the new computedImageSize
303                // is smaller, and if so, strink the alloc size in fImageAlloc.
304                fMemoryUsed += size;
305            }
306        }
307    }
308    return glyph.fImage;
309}
310
311const 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->countPoints() * sizeof(SkPoint);
318        }
319    }
320    return glyph.fPath;
321}
322
323///////////////////////////////////////////////////////////////////////////////
324
325bool 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
339void 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
360void SkGlyphCache::invokeAndRemoveAuxProcs() {
361    AuxProcRec* rec = fAuxProcList;
362    while (rec) {
363        rec->fProc(rec->fData);
364        AuxProcRec* next = rec->fNext;
365        SkDELETE(rec);
366        rec = next;
367    }
368}
369
370///////////////////////////////////////////////////////////////////////////////
371///////////////////////////////////////////////////////////////////////////////
372
373#ifndef SK_DEFAULT_FONT_CACHE_LIMIT
374    #define SK_DEFAULT_FONT_CACHE_LIMIT     (2 * 1024 * 1024)
375#endif
376
377#ifdef USE_CACHE_HASH
378    #define HASH_BITCOUNT   6
379    #define HASH_COUNT      (1 << HASH_BITCOUNT)
380    #define HASH_MASK       (HASH_COUNT - 1)
381
382    static unsigned desc_to_hashindex(const SkDescriptor* desc)
383    {
384        SkASSERT(HASH_MASK < 256);  // since our munging reduces to 8 bits
385
386        uint32_t n = *(const uint32_t*)desc;    //desc->getChecksum();
387        SkASSERT(n == desc->getChecksum());
388
389        // don't trust that the low bits of checksum vary enough, so...
390        n ^= (n >> 24) ^ (n >> 16) ^ (n >> 8) ^ (n >> 30);
391
392        return n & HASH_MASK;
393    }
394#endif
395
396#include "SkThread.h"
397
398class SkGlyphCache_Globals {
399public:
400    enum UseMutex {
401        kNo_UseMutex,  // thread-local cache
402        kYes_UseMutex  // shared cache
403    };
404
405    SkGlyphCache_Globals(UseMutex um) {
406        fHead = NULL;
407        fTotalMemoryUsed = 0;
408        fFontCacheLimit = SK_DEFAULT_FONT_CACHE_LIMIT;
409        fMutex = (kYes_UseMutex == um) ? SkNEW(SkMutex) : NULL;
410
411#ifdef USE_CACHE_HASH
412        sk_bzero(fHash, sizeof(fHash));
413#endif
414    }
415
416    ~SkGlyphCache_Globals() {
417        SkGlyphCache* cache = fHead;
418        while (cache) {
419            SkGlyphCache* next = cache->fNext;
420            SkDELETE(cache);
421            cache = next;
422        }
423
424        SkDELETE(fMutex);
425    }
426
427    SkMutex*        fMutex;
428    SkGlyphCache*   fHead;
429    size_t          fTotalMemoryUsed;
430#ifdef USE_CACHE_HASH
431    SkGlyphCache*   fHash[HASH_COUNT];
432#endif
433
434#ifdef SK_DEBUG
435    void validate() const;
436#else
437    void validate() const {}
438#endif
439
440    size_t  getFontCacheLimit() const { return fFontCacheLimit; }
441    size_t  setFontCacheLimit(size_t limit);
442    void    purgeAll(); // does not change budget
443
444    // can return NULL
445    static SkGlyphCache_Globals* FindTLS() {
446        return (SkGlyphCache_Globals*)SkTLS::Find(CreateTLS);
447    }
448
449    static SkGlyphCache_Globals& GetTLS() {
450        return *(SkGlyphCache_Globals*)SkTLS::Get(CreateTLS, DeleteTLS);
451    }
452
453    static void DeleteTLS() { SkTLS::Delete(CreateTLS); }
454
455private:
456    size_t  fFontCacheLimit;
457
458    static void* CreateTLS() {
459        return SkNEW_ARGS(SkGlyphCache_Globals, (kNo_UseMutex));
460    }
461
462    static void DeleteTLS(void* ptr) {
463        SkDELETE((SkGlyphCache_Globals*)ptr);
464    }
465};
466
467size_t SkGlyphCache_Globals::setFontCacheLimit(size_t newLimit) {
468    static const size_t minLimit = 256 * 1024;
469    if (newLimit < minLimit) {
470        newLimit = minLimit;
471    }
472
473    size_t prevLimit = fFontCacheLimit;
474    fFontCacheLimit = newLimit;
475
476    size_t currUsed = fTotalMemoryUsed;
477    if (currUsed > newLimit) {
478        SkAutoMutexAcquire    ac(fMutex);
479        SkGlyphCache::InternalFreeCache(this, currUsed - newLimit);
480    }
481    return prevLimit;
482}
483
484void SkGlyphCache_Globals::purgeAll() {
485    SkAutoMutexAcquire    ac(fMutex);
486    SkGlyphCache::InternalFreeCache(this, fTotalMemoryUsed);
487}
488
489// Returns the shared globals
490static SkGlyphCache_Globals& getSharedGlobals() {
491    // we leak this, so we don't incur any shutdown cost of the destructor
492    static SkGlyphCache_Globals* gGlobals = SkNEW_ARGS(SkGlyphCache_Globals,
493                                       (SkGlyphCache_Globals::kYes_UseMutex));
494    return *gGlobals;
495}
496
497// Returns the TLS globals (if set), or the shared globals
498static SkGlyphCache_Globals& getGlobals() {
499    SkGlyphCache_Globals* tls = SkGlyphCache_Globals::FindTLS();
500    return tls ? *tls : getSharedGlobals();
501}
502
503void SkGlyphCache::VisitAllCaches(bool (*proc)(SkGlyphCache*, void*),
504                                  void* context) {
505    SkGlyphCache_Globals& globals = getGlobals();
506    SkAutoMutexAcquire    ac(globals.fMutex);
507    SkGlyphCache*         cache;
508
509    globals.validate();
510
511    for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
512        if (proc(cache, context)) {
513            break;
514        }
515    }
516
517    globals.validate();
518}
519
520/*  This guy calls the visitor from within the mutext lock, so the visitor
521    cannot:
522    - take too much time
523    - try to acquire the mutext again
524    - call a fontscaler (which might call into the cache)
525*/
526SkGlyphCache* SkGlyphCache::VisitCache(const SkDescriptor* desc,
527                              bool (*proc)(const SkGlyphCache*, void*),
528                              void* context) {
529    SkASSERT(desc);
530
531    SkGlyphCache_Globals& globals = getGlobals();
532    SkAutoMutexAcquire    ac(globals.fMutex);
533    SkGlyphCache*         cache;
534    bool                  insideMutex = true;
535
536    globals.validate();
537
538#ifdef USE_CACHE_HASH
539    SkGlyphCache** hash = globals.fHash;
540    unsigned index = desc_to_hashindex(desc);
541    cache = hash[index];
542    if (cache && *cache->fDesc == *desc) {
543        cache->detach(&globals.fHead);
544        goto FOUND_IT;
545    }
546#endif
547
548    for (cache = globals.fHead; cache != NULL; cache = cache->fNext) {
549        if (cache->fDesc->equals(*desc)) {
550            cache->detach(&globals.fHead);
551            goto FOUND_IT;
552        }
553    }
554
555    /* Release the mutex now, before we create a new entry (which might have
556        side-effects like trying to access the cache/mutex (yikes!)
557    */
558    ac.release();           // release the mutex now
559    insideMutex = false;    // can't use globals anymore
560
561    cache = SkNEW_ARGS(SkGlyphCache, (desc));
562
563FOUND_IT:
564
565    AutoValidate av(cache);
566
567    if (proc(cache, context)) {   // stay detached
568        if (insideMutex) {
569            SkASSERT(globals.fTotalMemoryUsed >= cache->fMemoryUsed);
570            globals.fTotalMemoryUsed -= cache->fMemoryUsed;
571#ifdef USE_CACHE_HASH
572            hash[index] = NULL;
573#endif
574        }
575    } else {                        // reattach
576        if (insideMutex) {
577            cache->attachToHead(&globals.fHead);
578#ifdef USE_CACHE_HASH
579            hash[index] = cache;
580#endif
581        } else {
582            AttachCache(cache);
583        }
584        cache = NULL;
585    }
586    return cache;
587}
588
589void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
590    SkASSERT(cache);
591    SkASSERT(cache->fNext == NULL);
592
593    SkGlyphCache_Globals& globals = getGlobals();
594    SkAutoMutexAcquire    ac(globals.fMutex);
595
596    globals.validate();
597    cache->validate();
598
599    // if we have a fixed budget for our cache, do a purge here
600    {
601        size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed;
602        size_t budgeted = globals.getFontCacheLimit();
603        if (allocated > budgeted) {
604            (void)InternalFreeCache(&globals, allocated - budgeted);
605        }
606    }
607
608    cache->attachToHead(&globals.fHead);
609    globals.fTotalMemoryUsed += cache->fMemoryUsed;
610
611#ifdef USE_CACHE_HASH
612    unsigned index = desc_to_hashindex(cache->fDesc);
613    SkASSERT(globals.fHash[index] != cache);
614    globals.fHash[index] = cache;
615#endif
616
617    globals.validate();
618}
619
620///////////////////////////////////////////////////////////////////////////////
621
622SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) {
623    if (cache) {
624        while (cache->fNext) {
625            cache = cache->fNext;
626        }
627    }
628    return cache;
629}
630
631#ifdef SK_DEBUG
632void SkGlyphCache_Globals::validate() const {
633    size_t computed = 0;
634
635    const SkGlyphCache* head = fHead;
636    while (head != NULL) {
637        computed += head->fMemoryUsed;
638        head = head->fNext;
639    }
640
641    if (fTotalMemoryUsed != computed) {
642        printf("total %d, computed %d\n", (int)fTotalMemoryUsed, (int)computed);
643    }
644    SkASSERT(fTotalMemoryUsed == computed);
645}
646#endif
647
648size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals,
649                                       size_t bytesNeeded) {
650    globals->validate();
651
652    size_t  bytesFreed = 0;
653    int     count = 0;
654
655    // don't do any "small" purges
656    size_t minToPurge = globals->fTotalMemoryUsed >> 2;
657    if (bytesNeeded < minToPurge)
658        bytesNeeded = minToPurge;
659
660    SkGlyphCache* cache = FindTail(globals->fHead);
661    while (cache != NULL && bytesFreed < bytesNeeded) {
662        SkGlyphCache* prev = cache->fPrev;
663        bytesFreed += cache->fMemoryUsed;
664
665#ifdef USE_CACHE_HASH
666        unsigned index = desc_to_hashindex(cache->fDesc);
667        if (cache == globals->fHash[index]) {
668            globals->fHash[index] = NULL;
669        }
670#endif
671
672        cache->detach(&globals->fHead);
673        SkDELETE(cache);
674        cache = prev;
675        count += 1;
676    }
677
678    SkASSERT(bytesFreed <= globals->fTotalMemoryUsed);
679    globals->fTotalMemoryUsed -= bytesFreed;
680    globals->validate();
681
682#ifdef SPEW_PURGE_STATUS
683    if (count && !gSkSuppressFontCachePurgeSpew) {
684        SkDebugf("purging %dK from font cache [%d entries]\n",
685                 (int)(bytesFreed >> 10), count);
686    }
687#endif
688
689    return bytesFreed;
690}
691
692///////////////////////////////////////////////////////////////////////////////
693
694#ifdef SK_DEBUG
695void SkGlyphCache::validate() const {
696#ifdef SK_DEBUG_GLYPH_CACHE
697    int count = fGlyphArray.count();
698    for (int i = 0; i < count; i++) {
699        const SkGlyph* glyph = fGlyphArray[i];
700        SkASSERT(glyph);
701        SkASSERT(fGlyphAlloc.contains(glyph));
702        if (glyph->fImage) {
703            SkASSERT(fImageAlloc.contains(glyph->fImage));
704        }
705    }
706#endif
707}
708#endif
709
710///////////////////////////////////////////////////////////////////////////////
711///////////////////////////////////////////////////////////////////////////////
712
713#include "SkTypefaceCache.h"
714
715size_t SkGraphics::GetFontCacheLimit() {
716    return getSharedGlobals().getFontCacheLimit();
717}
718
719size_t SkGraphics::SetFontCacheLimit(size_t bytes) {
720    return getSharedGlobals().setFontCacheLimit(bytes);
721}
722
723size_t SkGraphics::GetFontCacheUsed() {
724    return getSharedGlobals().fTotalMemoryUsed;
725}
726
727void SkGraphics::PurgeFontCache() {
728    getSharedGlobals().purgeAll();
729    SkTypefaceCache::PurgeAll();
730}
731
732size_t SkGraphics::GetTLSFontCacheLimit() {
733    const SkGlyphCache_Globals* tls = SkGlyphCache_Globals::FindTLS();
734    return tls ? tls->getFontCacheLimit() : 0;
735}
736
737void SkGraphics::SetTLSFontCacheLimit(size_t bytes) {
738    if (0 == bytes) {
739        SkGlyphCache_Globals::DeleteTLS();
740    } else {
741        SkGlyphCache_Globals::GetTLS().setFontCacheLimit(bytes);
742    }
743}
744