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 {
if (fCache.getBytesUsed() == 0) {
if (fCache.getTotalBytesUsed() == 0) {
this->populateCache();
}

View File

@ -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',

View File

@ -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

View File

@ -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();

View File

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

View File

@ -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,

View File

@ -74,7 +74,7 @@ static void test_cache(skiatest::Reporter* reporter, SkScaledImageCache& cache,
}
}
cache.setByteLimit(0);
cache.setTotalByteLimit(0);
}
#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);
}