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