Diff
Modified: trunk/Source/WebCore/ChangeLog (136615 => 136616)
--- trunk/Source/WebCore/ChangeLog 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebCore/ChangeLog 2012-12-05 04:15:31 UTC (rev 136616)
@@ -1,3 +1,56 @@
+2012-12-04 Alpha Lam <[email protected]>
+
+ [chromium] Implement cache eviction logic in ImageDecodingStore
+ https://bugs.webkit.org/show_bug.cgi?id=103480
+
+ Reviewed by James Robinson.
+
+ This patch implements these features in the new image cache:
+ - Least-recently-used eviction logic
+ - Setting cache limit (default is 32MB)
+ - Remove cache entries when ImageFrameGenerator is removed
+
+ New tests in ImageDecodingTestTest. Also covered by platform/chromium/virtual/deferred.
+
+ * platform/graphics/chromium/ImageDecodingStore.cpp:
+ (WebCore::ImageDecodingStore::ImageDecodingStore):
+ (WebCore::ImageDecodingStore::~ImageDecodingStore):
+ Code to assert that everything is cleaned up.
+ (WebCore::ImageDecodingStore::lockCompleteCache):
+ (WebCore::ImageDecodingStore::unlockCache):
+ (WebCore::ImageDecodingStore::insertAndLockCache):
+ (WebCore::ImageDecodingStore::removeCacheIndexedByGenerator):
+ New method to remove all cache entries index by ImageFrameGenerator.
+ (WebCore):
+ (WebCore::ImageDecodingStore::setCacheLimitInBytes):
+ (WebCore::ImageDecodingStore::memoryUsageInBytes):
+ (WebCore::ImageDecodingStore::cacheEntries):
+ (WebCore::ImageDecodingStore::prune):
+ New code to implement LRU eviction logic.
+ (WebCore::ImageDecodingStore::insertCacheInternal):
+ New method to help new cache insertion. This is separately because it touches 3 containers.
+ (WebCore::ImageDecodingStore::removeFromCacheInternal):
+ New method to help remove a cache entry from containers.
+ (WebCore::ImageDecodingStore::removeFromCacheListInternal):
+ New helper method to help remove cache entry from doubly linked list.
+ * platform/graphics/chromium/ImageDecodingStore.h:
+ (ImageDecodingStore):
+ (CacheEntry):
+ (WebCore::ImageDecodingStore::CacheEntry::createAndUse):
+ (WebCore::ImageDecodingStore::CacheEntry::CacheEntry):
+ (WebCore::ImageDecodingStore::CacheEntry::~CacheEntry):
+ (WebCore::ImageDecodingStore::CacheEntry::cacheKey):
+ (WebCore::ImageDecodingStore::CacheEntry::cachedImage):
+ (WebCore::ImageDecodingStore::CacheEntry::useCount):
+ (WebCore::ImageDecodingStore::CacheEntry::incrementUseCount):
+ (WebCore::ImageDecodingStore::CacheEntry::decrementUseCount):
+ (WebCore::ImageDecodingStore::CacheEntry::memoryUsageInBytes):
+ (WebCore::ImageDecodingStore::incrementMemoryUsage):
+ (WebCore::ImageDecodingStore::decrementMemoryUsage):
+ New helper methods.
+ * platform/graphics/chromium/ImageFrameGenerator.cpp:
+ (WebCore::ImageFrameGenerator::~ImageFrameGenerator):
+
2012-12-04 Andreas Kling <[email protected]>
Style sharing: Allow sharing between elements with classes not referenced by any selectors.
Modified: trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.cpp (136615 => 136616)
--- trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.cpp 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.cpp 2012-12-05 04:15:31 UTC (rev 136616)
@@ -34,7 +34,9 @@
namespace {
-ImageDecodingStore* s_instance = 0;
+// 32MB memory limit for cache.
+static const size_t defaultCacheLimitInBytes = 32768 * 1024;
+static ImageDecodingStore* s_instance = 0;
static void setInstance(ImageDecodingStore* imageDecodingStore)
{
@@ -45,11 +47,19 @@
} // namespace
ImageDecodingStore::ImageDecodingStore()
+ : m_cacheLimitInBytes(defaultCacheLimitInBytes)
+ , m_memoryUsageInBytes(0)
{
}
ImageDecodingStore::~ImageDecodingStore()
{
+#ifndef NDEBUG
+ setCacheLimitInBytes(0);
+ ASSERT(!m_cacheMap.size());
+ ASSERT(!m_orderedCacheList.size());
+ ASSERT(!m_cachedSizeMap.size());
+#endif
}
ImageDecodingStore* ImageDecodingStore::instance()
@@ -75,15 +85,15 @@
CacheMap::iterator iter = m_cacheMap.find(std::make_pair(generator, scaledSize));
if (iter == m_cacheMap.end())
return 0;
- cacheEntry = iter->value;
- if (!cacheEntry->cachedImage->isComplete())
+ cacheEntry = iter->value.get();
+ if (!cacheEntry->cachedImage()->isComplete())
return 0;
// Increment use count such that it doesn't get evicted.
- ++cacheEntry->useCount;
+ cacheEntry->incrementUseCount();
}
- cacheEntry->cachedImage->bitmap().lockPixels();
- return cacheEntry->cachedImage.get();
+ cacheEntry->cachedImage()->bitmap().lockPixels();
+ return cacheEntry->cachedImage();
}
const ScaledImageFragment* ImageDecodingStore::lockIncompleteCache(const ImageFrameGenerator* generator, const SkISize& scaledSize)
@@ -105,9 +115,12 @@
CacheMap::iterator iter = m_cacheMap.find(std::make_pair(generator, cachedImage->scaledSize()));
ASSERT(iter != m_cacheMap.end());
- CacheEntry* cacheEntry = iter->value;
- --cacheEntry->useCount;
- ASSERT(cacheEntry->useCount >= 0);
+ CacheEntry* cacheEntry = iter->value.get();
+ cacheEntry->decrementUseCount();
+
+ // Put the entry to the end of list.
+ m_orderedCacheList.remove(cacheEntry);
+ m_orderedCacheList.append(cacheEntry);
}
const ScaledImageFragment* ImageDecodingStore::insertAndLockCache(const ImageFrameGenerator* generator, PassOwnPtr<ScaledImageFragment> image)
@@ -127,23 +140,125 @@
}
ScaledImageFragment* cachedImage = image.get();
- OwnPtr<CacheEntry> newCacheEntry = CacheEntry::createAndUse(image);
+ OwnPtr<CacheEntry> newCacheEntry = CacheEntry::createAndUse(generator, image);
- CacheIdentifier key = std::make_pair(generator, cachedImage->scaledSize());
MutexLocker lock(m_mutex);
- ASSERT(!m_cacheMap.contains(key));
-
- // m_cacheMap is used for indexing and quick lookup of a cached image.
- // m_cacheEntries is used to support LRU operations to reorder cache
- // entries quickly. It also owns cached images.
- m_cacheMap.add(key, newCacheEntry.get());
- m_cacheEntries.append(newCacheEntry.release());
+ ASSERT(!m_cacheMap.contains(newCacheEntry->cacheKey()));
+ insertCacheInternal(newCacheEntry.release());
return cachedImage;
}
+void ImageDecodingStore::removeCacheIndexedByGenerator(const ImageFrameGenerator* generator)
+{
+ Vector<OwnPtr<CacheEntry> > cacheEntriesToDelete;
+ {
+ MutexLocker lock(m_mutex);
+ CachedSizeMap::iterator iter = m_cachedSizeMap.find(generator);
+ if (iter == m_cachedSizeMap.end())
+ return;
+
+ // Get all cached sizes indexed by generator.
+ Vector<SkISize> cachedSizeList;
+ copyToVector(iter->value, cachedSizeList);
+
+ // For each cached size find the corresponding CacheEntry and remove it from
+ // m_cacheMap.
+ for (size_t i = 0; i < cachedSizeList.size(); ++i) {
+ CacheIdentifier key = std::make_pair(generator, cachedSizeList[i]);
+ ASSERT(m_cacheMap.contains(key));
+
+ const CacheEntry* cacheEntry = m_cacheMap.get(key);
+ ASSERT(!cacheEntry->useCount());
+ removeFromCacheInternal(cacheEntry, &cacheEntriesToDelete);
+ }
+
+ // Remove from cache list as well.
+ removeFromCacheListInternal(cacheEntriesToDelete);
+ }
+}
+
+void ImageDecodingStore::setCacheLimitInBytes(size_t cacheLimit)
+{
+ {
+ MutexLocker lock(m_mutex);
+ m_cacheLimitInBytes = cacheLimit;
+ }
+ prune();
+}
+
+size_t ImageDecodingStore::memoryUsageInBytes()
+{
+ MutexLocker lock(m_mutex);
+ return m_memoryUsageInBytes;
+}
+
+unsigned ImageDecodingStore::cacheEntries()
+{
+ MutexLocker lock(m_mutex);
+ return m_cacheMap.size();
+}
+
void ImageDecodingStore::prune()
{
- // TODO: Implement.
+ Vector<OwnPtr<CacheEntry> > cacheEntriesToDelete;
+ {
+ MutexLocker lock(m_mutex);
+
+ // Head of the list is the least recently used entry.
+ const CacheEntry* cacheEntry = m_orderedCacheList.head();
+
+ // Walk the list of cache entries starting from the least recently used
+ // and then keep them for deletion later.
+ while (cacheEntry && m_memoryUsageInBytes > m_cacheLimitInBytes) {
+ // Cache is not used; Remove it.
+ if (!cacheEntry->useCount())
+ removeFromCacheInternal(cacheEntry, &cacheEntriesToDelete);
+ cacheEntry = cacheEntry->next();
+ }
+
+ // Remove from cache list as well.
+ removeFromCacheListInternal(cacheEntriesToDelete);
+ }
}
+void ImageDecodingStore::insertCacheInternal(PassOwnPtr<CacheEntry> cacheEntry)
+{
+ incrementMemoryUsage(cacheEntry->memoryUsageInBytes());
+
+ // m_orderedCacheList is used to support LRU operations to reorder cache
+ // entries quickly.
+ m_orderedCacheList.append(cacheEntry.get());
+
+ CacheIdentifier key = cacheEntry->cacheKey();
+ // m_cacheMap is used for indexing and quick lookup of a cached image. It owns
+ // all cache entries.
+ m_cacheMap.add(key, cacheEntry);
+
+ // m_cachedSizeMap keeps all scaled sizes associated with an ImageFrameGenerator.
+ CachedSizeMap::AddResult result = m_cachedSizeMap.add(key.first, SizeSet());
+ result.iterator->value.add(key.second);
+}
+
+void ImageDecodingStore::removeFromCacheInternal(const CacheEntry* cacheEntry, Vector<OwnPtr<CacheEntry> >* deletionList)
+{
+ decrementMemoryUsage(cacheEntry->memoryUsageInBytes());
+
+ // Remove from m_cacheMap.
+ CacheIdentifier key = cacheEntry->cacheKey();
+ deletionList->append(m_cacheMap.take(key));
+
+ // Remove from m_cachedSizeMap.
+ CachedSizeMap::iterator iter = m_cachedSizeMap.find(key.first);
+ ASSERT(iter != m_cachedSizeMap.end());
+ iter->value.remove(key.second);
+ if (!iter->value.size())
+ m_cachedSizeMap.remove(iter);
+}
+
+void ImageDecodingStore::removeFromCacheListInternal(const Vector<OwnPtr<CacheEntry> >& deletionList)
+{
+ for (size_t i = 0; i < deletionList.size(); ++i)
+ m_orderedCacheList.remove(deletionList[i].get());
+}
+
} // namespace WebCore
Modified: trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.h (136615 => 136616)
--- trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.h 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebCore/platform/graphics/chromium/ImageDecodingStore.h 2012-12-05 04:15:31 UTC (rev 136616)
@@ -31,6 +31,8 @@
#include "SkSize.h"
#include "SkSizeHash.h"
+#include <wtf/DoublyLinkedList.h>
+#include <wtf/HashSet.h>
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/ThreadingPrimitives.h>
@@ -56,42 +58,91 @@
void unlockCache(const ImageFrameGenerator*, const ScaledImageFragment*);
const ScaledImageFragment* insertAndLockCache(const ImageFrameGenerator*, PassOwnPtr<ScaledImageFragment>);
+ // Remove all cache entries indexed by ImageFrameGenerator.
+ void removeCacheIndexedByGenerator(const ImageFrameGenerator*);
+
+ void setCacheLimitInBytes(size_t);
+ size_t memoryUsageInBytes();
+ unsigned cacheEntries();
+
private:
- struct CacheEntry {
- static PassOwnPtr<CacheEntry> create() { return adoptPtr(new CacheEntry()); }
- static PassOwnPtr<CacheEntry> createAndUse(PassOwnPtr<ScaledImageFragment> image) { return adoptPtr(new CacheEntry(image, 1)); }
+ typedef std::pair<const ImageFrameGenerator*, SkISize> CacheIdentifier;
- CacheEntry()
- : useCount(0)
+ class CacheEntry : public DoublyLinkedListNode<CacheEntry> {
+ friend class DoublyLinkedListNode<CacheEntry>;
+ public:
+ static PassOwnPtr<CacheEntry> createAndUse(const ImageFrameGenerator* generator, PassOwnPtr<ScaledImageFragment> image)
{
+ return adoptPtr(new CacheEntry(generator, image, 1));
}
- CacheEntry(PassOwnPtr<ScaledImageFragment> image, int count)
- : cachedImage(image)
- , useCount(count)
+ CacheEntry(const ImageFrameGenerator* generator, PassOwnPtr<ScaledImageFragment> image, int count)
+ : m_prev(0)
+ , m_next(0)
+ , m_generator(generator)
+ , m_cachedImage(image)
+ , m_useCount(count)
{
}
~CacheEntry()
{
- ASSERT(!useCount);
+ ASSERT(!m_useCount);
}
- OwnPtr<ScaledImageFragment> cachedImage;
- int useCount;
+ CacheIdentifier cacheKey() const { return std::make_pair(m_generator, m_cachedImage->scaledSize()); }
+ const ScaledImageFragment* cachedImage() const { return m_cachedImage.get(); }
+ int useCount() const { return m_useCount; }
+ void incrementUseCount() { ++m_useCount; }
+ void decrementUseCount() { --m_useCount; ASSERT(m_useCount >= 0); }
+
+ // FIXME: getSafeSize() returns size in bytes truncated to a 32-bits integer.
+ // Find a way to get the size in 64-bits.
+ size_t memoryUsageInBytes() const { return cachedImage()->bitmap().getSafeSize(); }
+
+ private:
+ CacheEntry* m_prev;
+ CacheEntry* m_next;
+ const ImageFrameGenerator* m_generator;
+ OwnPtr<ScaledImageFragment> m_cachedImage;
+ int m_useCount;
};
ImageDecodingStore();
+
void prune();
- Vector<OwnPtr<CacheEntry> > m_cacheEntries;
+ // These methods are called while m_mutex is locked.
+ void insertCacheInternal(PassOwnPtr<CacheEntry>);
+ void removeFromCacheInternal(const CacheEntry*, Vector<OwnPtr<CacheEntry> >* deletionList);
+ void removeFromCacheListInternal(const Vector<OwnPtr<CacheEntry> >& deletionList);
+ void incrementMemoryUsage(size_t size) { m_memoryUsageInBytes += size; }
+ void decrementMemoryUsage(size_t size)
+ {
+ ASSERT(m_memoryUsageInBytes >= size);
+ m_memoryUsageInBytes -= size;
+ }
- typedef std::pair<const ImageFrameGenerator*, SkISize> CacheIdentifier;
- typedef HashMap<CacheIdentifier, CacheEntry*> CacheMap;
+ // Head of this list is the least recently used cache entry.
+ // Tail of this list is the most recently used cache entry.
+ DoublyLinkedList<CacheEntry> m_orderedCacheList;
+
+ typedef HashMap<CacheIdentifier, OwnPtr<CacheEntry> > CacheMap;
CacheMap m_cacheMap;
- // Protect concurrent access to all instances of CacheEntry stored in m_cacheEntries and
- // m_cacheMap as well as the containers.
+ typedef HashSet<SkISize> SizeSet;
+ typedef HashMap<const ImageFrameGenerator*, SizeSet> CachedSizeMap;
+ CachedSizeMap m_cachedSizeMap;
+
+ size_t m_cacheLimitInBytes;
+ size_t m_memoryUsageInBytes;
+
+ // Protect concurrent access to these members:
+ // m_orderedCacheList
+ // m_cacheMap and all CacheEntrys stored in it
+ // m_cachedSizeMap
+ // m_cacheLimitInBytes
+ // m_memoryUsageInBytes
Mutex m_mutex;
};
Modified: trunk/Source/WebCore/platform/graphics/chromium/ImageFrameGenerator.cpp (136615 => 136616)
--- trunk/Source/WebCore/platform/graphics/chromium/ImageFrameGenerator.cpp 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebCore/platform/graphics/chromium/ImageFrameGenerator.cpp 2012-12-05 04:15:31 UTC (rev 136616)
@@ -54,8 +54,11 @@
ImageFrameGenerator::~ImageFrameGenerator()
{
- // TODO: Call ImageDecodingStore to remove all cache entries indexed
- // by this object.
+ // FIXME: This check is not really thread-safe. This should be changed to:
+ // ImageDecodingStore::removeCacheFromInstance(this);
+ // Which uses a lock internally.
+ if (ImageDecodingStore::instance())
+ ImageDecodingStore::instance()->removeCacheIndexedByGenerator(this);
}
void ImageFrameGenerator::setData(PassRefPtr<SharedBuffer> data, bool allDataReceived)
Modified: trunk/Source/WebKit/chromium/ChangeLog (136615 => 136616)
--- trunk/Source/WebKit/chromium/ChangeLog 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebKit/chromium/ChangeLog 2012-12-05 04:15:31 UTC (rev 136616)
@@ -1,3 +1,49 @@
+2012-12-04 Alpha Lam <[email protected]>
+
+ [chromium] Implement cache eviction logic in ImageDecodingStore
+ https://bugs.webkit.org/show_bug.cgi?id=103480
+
+ Reviewed by James Robinson.
+
+ Added ImageDecodingStoreTest for unit testing new image cache.
+
+ New tests:
+
+ ImageDecodingStoreTest.evictOneCache
+
+ Test eviction of one cache entry by setting a smaller cache limit.
+
+ ImageDecodingStoreTest.pruneOrderIsLeastRecentlyUsed
+
+ Test eviction order is least recently used.
+
+ ImageDecodingStoreTest.pruneCausedByInsertion
+
+ Test that eviction is triggered also by cache insertion.
+
+ ImageDecodingStoreTest.cacheInUseNotEvicted
+
+ Test that cache in use is not evicted.
+
+ ImageDecodingStoreTest.destroyImageFrameGenerator
+
+ Test that deleting a ImageFrameGenerator causes associated cache
+ entries to be removed.
+
+ * WebKit.gypi:
+ * tests/ImageDecodingStoreTest.cpp: Added.
+ (WebCore):
+ (ImageDecodingStoreTest):
+ (WebCore::ImageDecodingStoreTest::SetUp):
+ (WebCore::ImageDecodingStoreTest::TearDown):
+ (WebCore::ImageDecodingStoreTest::createCompleteImage):
+ (WebCore::ImageDecodingStoreTest::insertCache):
+ (WebCore::ImageDecodingStoreTest::lockCache):
+ (WebCore::ImageDecodingStoreTest::unlockCache):
+ (WebCore::ImageDecodingStoreTest::evictOneCache):
+ (WebCore::ImageDecodingStoreTest::isCacheAlive):
+ (WebCore::TEST_F):
+
2012-12-04 Kenneth Russell <[email protected]>
Associate URLs with GraphicsContext3D instances created for WebGL
Modified: trunk/Source/WebKit/chromium/WebKit.gypi (136615 => 136616)
--- trunk/Source/WebKit/chromium/WebKit.gypi 2012-12-05 04:05:33 UTC (rev 136615)
+++ trunk/Source/WebKit/chromium/WebKit.gypi 2012-12-05 04:15:31 UTC (rev 136616)
@@ -81,6 +81,7 @@
'tests/IDBKeyPathTest.cpp',
'tests/IDBLevelDBCodingTest.cpp',
'tests/IDBRequestTest.cpp',
+ 'tests/ImageDecodingStoreTest.cpp',
'tests/ImageFrameGeneratorTest.cpp',
'tests/ImageLayerChromiumTest.cpp',
'tests/MockImageDecoder.h',
Added: trunk/Source/WebKit/chromium/tests/ImageDecodingStoreTest.cpp (0 => 136616)
--- trunk/Source/WebKit/chromium/tests/ImageDecodingStoreTest.cpp (rev 0)
+++ trunk/Source/WebKit/chromium/tests/ImageDecodingStoreTest.cpp 2012-12-05 04:15:31 UTC (rev 136616)
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "ImageDecodingStore.h"
+
+#include "ImageFrameGenerator.h"
+#include "SharedBuffer.h"
+#include <gtest/gtest.h>
+
+using namespace WebCore;
+
+namespace {
+
+class ImageDecodingStoreTest : public ::testing::Test {
+public:
+ virtual void SetUp()
+ {
+ ImageDecodingStore::initializeOnce();
+ m_data = SharedBuffer::create();
+ m_generator = ImageFrameGenerator::create(SkISize::Make(100, 100), m_data, true);
+ }
+
+ virtual void TearDown()
+ {
+ ImageDecodingStore::shutdown();
+ }
+
+protected:
+ PassOwnPtr<ScaledImageFragment> createCompleteImage(const SkISize& size)
+ {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ bitmap.allocPixels();
+ return ScaledImageFragment::create(size, bitmap, true);
+ }
+
+ void insertCache(const SkISize& size)
+ {
+ const ScaledImageFragment* image = ImageDecodingStore::instance()->insertAndLockCache(
+ m_generator.get(), createCompleteImage(size));
+ unlockCache(image);
+ }
+
+ const ScaledImageFragment* lockCache(const SkISize& size)
+ {
+ return ImageDecodingStore::instance()->lockCompleteCache(m_generator.get(), size);
+ }
+
+ void unlockCache(const ScaledImageFragment* cachedImage)
+ {
+ ImageDecodingStore::instance()->unlockCache(m_generator.get(), cachedImage);
+ }
+
+ void evictOneCache()
+ {
+ size_t memoryUsageInBytes = ImageDecodingStore::instance()->memoryUsageInBytes();
+ if (memoryUsageInBytes)
+ ImageDecodingStore::instance()->setCacheLimitInBytes(memoryUsageInBytes - 1);
+ else
+ ImageDecodingStore::instance()->setCacheLimitInBytes(0);
+ }
+
+ bool isCacheAlive(const SkISize& size)
+ {
+ const ScaledImageFragment* cachedImage = lockCache(size);
+ if (!cachedImage)
+ return false;
+ ImageDecodingStore::instance()->unlockCache(m_generator.get(), cachedImage);
+ return true;
+ }
+
+ RefPtr<SharedBuffer> m_data;
+ RefPtr<ImageFrameGenerator> m_generator;
+};
+
+TEST_F(ImageDecodingStoreTest, evictOneCache)
+{
+ insertCache(SkISize::Make(1, 1));
+ insertCache(SkISize::Make(2, 2));
+ insertCache(SkISize::Make(3, 3));
+ EXPECT_EQ(3u, ImageDecodingStore::instance()->cacheEntries());
+
+ evictOneCache();
+ EXPECT_EQ(2u, ImageDecodingStore::instance()->cacheEntries());
+
+ evictOneCache();
+ EXPECT_EQ(1u, ImageDecodingStore::instance()->cacheEntries());
+}
+
+TEST_F(ImageDecodingStoreTest, pruneOrderIsLeastRecentlyUsed)
+{
+ insertCache(SkISize::Make(1, 1));
+ insertCache(SkISize::Make(2, 2));
+ insertCache(SkISize::Make(3, 3));
+ insertCache(SkISize::Make(4, 4));
+ insertCache(SkISize::Make(5, 5));
+ EXPECT_EQ(5u, ImageDecodingStore::instance()->cacheEntries());
+
+ // Use cache in the order 3, 2, 4, 1, 5.
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(3, 3)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(2, 2)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(4, 4)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(1, 1)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(5, 5)));
+
+ // Evict 3.
+ evictOneCache();
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(3, 3)));
+ EXPECT_EQ(4u, ImageDecodingStore::instance()->cacheEntries());
+
+ // Evict 2.
+ evictOneCache();
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(2, 2)));
+ EXPECT_EQ(3u, ImageDecodingStore::instance()->cacheEntries());
+
+ // Evict 4.
+ evictOneCache();
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(4, 4)));
+ EXPECT_EQ(2u, ImageDecodingStore::instance()->cacheEntries());
+
+ // Evict 1.
+ evictOneCache();
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(1, 1)));
+ EXPECT_EQ(1u, ImageDecodingStore::instance()->cacheEntries());
+
+ // Evict 5.
+ evictOneCache();
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(5, 5)));
+ EXPECT_EQ(0u, ImageDecodingStore::instance()->cacheEntries());
+}
+
+TEST_F(ImageDecodingStoreTest, pruneCausedByInsertion)
+{
+ ImageDecodingStore::instance()->setCacheLimitInBytes(100);
+
+ // Insert 100 entries.
+ // Cache entries stored should increase and eventually decrease to 1.
+ insertCache(SkISize::Make(1, 1));
+ insertCache(SkISize::Make(2, 2));
+ insertCache(SkISize::Make(3, 3));
+ EXPECT_EQ(3u, ImageDecodingStore::instance()->cacheEntries());
+
+ for (int i = 4; i <= 100; ++i)
+ insertCache(SkISize::Make(i, i));
+
+ EXPECT_EQ(1u, ImageDecodingStore::instance()->cacheEntries());
+ for (int i = 1; i <= 99; ++i)
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(i, i)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(100, 100)));
+}
+
+TEST_F(ImageDecodingStoreTest, cacheInUseNotEvicted)
+{
+ insertCache(SkISize::Make(1, 1));
+ insertCache(SkISize::Make(2, 2));
+ insertCache(SkISize::Make(3, 3));
+ EXPECT_EQ(3u, ImageDecodingStore::instance()->cacheEntries());
+
+ const ScaledImageFragment* cachedImage = lockCache(SkISize::Make(1, 1));
+ EXPECT_TRUE(cachedImage);
+
+ // Cache 2 is evicted because cache 1 is in use.
+ evictOneCache();
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(1, 1)));
+ EXPECT_FALSE(isCacheAlive(SkISize::Make(2, 2)));
+ EXPECT_TRUE(isCacheAlive(SkISize::Make(3, 3)));
+
+ EXPECT_EQ(2u, ImageDecodingStore::instance()->cacheEntries());
+ unlockCache(cachedImage);
+}
+
+TEST_F(ImageDecodingStoreTest, destroyImageFrameGenerator)
+{
+ insertCache(SkISize::Make(1, 1));
+ insertCache(SkISize::Make(2, 2));
+ insertCache(SkISize::Make(3, 3));
+ EXPECT_EQ(3u, ImageDecodingStore::instance()->cacheEntries());
+
+ m_generator.clear();
+ EXPECT_FALSE(ImageDecodingStore::instance()->cacheEntries());
+}
+
+} // namespace