| /* |
| * Copyright 2013 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/private/base/SkTemplates.h" |
| #include "include/private/base/SkMalloc.h" |
| #include "include/private/base/SkMutex.h" |
| #include "include/private/chromium/SkDiscardableMemory.h" |
| #include "src/core/SkTInternalLList.h" |
| #include "src/lazy/SkDiscardableMemoryPool.h" |
| |
| using namespace skia_private; |
| |
| // Note: |
| // A PoolDiscardableMemory is memory that is counted in a pool. |
| // A DiscardableMemoryPool is a pool of PoolDiscardableMemorys. |
| |
| namespace { |
| |
| class PoolDiscardableMemory; |
| |
| /** |
| * This non-global pool can be used for unit tests to verify that the |
| * pool works. |
| */ |
| class DiscardableMemoryPool : public SkDiscardableMemoryPool { |
| public: |
| DiscardableMemoryPool(size_t budget); |
| ~DiscardableMemoryPool() override; |
| |
| std::unique_ptr<SkDiscardableMemory> make(size_t bytes); |
| SkDiscardableMemory* create(size_t bytes) override { |
| return this->make(bytes).release(); // TODO: change API |
| } |
| |
| size_t getRAMUsed() override; |
| void setRAMBudget(size_t budget) override; |
| size_t getRAMBudget() override { return fBudget; } |
| |
| /** purges all unlocked DMs */ |
| void dumpPool() override; |
| |
| #if SK_LAZY_CACHE_STATS // Defined in SkDiscardableMemoryPool.h |
| int getCacheHits() override { return fCacheHits; } |
| int getCacheMisses() override { return fCacheMisses; } |
| void resetCacheHitsAndMisses() override { |
| fCacheHits = fCacheMisses = 0; |
| } |
| int fCacheHits; |
| int fCacheMisses; |
| #endif // SK_LAZY_CACHE_STATS |
| |
| private: |
| SkMutex fMutex; |
| size_t fBudget; |
| size_t fUsed; |
| SkTInternalLList<PoolDiscardableMemory> fList; |
| |
| /** Function called to free memory if needed */ |
| void dumpDownTo(size_t budget); |
| /** called by DiscardableMemoryPool upon destruction */ |
| void removeFromPool(PoolDiscardableMemory* dm); |
| /** called by DiscardableMemoryPool::lock() */ |
| bool lock(PoolDiscardableMemory* dm); |
| /** called by DiscardableMemoryPool::unlock() */ |
| void unlock(PoolDiscardableMemory* dm); |
| |
| friend class PoolDiscardableMemory; |
| |
| using INHERITED = SkDiscardableMemory::Factory; |
| }; |
| |
| /** |
| * A PoolDiscardableMemory is a SkDiscardableMemory that relies on |
| * a DiscardableMemoryPool object to manage the memory. |
| */ |
| class PoolDiscardableMemory : public SkDiscardableMemory { |
| public: |
| PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool, UniqueVoidPtr pointer, size_t bytes); |
| ~PoolDiscardableMemory() override; |
| bool lock() override; |
| void* data() override; |
| void unlock() override; |
| friend class DiscardableMemoryPool; |
| private: |
| SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory); |
| sk_sp<DiscardableMemoryPool> fPool; |
| bool fLocked; |
| UniqueVoidPtr fPointer; |
| const size_t fBytes; |
| }; |
| |
| PoolDiscardableMemory::PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool, |
| UniqueVoidPtr pointer, |
| size_t bytes) |
| : fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) { |
| SkASSERT(fPool != nullptr); |
| SkASSERT(fPointer != nullptr); |
| SkASSERT(fBytes > 0); |
| } |
| |
| PoolDiscardableMemory::~PoolDiscardableMemory() { |
| SkASSERT(!fLocked); // contract for SkDiscardableMemory |
| fPool->removeFromPool(this); |
| } |
| |
| bool PoolDiscardableMemory::lock() { |
| SkASSERT(!fLocked); // contract for SkDiscardableMemory |
| return fPool->lock(this); |
| } |
| |
| void* PoolDiscardableMemory::data() { |
| SkASSERT(fLocked); // contract for SkDiscardableMemory |
| return fPointer.get(); |
| } |
| |
| void PoolDiscardableMemory::unlock() { |
| SkASSERT(fLocked); // contract for SkDiscardableMemory |
| fPool->unlock(this); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| DiscardableMemoryPool::DiscardableMemoryPool(size_t budget) |
| : fBudget(budget) |
| , fUsed(0) { |
| #if SK_LAZY_CACHE_STATS |
| fCacheHits = 0; |
| fCacheMisses = 0; |
| #endif // SK_LAZY_CACHE_STATS |
| } |
| DiscardableMemoryPool::~DiscardableMemoryPool() { |
| // PoolDiscardableMemory objects that belong to this pool are |
| // always deleted before deleting this pool since each one has a |
| // ref to the pool. |
| SkASSERT(fList.isEmpty()); |
| } |
| |
| void DiscardableMemoryPool::dumpDownTo(size_t budget) { |
| fMutex.assertHeld(); |
| if (fUsed <= budget) { |
| return; |
| } |
| using Iter = SkTInternalLList<PoolDiscardableMemory>::Iter; |
| Iter iter; |
| PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart); |
| while ((fUsed > budget) && (cur)) { |
| if (!cur->fLocked) { |
| PoolDiscardableMemory* dm = cur; |
| SkASSERT(dm->fPointer != nullptr); |
| dm->fPointer = nullptr; |
| SkASSERT(fUsed >= dm->fBytes); |
| fUsed -= dm->fBytes; |
| cur = iter.prev(); |
| // Purged DMs are taken out of the list. This saves times |
| // looking them up. Purged DMs are NOT deleted. |
| fList.remove(dm); |
| } else { |
| cur = iter.prev(); |
| } |
| } |
| } |
| |
| std::unique_ptr<SkDiscardableMemory> DiscardableMemoryPool::make(size_t bytes) { |
| UniqueVoidPtr addr(sk_malloc_canfail(bytes)); |
| if (nullptr == addr) { |
| return nullptr; |
| } |
| auto dm = std::make_unique<PoolDiscardableMemory>(sk_ref_sp(this), std::move(addr), bytes); |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| fList.addToHead(dm.get()); |
| fUsed += bytes; |
| this->dumpDownTo(fBudget); |
| return std::move(dm); |
| } |
| |
| void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) { |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| // This is called by dm's destructor. |
| if (dm->fPointer != nullptr) { |
| SkASSERT(fUsed >= dm->fBytes); |
| fUsed -= dm->fBytes; |
| fList.remove(dm); |
| } else { |
| SkASSERT(!fList.isInList(dm)); |
| } |
| } |
| |
| bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) { |
| SkASSERT(dm != nullptr); |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| if (nullptr == dm->fPointer) { |
| // May have been purged while waiting for lock. |
| #if SK_LAZY_CACHE_STATS |
| ++fCacheMisses; |
| #endif // SK_LAZY_CACHE_STATS |
| return false; |
| } |
| dm->fLocked = true; |
| fList.remove(dm); |
| fList.addToHead(dm); |
| #if SK_LAZY_CACHE_STATS |
| ++fCacheHits; |
| #endif // SK_LAZY_CACHE_STATS |
| return true; |
| } |
| |
| void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) { |
| SkASSERT(dm != nullptr); |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| dm->fLocked = false; |
| this->dumpDownTo(fBudget); |
| } |
| |
| size_t DiscardableMemoryPool::getRAMUsed() { |
| return fUsed; |
| } |
| void DiscardableMemoryPool::setRAMBudget(size_t budget) { |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| fBudget = budget; |
| this->dumpDownTo(fBudget); |
| } |
| void DiscardableMemoryPool::dumpPool() { |
| SkAutoMutexExclusive autoMutexAcquire(fMutex); |
| this->dumpDownTo(0); |
| } |
| |
| } // namespace |
| |
| sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) { |
| return sk_make_sp<DiscardableMemoryPool>(size); |
| } |
| |
| SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() { |
| // Intentionally leak this global pool. |
| static SkDiscardableMemoryPool* global = |
| new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE); |
| return global; |
| } |