notify resource caches when pixelref genID goes stale
BUG=skia: Review URL: https://codereview.chromium.org/825263005
This commit is contained in:
parent
67b21a7eef
commit
4675819b9d
@ -33,11 +33,11 @@ static unsigned gBitmapKeyNamespaceLabel;
|
||||
|
||||
struct BitmapKey : public SkResourceCache::Key {
|
||||
public:
|
||||
BitmapKey(uint32_t genID, SkScalar scaleX, SkScalar scaleY, const SkIRect& bounds)
|
||||
: fGenID(genID)
|
||||
, fScaleX(scaleX)
|
||||
, fScaleY(scaleY)
|
||||
, fBounds(bounds)
|
||||
BitmapKey(uint32_t genID, SkScalar sx, SkScalar sy, const SkIRect& bounds)
|
||||
: fGenID(genID)
|
||||
, fScaleX(sx)
|
||||
, fScaleY(sy)
|
||||
, fBounds(bounds)
|
||||
{
|
||||
this->init(&gBitmapKeyNamespaceLabel,
|
||||
sizeof(fGenID) + sizeof(fScaleX) + sizeof(fScaleY) + sizeof(fBounds));
|
||||
@ -49,8 +49,6 @@ public:
|
||||
SkIRect fBounds;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct BitmapRec : public SkResourceCache::Rec {
|
||||
BitmapRec(uint32_t genID, SkScalar scaleX, SkScalar scaleY, const SkIRect& bounds,
|
||||
const SkBitmap& result)
|
||||
@ -58,13 +56,10 @@ struct BitmapRec : public SkResourceCache::Rec {
|
||||
, fBitmap(result)
|
||||
{}
|
||||
|
||||
BitmapKey fKey;
|
||||
SkBitmap fBitmap;
|
||||
|
||||
const Key& getKey() const SK_OVERRIDE { return fKey; }
|
||||
size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fBitmap.getSize(); }
|
||||
|
||||
static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
|
||||
static bool Finder(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
|
||||
const BitmapRec& rec = static_cast<const BitmapRec&>(baseRec);
|
||||
SkBitmap* result = (SkBitmap*)contextBitmap;
|
||||
|
||||
@ -72,6 +67,22 @@ struct BitmapRec : public SkResourceCache::Rec {
|
||||
result->lockPixels();
|
||||
return SkToBool(result->getPixels());
|
||||
}
|
||||
|
||||
static SkResourceCache::PurgeVisitorResult PurgeGenID(const SkResourceCache::Rec& baseRec,
|
||||
void* contextGenID) {
|
||||
const BitmapRec& rec = static_cast<const BitmapRec&>(baseRec);
|
||||
uintptr_t genID = (uintptr_t)contextGenID;
|
||||
if (rec.fKey.fGenID == genID) {
|
||||
// SkDebugf("BitmapRec purging genID %d\n", genID);
|
||||
return SkResourceCache::kPurgeAndContinue_PurgeVisitorResult;
|
||||
} else {
|
||||
return SkResourceCache::kRetainAndContinue_PurgeVisitorResult;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BitmapKey fKey;
|
||||
SkBitmap fBitmap;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@ -86,7 +97,7 @@ bool SkBitmapCache::Find(const SkBitmap& src, SkScalar invScaleX, SkScalar invSc
|
||||
}
|
||||
BitmapKey key(src.getGenerationID(), invScaleX, invScaleY, get_bounds_from_bitmap(src));
|
||||
|
||||
return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Visitor, result);
|
||||
return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Finder, result);
|
||||
}
|
||||
|
||||
void SkBitmapCache::Add(const SkBitmap& src, SkScalar invScaleX, SkScalar invScaleY,
|
||||
@ -105,7 +116,7 @@ bool SkBitmapCache::Find(uint32_t genID, const SkIRect& subset, SkBitmap* result
|
||||
SkResourceCache* localCache) {
|
||||
BitmapKey key(genID, SK_Scalar1, SK_Scalar1, subset);
|
||||
|
||||
return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Visitor, result);
|
||||
return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Finder, result);
|
||||
}
|
||||
|
||||
bool SkBitmapCache::Add(uint32_t genID, const SkIRect& subset, const SkBitmap& result,
|
||||
@ -125,11 +136,31 @@ bool SkBitmapCache::Add(uint32_t genID, const SkIRect& subset, const SkBitmap& r
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void SkBitmapCache::NotifyGenIDStale(uint32_t genID) {
|
||||
SkResourceCache::Purge(&gBitmapKeyNamespaceLabel, BitmapRec::PurgeGenID,
|
||||
(void*)((uintptr_t)genID));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
static unsigned gMipMapKeyNamespaceLabel;
|
||||
|
||||
struct MipMapKey : public SkResourceCache::Key {
|
||||
public:
|
||||
MipMapKey(uint32_t genID, const SkIRect& bounds) : fGenID(genID), fBounds(bounds) {
|
||||
this->init(&gMipMapKeyNamespaceLabel, sizeof(fGenID) + sizeof(fBounds));
|
||||
}
|
||||
|
||||
uint32_t fGenID;
|
||||
SkIRect fBounds;
|
||||
};
|
||||
|
||||
struct MipMapRec : public SkResourceCache::Rec {
|
||||
MipMapRec(const SkBitmap& src, const SkMipMap* result)
|
||||
: fKey(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src))
|
||||
: fKey(src.getGenerationID(), get_bounds_from_bitmap(src))
|
||||
, fMipMap(result)
|
||||
{
|
||||
fMipMap->attachToCacheAndRef();
|
||||
@ -142,7 +173,7 @@ struct MipMapRec : public SkResourceCache::Rec {
|
||||
const Key& getKey() const SK_OVERRIDE { return fKey; }
|
||||
size_t bytesUsed() const SK_OVERRIDE { return sizeof(fKey) + fMipMap->size(); }
|
||||
|
||||
static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextMip) {
|
||||
static bool Finder(const SkResourceCache::Rec& baseRec, void* contextMip) {
|
||||
const MipMapRec& rec = static_cast<const MipMapRec&>(baseRec);
|
||||
const SkMipMap* mm = SkRef(rec.fMipMap);
|
||||
// the call to ref() above triggers a "lock" in the case of discardable memory,
|
||||
@ -156,16 +187,29 @@ struct MipMapRec : public SkResourceCache::Rec {
|
||||
return true;
|
||||
}
|
||||
|
||||
static SkResourceCache::PurgeVisitorResult PurgeGenID(const SkResourceCache::Rec& baseRec,
|
||||
void* contextGenID) {
|
||||
const MipMapRec& rec = static_cast<const MipMapRec&>(baseRec);
|
||||
uintptr_t genID = (uintptr_t)contextGenID;
|
||||
if (rec.fKey.fGenID == genID) {
|
||||
// SkDebugf("MipMapRec purging genID %d\n", genID);
|
||||
return SkResourceCache::kPurgeAndContinue_PurgeVisitorResult;
|
||||
} else {
|
||||
return SkResourceCache::kRetainAndContinue_PurgeVisitorResult;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BitmapKey fKey;
|
||||
MipMapKey fKey;
|
||||
const SkMipMap* fMipMap;
|
||||
};
|
||||
}
|
||||
|
||||
const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmap& src, SkResourceCache* localCache) {
|
||||
BitmapKey key(src.getGenerationID(), 0, 0, get_bounds_from_bitmap(src));
|
||||
MipMapKey key(src.getGenerationID(), get_bounds_from_bitmap(src));
|
||||
const SkMipMap* result;
|
||||
|
||||
if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Visitor, &result)) {
|
||||
if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Finder, &result)) {
|
||||
result = NULL;
|
||||
}
|
||||
return result;
|
||||
@ -185,3 +229,7 @@ const SkMipMap* SkMipMapCache::AddAndRef(const SkBitmap& src, SkResourceCache* l
|
||||
return mipmap;
|
||||
}
|
||||
|
||||
void SkMipMapCache::NotifyGenIDStale(uint32_t genID) {
|
||||
SkResourceCache::Purge(&gMipMapKeyNamespaceLabel, MipMapRec::PurgeGenID,
|
||||
(void*)((uintptr_t)genID));
|
||||
}
|
||||
|
@ -48,12 +48,22 @@ public:
|
||||
*/
|
||||
static bool Add(uint32_t genID, const SkIRect& subset, const SkBitmap& result,
|
||||
SkResourceCache* localCache = NULL);
|
||||
|
||||
/**
|
||||
* Call this to (as a hint) preemptively purge caches related to this genID
|
||||
*/
|
||||
static void NotifyGenIDStale(uint32_t);
|
||||
};
|
||||
|
||||
class SkMipMapCache {
|
||||
public:
|
||||
static const SkMipMap* FindAndRef(const SkBitmap& src, SkResourceCache* localCache = NULL);
|
||||
static const SkMipMap* AddAndRef(const SkBitmap& src, SkResourceCache* localCache = NULL);
|
||||
|
||||
/**
|
||||
* Call this to (as a hint) preemptively purge caches related to this genID
|
||||
*/
|
||||
static void NotifyGenIDStale(uint32_t);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -5,6 +5,7 @@
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkBitmapCache.h"
|
||||
#include "SkPixelRef.h"
|
||||
#include "SkThread.h"
|
||||
|
||||
@ -222,6 +223,7 @@ void SkPixelRef::addGenIDChangeListener(GenIDChangeListener* listener) {
|
||||
*fGenIDChangeListeners.append() = listener;
|
||||
}
|
||||
|
||||
// we need to be called *before* the genID gets changed or zerod
|
||||
void SkPixelRef::callGenIDChangeListeners() {
|
||||
// We don't invalidate ourselves if we think another SkPixelRef is sharing our genID.
|
||||
if (fUniqueGenerationID) {
|
||||
@ -231,6 +233,15 @@ void SkPixelRef::callGenIDChangeListeners() {
|
||||
}
|
||||
// Listeners get at most one shot, so whether these triggered or not, blow them away.
|
||||
fGenIDChangeListeners.deleteAll();
|
||||
|
||||
// if fGenerationID is 0, then perhaps we never had one, and we are in the destructor
|
||||
if (fGenerationID) {
|
||||
// If the ResourceCache ever generalizes/standardizes on accessing the gen-id field,
|
||||
// then it would be more efficient to roll these together, and only grab the mutex
|
||||
// and fixing the resources once...
|
||||
SkBitmapCache::NotifyGenIDStale(fGenerationID);
|
||||
SkMipMapCache::NotifyGenIDStale(fGenerationID);
|
||||
}
|
||||
}
|
||||
|
||||
void SkPixelRef::notifyPixelsChanged() {
|
||||
|
@ -61,6 +61,8 @@ void SkResourceCache::init() {
|
||||
// One of these should be explicit set by the caller after we return.
|
||||
fTotalByteLimit = 0;
|
||||
fDiscardableFactory = NULL;
|
||||
|
||||
fInsidePurgeAllCounter = 0;
|
||||
}
|
||||
|
||||
#include "SkDiscardableMemory.h"
|
||||
@ -199,7 +201,7 @@ SkResourceCache::~SkResourceCache() {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool SkResourceCache::find(const Key& key, VisitorProc visitor, void* context) {
|
||||
bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) {
|
||||
Rec* rec = fHash->find(key);
|
||||
if (rec) {
|
||||
if (visitor(*rec, context)) {
|
||||
@ -294,6 +296,34 @@ void SkResourceCache::purgeAsNeeded(bool forcePurge) {
|
||||
}
|
||||
}
|
||||
|
||||
void SkResourceCache::purge(const void* nameSpace, PurgeVisitor proc, void* context) {
|
||||
if (this->insidePurgeAll()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// go backwards, just like purgeAsNeeded, just to make the code similar.
|
||||
// could iterate either direction and still be correct.
|
||||
Rec* rec = fTail;
|
||||
while (rec) {
|
||||
Rec* prev = rec->fPrev;
|
||||
if (rec->getKey().getNamespace() == nameSpace) {
|
||||
switch (proc(*rec, context)) {
|
||||
case kRetainAndContinue_PurgeVisitorResult:
|
||||
break;
|
||||
case kPurgeAndContinue_PurgeVisitorResult:
|
||||
this->remove(rec);
|
||||
break;
|
||||
case kRetainAndStop_PurgeVisitorResult:
|
||||
return;
|
||||
case kPurgeAndStop_PurgeVisitorResult:
|
||||
this->remove(rec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
rec = prev;
|
||||
}
|
||||
}
|
||||
|
||||
size_t SkResourceCache::setTotalByteLimit(size_t newLimit) {
|
||||
size_t prevLimit = fTotalByteLimit;
|
||||
fTotalByteLimit = newLimit;
|
||||
@ -532,12 +562,17 @@ size_t SkResourceCache::GetEffectiveSingleAllocationByteLimit() {
|
||||
return get_cache()->getEffectiveSingleAllocationByteLimit();
|
||||
}
|
||||
|
||||
void SkResourceCache::Purge(const void* nameSpace, PurgeVisitor proc, void* context) {
|
||||
SkAutoMutexAcquire am(gMutex);
|
||||
return get_cache()->purge(nameSpace, proc, context);
|
||||
}
|
||||
|
||||
void SkResourceCache::PurgeAll() {
|
||||
SkAutoMutexAcquire am(gMutex);
|
||||
return get_cache()->purgeAll();
|
||||
}
|
||||
|
||||
bool SkResourceCache::Find(const Key& key, VisitorProc visitor, void* context) {
|
||||
bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) {
|
||||
SkAutoMutexAcquire am(gMutex);
|
||||
return get_cache()->find(key, visitor, context);
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ public:
|
||||
// length must be a multiple of 4
|
||||
void init(void* nameSpace, size_t length);
|
||||
|
||||
void* getNamespace() const { return fNamespace; }
|
||||
|
||||
// This is only valid after having called init().
|
||||
uint32_t hash() const { return fHash; }
|
||||
|
||||
@ -92,7 +94,21 @@ public:
|
||||
* true, then the Rec is considered "valid". If false is returned, the Rec will be considered
|
||||
* "stale" and will be purged from the cache.
|
||||
*/
|
||||
typedef bool (*VisitorProc)(const Rec&, void* context);
|
||||
typedef bool (*FindVisitor)(const Rec&, void* context);
|
||||
|
||||
enum PurgeVisitorResult {
|
||||
kRetainAndContinue_PurgeVisitorResult,
|
||||
kPurgeAndContinue_PurgeVisitorResult,
|
||||
kRetainAndStop_PurgeVisitorResult,
|
||||
kPurgeAndStop_PurgeVisitorResult,
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for purge(). If called, the cache will have found a match for the
|
||||
* specified Key, and will pass in the corresponding Rec, along with a caller-specified
|
||||
* context. The function can read the data in Rec.
|
||||
*/
|
||||
typedef PurgeVisitorResult (*PurgeVisitor)(const Rec&, void* context);
|
||||
|
||||
/**
|
||||
* Returns a locked/pinned SkDiscardableMemory instance for the specified
|
||||
@ -114,7 +130,7 @@ public:
|
||||
* true : Rec is valid
|
||||
* false : Rec is "stale" -- the cache will purge it.
|
||||
*/
|
||||
static bool Find(const Key& key, VisitorProc, void* context);
|
||||
static bool Find(const Key& key, FindVisitor, void* context);
|
||||
static void Add(Rec*);
|
||||
|
||||
static size_t GetTotalBytesUsed();
|
||||
@ -125,6 +141,12 @@ public:
|
||||
static size_t GetSingleAllocationByteLimit();
|
||||
static size_t GetEffectiveSingleAllocationByteLimit();
|
||||
|
||||
/**
|
||||
* Visit all Rec that match the specified namespace, and purge entries as indicated by the
|
||||
* visitor.
|
||||
*/
|
||||
static void Purge(const void* nameSpace, PurgeVisitor, void* context);
|
||||
|
||||
static void PurgeAll();
|
||||
|
||||
/**
|
||||
@ -174,7 +196,7 @@ public:
|
||||
* true : Rec is valid
|
||||
* false : Rec is "stale" -- the cache will purge it.
|
||||
*/
|
||||
bool find(const Key&, VisitorProc, void* context);
|
||||
bool find(const Key&, FindVisitor, void* context);
|
||||
void add(Rec*);
|
||||
|
||||
size_t getTotalBytesUsed() const { return fTotalBytesUsed; }
|
||||
@ -198,8 +220,12 @@ public:
|
||||
*/
|
||||
size_t setTotalByteLimit(size_t newLimit);
|
||||
|
||||
void purge(const void* nameSpace, PurgeVisitor, void* context);
|
||||
|
||||
void purgeAll() {
|
||||
fInsidePurgeAllCounter += 1;
|
||||
this->purgeAsNeeded(true);
|
||||
fInsidePurgeAllCounter -= 1;
|
||||
}
|
||||
|
||||
DiscardableFactory discardableFactory() const { return fDiscardableFactory; }
|
||||
@ -228,6 +254,12 @@ private:
|
||||
size_t fSingleAllocationByteLimit;
|
||||
int fCount;
|
||||
|
||||
bool insidePurgeAll() const {
|
||||
SkASSERT(fInsidePurgeAllCounter >= 0);
|
||||
return fInsidePurgeAllCounter > 0;
|
||||
}
|
||||
int fInsidePurgeAllCounter;
|
||||
|
||||
void purgeAsNeeded(bool forcePurge = false);
|
||||
|
||||
// linklist management
|
||||
|
@ -156,6 +156,14 @@ static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cach
|
||||
|
||||
mipmap = SkMipMapCache::AddAndRef(src, cache);
|
||||
REPORTER_ASSERT(reporter, mipmap);
|
||||
|
||||
{
|
||||
const SkMipMap* mm = SkMipMapCache::FindAndRef(src, cache);
|
||||
REPORTER_ASSERT(reporter, mm);
|
||||
REPORTER_ASSERT(reporter, mm == mipmap);
|
||||
mm->unref();
|
||||
}
|
||||
|
||||
check_data(reporter, mipmap, 2, kInCache, kLocked);
|
||||
|
||||
mipmap->unref();
|
||||
@ -173,6 +181,67 @@ static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cach
|
||||
mipmap->unref();
|
||||
}
|
||||
|
||||
// In a multi-threaded run, we can't reliably assert that something is in the global cache
|
||||
// even if we just added it, since another thread might have caused a purge, hence we guard
|
||||
// those checks with this flag (to be defined only when run locally in 1 thread).
|
||||
#define ONLY_WORKS_RELIABLY_SINGLE_THREADED_SINCE_CACHE_MAY_HAVE_BEEN_PURGED
|
||||
|
||||
static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) {
|
||||
// TODO make our pixelref notification work for all caches (right now it is only global caches)
|
||||
cache = NULL;
|
||||
|
||||
const int N = 3;
|
||||
SkBitmap src[N];
|
||||
for (int i = 0; i < N; ++i) {
|
||||
src[i].allocN32Pixels(5, 5);
|
||||
src[i].setImmutable();
|
||||
SkMipMapCache::AddAndRef(src[i], cache)->unref();
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; ++i) {
|
||||
const SkMipMap* mipmap = SkMipMapCache::FindAndRef(src[i], cache);
|
||||
#ifdef ONLY_WORKS_RELIABLY_SINGLE_THREADED_SINCE_CACHE_MAY_HAVE_BEEN_PURGED
|
||||
REPORTER_ASSERT(reporter, mipmap);
|
||||
#endif
|
||||
SkSafeUnref(mipmap);
|
||||
|
||||
src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache
|
||||
|
||||
mipmap = SkMipMapCache::FindAndRef(src[i], cache);
|
||||
REPORTER_ASSERT(reporter, !mipmap);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_bitmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) {
|
||||
// TODO make our pixelref notification work for all caches (right now it is only global caches)
|
||||
cache = NULL;
|
||||
|
||||
const SkIRect subset = SkIRect::MakeWH(5, 5);
|
||||
const int N = 3;
|
||||
SkBitmap src[N], dst[N];
|
||||
for (int i = 0; i < N; ++i) {
|
||||
src[i].allocN32Pixels(5, 5);
|
||||
src[i].setImmutable();
|
||||
dst[i].allocN32Pixels(5, 5);
|
||||
dst[i].setImmutable();
|
||||
SkBitmapCache::Add(src[i].getGenerationID(), subset, dst[i], cache);
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; ++i) {
|
||||
const uint32_t genID = src[i].getGenerationID();
|
||||
SkBitmap result;
|
||||
bool found = SkBitmapCache::Find(genID, subset, &result, cache);
|
||||
#ifdef ONLY_WORKS_RELIABLY_SINGLE_THREADED_SINCE_CACHE_MAY_HAVE_BEEN_PURGED
|
||||
REPORTER_ASSERT(reporter, found);
|
||||
#endif
|
||||
|
||||
src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache
|
||||
|
||||
found = SkBitmapCache::Find(genID, subset, &result, cache);
|
||||
REPORTER_ASSERT(reporter, !found);
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(BitmapCache_discarded_bitmap, reporter) {
|
||||
SkResourceCache::DiscardableFactory factory = SkResourceCache::GetDiscardableFactory();
|
||||
SkBitmap::Allocator* allocator = SkBitmapCache::GetAllocator();
|
||||
@ -219,4 +288,6 @@ DEF_TEST(BitmapCache_discarded_bitmap, reporter) {
|
||||
REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm, cache));
|
||||
|
||||
test_mipmapcache(reporter, cache);
|
||||
test_bitmap_notify(reporter, cache);
|
||||
test_mipmap_notify(reporter, cache);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user