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