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