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 "SkDiscardableMemoryPool.h"
10#include "SkImageGenerator.h"
11#include "SkMalloc.h"
12#include "SkMutex.h"
13#include "SkOnce.h"
14#include "SkTInternalLList.h"
15
16// Note:
17// A PoolDiscardableMemory is memory that is counted in a pool.
18// A DiscardableMemoryPool is a pool of PoolDiscardableMemorys.
19
20namespace {
21
22class PoolDiscardableMemory;
23
24/**
25 *  This non-global pool can be used for unit tests to verify that the
26 *  pool works.
27 */
28class DiscardableMemoryPool : public SkDiscardableMemoryPool {
29public:
30    /**
31     *  Without mutex, will be not be thread safe.
32     */
33    DiscardableMemoryPool(size_t budget, SkBaseMutex* mutex = nullptr);
34    ~DiscardableMemoryPool() override;
35
36    SkDiscardableMemory* create(size_t bytes) override;
37
38    size_t getRAMUsed() override;
39    void setRAMBudget(size_t budget) override;
40    size_t getRAMBudget() override { return fBudget; }
41
42    /** purges all unlocked DMs */
43    void dumpPool() override;
44
45    #if SK_LAZY_CACHE_STATS  // Defined in SkDiscardableMemoryPool.h
46    int getCacheHits() override { return fCacheHits; }
47    int getCacheMisses() override { return fCacheMisses; }
48    void resetCacheHitsAndMisses() override {
49        fCacheHits = fCacheMisses = 0;
50    }
51    int          fCacheHits;
52    int          fCacheMisses;
53    #endif  // SK_LAZY_CACHE_STATS
54
55private:
56    SkBaseMutex* fMutex;
57    size_t       fBudget;
58    size_t       fUsed;
59    SkTInternalLList<PoolDiscardableMemory> fList;
60
61    /** Function called to free memory if needed */
62    void dumpDownTo(size_t budget);
63    /** called by DiscardableMemoryPool upon destruction */
64    void free(PoolDiscardableMemory* dm);
65    /** called by DiscardableMemoryPool::lock() */
66    bool lock(PoolDiscardableMemory* dm);
67    /** called by DiscardableMemoryPool::unlock() */
68    void unlock(PoolDiscardableMemory* dm);
69
70    friend class PoolDiscardableMemory;
71
72    typedef SkDiscardableMemory::Factory INHERITED;
73};
74
75/**
76 *  A PoolDiscardableMemory is a SkDiscardableMemory that relies on
77 *  a DiscardableMemoryPool object to manage the memory.
78 */
79class PoolDiscardableMemory : public SkDiscardableMemory {
80public:
81    PoolDiscardableMemory(DiscardableMemoryPool* pool,
82                            void* pointer, size_t bytes);
83    ~PoolDiscardableMemory() override;
84    bool lock() override;
85    void* data() override;
86    void unlock() override;
87    friend class DiscardableMemoryPool;
88private:
89    SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory);
90    DiscardableMemoryPool* const fPool;
91    bool                         fLocked;
92    void*                        fPointer;
93    const size_t                 fBytes;
94};
95
96PoolDiscardableMemory::PoolDiscardableMemory(DiscardableMemoryPool* pool,
97                                             void* pointer,
98                                             size_t bytes)
99    : fPool(pool)
100    , fLocked(true)
101    , fPointer(pointer)
102    , fBytes(bytes) {
103    SkASSERT(fPool != nullptr);
104    SkASSERT(fPointer != nullptr);
105    SkASSERT(fBytes > 0);
106    fPool->ref();
107}
108
109PoolDiscardableMemory::~PoolDiscardableMemory() {
110    SkASSERT(!fLocked); // contract for SkDiscardableMemory
111    fPool->free(this);
112    fPool->unref();
113}
114
115bool PoolDiscardableMemory::lock() {
116    SkASSERT(!fLocked); // contract for SkDiscardableMemory
117    return fPool->lock(this);
118}
119
120void* PoolDiscardableMemory::data() {
121    SkASSERT(fLocked); // contract for SkDiscardableMemory
122    return fPointer;
123}
124
125void PoolDiscardableMemory::unlock() {
126    SkASSERT(fLocked); // contract for SkDiscardableMemory
127    fPool->unlock(this);
128}
129
130////////////////////////////////////////////////////////////////////////////////
131
132DiscardableMemoryPool::DiscardableMemoryPool(size_t budget,
133                                             SkBaseMutex* mutex)
134    : fMutex(mutex)
135    , fBudget(budget)
136    , fUsed(0) {
137    #if SK_LAZY_CACHE_STATS
138    fCacheHits = 0;
139    fCacheMisses = 0;
140    #endif  // SK_LAZY_CACHE_STATS
141}
142DiscardableMemoryPool::~DiscardableMemoryPool() {
143    // PoolDiscardableMemory objects that belong to this pool are
144    // always deleted before deleting this pool since each one has a
145    // ref to the pool.
146    SkASSERT(fList.isEmpty());
147}
148
149void DiscardableMemoryPool::dumpDownTo(size_t budget) {
150    if (fMutex != nullptr) {
151        fMutex->assertHeld();
152    }
153    if (fUsed <= budget) {
154        return;
155    }
156    typedef SkTInternalLList<PoolDiscardableMemory>::Iter Iter;
157    Iter iter;
158    PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart);
159    while ((fUsed > budget) && (cur)) {
160        if (!cur->fLocked) {
161            PoolDiscardableMemory* dm = cur;
162            SkASSERT(dm->fPointer != nullptr);
163            sk_free(dm->fPointer);
164            dm->fPointer = nullptr;
165            SkASSERT(fUsed >= dm->fBytes);
166            fUsed -= dm->fBytes;
167            cur = iter.prev();
168            // Purged DMs are taken out of the list.  This saves times
169            // looking them up.  Purged DMs are NOT deleted.
170            fList.remove(dm);
171        } else {
172            cur = iter.prev();
173        }
174    }
175}
176
177SkDiscardableMemory* DiscardableMemoryPool::create(size_t bytes) {
178    void* addr = sk_malloc_flags(bytes, 0);
179    if (nullptr == addr) {
180        return nullptr;
181    }
182    PoolDiscardableMemory* dm = new PoolDiscardableMemory(this, addr, bytes);
183    SkAutoMutexAcquire autoMutexAcquire(fMutex);
184    fList.addToHead(dm);
185    fUsed += bytes;
186    this->dumpDownTo(fBudget);
187    return dm;
188}
189
190void DiscardableMemoryPool::free(PoolDiscardableMemory* dm) {
191    SkAutoMutexAcquire autoMutexAcquire(fMutex);
192    // This is called by dm's destructor.
193    if (dm->fPointer != nullptr) {
194        sk_free(dm->fPointer);
195        dm->fPointer = nullptr;
196        SkASSERT(fUsed >= dm->fBytes);
197        fUsed -= dm->fBytes;
198        fList.remove(dm);
199    } else {
200        SkASSERT(!fList.isInList(dm));
201    }
202}
203
204bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) {
205    SkASSERT(dm != nullptr);
206    SkAutoMutexAcquire autoMutexAcquire(fMutex);
207    if (nullptr == dm->fPointer) {
208        // May have been purged while waiting for lock.
209        #if SK_LAZY_CACHE_STATS
210        ++fCacheMisses;
211        #endif  // SK_LAZY_CACHE_STATS
212        return false;
213    }
214    dm->fLocked = true;
215    fList.remove(dm);
216    fList.addToHead(dm);
217    #if SK_LAZY_CACHE_STATS
218    ++fCacheHits;
219    #endif  // SK_LAZY_CACHE_STATS
220    return true;
221}
222
223void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
224    SkASSERT(dm != nullptr);
225    SkAutoMutexAcquire autoMutexAcquire(fMutex);
226    dm->fLocked = false;
227    this->dumpDownTo(fBudget);
228}
229
230size_t DiscardableMemoryPool::getRAMUsed() {
231    return fUsed;
232}
233void DiscardableMemoryPool::setRAMBudget(size_t budget) {
234    SkAutoMutexAcquire autoMutexAcquire(fMutex);
235    fBudget = budget;
236    this->dumpDownTo(fBudget);
237}
238void DiscardableMemoryPool::dumpPool() {
239    SkAutoMutexAcquire autoMutexAcquire(fMutex);
240    this->dumpDownTo(0);
241}
242
243}  // namespace
244
245SkDiscardableMemoryPool* SkDiscardableMemoryPool::Create(size_t size, SkBaseMutex* mutex) {
246    return new DiscardableMemoryPool(size, mutex);
247}
248
249SK_DECLARE_STATIC_MUTEX(gMutex);
250
251SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() {
252    static SkOnce once;
253    static SkDiscardableMemoryPool* global;
254    once([]{
255        global = SkDiscardableMemoryPool::Create(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE,
256                                                 &gMutex);
257    });
258    return global;
259}
260