Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2013 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkChecksum.h"
      9 #include "SkMessageBus.h"
     10 #include "SkMipMap.h"
     11 #include "SkMutex.h"
     12 #include "SkPixelRef.h"
     13 #include "SkResourceCache.h"
     14 #include "SkTraceMemoryDump.h"
     15 
     16 #include <stddef.h>
     17 #include <stdlib.h>
     18 
     19 DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage)
     20 
     21 // This can be defined by the caller's build system
     22 //#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
     23 
     24 #ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
     25 #   define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT   1024
     26 #endif
     27 
     28 #ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
     29     #define SK_DEFAULT_IMAGE_CACHE_LIMIT     (32 * 1024 * 1024)
     30 #endif
     31 
     32 void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t dataSize) {
     33     SkASSERT(SkAlign4(dataSize) == dataSize);
     34 
     35     // fCount32 and fHash are not hashed
     36     static const int kUnhashedLocal32s = 2; // fCache32 + fHash
     37     static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi
     38     static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2);
     39     static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s;
     40 
     41     static_assert(sizeof(Key) == (kLocal32s << 2), "unaccounted_key_locals");
     42     static_assert(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace),
     43                  "namespace_field_must_be_last");
     44 
     45     fCount32 = SkToS32(kLocal32s + (dataSize >> 2));
     46     fSharedID_lo = (uint32_t)sharedID;
     47     fSharedID_hi = (uint32_t)(sharedID >> 32);
     48     fNamespace = nameSpace;
     49     // skip unhashed fields when computing the murmur
     50     fHash = SkChecksum::Murmur3(this->as32() + kUnhashedLocal32s,
     51                                 (fCount32 - kUnhashedLocal32s) << 2);
     52 }
     53 
     54 #include "SkTDynamicHash.h"
     55 
     56 class SkResourceCache::Hash :
     57     public SkTDynamicHash<SkResourceCache::Rec, SkResourceCache::Key> {};
     58 
     59 
     60 ///////////////////////////////////////////////////////////////////////////////
     61 
     62 void SkResourceCache::init() {
     63     fHead = nullptr;
     64     fTail = nullptr;
     65     fHash = new Hash;
     66     fTotalBytesUsed = 0;
     67     fCount = 0;
     68     fSingleAllocationByteLimit = 0;
     69     fAllocator = nullptr;
     70 
     71     // One of these should be explicit set by the caller after we return.
     72     fTotalByteLimit = 0;
     73     fDiscardableFactory = nullptr;
     74 }
     75 
     76 #include "SkDiscardableMemory.h"
     77 
     78 class SkOneShotDiscardablePixelRef : public SkPixelRef {
     79 public:
     80 
     81     // Ownership of the discardablememory is transfered to the pixelref
     82     // The pixelref will ref() the colortable (if not NULL), and unref() in destructor
     83     SkOneShotDiscardablePixelRef(const SkImageInfo&, SkDiscardableMemory*, size_t rowBytes,
     84                                  SkColorTable*);
     85     ~SkOneShotDiscardablePixelRef();
     86 
     87 protected:
     88     bool onNewLockPixels(LockRec*) override;
     89     void onUnlockPixels() override;
     90     size_t getAllocatedSizeInBytes() const override;
     91 
     92     SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return fDM; }
     93 
     94 private:
     95     SkDiscardableMemory* fDM;
     96     size_t               fRB;
     97     bool                 fFirstTime;
     98     SkColorTable*        fCTable;
     99 
    100     typedef SkPixelRef INHERITED;
    101 };
    102 
    103 SkOneShotDiscardablePixelRef::SkOneShotDiscardablePixelRef(const SkImageInfo& info,
    104                                              SkDiscardableMemory* dm,
    105                                              size_t rowBytes,
    106                                              SkColorTable* ctable)
    107     : INHERITED(info)
    108     , fDM(dm)
    109     , fRB(rowBytes)
    110     , fCTable(ctable)
    111 {
    112     SkASSERT(dm->data());
    113     fFirstTime = true;
    114     SkSafeRef(ctable);
    115 }
    116 
    117 SkOneShotDiscardablePixelRef::~SkOneShotDiscardablePixelRef() {
    118     delete fDM;
    119     SkSafeUnref(fCTable);
    120 }
    121 
    122 bool SkOneShotDiscardablePixelRef::onNewLockPixels(LockRec* rec) {
    123     if (fFirstTime) {
    124         // we're already locked
    125         SkASSERT(fDM->data());
    126         fFirstTime = false;
    127         goto SUCCESS;
    128     }
    129 
    130     // A previous call to onUnlock may have deleted our DM, so check for that
    131     if (nullptr == fDM) {
    132         return false;
    133     }
    134 
    135     if (!fDM->lock()) {
    136         // since it failed, we delete it now, to free-up the resource
    137         delete fDM;
    138         fDM = nullptr;
    139         return false;
    140     }
    141 
    142 SUCCESS:
    143     rec->fPixels = fDM->data();
    144     rec->fColorTable = fCTable;
    145     rec->fRowBytes = fRB;
    146     return true;
    147 }
    148 
    149 void SkOneShotDiscardablePixelRef::onUnlockPixels() {
    150     SkASSERT(!fFirstTime);
    151     fDM->unlock();
    152 }
    153 
    154 size_t SkOneShotDiscardablePixelRef::getAllocatedSizeInBytes() const {
    155     return this->info().getSafeSize(fRB);
    156 }
    157 
    158 class SkResourceCacheDiscardableAllocator : public SkBitmap::Allocator {
    159 public:
    160     SkResourceCacheDiscardableAllocator(SkResourceCache::DiscardableFactory factory) {
    161         SkASSERT(factory);
    162         fFactory = factory;
    163     }
    164 
    165     bool allocPixelRef(SkBitmap*, SkColorTable*) override;
    166 
    167 private:
    168     SkResourceCache::DiscardableFactory fFactory;
    169 };
    170 
    171 bool SkResourceCacheDiscardableAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
    172     size_t size = bitmap->getSize();
    173     uint64_t size64 = bitmap->computeSize64();
    174     if (0 == size || size64 > (uint64_t)size) {
    175         return false;
    176     }
    177 
    178     if (kIndex_8_SkColorType == bitmap->colorType()) {
    179         if (!ctable) {
    180             return false;
    181         }
    182     } else {
    183         ctable = nullptr;
    184     }
    185 
    186     SkDiscardableMemory* dm = fFactory(size);
    187     if (nullptr == dm) {
    188         return false;
    189     }
    190 
    191     SkImageInfo info = bitmap->info();
    192     bitmap->setPixelRef(new SkOneShotDiscardablePixelRef(info, dm, bitmap->rowBytes(),
    193                                                          ctable))->unref();
    194     bitmap->lockPixels();
    195     return bitmap->readyToDraw();
    196 }
    197 
    198 SkResourceCache::SkResourceCache(DiscardableFactory factory) {
    199     this->init();
    200     fDiscardableFactory = factory;
    201 
    202     fAllocator = new SkResourceCacheDiscardableAllocator(factory);
    203 }
    204 
    205 SkResourceCache::SkResourceCache(size_t byteLimit) {
    206     this->init();
    207     fTotalByteLimit = byteLimit;
    208 }
    209 
    210 SkResourceCache::~SkResourceCache() {
    211     SkSafeUnref(fAllocator);
    212 
    213     Rec* rec = fHead;
    214     while (rec) {
    215         Rec* next = rec->fNext;
    216         delete rec;
    217         rec = next;
    218     }
    219     delete fHash;
    220 }
    221 
    222 ////////////////////////////////////////////////////////////////////////////////
    223 
    224 bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) {
    225     this->checkMessages();
    226 
    227     Rec* rec = fHash->find(key);
    228     if (rec) {
    229         if (visitor(*rec, context)) {
    230             this->moveToHead(rec);  // for our LRU
    231             return true;
    232         } else {
    233             this->remove(rec);  // stale
    234             return false;
    235         }
    236     }
    237     return false;
    238 }
    239 
    240 static void make_size_str(size_t size, SkString* str) {
    241     const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 };
    242     int i = 0;
    243     while (suffix[i] && (size > 1024)) {
    244         i += 1;
    245         size >>= 10;
    246     }
    247     str->printf("%zu%c", size, suffix[i]);
    248 }
    249 
    250 static bool gDumpCacheTransactions;
    251 
    252 void SkResourceCache::add(Rec* rec) {
    253     this->checkMessages();
    254 
    255     SkASSERT(rec);
    256     // See if we already have this key (racy inserts, etc.)
    257     Rec* existing = fHash->find(rec->getKey());
    258     if (existing) {
    259         delete rec;
    260         return;
    261     }
    262 
    263     this->addToHead(rec);
    264     fHash->add(rec);
    265 
    266     if (gDumpCacheTransactions) {
    267         SkString bytesStr, totalStr;
    268         make_size_str(rec->bytesUsed(), &bytesStr);
    269         make_size_str(fTotalBytesUsed, &totalStr);
    270         SkDebugf("RC:    add %5s %12p key %08x -- total %5s, count %d\n",
    271                  bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
    272     }
    273 
    274     // since the new rec may push us over-budget, we perform a purge check now
    275     this->purgeAsNeeded();
    276 }
    277 
    278 void SkResourceCache::remove(Rec* rec) {
    279     size_t used = rec->bytesUsed();
    280     SkASSERT(used <= fTotalBytesUsed);
    281 
    282     this->detach(rec);
    283     fHash->remove(rec->getKey());
    284 
    285     fTotalBytesUsed -= used;
    286     fCount -= 1;
    287 
    288     if (gDumpCacheTransactions) {
    289         SkString bytesStr, totalStr;
    290         make_size_str(used, &bytesStr);
    291         make_size_str(fTotalBytesUsed, &totalStr);
    292         SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n",
    293                  bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
    294     }
    295 
    296     delete rec;
    297 }
    298 
    299 void SkResourceCache::purgeAsNeeded(bool forcePurge) {
    300     size_t byteLimit;
    301     int    countLimit;
    302 
    303     if (fDiscardableFactory) {
    304         countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT;
    305         byteLimit = SK_MaxU32;  // no limit based on bytes
    306     } else {
    307         countLimit = SK_MaxS32; // no limit based on count
    308         byteLimit = fTotalByteLimit;
    309     }
    310 
    311     Rec* rec = fTail;
    312     while (rec) {
    313         if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) {
    314             break;
    315         }
    316 
    317         Rec* prev = rec->fPrev;
    318         this->remove(rec);
    319         rec = prev;
    320     }
    321 }
    322 
    323 //#define SK_TRACK_PURGE_SHAREDID_HITRATE
    324 
    325 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
    326 static int gPurgeCallCounter;
    327 static int gPurgeHitCounter;
    328 #endif
    329 
    330 void SkResourceCache::purgeSharedID(uint64_t sharedID) {
    331     if (0 == sharedID) {
    332         return;
    333     }
    334 
    335 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
    336     gPurgeCallCounter += 1;
    337     bool found = false;
    338 #endif
    339     // go backwards, just like purgeAsNeeded, just to make the code similar.
    340     // could iterate either direction and still be correct.
    341     Rec* rec = fTail;
    342     while (rec) {
    343         Rec* prev = rec->fPrev;
    344         if (rec->getKey().getSharedID() == sharedID) {
    345 //            SkDebugf("purgeSharedID id=%llx rec=%p\n", sharedID, rec);
    346             this->remove(rec);
    347 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
    348             found = true;
    349 #endif
    350         }
    351         rec = prev;
    352     }
    353 
    354 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
    355     if (found) {
    356         gPurgeHitCounter += 1;
    357     }
    358 
    359     SkDebugf("PurgeShared calls=%d hits=%d rate=%g\n", gPurgeCallCounter, gPurgeHitCounter,
    360              gPurgeHitCounter * 100.0 / gPurgeCallCounter);
    361 #endif
    362 }
    363 
    364 void SkResourceCache::visitAll(Visitor visitor, void* context) {
    365     // go backwards, just like purgeAsNeeded, just to make the code similar.
    366     // could iterate either direction and still be correct.
    367     Rec* rec = fTail;
    368     while (rec) {
    369         visitor(*rec, context);
    370         rec = rec->fPrev;
    371     }
    372 }
    373 
    374 ///////////////////////////////////////////////////////////////////////////////////////////////////
    375 
    376 size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
    377     size_t prevLimit = fTotalByteLimit;
    378     fTotalByteLimit = newLimit;
    379     if (newLimit < prevLimit) {
    380         this->purgeAsNeeded();
    381     }
    382     return prevLimit;
    383 }
    384 
    385 SkCachedData* SkResourceCache::newCachedData(size_t bytes) {
    386     this->checkMessages();
    387 
    388     if (fDiscardableFactory) {
    389         SkDiscardableMemory* dm = fDiscardableFactory(bytes);
    390         return dm ? new SkCachedData(bytes, dm) : nullptr;
    391     } else {
    392         return new SkCachedData(sk_malloc_throw(bytes), bytes);
    393     }
    394 }
    395 
    396 ///////////////////////////////////////////////////////////////////////////////
    397 
    398 void SkResourceCache::detach(Rec* rec) {
    399     Rec* prev = rec->fPrev;
    400     Rec* next = rec->fNext;
    401 
    402     if (!prev) {
    403         SkASSERT(fHead == rec);
    404         fHead = next;
    405     } else {
    406         prev->fNext = next;
    407     }
    408 
    409     if (!next) {
    410         fTail = prev;
    411     } else {
    412         next->fPrev = prev;
    413     }
    414 
    415     rec->fNext = rec->fPrev = nullptr;
    416 }
    417 
    418 void SkResourceCache::moveToHead(Rec* rec) {
    419     if (fHead == rec) {
    420         return;
    421     }
    422 
    423     SkASSERT(fHead);
    424     SkASSERT(fTail);
    425 
    426     this->validate();
    427 
    428     this->detach(rec);
    429 
    430     fHead->fPrev = rec;
    431     rec->fNext = fHead;
    432     fHead = rec;
    433 
    434     this->validate();
    435 }
    436 
    437 void SkResourceCache::addToHead(Rec* rec) {
    438     this->validate();
    439 
    440     rec->fPrev = nullptr;
    441     rec->fNext = fHead;
    442     if (fHead) {
    443         fHead->fPrev = rec;
    444     }
    445     fHead = rec;
    446     if (!fTail) {
    447         fTail = rec;
    448     }
    449     fTotalBytesUsed += rec->bytesUsed();
    450     fCount += 1;
    451 
    452     this->validate();
    453 }
    454 
    455 ///////////////////////////////////////////////////////////////////////////////
    456 
    457 #ifdef SK_DEBUG
    458 void SkResourceCache::validate() const {
    459     if (nullptr == fHead) {
    460         SkASSERT(nullptr == fTail);
    461         SkASSERT(0 == fTotalBytesUsed);
    462         return;
    463     }
    464 
    465     if (fHead == fTail) {
    466         SkASSERT(nullptr == fHead->fPrev);
    467         SkASSERT(nullptr == fHead->fNext);
    468         SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
    469         return;
    470     }
    471 
    472     SkASSERT(nullptr == fHead->fPrev);
    473     SkASSERT(fHead->fNext);
    474     SkASSERT(nullptr == fTail->fNext);
    475     SkASSERT(fTail->fPrev);
    476 
    477     size_t used = 0;
    478     int count = 0;
    479     const Rec* rec = fHead;
    480     while (rec) {
    481         count += 1;
    482         used += rec->bytesUsed();
    483         SkASSERT(used <= fTotalBytesUsed);
    484         rec = rec->fNext;
    485     }
    486     SkASSERT(fCount == count);
    487 
    488     rec = fTail;
    489     while (rec) {
    490         SkASSERT(count > 0);
    491         count -= 1;
    492         SkASSERT(used >= rec->bytesUsed());
    493         used -= rec->bytesUsed();
    494         rec = rec->fPrev;
    495     }
    496 
    497     SkASSERT(0 == count);
    498     SkASSERT(0 == used);
    499 }
    500 #endif
    501 
    502 void SkResourceCache::dump() const {
    503     this->validate();
    504 
    505     SkDebugf("SkResourceCache: count=%d bytes=%d %s\n",
    506              fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc");
    507 }
    508 
    509 size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) {
    510     size_t oldLimit = fSingleAllocationByteLimit;
    511     fSingleAllocationByteLimit = newLimit;
    512     return oldLimit;
    513 }
    514 
    515 size_t SkResourceCache::getSingleAllocationByteLimit() const {
    516     return fSingleAllocationByteLimit;
    517 }
    518 
    519 size_t SkResourceCache::getEffectiveSingleAllocationByteLimit() const {
    520     // fSingleAllocationByteLimit == 0 means the caller is asking for our default
    521     size_t limit = fSingleAllocationByteLimit;
    522 
    523     // if we're not discardable (i.e. we are fixed-budget) then cap the single-limit
    524     // to our budget.
    525     if (nullptr == fDiscardableFactory) {
    526         if (0 == limit) {
    527             limit = fTotalByteLimit;
    528         } else {
    529             limit = SkTMin(limit, fTotalByteLimit);
    530         }
    531     }
    532     return limit;
    533 }
    534 
    535 void SkResourceCache::checkMessages() {
    536     SkTArray<PurgeSharedIDMessage> msgs;
    537     fPurgeSharedIDInbox.poll(&msgs);
    538     for (int i = 0; i < msgs.count(); ++i) {
    539         this->purgeSharedID(msgs[i].fSharedID);
    540     }
    541 }
    542 
    543 ///////////////////////////////////////////////////////////////////////////////
    544 
    545 SK_DECLARE_STATIC_MUTEX(gMutex);
    546 static SkResourceCache* gResourceCache = nullptr;
    547 static void cleanup_gResourceCache() {
    548     // We'll clean this up in our own tests, but disable for clients.
    549     // Chrome seems to have funky multi-process things going on in unit tests that
    550     // makes this unsafe to delete when the main process atexit()s.
    551     // SkLazyPtr does the same sort of thing.
    552 #if SK_DEVELOPER
    553     delete gResourceCache;
    554 #endif
    555 }
    556 
    557 /** Must hold gMutex when calling. */
    558 static SkResourceCache* get_cache() {
    559     // gMutex is always held when this is called, so we don't need to be fancy in here.
    560     gMutex.assertHeld();
    561     if (nullptr == gResourceCache) {
    562 #ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
    563         gResourceCache = new SkResourceCache(SkDiscardableMemory::Create);
    564 #else
    565         gResourceCache = new SkResourceCache(SK_DEFAULT_IMAGE_CACHE_LIMIT);
    566 #endif
    567         atexit(cleanup_gResourceCache);
    568     }
    569     return gResourceCache;
    570 }
    571 
    572 size_t SkResourceCache::GetTotalBytesUsed() {
    573     SkAutoMutexAcquire am(gMutex);
    574     return get_cache()->getTotalBytesUsed();
    575 }
    576 
    577 size_t SkResourceCache::GetTotalByteLimit() {
    578     SkAutoMutexAcquire am(gMutex);
    579     return get_cache()->getTotalByteLimit();
    580 }
    581 
    582 size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) {
    583     SkAutoMutexAcquire am(gMutex);
    584     return get_cache()->setTotalByteLimit(newLimit);
    585 }
    586 
    587 SkResourceCache::DiscardableFactory SkResourceCache::GetDiscardableFactory() {
    588     SkAutoMutexAcquire am(gMutex);
    589     return get_cache()->discardableFactory();
    590 }
    591 
    592 SkBitmap::Allocator* SkResourceCache::GetAllocator() {
    593     SkAutoMutexAcquire am(gMutex);
    594     return get_cache()->allocator();
    595 }
    596 
    597 SkCachedData* SkResourceCache::NewCachedData(size_t bytes) {
    598     SkAutoMutexAcquire am(gMutex);
    599     return get_cache()->newCachedData(bytes);
    600 }
    601 
    602 void SkResourceCache::Dump() {
    603     SkAutoMutexAcquire am(gMutex);
    604     get_cache()->dump();
    605 }
    606 
    607 size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) {
    608     SkAutoMutexAcquire am(gMutex);
    609     return get_cache()->setSingleAllocationByteLimit(size);
    610 }
    611 
    612 size_t SkResourceCache::GetSingleAllocationByteLimit() {
    613     SkAutoMutexAcquire am(gMutex);
    614     return get_cache()->getSingleAllocationByteLimit();
    615 }
    616 
    617 size_t SkResourceCache::GetEffectiveSingleAllocationByteLimit() {
    618     SkAutoMutexAcquire am(gMutex);
    619     return get_cache()->getEffectiveSingleAllocationByteLimit();
    620 }
    621 
    622 void SkResourceCache::PurgeAll() {
    623     SkAutoMutexAcquire am(gMutex);
    624     return get_cache()->purgeAll();
    625 }
    626 
    627 bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) {
    628     SkAutoMutexAcquire am(gMutex);
    629     return get_cache()->find(key, visitor, context);
    630 }
    631 
    632 void SkResourceCache::Add(Rec* rec) {
    633     SkAutoMutexAcquire am(gMutex);
    634     get_cache()->add(rec);
    635 }
    636 
    637 void SkResourceCache::VisitAll(Visitor visitor, void* context) {
    638     SkAutoMutexAcquire am(gMutex);
    639     get_cache()->visitAll(visitor, context);
    640 }
    641 
    642 void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) {
    643     if (sharedID) {
    644         SkMessageBus<PurgeSharedIDMessage>::Post(PurgeSharedIDMessage(sharedID));
    645     }
    646 }
    647 
    648 ///////////////////////////////////////////////////////////////////////////////
    649 
    650 #include "SkGraphics.h"
    651 #include "SkImageFilter.h"
    652 
    653 size_t SkGraphics::GetResourceCacheTotalBytesUsed() {
    654     return SkResourceCache::GetTotalBytesUsed();
    655 }
    656 
    657 size_t SkGraphics::GetResourceCacheTotalByteLimit() {
    658     return SkResourceCache::GetTotalByteLimit();
    659 }
    660 
    661 size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) {
    662     return SkResourceCache::SetTotalByteLimit(newLimit);
    663 }
    664 
    665 size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() {
    666     return SkResourceCache::GetSingleAllocationByteLimit();
    667 }
    668 
    669 size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) {
    670     return SkResourceCache::SetSingleAllocationByteLimit(newLimit);
    671 }
    672 
    673 void SkGraphics::PurgeResourceCache() {
    674     SkImageFilter::PurgeCache();
    675     return SkResourceCache::PurgeAll();
    676 }
    677 
    678 /////////////
    679 
    680 static void dump_visitor(const SkResourceCache::Rec& rec, void*) {
    681     SkDebugf("RC: %12s bytes %9lu  discardable %p\n",
    682              rec.getCategory(), rec.bytesUsed(), rec.diagnostic_only_getDiscardable());
    683 }
    684 
    685 void SkResourceCache::TestDumpMemoryStatistics() {
    686     VisitAll(dump_visitor, nullptr);
    687 }
    688 
    689 static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) {
    690     SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context);
    691     SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec);
    692     SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable();
    693     if (discardable) {
    694         dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable);
    695 
    696         // The discardable memory size will be calculated by dumper, but we also dump what we think
    697         // the size of object in memory is irrespective of whether object is live or dead.
    698         dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed());
    699     } else {
    700         dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed());
    701         dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
    702     }
    703 }
    704 
    705 void SkResourceCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
    706     // Since resource could be backed by malloc or discardable, the cache always dumps detailed
    707     // stats to be accurate.
    708     VisitAll(sk_trace_dump_visitor, dump);
    709 }
    710