diff --git a/bench/ImageCacheBench.cpp b/bench/ImageCacheBench.cpp index 5f1715fc31..e65d1fc3e8 100644 --- a/bench/ImageCacheBench.cpp +++ b/bench/ImageCacheBench.cpp @@ -37,7 +37,7 @@ protected: } virtual void onDraw(const int loops, SkCanvas*) SK_OVERRIDE { - if (fCache.getBytesUsed() == 0) { + if (fCache.getTotalBytesUsed() == 0) { this->populateCache(); } diff --git a/gyp/tests.gypi b/gyp/tests.gypi index 99a52a4b57..6a6447963f 100644 --- a/gyp/tests.gypi +++ b/gyp/tests.gypi @@ -162,6 +162,7 @@ '../tests/SHA1Test.cpp', '../tests/SListTest.cpp', '../tests/ScalarTest.cpp', + '../tests/ScaledImageCache.cpp', '../tests/SerializationTest.cpp', '../tests/ShaderImageFilterTest.cpp', '../tests/ShaderOpacityTest.cpp', diff --git a/include/core/SkGraphics.h b/include/core/SkGraphics.h index 2667a388d2..e7865ca5af 100644 --- a/include/core/SkGraphics.h +++ b/include/core/SkGraphics.h @@ -79,9 +79,47 @@ public: */ static void PurgeFontCache(); - static size_t GetImageCacheBytesUsed(); - static size_t GetImageCacheByteLimit(); - static size_t SetImageCacheByteLimit(size_t newLimit); + /** + * Scaling bitmaps with the SkPaint::kHigh_FilterLevel setting is + * expensive, so the result is saved in the global Scaled Image + * Cache. + * + * This function returns the memory usage of the Scaled Image Cache. + */ + static size_t GetImageCacheTotalBytesUsed(); + /** + * These functions get/set the memory usage limit for the Scaled + * Image Cache. Bitmaps are purged from the cache when the + * memory useage exceeds this limit. + */ + static size_t GetImageCacheTotalByteLimit(); + static size_t SetImageCacheTotalByteLimit(size_t newLimit); + + // DEPRECATED + static size_t GetImageCacheBytesUsed() { + return GetImageCacheTotalBytesUsed(); + } + // DEPRECATED + static size_t GetImageCacheByteLimit() { + return GetImageCacheTotalByteLimit(); + } + // DEPRECATED + static size_t SetImageCacheByteLimit(size_t newLimit) { + return SetImageCacheTotalByteLimit(newLimit); + } + + /** + * Scaling bitmaps with the SkPaint::kHigh_FilterLevel setting is + * expensive, so the result is saved in the global Scaled Image + * Cache. When the resulting bitmap is too large, this can + * overload the cache. If the ImageCacheSingleAllocationByteLimit + * is set to a non-zero number, and the resulting bitmap would be + * larger than that value, the bitmap scaling algorithm falls + * back onto a cheaper algorithm and does not cache the result. + * Zero is the default value. + */ + static size_t GetImageCacheSingleAllocationByteLimit(); + static size_t SetImageCacheSingleAllocationByteLimit(size_t newLimit); /** * Applications with command line options may pass optional state, such diff --git a/src/core/SkBitmapProcState.cpp b/src/core/SkBitmapProcState.cpp index f8ab8ab351..d48679e8c9 100644 --- a/src/core/SkBitmapProcState.cpp +++ b/src/core/SkBitmapProcState.cpp @@ -127,6 +127,21 @@ private: }; #define AutoScaledCacheUnlocker(...) SK_REQUIRE_LOCAL_VAR(AutoScaledCacheUnlocker) +// Check to see that the size of the bitmap that would be produced by +// scaling by the given inverted matrix is less than the maximum allowed. +static inline bool cache_size_okay(const SkBitmap& bm, const SkMatrix& invMat) { + size_t maximumAllocation + = SkScaledImageCache::GetSingleAllocationByteLimit(); + if (0 == maximumAllocation) { + return true; + } + // float matrixScaleFactor = 1.0 / (invMat.scaleX * invMat.scaleY); + // return ((origBitmapSize * matrixScaleFactor) < maximumAllocationSize); + // Skip the division step: + return bm.info().getSafeSize(bm.info().minRowBytes()) + < (maximumAllocation * invMat.getScaleX() * invMat.getScaleY()); +} + // TODO -- we may want to pass the clip into this function so we only scale // the portion of the image that we're going to need. This will complicate // the interface to the cache, but might be well worth it. @@ -140,14 +155,14 @@ bool SkBitmapProcState::possiblyScaleImage() { if (fFilterLevel <= SkPaint::kLow_FilterLevel) { return false; } - // Check to see if the transformation matrix is simple, and if we're // doing high quality scaling. If so, do the bitmap scale here and // remove the scaling component from the matrix. if (SkPaint::kHigh_FilterLevel == fFilterLevel && fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) && - kN32_SkColorType == fOrigBitmap.colorType()) { + kN32_SkColorType == fOrigBitmap.colorType() && + cache_size_okay(fOrigBitmap, fInvMatrix)) { SkScalar invScaleX = fInvMatrix.getScaleX(); SkScalar invScaleY = fInvMatrix.getScaleY(); diff --git a/src/core/SkScaledImageCache.cpp b/src/core/SkScaledImageCache.cpp index 6d63edb468..dca75969f8 100644 --- a/src/core/SkScaledImageCache.cpp +++ b/src/core/SkScaledImageCache.cpp @@ -140,12 +140,13 @@ void SkScaledImageCache::init() { #else fHash = NULL; #endif - fBytesUsed = 0; + fTotalBytesUsed = 0; fCount = 0; + fSingleAllocationByteLimit = 0; fAllocator = NULL; // One of these should be explicit set by the caller after we return. - fByteLimit = 0; + fTotalByteLimit = 0; fDiscardableFactory = NULL; } @@ -270,7 +271,7 @@ SkScaledImageCache::SkScaledImageCache(DiscardableFactory factory) { SkScaledImageCache::SkScaledImageCache(size_t byteLimit) { this->init(); - fByteLimit = byteLimit; + fTotalByteLimit = byteLimit; } SkScaledImageCache::~SkScaledImageCache() { @@ -475,10 +476,10 @@ void SkScaledImageCache::purgeAsNeeded() { byteLimit = SK_MaxU32; // no limit based on bytes } else { countLimit = SK_MaxS32; // no limit based on count - byteLimit = fByteLimit; + byteLimit = fTotalByteLimit; } - size_t bytesUsed = fBytesUsed; + size_t bytesUsed = fTotalBytesUsed; int countUsed = fCount; Rec* rec = fTail; @@ -504,13 +505,13 @@ void SkScaledImageCache::purgeAsNeeded() { rec = prev; } - fBytesUsed = bytesUsed; + fTotalBytesUsed = bytesUsed; fCount = countUsed; } -size_t SkScaledImageCache::setByteLimit(size_t newLimit) { - size_t prevLimit = fByteLimit; - fByteLimit = newLimit; +size_t SkScaledImageCache::setTotalByteLimit(size_t newLimit) { + size_t prevLimit = fTotalByteLimit; + fTotalByteLimit = newLimit; if (newLimit < prevLimit) { this->purgeAsNeeded(); } @@ -570,7 +571,7 @@ void SkScaledImageCache::addToHead(Rec* rec) { if (!fTail) { fTail = rec; } - fBytesUsed += rec->bytesUsed(); + fTotalBytesUsed += rec->bytesUsed(); fCount += 1; this->validate(); @@ -582,14 +583,14 @@ void SkScaledImageCache::addToHead(Rec* rec) { void SkScaledImageCache::validate() const { if (NULL == fHead) { SkASSERT(NULL == fTail); - SkASSERT(0 == fBytesUsed); + SkASSERT(0 == fTotalBytesUsed); return; } if (fHead == fTail) { SkASSERT(NULL == fHead->fPrev); SkASSERT(NULL == fHead->fNext); - SkASSERT(fHead->bytesUsed() == fBytesUsed); + SkASSERT(fHead->bytesUsed() == fTotalBytesUsed); return; } @@ -604,7 +605,7 @@ void SkScaledImageCache::validate() const { while (rec) { count += 1; used += rec->bytesUsed(); - SkASSERT(used <= fBytesUsed); + SkASSERT(used <= fTotalBytesUsed); rec = rec->fNext; } SkASSERT(fCount == count); @@ -634,10 +635,20 @@ void SkScaledImageCache::dump() const { } SkDebugf("SkScaledImageCache: count=%d bytes=%d locked=%d %s\n", - fCount, fBytesUsed, locked, + fCount, fTotalBytesUsed, locked, fDiscardableFactory ? "discardable" : "malloc"); } +size_t SkScaledImageCache::setSingleAllocationByteLimit(size_t newLimit) { + size_t oldLimit = fSingleAllocationByteLimit; + fSingleAllocationByteLimit = newLimit; + return oldLimit; +} + +size_t SkScaledImageCache::getSingleAllocationByteLimit() const { + return fSingleAllocationByteLimit; +} + /////////////////////////////////////////////////////////////////////////////// #include "SkThread.h" @@ -724,19 +735,19 @@ void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) { // get_cache()->dump(); } -size_t SkScaledImageCache::GetBytesUsed() { +size_t SkScaledImageCache::GetTotalBytesUsed() { SkAutoMutexAcquire am(gMutex); - return get_cache()->getBytesUsed(); + return get_cache()->getTotalBytesUsed(); } -size_t SkScaledImageCache::GetByteLimit() { +size_t SkScaledImageCache::GetTotalByteLimit() { SkAutoMutexAcquire am(gMutex); - return get_cache()->getByteLimit(); + return get_cache()->getTotalByteLimit(); } -size_t SkScaledImageCache::SetByteLimit(size_t newLimit) { +size_t SkScaledImageCache::SetTotalByteLimit(size_t newLimit) { SkAutoMutexAcquire am(gMutex); - return get_cache()->setByteLimit(newLimit); + return get_cache()->setTotalByteLimit(newLimit); } SkBitmap::Allocator* SkScaledImageCache::GetAllocator() { @@ -749,18 +760,37 @@ void SkScaledImageCache::Dump() { get_cache()->dump(); } +size_t SkScaledImageCache::SetSingleAllocationByteLimit(size_t size) { + SkAutoMutexAcquire am(gMutex); + return get_cache()->setSingleAllocationByteLimit(size); +} + +size_t SkScaledImageCache::GetSingleAllocationByteLimit() { + SkAutoMutexAcquire am(gMutex); + return get_cache()->getSingleAllocationByteLimit(); +} + /////////////////////////////////////////////////////////////////////////////// #include "SkGraphics.h" -size_t SkGraphics::GetImageCacheBytesUsed() { - return SkScaledImageCache::GetBytesUsed(); +size_t SkGraphics::GetImageCacheTotalBytesUsed() { + return SkScaledImageCache::GetTotalBytesUsed(); } -size_t SkGraphics::GetImageCacheByteLimit() { - return SkScaledImageCache::GetByteLimit(); +size_t SkGraphics::GetImageCacheTotalByteLimit() { + return SkScaledImageCache::GetTotalByteLimit(); } -size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) { - return SkScaledImageCache::SetByteLimit(newLimit); +size_t SkGraphics::SetImageCacheTotalByteLimit(size_t newLimit) { + return SkScaledImageCache::SetTotalByteLimit(newLimit); } + +size_t SkGraphics::GetImageCacheSingleAllocationByteLimit() { + return SkScaledImageCache::GetSingleAllocationByteLimit(); +} + +size_t SkGraphics::SetImageCacheSingleAllocationByteLimit(size_t newLimit) { + return SkScaledImageCache::SetSingleAllocationByteLimit(newLimit); +} + diff --git a/src/core/SkScaledImageCache.h b/src/core/SkScaledImageCache.h index fe072306d3..817147e3b8 100644 --- a/src/core/SkScaledImageCache.h +++ b/src/core/SkScaledImageCache.h @@ -60,9 +60,12 @@ public: static void Unlock(ID*); - static size_t GetBytesUsed(); - static size_t GetByteLimit(); - static size_t SetByteLimit(size_t newLimit); + static size_t GetTotalBytesUsed(); + static size_t GetTotalByteLimit(); + static size_t SetTotalByteLimit(size_t newLimit); + + static size_t SetSingleAllocationByteLimit(size_t); + static size_t GetSingleAllocationByteLimit(); static SkBitmap::Allocator* GetAllocator(); @@ -76,9 +79,9 @@ public: /** * Construct the cache to call DiscardableFactory when it * allocates memory for the pixels. In this mode, the cache has - * not explicit budget, and so methods like getBytesUsed() and - * getByteLimit() will return 0, and setByteLimit will ignore its argument - * and return 0. + * not explicit budget, and so methods like getTotalBytesUsed() + * and getTotalByteLimit() will return 0, and setTotalByteLimit + * will ignore its argument and return 0. */ SkScaledImageCache(DiscardableFactory); @@ -86,7 +89,7 @@ public: * Construct the cache, allocating memory with malloc, and respect the * byteLimit, purging automatically when a new image is added to the cache * that pushes the total bytesUsed over the limit. Note: The limit can be - * changed at runtime with setByteLimit. + * changed at runtime with setTotalByteLimit. */ SkScaledImageCache(size_t byteLimit); @@ -144,15 +147,22 @@ public: */ void unlock(ID*); - size_t getBytesUsed() const { return fBytesUsed; } - size_t getByteLimit() const { return fByteLimit; } + size_t getTotalBytesUsed() const { return fTotalBytesUsed; } + size_t getTotalByteLimit() const { return fTotalByteLimit; } + /** + * This is respected by SkBitmapProcState::possiblyScaleImage. + * 0 is no maximum at all; this is the default. + * setSingleAllocationByteLimit() returns the previous value. + */ + size_t setSingleAllocationByteLimit(size_t maximumAllocationSize); + size_t getSingleAllocationByteLimit() const; /** * Set the maximum number of bytes available to this cache. If the current * cache exceeds this new value, it will be purged to try to fit within * this new limit. */ - size_t setByteLimit(size_t newLimit); + size_t setTotalByteLimit(size_t newLimit); SkBitmap::Allocator* allocator() const { return fAllocator; }; @@ -175,8 +185,9 @@ private: // the allocator is NULL or one that matches discardables SkBitmap::Allocator* fAllocator; - size_t fBytesUsed; - size_t fByteLimit; + size_t fTotalBytesUsed; + size_t fTotalByteLimit; + size_t fSingleAllocationByteLimit; int fCount; Rec* findAndLock(uint32_t generationID, SkScalar sx, SkScalar sy, diff --git a/tests/ImageCacheTest.cpp b/tests/ImageCacheTest.cpp index 92d0b519d0..00f6c77aa3 100644 --- a/tests/ImageCacheTest.cpp +++ b/tests/ImageCacheTest.cpp @@ -74,7 +74,7 @@ static void test_cache(skiatest::Reporter* reporter, SkScaledImageCache& cache, } } - cache.setByteLimit(0); + cache.setTotalByteLimit(0); } #include "SkDiscardableMemoryPool.h" diff --git a/tests/ScaledImageCache.cpp b/tests/ScaledImageCache.cpp new file mode 100644 index 0000000000..2040afea27 --- /dev/null +++ b/tests/ScaledImageCache.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "Test.h" +#include "SkGraphics.h" +#include "SkCanvas.h" + +static const int kCanvasSize = 1; +static const int kBitmapSize = 16; +static const int kScale = 8; + +static size_t test_scaled_image_cache_useage() { + SkAutoTUnref canvas( + SkCanvas::NewRasterN32(kCanvasSize, kCanvasSize)); + SkBitmap bitmap; + SkAssertResult(bitmap.allocN32Pixels(kBitmapSize, kBitmapSize)); + SkScalar scaledSize = SkIntToScalar(kScale * kBitmapSize); + canvas->clipRect(SkRect::MakeLTRB(0, 0, scaledSize, scaledSize)); + SkPaint paint; + paint.setFilterLevel(SkPaint::kHigh_FilterLevel); + size_t bytesUsed = SkGraphics::GetImageCacheBytesUsed(); + canvas->drawBitmapRect(bitmap, + SkRect::MakeLTRB(0, 0, scaledSize, scaledSize), + &paint); + return SkGraphics::GetImageCacheBytesUsed() - bytesUsed; +} + +// http://crbug.com/389439 +DEF_TEST(ScaledImageCache_SingleAllocationByteLimit, reporter) { + size_t originalByteLimit = SkGraphics::GetImageCacheByteLimit(); + size_t originalAllocationLimit = + SkGraphics::GetImageCacheSingleAllocationByteLimit(); + + size_t size = kBitmapSize * kScale * kBitmapSize * kScale + * SkColorTypeBytesPerPixel(kN32_SkColorType); + + SkGraphics::SetImageCacheByteLimit(0); // clear cache + SkGraphics::SetImageCacheByteLimit(2 * size); + SkGraphics::SetImageCacheSingleAllocationByteLimit(0); + + REPORTER_ASSERT(reporter, size == test_scaled_image_cache_useage()); + + SkGraphics::SetImageCacheByteLimit(0); // clear cache + SkGraphics::SetImageCacheByteLimit(2 * size); + SkGraphics::SetImageCacheSingleAllocationByteLimit(size * 2); + + REPORTER_ASSERT(reporter, size == test_scaled_image_cache_useage()); + + SkGraphics::SetImageCacheByteLimit(0); // clear cache + SkGraphics::SetImageCacheByteLimit(2 * size); + SkGraphics::SetImageCacheSingleAllocationByteLimit(size / 2); + + REPORTER_ASSERT(reporter, 0 == test_scaled_image_cache_useage()); + + SkGraphics::SetImageCacheSingleAllocationByteLimit(originalAllocationLimit); + SkGraphics::SetImageCacheByteLimit(originalByteLimit); +}