Set maximum output size for scaled-image-cache images

Accessable via:
  SkScaledImageCache::{G,S}etMaximumOutputSizeForHighQualityFilter

Also, a unit test.

BUG=389439
R=humper@google.com, tomhudson@google.com, reveman@chromium.org, vangelis@chromium.org, reed@google.com

Author: halcanary@google.com

Review URL: https://codereview.chromium.org/394003003
This commit is contained in:
halcanary 2014-07-17 06:58:01 -07:00 committed by Commit bot
parent 3f376a5500
commit 805ef159d1
8 changed files with 200 additions and 45 deletions

View File

@ -37,7 +37,7 @@ protected:
} }
virtual void onDraw(const int loops, SkCanvas*) SK_OVERRIDE { virtual void onDraw(const int loops, SkCanvas*) SK_OVERRIDE {
if (fCache.getBytesUsed() == 0) { if (fCache.getTotalBytesUsed() == 0) {
this->populateCache(); this->populateCache();
} }

View File

@ -162,6 +162,7 @@
'../tests/SHA1Test.cpp', '../tests/SHA1Test.cpp',
'../tests/SListTest.cpp', '../tests/SListTest.cpp',
'../tests/ScalarTest.cpp', '../tests/ScalarTest.cpp',
'../tests/ScaledImageCache.cpp',
'../tests/SerializationTest.cpp', '../tests/SerializationTest.cpp',
'../tests/ShaderImageFilterTest.cpp', '../tests/ShaderImageFilterTest.cpp',
'../tests/ShaderOpacityTest.cpp', '../tests/ShaderOpacityTest.cpp',

View File

@ -79,9 +79,47 @@ public:
*/ */
static void PurgeFontCache(); static void PurgeFontCache();
static size_t GetImageCacheBytesUsed(); /**
static size_t GetImageCacheByteLimit(); * Scaling bitmaps with the SkPaint::kHigh_FilterLevel setting is
static size_t SetImageCacheByteLimit(size_t newLimit); * 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 * Applications with command line options may pass optional state, such

View File

@ -127,6 +127,21 @@ private:
}; };
#define AutoScaledCacheUnlocker(...) SK_REQUIRE_LOCAL_VAR(AutoScaledCacheUnlocker) #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 // 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 portion of the image that we're going to need. This will complicate
// the interface to the cache, but might be well worth it. // the interface to the cache, but might be well worth it.
@ -140,14 +155,14 @@ bool SkBitmapProcState::possiblyScaleImage() {
if (fFilterLevel <= SkPaint::kLow_FilterLevel) { if (fFilterLevel <= SkPaint::kLow_FilterLevel) {
return false; return false;
} }
// Check to see if the transformation matrix is simple, and if we're // 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 // doing high quality scaling. If so, do the bitmap scale here and
// remove the scaling component from the matrix. // remove the scaling component from the matrix.
if (SkPaint::kHigh_FilterLevel == fFilterLevel && if (SkPaint::kHigh_FilterLevel == fFilterLevel &&
fInvMatrix.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask) && 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 invScaleX = fInvMatrix.getScaleX();
SkScalar invScaleY = fInvMatrix.getScaleY(); SkScalar invScaleY = fInvMatrix.getScaleY();

View File

@ -140,12 +140,13 @@ void SkScaledImageCache::init() {
#else #else
fHash = NULL; fHash = NULL;
#endif #endif
fBytesUsed = 0; fTotalBytesUsed = 0;
fCount = 0; fCount = 0;
fSingleAllocationByteLimit = 0;
fAllocator = NULL; fAllocator = NULL;
// One of these should be explicit set by the caller after we return. // One of these should be explicit set by the caller after we return.
fByteLimit = 0; fTotalByteLimit = 0;
fDiscardableFactory = NULL; fDiscardableFactory = NULL;
} }
@ -270,7 +271,7 @@ SkScaledImageCache::SkScaledImageCache(DiscardableFactory factory) {
SkScaledImageCache::SkScaledImageCache(size_t byteLimit) { SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
this->init(); this->init();
fByteLimit = byteLimit; fTotalByteLimit = byteLimit;
} }
SkScaledImageCache::~SkScaledImageCache() { SkScaledImageCache::~SkScaledImageCache() {
@ -475,10 +476,10 @@ void SkScaledImageCache::purgeAsNeeded() {
byteLimit = SK_MaxU32; // no limit based on bytes byteLimit = SK_MaxU32; // no limit based on bytes
} else { } else {
countLimit = SK_MaxS32; // no limit based on count countLimit = SK_MaxS32; // no limit based on count
byteLimit = fByteLimit; byteLimit = fTotalByteLimit;
} }
size_t bytesUsed = fBytesUsed; size_t bytesUsed = fTotalBytesUsed;
int countUsed = fCount; int countUsed = fCount;
Rec* rec = fTail; Rec* rec = fTail;
@ -504,13 +505,13 @@ void SkScaledImageCache::purgeAsNeeded() {
rec = prev; rec = prev;
} }
fBytesUsed = bytesUsed; fTotalBytesUsed = bytesUsed;
fCount = countUsed; fCount = countUsed;
} }
size_t SkScaledImageCache::setByteLimit(size_t newLimit) { size_t SkScaledImageCache::setTotalByteLimit(size_t newLimit) {
size_t prevLimit = fByteLimit; size_t prevLimit = fTotalByteLimit;
fByteLimit = newLimit; fTotalByteLimit = newLimit;
if (newLimit < prevLimit) { if (newLimit < prevLimit) {
this->purgeAsNeeded(); this->purgeAsNeeded();
} }
@ -570,7 +571,7 @@ void SkScaledImageCache::addToHead(Rec* rec) {
if (!fTail) { if (!fTail) {
fTail = rec; fTail = rec;
} }
fBytesUsed += rec->bytesUsed(); fTotalBytesUsed += rec->bytesUsed();
fCount += 1; fCount += 1;
this->validate(); this->validate();
@ -582,14 +583,14 @@ void SkScaledImageCache::addToHead(Rec* rec) {
void SkScaledImageCache::validate() const { void SkScaledImageCache::validate() const {
if (NULL == fHead) { if (NULL == fHead) {
SkASSERT(NULL == fTail); SkASSERT(NULL == fTail);
SkASSERT(0 == fBytesUsed); SkASSERT(0 == fTotalBytesUsed);
return; return;
} }
if (fHead == fTail) { if (fHead == fTail) {
SkASSERT(NULL == fHead->fPrev); SkASSERT(NULL == fHead->fPrev);
SkASSERT(NULL == fHead->fNext); SkASSERT(NULL == fHead->fNext);
SkASSERT(fHead->bytesUsed() == fBytesUsed); SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
return; return;
} }
@ -604,7 +605,7 @@ void SkScaledImageCache::validate() const {
while (rec) { while (rec) {
count += 1; count += 1;
used += rec->bytesUsed(); used += rec->bytesUsed();
SkASSERT(used <= fBytesUsed); SkASSERT(used <= fTotalBytesUsed);
rec = rec->fNext; rec = rec->fNext;
} }
SkASSERT(fCount == count); SkASSERT(fCount == count);
@ -634,10 +635,20 @@ void SkScaledImageCache::dump() const {
} }
SkDebugf("SkScaledImageCache: count=%d bytes=%d locked=%d %s\n", SkDebugf("SkScaledImageCache: count=%d bytes=%d locked=%d %s\n",
fCount, fBytesUsed, locked, fCount, fTotalBytesUsed, locked,
fDiscardableFactory ? "discardable" : "malloc"); 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" #include "SkThread.h"
@ -724,19 +735,19 @@ void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) {
// get_cache()->dump(); // get_cache()->dump();
} }
size_t SkScaledImageCache::GetBytesUsed() { size_t SkScaledImageCache::GetTotalBytesUsed() {
SkAutoMutexAcquire am(gMutex); SkAutoMutexAcquire am(gMutex);
return get_cache()->getBytesUsed(); return get_cache()->getTotalBytesUsed();
} }
size_t SkScaledImageCache::GetByteLimit() { size_t SkScaledImageCache::GetTotalByteLimit() {
SkAutoMutexAcquire am(gMutex); 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); SkAutoMutexAcquire am(gMutex);
return get_cache()->setByteLimit(newLimit); return get_cache()->setTotalByteLimit(newLimit);
} }
SkBitmap::Allocator* SkScaledImageCache::GetAllocator() { SkBitmap::Allocator* SkScaledImageCache::GetAllocator() {
@ -749,18 +760,37 @@ void SkScaledImageCache::Dump() {
get_cache()->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" #include "SkGraphics.h"
size_t SkGraphics::GetImageCacheBytesUsed() { size_t SkGraphics::GetImageCacheTotalBytesUsed() {
return SkScaledImageCache::GetBytesUsed(); return SkScaledImageCache::GetTotalBytesUsed();
} }
size_t SkGraphics::GetImageCacheByteLimit() { size_t SkGraphics::GetImageCacheTotalByteLimit() {
return SkScaledImageCache::GetByteLimit(); return SkScaledImageCache::GetTotalByteLimit();
} }
size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) { size_t SkGraphics::SetImageCacheTotalByteLimit(size_t newLimit) {
return SkScaledImageCache::SetByteLimit(newLimit); return SkScaledImageCache::SetTotalByteLimit(newLimit);
} }
size_t SkGraphics::GetImageCacheSingleAllocationByteLimit() {
return SkScaledImageCache::GetSingleAllocationByteLimit();
}
size_t SkGraphics::SetImageCacheSingleAllocationByteLimit(size_t newLimit) {
return SkScaledImageCache::SetSingleAllocationByteLimit(newLimit);
}

View File

@ -60,9 +60,12 @@ public:
static void Unlock(ID*); static void Unlock(ID*);
static size_t GetBytesUsed(); static size_t GetTotalBytesUsed();
static size_t GetByteLimit(); static size_t GetTotalByteLimit();
static size_t SetByteLimit(size_t newLimit); static size_t SetTotalByteLimit(size_t newLimit);
static size_t SetSingleAllocationByteLimit(size_t);
static size_t GetSingleAllocationByteLimit();
static SkBitmap::Allocator* GetAllocator(); static SkBitmap::Allocator* GetAllocator();
@ -76,9 +79,9 @@ public:
/** /**
* Construct the cache to call DiscardableFactory when it * Construct the cache to call DiscardableFactory when it
* allocates memory for the pixels. In this mode, the cache has * allocates memory for the pixels. In this mode, the cache has
* not explicit budget, and so methods like getBytesUsed() and * not explicit budget, and so methods like getTotalBytesUsed()
* getByteLimit() will return 0, and setByteLimit will ignore its argument * and getTotalByteLimit() will return 0, and setTotalByteLimit
* and return 0. * will ignore its argument and return 0.
*/ */
SkScaledImageCache(DiscardableFactory); SkScaledImageCache(DiscardableFactory);
@ -86,7 +89,7 @@ public:
* Construct the cache, allocating memory with malloc, and respect the * Construct the cache, allocating memory with malloc, and respect the
* byteLimit, purging automatically when a new image is added to the cache * 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 * 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); SkScaledImageCache(size_t byteLimit);
@ -144,15 +147,22 @@ public:
*/ */
void unlock(ID*); void unlock(ID*);
size_t getBytesUsed() const { return fBytesUsed; } size_t getTotalBytesUsed() const { return fTotalBytesUsed; }
size_t getByteLimit() const { return fByteLimit; } 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 * 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 * cache exceeds this new value, it will be purged to try to fit within
* this new limit. * this new limit.
*/ */
size_t setByteLimit(size_t newLimit); size_t setTotalByteLimit(size_t newLimit);
SkBitmap::Allocator* allocator() const { return fAllocator; }; SkBitmap::Allocator* allocator() const { return fAllocator; };
@ -175,8 +185,9 @@ private:
// the allocator is NULL or one that matches discardables // the allocator is NULL or one that matches discardables
SkBitmap::Allocator* fAllocator; SkBitmap::Allocator* fAllocator;
size_t fBytesUsed; size_t fTotalBytesUsed;
size_t fByteLimit; size_t fTotalByteLimit;
size_t fSingleAllocationByteLimit;
int fCount; int fCount;
Rec* findAndLock(uint32_t generationID, SkScalar sx, SkScalar sy, Rec* findAndLock(uint32_t generationID, SkScalar sx, SkScalar sy,

View File

@ -74,7 +74,7 @@ static void test_cache(skiatest::Reporter* reporter, SkScaledImageCache& cache,
} }
} }
cache.setByteLimit(0); cache.setTotalByteLimit(0);
} }
#include "SkDiscardableMemoryPool.h" #include "SkDiscardableMemoryPool.h"

View File

@ -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<SkCanvas> 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);
}