Improvements/additions to SkImageCache/SkLazyPixelRef.

SkPurgeableImageCache:
New image cache that uses virtual memory to store the pixels. Combines
features of SkAshmemImageCache (which has been removed) with SkPurgeableMemoryBlock, which has android and Mac versions.

SkImageCache:
Modified the API. pinCache now returns a status out parameter which
states whether the pinned memory retained the old data. This allows
allocAndPinCache to only be used for allocations.
Add a new debug only interface to purge unpinned data.
Updates to documentation, clarifying behavior.
Changed CachedStatus to MemoryStatus

SkLruImageCache:
Implement the new function purgeAllUnpinnedCaches and change implementation
of pinCache for the new behavior.

SkLazyPixelRef:
Rewrite onLockPixels to account for the new behavior of pinCache.

BitmapFactoryTest:
Test the new SkPurgeableImageCache.
Write tests which directly test the SkImageCaches.
Create a larger bitmap, since some of the SkImageCaches are designed
to handle large bitmaps.

bench_ and render_pictures:
Consolidate lazy_decode_bitmap into one function.
Allow using a flag to specify using the purgeable image cache.
Clean up some #includes.

Review URL: https://codereview.chromium.org/12433020

git-svn-id: http://skia.googlecode.com/svn/trunk@8207 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
scroggo@google.com 2013-03-18 21:37:39 +00:00
parent e1575aa216
commit bb281f7f96
19 changed files with 533 additions and 369 deletions

View File

@ -288,6 +288,7 @@
'<(skia_include_path)/lazy/SkBitmapFactory.h',
'<(skia_include_path)/lazy/SkImageCache.h',
'<(skia_include_path)/lazy/SkLruImageCache.h',
'<(skia_include_path)/lazy/SkPurgeableImageCache.h',
'<(skia_src_path)/lazy/SkBitmapFactory.cpp',
'<(skia_src_path)/lazy/SkLazyPixelRef.h',
@ -295,6 +296,7 @@
'<(skia_src_path)/lazy/SkLruImageCache.cpp',
'<(skia_src_path)/lazy/SkPurgeableMemoryBlock.h',
'<(skia_src_path)/lazy/SkPurgeableMemoryBlock_common.cpp',
'<(skia_src_path)/lazy/SkPurgeableImageCache.cpp',
],
}

View File

@ -167,16 +167,13 @@
'../src/ports/SkPurgeableMemoryBlock_none.cpp',
],
'sources': [
'../include/ports/SkAshmemImageCache.h',
'../src/ports/FontHostConfiguration_android.cpp',
'../src/ports/SkDebug_android.cpp',
'../src/ports/SkThread_pthread.cpp',
'../src/ports/SkFontHost_android.cpp',
'../src/ports/SkFontHost_FreeType.cpp',
'../src/ports/SkFontHost_FreeType_common.cpp',
'../src/ports/SkPurgeableMemoryBlock_android.cpp',
'../src/ports/FontHostConfiguration_android.cpp',
'../src/ports/SkAshmemImageCache.cpp',
],
'dependencies': [
'freetype.gyp:freetype',

View File

@ -100,6 +100,7 @@
'skia_base_libs.gyp:skia_base_libs',
'tools.gyp:picture_renderer',
'tools.gyp:picture_utils',
'ports.gyp:ports',
],
},
{
@ -123,6 +124,7 @@
'tools.gyp:picture_utils',
'tools.gyp:picture_renderer',
'bench.gyp:bench_timer',
'ports.gyp:ports',
],
},
{

View File

@ -67,20 +67,29 @@ public:
bool installPixelRef(SkData*, SkBitmap*);
/**
* A function for selecting an SkImageCache to use based on an SkImage::Info.
* An object for selecting an SkImageCache to use based on an SkImage::Info.
*/
typedef SkImageCache* (*CacheSelector)(const SkImage::Info&);
class CacheSelector : public SkRefCnt {
public:
/**
* Return an SkImageCache to use based on the provided SkImage::Info. If the caller decides
* to hang on to the result, it will call ref, so the implementation should not add a ref
* as a result of this call.
*/
virtual SkImageCache* selectCache(const SkImage::Info&) = 0;
};
/**
* Set the function to be used to select which SkImageCache to use. Mutually exclusive with
* fImageCache.
*/
void setCacheSelector(CacheSelector);
void setCacheSelector(CacheSelector*);
private:
DecodeProc fDecodeProc;
SkImageCache* fImageCache;
CacheSelector fCacheSelector;
DecodeProc fDecodeProc;
SkImageCache* fImageCache;
CacheSelector* fCacheSelector;
};
#endif // SkBitmapFactory_DEFINED

View File

@ -22,22 +22,45 @@ public:
* call to releaseCache and a call to throwAwayCache.
* @param bytes Number of bytes needed.
* @param ID Output parameter which must not be NULL. On success, ID will be set to a value
* associated with that memory which can be used as a parameter to the other functions
* in SkImageCache. On failure, ID is unchanged.
* associated with that memory which can be used as a parameter to the other functions
* in SkImageCache. On failure, ID is unchanged.
* @return Pointer to the newly allocated memory, or NULL. This memory is safe to use until
* releaseCache is called with ID.
* releaseCache is called with ID.
*/
virtual void* allocAndPinCache(size_t bytes, intptr_t* ID) = 0;
/**
* Re-request the memory associated with ID.
* @param ID Unique ID for the memory block.
* @return Pointer: If non-NULL, points to the previously allocated memory, in which case
* this call must be balanced with a call to releaseCache. If NULL, the memory
* has been reclaimed, so allocAndPinCache must be called again with a pointer to
* the same ID.
* Output parameter for pinCache, stating whether the memory still contains the data it held
* when releaseCache was last called for the same ID.
*/
virtual void* pinCache(intptr_t ID) = 0;
enum DataStatus {
/**
* The data has been purged, and therefore needs to be rewritten to the returned memory.
*/
kUninitialized_DataStatus,
/**
* The memory still contains the data it held when releaseCache was last called with the
* same ID.
*/
kRetained_DataStatus,
};
/**
* Re-request the memory associated with ID and pin it so that it will not be reclaimed until
* the next call to releaseCache with the same ID.
* @param ID Unique ID for the memory block.
* @param status Output parameter which must not be NULL. On success (i.e. the return value is
* not NULL), status will be set to one of two states representing the cached memory. If
* status is set to kRetained_DataStatus, the memory contains the same data it did
* before releaseCache was called with this ID. If status is set to
* kUninitialized_DataStatus, the memory is still pinned, but the previous data is no
* longer available. If the return value is NULL, status is unchanged.
* @return Pointer: If non-NULL, points to the previously allocated memory, in which case
* this call must be balanced with a call to releaseCache. If NULL, the memory
* has been reclaimed, and throwAwayCache MUST NOT be called.
*/
virtual void* pinCache(intptr_t ID, DataStatus* status) = 0;
/**
* Inform the cache that it is safe to free the block of memory corresponding to ID. After
@ -61,16 +84,42 @@ public:
static const intptr_t UNINITIALIZED_ID = 0;
#ifdef SK_DEBUG
enum CacheStatus {
kPinned_CacheStatus,
kUnpinned_CacheStatus,
kThrownAway_CacheStatus,
/**
* Debug only status of a memory block.
*/
enum MemoryStatus {
/**
* It is safe to use the pointer returned by the most recent of allocAndPinCache(ID) or
* pinCache(ID) with the same ID.
*/
kPinned_MemoryStatus,
/**
* The pointer returned by the most recent call to allocAndPinCache(ID) or pinCache(ID) has
* since been released by releaseCache(ID). In order to reuse it, pinCache(ID) must be
* called again. Note that after calling releaseCache(ID), the status of that particular
* ID may not be kUnpinned_MemoryStatus, depending on the implementation, but it will not
* be kPinned_MemoryStatus.
*/
kUnpinned_MemoryStatus,
/**
* The memory associated with ID has been thrown away. No calls should be made using the
* same ID.
*/
kFreed_MemoryStatus,
};
/**
* Debug only function to get the status of a particular block of memory.
* Debug only function to get the status of a particular block of memory. Safe to call after
* throwAwayCache has been called with this ID.
*/
virtual CacheStatus getCacheStatus(intptr_t ID) const = 0;
virtual MemoryStatus getMemoryStatus(intptr_t ID) const = 0;
/**
* Debug only function to clear all unpinned caches.
*/
virtual void purgeAllUnpinnedCaches() = 0;
#endif
};
#endif // SkImageCache_DEFINED

View File

@ -25,7 +25,8 @@ public:
virtual ~SkLruImageCache();
#ifdef SK_DEBUG
CacheStatus getCacheStatus(intptr_t ID) const SK_OVERRIDE;
virtual MemoryStatus getMemoryStatus(intptr_t ID) const SK_OVERRIDE;
virtual void purgeAllUnpinnedCaches() SK_OVERRIDE;
#endif
/**
@ -45,7 +46,7 @@ public:
size_t getImageCacheUsed() const { return fRamUsed; }
virtual void* allocAndPinCache(size_t bytes, intptr_t* ID) SK_OVERRIDE;
virtual void* pinCache(intptr_t ID) SK_OVERRIDE;
virtual void* pinCache(intptr_t ID, SkImageCache::DataStatus*) SK_OVERRIDE;
virtual void releaseCache(intptr_t ID) SK_OVERRIDE;
virtual void throwAwayCache(intptr_t ID) SK_OVERRIDE;
@ -55,7 +56,7 @@ private:
typedef SkTInternalLList<CachedPixels>::Iter Iter;
#ifdef SK_DEBUG
// fMutex is mutable so that getCacheStatus can be const
// fMutex is mutable so that getMemoryStatus can be const
mutable
#endif
SkMutex fMutex;

View File

@ -0,0 +1,45 @@
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPurgeableImageCache_DEFINED
#define SkPurgeableImageCache_DEFINED
#include "SkImageCache.h"
#ifdef SK_DEBUG
#include "SkTDArray.h"
#endif
/**
* Implementation for SkImageCache that uses system defined purgeable memory.
*/
class SkPurgeableImageCache : public SkImageCache {
public:
static SkImageCache* Create();
virtual void* allocAndPinCache(size_t bytes, intptr_t* ID) SK_OVERRIDE;
virtual void* pinCache(intptr_t ID, SkImageCache::DataStatus*) SK_OVERRIDE;
virtual void releaseCache(intptr_t ID) SK_OVERRIDE;
virtual void throwAwayCache(intptr_t ID) SK_OVERRIDE;
#ifdef SK_DEBUG
virtual MemoryStatus getMemoryStatus(intptr_t ID) const SK_OVERRIDE;
virtual void purgeAllUnpinnedCaches() SK_OVERRIDE;
virtual ~SkPurgeableImageCache();
#endif
private:
SkPurgeableImageCache();
#ifdef SK_DEBUG
SkTDArray<intptr_t> fRecs;
int findRec(intptr_t) const;
#endif
void removeRec(intptr_t);
};
#endif // SkPurgeableImageCache_DEFINED

View File

@ -1,72 +0,0 @@
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkAshmemImageCache_DEFINED
#define SkAshmemImageCache_DEFINED
#include "SkImageCache.h"
#include "SkTDArray.h"
#include "SkTypes.h"
class SkAshmemImageCache : public SkImageCache {
public:
/**
* Get a pointer to the single global instance of SkAshmemImageCache.
*/
static SkAshmemImageCache* GetAshmemImageCache();
virtual void* allocAndPinCache(size_t bytes, intptr_t* ID) SK_OVERRIDE;
virtual void* pinCache(intptr_t ID) SK_OVERRIDE;
virtual void releaseCache(intptr_t ID) SK_OVERRIDE;
virtual void throwAwayCache(intptr_t ID) SK_OVERRIDE;
#ifdef SK_DEBUG
SkImageCache::CacheStatus getCacheStatus(intptr_t ID) const SK_OVERRIDE;
virtual ~SkAshmemImageCache();
#endif
private:
struct AshmemRec {
int fFD;
void* fAddr;
size_t fSize;
#ifdef SK_DEBUG
bool fPinned;
static int Compare(const AshmemRec*, const AshmemRec*);
#endif
};
/**
* Constructor is private. The correct way to get this cache is through
* GetAshmemImageCache, so that all callers can get the single global.
*/
SkAshmemImageCache();
#ifdef SK_DEBUG
// Stores a list of AshmemRecs to track deletion.
SkTDArray<AshmemRec*> fRecs;
/**
* Debug only function to add an AshmemRec to the list.
*/
void appendRec(AshmemRec*);
/**
* Return the index of AshmemRec.
*/
int findRec(const AshmemRec*) const;
#endif
/**
* Deletes AshmemRec. In debug, also removes from the list.
*/
void removeRec(AshmemRec*);
};
#endif // SkAshmemImageCache_DEFINED

View File

@ -22,17 +22,19 @@ SkBitmapFactory::SkBitmapFactory(SkBitmapFactory::DecodeProc proc)
SkBitmapFactory::~SkBitmapFactory() {
SkSafeUnref(fImageCache);
SkSafeUnref(fCacheSelector);
}
void SkBitmapFactory::setImageCache(SkImageCache *cache) {
SkRefCnt_SafeAssign(fImageCache, cache);
if (cache != NULL) {
SkSafeUnref(fCacheSelector);
fCacheSelector = NULL;
}
}
void SkBitmapFactory::setCacheSelector(CacheSelector selector) {
fCacheSelector = selector;
void SkBitmapFactory::setCacheSelector(CacheSelector* selector) {
SkRefCnt_SafeAssign(fCacheSelector, selector);
if (selector != NULL) {
SkSafeUnref(fImageCache);
fImageCache = NULL;
@ -63,7 +65,7 @@ bool SkBitmapFactory::installPixelRef(SkData* data, SkBitmap* dst) {
// fImageCache and fCacheSelector are mutually exclusive.
SkASSERT(NULL == fImageCache || NULL == fCacheSelector);
SkImageCache* cache = NULL == fCacheSelector ? fImageCache : fCacheSelector(info);
SkImageCache* cache = NULL == fCacheSelector ? fImageCache : fCacheSelector->selectCache(info);
if (cache != NULL) {
// Now set a new LazyPixelRef on dst.

View File

@ -24,7 +24,8 @@ SkLazyPixelRef::SkLazyPixelRef(SkData* data, SkBitmapFactory::DecodeProc proc, S
: INHERITED(NULL)
, fDecodeProc(proc)
, fImageCache(cache)
, fCacheId(SkImageCache::UNINITIALIZED_ID) {
, fCacheId(SkImageCache::UNINITIALIZED_ID)
, fRowBytes(0) {
SkASSERT(fDecodeProc != NULL);
if (NULL == data) {
fData = SkData::NewEmpty();
@ -71,37 +72,54 @@ void* SkLazyPixelRef::onLockPixels(SkColorTable**) {
if (SkImageCache::UNINITIALIZED_ID == fCacheId) {
target.fAddr = NULL;
} else {
target.fAddr = fImageCache->pinCache(fCacheId);
if (NULL != target.fAddr) {
SkImageCache::DataStatus status;
target.fAddr = fImageCache->pinCache(fCacheId, &status);
if (target.fAddr == NULL) {
fCacheId = SkImageCache::UNINITIALIZED_ID;
} else {
if (SkImageCache::kRetained_DataStatus == status) {
#if LAZY_CACHE_STATS
sk_atomic_inc(&gCacheHits);
sk_atomic_inc(&gCacheHits);
#endif
return target.fAddr;
return target.fAddr;
}
SkASSERT(SkImageCache::kUninitialized_DataStatus == status);
}
// Cache miss. Either pinCache returned NULL or it returned a memory address without the old
// data
#if LAZY_CACHE_STATS
sk_atomic_inc(&gCacheMisses);
#endif
}
SkASSERT(NULL == target.fAddr);
SkImage::Info info;
SkASSERT(fData != NULL && fData->size() > 0);
// FIXME: As an optimization, only do this part once.
fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
if (fErrorInDecoding) {
// In case a previous call to allocAndPinCache succeeded.
fImageCache->throwAwayCache(fCacheId);
fCacheId = SkImageCache::UNINITIALIZED_ID;
return NULL;
}
// Allocate the memory.
size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
if (NULL == target.fAddr) {
// Space could not be allocated.
fCacheId = SkImageCache::UNINITIALIZED_ID;
return NULL;
// Determine the size of the image in order to determine how much memory to allocate.
// FIXME: As an optimization, only do this part once.
fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
if (fErrorInDecoding) {
// We can only reach here if fCacheId was already set to UNINITIALIZED_ID, or if
// pinCache returned NULL, in which case it was reset to UNINITIALIZED_ID.
SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
return NULL;
}
size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
if (NULL == target.fAddr) {
// Space could not be allocated.
// Just like the last assert, fCacheId must be UNINITIALIZED_ID.
SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
return NULL;
}
} else {
// pinCache returned purged memory to which target.fAddr already points. Set
// target.fRowBytes properly.
target.fRowBytes = fRowBytes;
// Assume that the size is correct, since it was determined by this same function
// previously.
}
SkASSERT(target.fAddr != NULL);
SkASSERT(SkImageCache::UNINITIALIZED_ID != fCacheId);
fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, &target);
if (fErrorInDecoding) {
@ -109,6 +127,8 @@ void* SkLazyPixelRef::onLockPixels(SkColorTable**) {
fCacheId = SkImageCache::UNINITIALIZED_ID;
return NULL;
}
// Upon success, store fRowBytes so it can be used in case pinCache later returns purged memory.
fRowBytes = target.fRowBytes;
return target.fAddr;
}

View File

@ -68,6 +68,7 @@ private:
SkBitmapFactory::DecodeProc fDecodeProc;
SkImageCache* fImageCache;
intptr_t fCacheId;
size_t fRowBytes;
#if LAZY_CACHE_STATS
static int32_t gCacheHits;

View File

@ -74,16 +74,24 @@ SkLruImageCache::~SkLruImageCache() {
}
#ifdef SK_DEBUG
SkImageCache::CacheStatus SkLruImageCache::getCacheStatus(intptr_t ID) const {
SkImageCache::MemoryStatus SkLruImageCache::getMemoryStatus(intptr_t ID) const {
if (SkImageCache::UNINITIALIZED_ID == ID) {
return SkImageCache::kFreed_MemoryStatus;
}
SkAutoMutexAcquire ac(&fMutex);
CachedPixels* pixels = this->findByID(ID);
if (NULL == pixels) {
return SkImageCache::kThrownAway_CacheStatus;
return SkImageCache::kFreed_MemoryStatus;
}
if (pixels->isLocked()) {
return SkImageCache::kPinned_CacheStatus;
return SkImageCache::kPinned_MemoryStatus;
}
return SkImageCache::kUnpinned_CacheStatus;
return SkImageCache::kUnpinned_MemoryStatus;
}
void SkLruImageCache::purgeAllUnpinnedCaches() {
SkAutoMutexAcquire ac(&fMutex);
this->purgeTilAtOrBelow(0);
}
#endif
@ -108,7 +116,7 @@ void* SkLruImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
return pixels->getData();
}
void* SkLruImageCache::pinCache(intptr_t ID) {
void* SkLruImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
SkAutoMutexAcquire ac(&fMutex);
CachedPixels* pixels = this->findByID(ID);
@ -119,6 +127,9 @@ void* SkLruImageCache::pinCache(intptr_t ID) {
fLRU.remove(pixels);
fLRU.addToHead(pixels);
}
SkASSERT(status != NULL);
// This cache will never return pinned memory whose data has been overwritten.
*status = SkImageCache::kRetained_DataStatus;
pixels->lock();
return pixels->getData();
}
@ -133,6 +144,7 @@ void SkLruImageCache::releaseCache(intptr_t ID) {
}
void SkLruImageCache::throwAwayCache(intptr_t ID) {
SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
SkAutoMutexAcquire ac(&fMutex);
CachedPixels* pixels = this->findByID(ID);
if (pixels != NULL) {
@ -155,9 +167,6 @@ void SkLruImageCache::removePixels(CachedPixels* pixels) {
CachedPixels* SkLruImageCache::findByID(intptr_t ID) const {
// Mutex is already locked.
if (SkImageCache::UNINITIALIZED_ID == ID) {
return NULL;
}
Iter iter;
// Start from the head, most recently used.
CachedPixels* pixels = iter.init(fLRU, Iter::kHead_IterStart);

View File

@ -0,0 +1,159 @@
/*
* 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 "SkThread.h"
#include "SkPurgeableImageCache.h"
#include "SkPurgeableMemoryBlock.h"
#ifdef SK_DEBUG
#include "SkTSearch.h"
#endif
SK_DECLARE_STATIC_MUTEX(gPurgeableImageMutex);
SkImageCache* SkPurgeableImageCache::Create() {
if (!SkPurgeableMemoryBlock::IsSupported()) {
return NULL;
}
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
static SkPurgeableImageCache gCache;
gCache.ref();
return &gCache;
}
SkPurgeableImageCache::SkPurgeableImageCache() {}
#ifdef SK_DEBUG
SkPurgeableImageCache::~SkPurgeableImageCache() {
SkASSERT(fRecs.count() == 0);
}
#endif
void* SkPurgeableImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
SkPurgeableMemoryBlock* block = SkPurgeableMemoryBlock::Create(bytes);
if (NULL == block) {
return NULL;
}
SkPurgeableMemoryBlock::PinResult pinResult;
void* data = block->pin(&pinResult);
if (NULL == data) {
SkDELETE(block);
return NULL;
}
SkASSERT(ID != NULL);
*ID = reinterpret_cast<intptr_t>(block);
#ifdef SK_DEBUG
// Insert into the array of all recs:
int index = this->findRec(*ID);
SkASSERT(index < 0);
fRecs.insert(~index, 1, ID);
#endif
return data;
}
void* SkPurgeableImageCache::pinCache(intptr_t ID, SkImageCache::DataStatus* status) {
SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
SkASSERT(this->findRec(ID) >= 0);
SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
SkPurgeableMemoryBlock::PinResult pinResult;
void* data = block->pin(&pinResult);
if (NULL == data) {
this->removeRec(ID);
return NULL;
}
switch (pinResult) {
case SkPurgeableMemoryBlock::kRetained_PinResult:
*status = SkImageCache::kRetained_DataStatus;
break;
case SkPurgeableMemoryBlock::kUninitialized_PinResult:
*status = SkImageCache::kUninitialized_DataStatus;
break;
default:
// Invalid value. Treat as a failure to pin.
SkASSERT(false);
this->removeRec(ID);
return NULL;
}
return data;
}
void SkPurgeableImageCache::releaseCache(intptr_t ID) {
SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
SkASSERT(this->findRec(ID) >= 0);
SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
block->unpin();
}
void SkPurgeableImageCache::throwAwayCache(intptr_t ID) {
SkASSERT(ID != SkImageCache::UNINITIALIZED_ID);
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
this->removeRec(ID);
}
#ifdef SK_DEBUG
SkImageCache::MemoryStatus SkPurgeableImageCache::getMemoryStatus(intptr_t ID) const {
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
if (SkImageCache::UNINITIALIZED_ID == ID || this->findRec(ID) < 0) {
return SkImageCache::kFreed_MemoryStatus;
}
SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
if (block->isPinned()) {
return SkImageCache::kPinned_MemoryStatus;
}
return SkImageCache::kUnpinned_MemoryStatus;
}
void SkPurgeableImageCache::purgeAllUnpinnedCaches() {
SkAutoMutexAcquire ac(&gPurgeableImageMutex);
if (SkPurgeableMemoryBlock::PlatformSupportsPurgingAllUnpinnedBlocks()) {
SkPurgeableMemoryBlock::PurgeAllUnpinnedBlocks();
} else {
// Go through the blocks, and purge them individually.
// Rather than deleting the blocks, which would interfere with further calls, purge them
// and keep them around.
for (int i = 0; i < fRecs.count(); i++) {
SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(fRecs[i]);
if (!block->isPinned()) {
if (!block->purge()) {
// FIXME: This should be more meaningful (which one, etc...)
SkDebugf("Failed to purge\n");
}
}
}
}
}
int SkPurgeableImageCache::findRec(intptr_t rec) const {
return SkTSearch(fRecs.begin(), fRecs.count(), rec, sizeof(intptr_t));
}
#endif
void SkPurgeableImageCache::removeRec(intptr_t ID) {
#ifdef SK_DEBUG
int index = this->findRec(ID);
SkASSERT(index >= 0);
fRecs.remove(index);
#endif
SkPurgeableMemoryBlock* block = reinterpret_cast<SkPurgeableMemoryBlock*>(ID);
SkASSERT(!block->isPinned());
SkDELETE(block);
}

View File

@ -1,161 +0,0 @@
/*
* 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 "SkAshmemImageCache.h"
#include "SkThread.h"
#ifdef SK_DEBUG
#include "SkTSearch.h"
#endif
#include "android/ashmem.h"
#include <sys/mman.h>
#include <unistd.h>
SkAshmemImageCache::SkAshmemImageCache() {}
SK_DECLARE_STATIC_MUTEX(gAshmemMutex);
SkAshmemImageCache* SkAshmemImageCache::GetAshmemImageCache() {
SkAutoMutexAcquire ac(&gAshmemMutex);
static SkAshmemImageCache gCache;
return &gCache;
}
#ifdef SK_DEBUG
SkAshmemImageCache::~SkAshmemImageCache() {
SkASSERT(fRecs.count() == 0);
}
#endif
// ashmem likes lengths on page boundaries.
static size_t roundToPageSize(size_t size) {
const size_t mask = getpagesize() - 1;
size_t newSize = (size + mask) & ~mask;
return newSize;
}
void* SkAshmemImageCache::allocAndPinCache(size_t bytes, intptr_t* ID) {
SkASSERT(ID != NULL);
SkAutoMutexAcquire ac(&gAshmemMutex);
if (*ID != SkImageCache::UNINITIALIZED_ID) {
// This rec was previously allocated, but pinCache subsequently
// failed.
AshmemRec* pRec = reinterpret_cast<AshmemRec*>(*ID);
SkASSERT(roundToPageSize(bytes) == pRec->fSize);
SkASSERT(pRec->fFD != -1);
(void) ashmem_pin_region(pRec->fFD, 0, 0);
#ifdef SK_DEBUG
pRec->fPinned = true;
#endif
return pRec->fAddr;
}
AshmemRec rec;
rec.fSize = roundToPageSize(bytes);
rec.fFD = ashmem_create_region(NULL, rec.fSize);
if (-1 == rec.fFD) {
SkDebugf("ashmem_create_region failed\n");
return NULL;
}
int err = ashmem_set_prot_region(rec.fFD, PROT_READ | PROT_WRITE);
if (err != 0) {
SkDebugf("ashmem_set_prot_region failed\n");
close(rec.fFD);
return NULL;
}
rec.fAddr = mmap(NULL, rec.fSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, rec.fFD, 0);
if (-1 == (long) rec.fAddr) {
SkDebugf("mmap failed\n");
close(rec.fFD);
return NULL;
}
(void) ashmem_pin_region(rec.fFD, 0, 0);
#ifdef SK_DEBUG
rec.fPinned = true;
#endif
// In release mode, we do not keep a pointer to this object. It will be destroyed
// either when pinCache returns NULL or when throwAwayCache is called.
AshmemRec* pRec = SkNEW_ARGS(AshmemRec, (rec));
*ID = reinterpret_cast<intptr_t>(pRec);
#ifdef SK_DEBUG
this->appendRec(pRec);
#endif
return rec.fAddr;
}
void* SkAshmemImageCache::pinCache(intptr_t ID) {
SkAutoMutexAcquire ac(&gAshmemMutex);
AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
const int fd = rec->fFD;
int pin = ashmem_pin_region(fd, 0, 0);
if (ASHMEM_NOT_PURGED == pin) {
#ifdef SK_DEBUG
rec->fPinned = true;
#endif
return rec->fAddr;
}
ashmem_unpin_region(fd, 0, 0);
return NULL;
}
void SkAshmemImageCache::releaseCache(intptr_t ID) {
SkAutoMutexAcquire ac(&gAshmemMutex);
AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
ashmem_unpin_region(rec->fFD, 0, 0);
#ifdef SK_DEBUG
rec->fPinned = false;
#endif
}
void SkAshmemImageCache::throwAwayCache(intptr_t ID) {
SkAutoMutexAcquire ac(&gAshmemMutex);
AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
munmap(rec->fAddr, rec->fSize);
close(rec->fFD);
#ifdef SK_DEBUG
SkASSERT(!rec->fPinned);
int index = this->findRec(rec);
SkASSERT(index >= 0);
fRecs.remove(index);
#endif
SkDELETE(rec);
}
#ifdef SK_DEBUG
void SkAshmemImageCache::appendRec(SkAshmemImageCache::AshmemRec* rec) {
int index = this->findRec(rec);
// Should not already exist.
SkASSERT(index < 0);
fRecs.insert(~index, 1, &rec);
}
int SkAshmemImageCache::AshmemRec::Compare(const SkAshmemImageCache::AshmemRec* a,
const SkAshmemImageCache::AshmemRec* b) {
return reinterpret_cast<intptr_t>(a) - reinterpret_cast<intptr_t>(b);
}
int SkAshmemImageCache::findRec(const SkAshmemImageCache::AshmemRec* rec) const {
return SkTSearch<AshmemRec>((const AshmemRec**)fRecs.begin(), fRecs.count(), rec,
sizeof(intptr_t), AshmemRec::Compare);
}
SkImageCache::CacheStatus SkAshmemImageCache::getCacheStatus(intptr_t ID) const {
SkAutoMutexAcquire ac(&gAshmemMutex);
AshmemRec* rec = reinterpret_cast<AshmemRec*>(ID);
int index = this->findRec(rec);
if (index < 0) {
return SkImageCache::kThrownAway_CacheStatus;
}
return rec->fPinned ? SkImageCache::kPinned_CacheStatus
: SkImageCache::kUnpinned_CacheStatus;
}
#endif

View File

@ -17,17 +17,15 @@
#include "SkLazyPixelRef.h"
#include "SkLruImageCache.h"
#include "SkPaint.h"
#include "SkPurgeableImageCache.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "Test.h"
#ifdef SK_BUILD_FOR_ANDROID
#include "SkAshmemImageCache.h"
#endif
static SkBitmap* create_bitmap() {
SkBitmap* bm = SkNEW(SkBitmap);
const int W = 100, H = 100;
// Use a large bitmap.
const int W = 1000, H = 1000;
bm->setConfig(SkBitmap::kARGB_8888_Config, W, H);
bm->allocPixels();
bm->eraseColor(SK_ColorBLACK);
@ -52,7 +50,53 @@ static void assert_bounds_equal(skiatest::Reporter* reporter, const SkBitmap& bm
REPORTER_ASSERT(reporter, bm1.height() == bm2.height());
}
static void test_cache(skiatest::Reporter* reporter, SkImageCache* cache, SkData* encodedData,
static void test_cache(skiatest::Reporter* reporter, SkImageCache* cache) {
// Test the cache directly:
cache->purgeAllUnpinnedCaches();
intptr_t ID = SkImageCache::UNINITIALIZED_ID;
const size_t size = 1000;
char buffer[size];
sk_bzero((void*) buffer, size);
void* memory = cache->allocAndPinCache(size, &ID);
if (memory != NULL) {
memcpy(memory, (void*)buffer, size);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID) == SkImageCache::kPinned_MemoryStatus);
cache->releaseCache(ID);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID) != SkImageCache::kPinned_MemoryStatus);
SkImageCache::DataStatus dataStatus;
memory = cache->pinCache(ID, &dataStatus);
if (memory != NULL) {
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
== SkImageCache::kPinned_MemoryStatus);
if (SkImageCache::kRetained_DataStatus == dataStatus) {
REPORTER_ASSERT(reporter, !memcmp(memory, (void*) buffer, size));
}
cache->releaseCache(ID);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
!= SkImageCache::kPinned_MemoryStatus);
cache->purgeAllUnpinnedCaches();
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
!= SkImageCache::kPinned_MemoryStatus);
memory = cache->pinCache(ID, &dataStatus);
if (memory != NULL) {
// Since the cache was thrown away, and ID was not pinned, it should have
// been purged.
REPORTER_ASSERT(reporter, SkImageCache::kUninitialized_DataStatus == dataStatus);
cache->releaseCache(ID);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
!= SkImageCache::kPinned_MemoryStatus);
cache->throwAwayCache(ID);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
== SkImageCache::kFreed_MemoryStatus);
} else {
REPORTER_ASSERT(reporter, cache->getMemoryStatus(ID)
== SkImageCache::kFreed_MemoryStatus);
}
}
}
}
static void test_factory(skiatest::Reporter* reporter, SkImageCache* cache, SkData* encodedData,
const SkBitmap& origBitmap) {
SkBitmapFactory factory(&SkImageDecoder::DecodeMemoryToTarget);
factory.setImageCache(cache);
@ -72,41 +116,80 @@ static void test_cache(skiatest::Reporter* reporter, SkImageCache* cache, SkData
// Lazy decoding
REPORTER_ASSERT(reporter, !bitmapFromFactory->readyToDraw());
SkLazyPixelRef* lazyRef = static_cast<SkLazyPixelRef*>(pixelRef);
int32_t cacheID = lazyRef->getCacheId();
REPORTER_ASSERT(reporter, cache->getCacheStatus(cacheID)
!= SkImageCache::kPinned_CacheStatus);
intptr_t cacheID = lazyRef->getCacheId();
REPORTER_ASSERT(reporter, cache->getMemoryStatus(cacheID)
!= SkImageCache::kPinned_MemoryStatus);
{
SkAutoLockPixels alp(*bitmapFromFactory.get());
REPORTER_ASSERT(reporter, bitmapFromFactory->readyToDraw());
cacheID = lazyRef->getCacheId();
REPORTER_ASSERT(reporter, cache->getCacheStatus(cacheID)
== SkImageCache::kPinned_CacheStatus);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(cacheID)
== SkImageCache::kPinned_MemoryStatus);
}
REPORTER_ASSERT(reporter, !bitmapFromFactory->readyToDraw());
REPORTER_ASSERT(reporter, cache->getCacheStatus(cacheID)
!= SkImageCache::kPinned_CacheStatus);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(cacheID)
!= SkImageCache::kPinned_MemoryStatus);
bitmapFromFactory.free();
REPORTER_ASSERT(reporter, cache->getCacheStatus(cacheID)
== SkImageCache::kThrownAway_CacheStatus);
REPORTER_ASSERT(reporter, cache->getMemoryStatus(cacheID)
== SkImageCache::kFreed_MemoryStatus);
}
}
class ImageCacheHolder : public SkNoncopyable {
public:
~ImageCacheHolder() {
fCaches.safeUnrefAll();
}
void addImageCache(SkImageCache* cache) {
SkSafeRef(cache);
*fCaches.append() = cache;
}
int count() const { return fCaches.count(); }
SkImageCache* getAt(int i) {
if (i < 0 || i > fCaches.count()) {
return NULL;
}
return fCaches.getAt(i);
}
private:
SkTDArray<SkImageCache*> fCaches;
};
static void TestBitmapFactory(skiatest::Reporter* reporter) {
SkAutoTDelete<SkBitmap> bitmap(create_bitmap());
SkASSERT(bitmap.get() != NULL);
SkAutoDataUnref encodedBitmap(create_data_from_bitmap(*bitmap.get()));
if (encodedBitmap.get() == NULL) {
// Encoding failed.
return;
}
bool encodeSucceeded = encodedBitmap.get() != NULL;
SkASSERT(encodeSucceeded);
ImageCacheHolder cacheHolder;
SkAutoTUnref<SkLruImageCache> lruCache(SkNEW_ARGS(SkLruImageCache, (1024 * 1024)));
test_cache(reporter, lruCache, encodedBitmap, *bitmap.get());
test_cache(reporter, NULL, encodedBitmap, *bitmap.get());
#ifdef SK_BUILD_FOR_ANDROID
test_cache(reporter, SkAshmemImageCache::GetAshmemImageCache(), encodedBitmap, *bitmap.get());
#endif
cacheHolder.addImageCache(lruCache);
cacheHolder.addImageCache(NULL);
SkImageCache* purgeableCache = SkPurgeableImageCache::Create();
if (purgeableCache != NULL) {
cacheHolder.addImageCache(purgeableCache);
purgeableCache->unref();
}
for (int i = 0; i < cacheHolder.count(); i++) {
SkImageCache* cache = cacheHolder.getAt(i);
if (cache != NULL) {
test_cache(reporter, cache);
}
if (encodeSucceeded) {
test_factory(reporter, cache, encodedBitmap, *bitmap.get());
}
}
}
#include "TestClassDef.h"

View File

@ -11,7 +11,14 @@
#include "PictureRenderer.h"
#include "picture_utils.h"
#include "SkBitmapFactory.h"
#include "SkData.h"
#include "SkFlags.h"
#include "SkImage.h"
#include "SkImageDecoder.h"
#include "SkLruImageCache.h"
#include "SkPurgeableImageCache.h"
#include "SkString.h"
// Alphabetized list of flags used by this file or bench_ and render_pictures.
DEFINE_string(bbh, "none", "bbhType [width height]: Set the bounding box hierarchy type to "
@ -57,6 +64,9 @@ DEFINE_string(r, "", "skp files or directories of skp files to process.");
DEFINE_double(scale, 1, "Set the scale factor.");
DEFINE_string(tiles, "", "Used with --mode copyTile to specify number of tiles per larger tile "
"in the x and y directions.");
DEFINE_bool(useVolatileCache, false, "Use a volatile cache for deferred image decoding pixels. "
"Only meaningful if --deferImageDecoding is set to true and the platform has an "
"implementation.");
DEFINE_string(viewport, "", "width height: Set the viewport.");
sk_tools::PictureRenderer* parseRenderer(SkString& error, PictureTool tool) {
@ -308,3 +318,52 @@ sk_tools::PictureRenderer* parseRenderer(SkString& error, PictureTool tool) {
return renderer.detach();
}
SkLruImageCache gLruImageCache(1024*1024);
// Simple cache selector to choose between a purgeable cache for large images and the standard one
// for smaller images.
class MyCacheSelector : public SkBitmapFactory::CacheSelector {
public:
MyCacheSelector() {
fPurgeableImageCache = SkPurgeableImageCache::Create();
}
~MyCacheSelector() {
SkSafeUnref(fPurgeableImageCache);
}
virtual SkImageCache* selectCache(const SkImage::Info& info) SK_OVERRIDE {
if (info.fWidth * info.fHeight > 32 * 1024 && fPurgeableImageCache != NULL) {
return fPurgeableImageCache;
}
return &gLruImageCache;
}
private:
SkImageCache* fPurgeableImageCache;
};
static MyCacheSelector gCacheSelector;
static SkBitmapFactory gFactory(&SkImageDecoder::DecodeMemoryToTarget);
bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap);
bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap) {
void* copiedBuffer = sk_malloc_throw(size);
memcpy(copiedBuffer, buffer, size);
SkAutoDataUnref data(SkData::NewFromMalloc(copiedBuffer, size));
static bool gOnce;
if (!gOnce) {
// Only use the cache selector if there is a purgeable image cache to use for large
// images.
if (FLAGS_useVolatileCache && SkAutoTUnref<SkImageCache>(
SkPurgeableImageCache::Create()).get() != NULL) {
gFactory.setCacheSelector(&gCacheSelector);
} else {
gFactory.setImageCache(&gLruImageCache);
}
gOnce = true;
}
return gFactory.installPixelRef(data, bitmap);
}

View File

@ -8,7 +8,7 @@
#ifndef PICTURE_RENDERING_FLAGS
#define PICTURE_RENDERING_FLAGS
#include "SkString.h"
class SkString;
namespace sk_tools {
class PictureRenderer;

View File

@ -10,16 +10,17 @@
#include "PictureBenchmark.h"
#include "PictureRenderingFlags.h"
#include "SkBenchLogger.h"
#include "SkBitmapFactory.h"
#include "SkCanvas.h"
#include "SkFlags.h"
#include "SkGraphics.h"
#include "SkImageDecoder.h"
#if LAZY_CACHE_STATS
#include "SkLazyPixelRef.h"
#endif
#include "SkLruImageCache.h"
#include "SkMath.h"
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkStream.h"
#include "SkTArray.h"
#include "picture_utils.h"
@ -142,20 +143,9 @@ static SkString filterFlagsUsage() {
return result;
}
#include "SkData.h"
#include "SkLruImageCache.h"
#include "SkLazyPixelRef.h"
static SkLruImageCache gLruImageCache(1024*1024);
static bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap) {
void* copiedBuffer = sk_malloc_throw(size);
memcpy(copiedBuffer, buffer, size);
SkAutoDataUnref data(SkData::NewFromMalloc(copiedBuffer, size));
SkBitmapFactory factory(&SkImageDecoder::DecodeMemoryToTarget);
factory.setImageCache(&gLruImageCache);
return factory.installPixelRef(data, bitmap);
}
// These are defined in PictureRenderingFlags.cpp
extern SkLruImageCache gLruImageCache;
extern bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap);
#if LAZY_CACHE_STATS
static int32_t gTotalCacheHits;

View File

@ -7,8 +7,6 @@
#include "CopyTilesRenderer.h"
#include "SkBitmap.h"
#include "SkBitmapFactory.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkFlags.h"
#include "SkGraphics.h"
@ -19,7 +17,6 @@
#include "SkPicture.h"
#include "SkStream.h"
#include "SkString.h"
#include "SkTArray.h"
#include "PictureRenderer.h"
#include "PictureRenderingFlags.h"
#include "picture_utils.h"
@ -45,36 +42,8 @@ static void make_output_filepath(SkString* path, const SkString& dir,
path->remove(path->size() - 4, 4);
}
#include "SkData.h"
#include "SkLruImageCache.h"
static SkLruImageCache gLruImageCache(1024*1024);
#ifdef SK_BUILD_FOR_ANDROID
#include "SkAshmemImageCache.h"
#include "SkImage.h"
static SkImageCache* cache_selector(const SkImage::Info& info) {
if (info.fWidth * info.fHeight > 32 * 1024) {
return SkAshmemImageCache::GetAshmemImageCache();
}
return &gLruImageCache;
}
#endif
static bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap) {
void* copiedBuffer = sk_malloc_throw(size);
memcpy(copiedBuffer, buffer, size);
SkAutoDataUnref data(SkData::NewFromMalloc(copiedBuffer, size));
SkBitmapFactory factory(&SkImageDecoder::DecodeMemoryToTarget);
#ifdef SK_BUILD_FOR_ANDROID
factory.setCacheSelector(&cache_selector);
#else
factory.setImageCache(&gLruImageCache);
#endif
return factory.installPixelRef(data, bitmap);
}
// Defined in PictureRenderingFlags.cpp
extern bool lazy_decode_bitmap(const void* buffer, size_t size, SkBitmap* bitmap);
static bool render_picture(const SkString& inputPath, const SkString* outputDir,
sk_tools::PictureRenderer& renderer,