• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkResourceCache.h"
9 
10 #include "SkDiscardableMemory.h"
11 #include "SkMessageBus.h"
12 #include "SkMipMap.h"
13 #include "SkMutex.h"
14 #include "SkOpts.h"
15 #include "SkTo.h"
16 #include "SkTraceMemoryDump.h"
17 
18 #include <stddef.h>
19 #include <stdlib.h>
20 
DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage)21 DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage)
22 
23 static inline bool SkShouldPostMessageToBus(
24         const SkResourceCache::PurgeSharedIDMessage&, uint32_t) {
25     // SkResourceCache is typically used as a singleton and we don't label Inboxes so all messages
26     // go to all inboxes.
27     return true;
28 }
29 
30 // This can be defined by the caller's build system
31 //#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
32 
33 #ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
34 #   define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT   1024
35 #endif
36 
37 #ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
38     #define SK_DEFAULT_IMAGE_CACHE_LIMIT     (32 * 1024 * 1024)
39 #endif
40 
init(void * nameSpace,uint64_t sharedID,size_t dataSize)41 void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t dataSize) {
42     SkASSERT(SkAlign4(dataSize) == dataSize);
43 
44     // fCount32 and fHash are not hashed
45     static const int kUnhashedLocal32s = 2; // fCache32 + fHash
46     static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi
47     static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2);
48     static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s;
49 
50     static_assert(sizeof(Key) == (kLocal32s << 2), "unaccounted_key_locals");
51     static_assert(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace),
52                  "namespace_field_must_be_last");
53 
54     fCount32 = SkToS32(kLocal32s + (dataSize >> 2));
55     fSharedID_lo = (uint32_t)sharedID;
56     fSharedID_hi = (uint32_t)(sharedID >> 32);
57     fNamespace = nameSpace;
58     // skip unhashed fields when computing the hash
59     fHash = SkOpts::hash(this->as32() + kUnhashedLocal32s,
60                          (fCount32 - kUnhashedLocal32s) << 2);
61 }
62 
63 #include "SkTHash.h"
64 
65 namespace {
66     struct HashTraits {
Hash__anon865da5440111::HashTraits67         static uint32_t Hash(const SkResourceCache::Key& key) { return key.hash(); }
GetKey__anon865da5440111::HashTraits68         static const SkResourceCache::Key& GetKey(const SkResourceCache::Rec* rec) {
69             return rec->getKey();
70         }
71     };
72 }
73 
74 class SkResourceCache::Hash :
75     public SkTHashTable<SkResourceCache::Rec*, SkResourceCache::Key, HashTraits> {};
76 
77 
78 ///////////////////////////////////////////////////////////////////////////////
79 
init()80 void SkResourceCache::init() {
81     fHead = nullptr;
82     fTail = nullptr;
83     fHash = new Hash;
84     fTotalBytesUsed = 0;
85     fCount = 0;
86     fSingleAllocationByteLimit = 0;
87 
88     // One of these should be explicit set by the caller after we return.
89     fTotalByteLimit = 0;
90     fDiscardableFactory = nullptr;
91 }
92 
SkResourceCache(DiscardableFactory factory)93 SkResourceCache::SkResourceCache(DiscardableFactory factory) {
94     this->init();
95     fDiscardableFactory = factory;
96 }
97 
SkResourceCache(size_t byteLimit)98 SkResourceCache::SkResourceCache(size_t byteLimit) {
99     this->init();
100     fTotalByteLimit = byteLimit;
101 }
102 
~SkResourceCache()103 SkResourceCache::~SkResourceCache() {
104     Rec* rec = fHead;
105     while (rec) {
106         Rec* next = rec->fNext;
107         delete rec;
108         rec = next;
109     }
110     delete fHash;
111 }
112 
113 ////////////////////////////////////////////////////////////////////////////////
114 
find(const Key & key,FindVisitor visitor,void * context)115 bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) {
116     this->checkMessages();
117 
118     if (auto found = fHash->find(key)) {
119         Rec* rec = *found;
120         if (visitor(*rec, context)) {
121             this->moveToHead(rec);  // for our LRU
122             return true;
123         } else {
124             this->remove(rec);  // stale
125             return false;
126         }
127     }
128     return false;
129 }
130 
make_size_str(size_t size,SkString * str)131 static void make_size_str(size_t size, SkString* str) {
132     const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 };
133     int i = 0;
134     while (suffix[i] && (size > 1024)) {
135         i += 1;
136         size >>= 10;
137     }
138     str->printf("%zu%c", size, suffix[i]);
139 }
140 
141 static bool gDumpCacheTransactions;
142 
add(Rec * rec,void * payload)143 void SkResourceCache::add(Rec* rec, void* payload) {
144     this->checkMessages();
145 
146     SkASSERT(rec);
147     // See if we already have this key (racy inserts, etc.)
148     if (Rec** preexisting = fHash->find(rec->getKey())) {
149         Rec* prev = *preexisting;
150         if (prev->canBePurged()) {
151             // if it can be purged, the install may fail, so we have to remove it
152             this->remove(prev);
153         } else {
154             // if it cannot be purged, we reuse it and delete the new one
155             prev->postAddInstall(payload);
156             delete rec;
157             return;
158         }
159     }
160 
161     this->addToHead(rec);
162     fHash->set(rec);
163     rec->postAddInstall(payload);
164 
165     if (gDumpCacheTransactions) {
166         SkString bytesStr, totalStr;
167         make_size_str(rec->bytesUsed(), &bytesStr);
168         make_size_str(fTotalBytesUsed, &totalStr);
169         SkDebugf("RC:    add %5s %12p key %08x -- total %5s, count %d\n",
170                  bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
171     }
172 
173     // since the new rec may push us over-budget, we perform a purge check now
174     this->purgeAsNeeded();
175 }
176 
remove(Rec * rec)177 void SkResourceCache::remove(Rec* rec) {
178     SkASSERT(rec->canBePurged());
179     size_t used = rec->bytesUsed();
180     SkASSERT(used <= fTotalBytesUsed);
181 
182     this->release(rec);
183     fHash->remove(rec->getKey());
184 
185     fTotalBytesUsed -= used;
186     fCount -= 1;
187 
188     //SkDebugf("-RC count [%3d] bytes %d\n", fCount, fTotalBytesUsed);
189 
190     if (gDumpCacheTransactions) {
191         SkString bytesStr, totalStr;
192         make_size_str(used, &bytesStr);
193         make_size_str(fTotalBytesUsed, &totalStr);
194         SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n",
195                  bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
196     }
197 
198     delete rec;
199 }
200 
purgeAsNeeded(bool forcePurge)201 void SkResourceCache::purgeAsNeeded(bool forcePurge) {
202     size_t byteLimit;
203     int    countLimit;
204 
205     if (fDiscardableFactory) {
206         countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT;
207         byteLimit = UINT32_MAX;  // no limit based on bytes
208     } else {
209         countLimit = SK_MaxS32; // no limit based on count
210         byteLimit = fTotalByteLimit;
211     }
212 
213     Rec* rec = fTail;
214     while (rec) {
215         if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) {
216             break;
217         }
218 
219         Rec* prev = rec->fPrev;
220         if (rec->canBePurged()) {
221             this->remove(rec);
222         }
223         rec = prev;
224     }
225 }
226 
227 //#define SK_TRACK_PURGE_SHAREDID_HITRATE
228 
229 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
230 static int gPurgeCallCounter;
231 static int gPurgeHitCounter;
232 #endif
233 
purgeSharedID(uint64_t sharedID)234 void SkResourceCache::purgeSharedID(uint64_t sharedID) {
235     if (0 == sharedID) {
236         return;
237     }
238 
239 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
240     gPurgeCallCounter += 1;
241     bool found = false;
242 #endif
243     // go backwards, just like purgeAsNeeded, just to make the code similar.
244     // could iterate either direction and still be correct.
245     Rec* rec = fTail;
246     while (rec) {
247         Rec* prev = rec->fPrev;
248         if (rec->getKey().getSharedID() == sharedID) {
249             // even though the "src" is now dead, caches could still be in-flight, so
250             // we have to check if it can be removed.
251             if (rec->canBePurged()) {
252                 this->remove(rec);
253             }
254 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
255             found = true;
256 #endif
257         }
258         rec = prev;
259     }
260 
261 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
262     if (found) {
263         gPurgeHitCounter += 1;
264     }
265 
266     SkDebugf("PurgeShared calls=%d hits=%d rate=%g\n", gPurgeCallCounter, gPurgeHitCounter,
267              gPurgeHitCounter * 100.0 / gPurgeCallCounter);
268 #endif
269 }
270 
visitAll(Visitor visitor,void * context)271 void SkResourceCache::visitAll(Visitor visitor, void* context) {
272     // go backwards, just like purgeAsNeeded, just to make the code similar.
273     // could iterate either direction and still be correct.
274     Rec* rec = fTail;
275     while (rec) {
276         visitor(*rec, context);
277         rec = rec->fPrev;
278     }
279 }
280 
281 ///////////////////////////////////////////////////////////////////////////////////////////////////
282 
setTotalByteLimit(size_t newLimit)283 size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
284     size_t prevLimit = fTotalByteLimit;
285     fTotalByteLimit = newLimit;
286     if (newLimit < prevLimit) {
287         this->purgeAsNeeded();
288     }
289     return prevLimit;
290 }
291 
newCachedData(size_t bytes)292 SkCachedData* SkResourceCache::newCachedData(size_t bytes) {
293     this->checkMessages();
294 
295     if (fDiscardableFactory) {
296         SkDiscardableMemory* dm = fDiscardableFactory(bytes);
297         return dm ? new SkCachedData(bytes, dm) : nullptr;
298     } else {
299         return new SkCachedData(sk_malloc_throw(bytes), bytes);
300     }
301 }
302 
303 ///////////////////////////////////////////////////////////////////////////////
304 
release(Rec * rec)305 void SkResourceCache::release(Rec* rec) {
306     Rec* prev = rec->fPrev;
307     Rec* next = rec->fNext;
308 
309     if (!prev) {
310         SkASSERT(fHead == rec);
311         fHead = next;
312     } else {
313         prev->fNext = next;
314     }
315 
316     if (!next) {
317         fTail = prev;
318     } else {
319         next->fPrev = prev;
320     }
321 
322     rec->fNext = rec->fPrev = nullptr;
323 }
324 
moveToHead(Rec * rec)325 void SkResourceCache::moveToHead(Rec* rec) {
326     if (fHead == rec) {
327         return;
328     }
329 
330     SkASSERT(fHead);
331     SkASSERT(fTail);
332 
333     this->validate();
334 
335     this->release(rec);
336 
337     fHead->fPrev = rec;
338     rec->fNext = fHead;
339     fHead = rec;
340 
341     this->validate();
342 }
343 
addToHead(Rec * rec)344 void SkResourceCache::addToHead(Rec* rec) {
345     this->validate();
346 
347     rec->fPrev = nullptr;
348     rec->fNext = fHead;
349     if (fHead) {
350         fHead->fPrev = rec;
351     }
352     fHead = rec;
353     if (!fTail) {
354         fTail = rec;
355     }
356     fTotalBytesUsed += rec->bytesUsed();
357     fCount += 1;
358 
359     this->validate();
360 }
361 
362 ///////////////////////////////////////////////////////////////////////////////
363 
364 #ifdef SK_DEBUG
validate() const365 void SkResourceCache::validate() const {
366     if (nullptr == fHead) {
367         SkASSERT(nullptr == fTail);
368         SkASSERT(0 == fTotalBytesUsed);
369         return;
370     }
371 
372     if (fHead == fTail) {
373         SkASSERT(nullptr == fHead->fPrev);
374         SkASSERT(nullptr == fHead->fNext);
375         SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
376         return;
377     }
378 
379     SkASSERT(nullptr == fHead->fPrev);
380     SkASSERT(fHead->fNext);
381     SkASSERT(nullptr == fTail->fNext);
382     SkASSERT(fTail->fPrev);
383 
384     size_t used = 0;
385     int count = 0;
386     const Rec* rec = fHead;
387     while (rec) {
388         count += 1;
389         used += rec->bytesUsed();
390         SkASSERT(used <= fTotalBytesUsed);
391         rec = rec->fNext;
392     }
393     SkASSERT(fCount == count);
394 
395     rec = fTail;
396     while (rec) {
397         SkASSERT(count > 0);
398         count -= 1;
399         SkASSERT(used >= rec->bytesUsed());
400         used -= rec->bytesUsed();
401         rec = rec->fPrev;
402     }
403 
404     SkASSERT(0 == count);
405     SkASSERT(0 == used);
406 }
407 #endif
408 
dump() const409 void SkResourceCache::dump() const {
410     this->validate();
411 
412     SkDebugf("SkResourceCache: count=%d bytes=%d %s\n",
413              fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc");
414 }
415 
setSingleAllocationByteLimit(size_t newLimit)416 size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) {
417     size_t oldLimit = fSingleAllocationByteLimit;
418     fSingleAllocationByteLimit = newLimit;
419     return oldLimit;
420 }
421 
getSingleAllocationByteLimit() const422 size_t SkResourceCache::getSingleAllocationByteLimit() const {
423     return fSingleAllocationByteLimit;
424 }
425 
getEffectiveSingleAllocationByteLimit() const426 size_t SkResourceCache::getEffectiveSingleAllocationByteLimit() const {
427     // fSingleAllocationByteLimit == 0 means the caller is asking for our default
428     size_t limit = fSingleAllocationByteLimit;
429 
430     // if we're not discardable (i.e. we are fixed-budget) then cap the single-limit
431     // to our budget.
432     if (nullptr == fDiscardableFactory) {
433         if (0 == limit) {
434             limit = fTotalByteLimit;
435         } else {
436             limit = SkTMin(limit, fTotalByteLimit);
437         }
438     }
439     return limit;
440 }
441 
checkMessages()442 void SkResourceCache::checkMessages() {
443     SkTArray<PurgeSharedIDMessage> msgs;
444     fPurgeSharedIDInbox.poll(&msgs);
445     for (int i = 0; i < msgs.count(); ++i) {
446         this->purgeSharedID(msgs[i].fSharedID);
447     }
448 }
449 
450 ///////////////////////////////////////////////////////////////////////////////
451 
452 SK_DECLARE_STATIC_MUTEX(gMutex);
453 static SkResourceCache* gResourceCache = nullptr;
454 
455 /** Must hold gMutex when calling. */
get_cache()456 static SkResourceCache* get_cache() {
457     // gMutex is always held when this is called, so we don't need to be fancy in here.
458     gMutex.assertHeld();
459     if (nullptr == gResourceCache) {
460 #ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
461         gResourceCache = new SkResourceCache(SkDiscardableMemory::Create);
462 #else
463         gResourceCache = new SkResourceCache(SK_DEFAULT_IMAGE_CACHE_LIMIT);
464 #endif
465     }
466     return gResourceCache;
467 }
468 
GetTotalBytesUsed()469 size_t SkResourceCache::GetTotalBytesUsed() {
470     SkAutoMutexAcquire am(gMutex);
471     return get_cache()->getTotalBytesUsed();
472 }
473 
GetTotalByteLimit()474 size_t SkResourceCache::GetTotalByteLimit() {
475     SkAutoMutexAcquire am(gMutex);
476     return get_cache()->getTotalByteLimit();
477 }
478 
SetTotalByteLimit(size_t newLimit)479 size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) {
480     SkAutoMutexAcquire am(gMutex);
481     return get_cache()->setTotalByteLimit(newLimit);
482 }
483 
GetDiscardableFactory()484 SkResourceCache::DiscardableFactory SkResourceCache::GetDiscardableFactory() {
485     SkAutoMutexAcquire am(gMutex);
486     return get_cache()->discardableFactory();
487 }
488 
NewCachedData(size_t bytes)489 SkCachedData* SkResourceCache::NewCachedData(size_t bytes) {
490     SkAutoMutexAcquire am(gMutex);
491     return get_cache()->newCachedData(bytes);
492 }
493 
Dump()494 void SkResourceCache::Dump() {
495     SkAutoMutexAcquire am(gMutex);
496     get_cache()->dump();
497 }
498 
SetSingleAllocationByteLimit(size_t size)499 size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) {
500     SkAutoMutexAcquire am(gMutex);
501     return get_cache()->setSingleAllocationByteLimit(size);
502 }
503 
GetSingleAllocationByteLimit()504 size_t SkResourceCache::GetSingleAllocationByteLimit() {
505     SkAutoMutexAcquire am(gMutex);
506     return get_cache()->getSingleAllocationByteLimit();
507 }
508 
GetEffectiveSingleAllocationByteLimit()509 size_t SkResourceCache::GetEffectiveSingleAllocationByteLimit() {
510     SkAutoMutexAcquire am(gMutex);
511     return get_cache()->getEffectiveSingleAllocationByteLimit();
512 }
513 
PurgeAll()514 void SkResourceCache::PurgeAll() {
515     SkAutoMutexAcquire am(gMutex);
516     return get_cache()->purgeAll();
517 }
518 
Find(const Key & key,FindVisitor visitor,void * context)519 bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) {
520     SkAutoMutexAcquire am(gMutex);
521     return get_cache()->find(key, visitor, context);
522 }
523 
Add(Rec * rec,void * payload)524 void SkResourceCache::Add(Rec* rec, void* payload) {
525     SkAutoMutexAcquire am(gMutex);
526     get_cache()->add(rec, payload);
527 }
528 
VisitAll(Visitor visitor,void * context)529 void SkResourceCache::VisitAll(Visitor visitor, void* context) {
530     SkAutoMutexAcquire am(gMutex);
531     get_cache()->visitAll(visitor, context);
532 }
533 
PostPurgeSharedID(uint64_t sharedID)534 void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) {
535     if (sharedID) {
536         SkMessageBus<PurgeSharedIDMessage>::Post(PurgeSharedIDMessage(sharedID));
537     }
538 }
539 
540 ///////////////////////////////////////////////////////////////////////////////
541 
542 #include "SkGraphics.h"
543 #include "SkImageFilter.h"
544 
GetResourceCacheTotalBytesUsed()545 size_t SkGraphics::GetResourceCacheTotalBytesUsed() {
546     return SkResourceCache::GetTotalBytesUsed();
547 }
548 
GetResourceCacheTotalByteLimit()549 size_t SkGraphics::GetResourceCacheTotalByteLimit() {
550     return SkResourceCache::GetTotalByteLimit();
551 }
552 
SetResourceCacheTotalByteLimit(size_t newLimit)553 size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) {
554     return SkResourceCache::SetTotalByteLimit(newLimit);
555 }
556 
GetResourceCacheSingleAllocationByteLimit()557 size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() {
558     return SkResourceCache::GetSingleAllocationByteLimit();
559 }
560 
SetResourceCacheSingleAllocationByteLimit(size_t newLimit)561 size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) {
562     return SkResourceCache::SetSingleAllocationByteLimit(newLimit);
563 }
564 
PurgeResourceCache()565 void SkGraphics::PurgeResourceCache() {
566     SkImageFilter::PurgeCache();
567     return SkResourceCache::PurgeAll();
568 }
569 
570 /////////////
571 
dump_visitor(const SkResourceCache::Rec & rec,void *)572 static void dump_visitor(const SkResourceCache::Rec& rec, void*) {
573     SkDebugf("RC: %12s bytes %9lu  discardable %p\n",
574              rec.getCategory(), rec.bytesUsed(), rec.diagnostic_only_getDiscardable());
575 }
576 
TestDumpMemoryStatistics()577 void SkResourceCache::TestDumpMemoryStatistics() {
578     VisitAll(dump_visitor, nullptr);
579 }
580 
sk_trace_dump_visitor(const SkResourceCache::Rec & rec,void * context)581 static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) {
582     SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context);
583     SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec);
584     SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable();
585     if (discardable) {
586         dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable);
587 
588         // The discardable memory size will be calculated by dumper, but we also dump what we think
589         // the size of object in memory is irrespective of whether object is live or dead.
590         dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed());
591     } else {
592         dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed());
593         dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
594     }
595 }
596 
DumpMemoryStatistics(SkTraceMemoryDump * dump)597 void SkResourceCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
598     // Since resource could be backed by malloc or discardable, the cache always dumps detailed
599     // stats to be accurate.
600     VisitAll(sk_trace_dump_visitor, dump);
601 }
602