Adds a mechanism for GrCacheable objects to notify the resource cache
when their size has changed. GrResourceCacheEntry now holds a
reference to the cache, and a cached value of the resource's most
recent size.
Also utilizes this new functionality for mipmaps, and adds a test for
changing resource sizes.
R=bsalomon@google.com, robertphillips@google.com
Author: cdalton@nvidia.com
Review URL: https://codereview.chromium.org/257093002
git-svn-id: http://skia.googlecode.com/svn/trunk@14576 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/gpu/GrCacheable.h b/include/gpu/GrCacheable.h
index 75dec16..39c62b1 100644
--- a/include/gpu/GrCacheable.h
+++ b/include/gpu/GrCacheable.h
@@ -46,6 +46,14 @@
bool isInCache() const { return NULL != fCacheEntry; }
+ /**
+ * This entry point should be called whenever gpuMemorySize() begins
+ * reporting a different size. If the object is in the cache, it will call
+ * gpuMemorySize() immediately and pass the new size on to the resource
+ * cache.
+ */
+ void didChangeGpuMemorySize() const;
+
private:
GrResourceCacheEntry* fCacheEntry; // NULL if not in cache
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index 1b9f7b7..ac31f51 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -44,22 +44,16 @@
return 0 != (fDesc.fFlags & flags);
}
- void dirtyMipMaps(bool mipMapsDirty) {
- fMipMapsDirty = mipMapsDirty;
- }
+ void dirtyMipMaps(bool mipMapsDirty);
bool mipMapsAreDirty() const {
- return fMipMapsDirty;
+ return kValid_MipMapsStatus != fMipMapsStatus;
}
/**
* Approximate number of bytes used by the texture
*/
- virtual size_t gpuMemorySize() const SK_OVERRIDE {
- return (size_t) fDesc.fWidth *
- fDesc.fHeight *
- GrBytesPerPixel(fDesc.fConfig);
- }
+ virtual size_t gpuMemorySize() const SK_OVERRIDE;
// GrSurface overrides
virtual bool readPixels(int left, int top, int width, int height,
@@ -144,7 +138,7 @@
GrTexture(GrGpu* gpu, bool isWrapped, const GrTextureDesc& desc)
: INHERITED(gpu, isWrapped, desc)
, fRenderTarget(NULL)
- , fMipMapsDirty(true) {
+ , fMipMapsStatus(kNotAllocated_MipMapsStatus) {
// only make sense if alloc size is pow2
fShiftFixedX = 31 - SkCLZ(fDesc.fWidth);
@@ -159,12 +153,18 @@
void validateDesc() const;
private:
+ enum MipMapsStatus {
+ kNotAllocated_MipMapsStatus,
+ kAllocated_MipMapsStatus,
+ kValid_MipMapsStatus
+ };
+
// these two shift a fixed-point value into normalized coordinates
// for this texture if the texture is power of two sized.
int fShiftFixedX;
int fShiftFixedY;
- bool fMipMapsDirty;
+ MipMapsStatus fMipMapsStatus;
virtual void internal_dispose() const SK_OVERRIDE;
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 26f7592..529c3a5 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -13,6 +13,16 @@
DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage);
+///////////////////////////////////////////////////////////////////////////////
+
+void GrCacheable::didChangeGpuMemorySize() const {
+ if (this->isInCache()) {
+ fCacheEntry->didChangeResourceSize();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() {
static int32_t gNextType = 0;
@@ -26,8 +36,14 @@
///////////////////////////////////////////////////////////////////////////////
-GrResourceCacheEntry::GrResourceCacheEntry(const GrResourceKey& key, GrCacheable* resource)
- : fKey(key), fResource(resource) {
+GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache,
+ const GrResourceKey& key,
+ GrCacheable* resource)
+ : fResourceCache(resourceCache),
+ fKey(key),
+ fResource(resource),
+ fCachedSize(resource->gpuMemorySize()),
+ fIsExclusive(false) {
// we assume ownership of the resource, and will unref it when we die
SkASSERT(resource);
resource->ref();
@@ -40,12 +56,24 @@
#ifdef SK_DEBUG
void GrResourceCacheEntry::validate() const {
+ SkASSERT(fResourceCache);
SkASSERT(fResource);
SkASSERT(fResource->getCacheEntry() == this);
+ SkASSERT(fResource->gpuMemorySize() == fCachedSize);
fResource->validate();
}
#endif
+void GrResourceCacheEntry::didChangeResourceSize() {
+ size_t oldSize = fCachedSize;
+ fCachedSize = fResource->gpuMemorySize();
+ if (fCachedSize > oldSize) {
+ fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize);
+ } else if (fCachedSize < oldSize) {
+ fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize);
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) :
@@ -115,7 +143,7 @@
// update our stats
if (kIgnore_BudgetBehavior == behavior) {
fClientDetachedCount += 1;
- fClientDetachedBytes += entry->resource()->gpuMemorySize();
+ fClientDetachedBytes += entry->fCachedSize;
#if GR_CACHE_STATS
if (fHighWaterClientDetachedCount < fClientDetachedCount) {
@@ -130,7 +158,7 @@
SkASSERT(kAccountFor_BudgetBehavior == behavior);
fEntryCount -= 1;
- fEntryBytes -= entry->resource()->gpuMemorySize();
+ fEntryBytes -= entry->fCachedSize;
}
}
@@ -141,12 +169,12 @@
// update our stats
if (kIgnore_BudgetBehavior == behavior) {
fClientDetachedCount -= 1;
- fClientDetachedBytes -= entry->resource()->gpuMemorySize();
+ fClientDetachedBytes -= entry->fCachedSize;
} else {
SkASSERT(kAccountFor_BudgetBehavior == behavior);
fEntryCount += 1;
- fEntryBytes += entry->resource()->gpuMemorySize();
+ fEntryBytes += entry->fCachedSize;
#if GR_CACHE_STATS
if (fHighWaterEntryCount < fEntryCount) {
@@ -208,7 +236,7 @@
SkASSERT(!fPurging);
GrAutoResourceCacheValidate atcv(this);
- GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (key, resource));
+ GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, key, resource));
resource->setCacheEntry(entry);
this->attachToHead(entry);
@@ -223,6 +251,9 @@
void GrResourceCache::makeExclusive(GrResourceCacheEntry* entry) {
GrAutoResourceCacheValidate atcv(this);
+ SkASSERT(!entry->fIsExclusive);
+ entry->fIsExclusive = true;
+
// When scratch textures are detached (to hide them from future finds) they
// still count against the resource budget
this->internalDetach(entry, kIgnore_BudgetBehavior);
@@ -239,11 +270,12 @@
// the client called GrContext::contextDestroyed() to notify Gr,
// and then later an SkGpuDevice's destructor releases its backing
// texture (which was invalidated at contextDestroyed time).
+ // TODO: Safely delete the GrResourceCacheEntry as well.
fClientDetachedCount -= 1;
fEntryCount -= 1;
- size_t size = entry->resource()->gpuMemorySize();
- fClientDetachedBytes -= size;
- fEntryBytes -= size;
+ fClientDetachedBytes -= entry->fCachedSize;
+ fEntryBytes -= entry->fCachedSize;
+ entry->fCachedSize = 0;
}
void GrResourceCache::makeNonExclusive(GrResourceCacheEntry* entry) {
@@ -259,11 +291,32 @@
// alter the budget information.
attachToHead(entry, kIgnore_BudgetBehavior);
fCache.insert(entry->key(), entry);
+
+ SkASSERT(entry->fIsExclusive);
+ entry->fIsExclusive = false;
} else {
this->removeInvalidResource(entry);
}
}
+void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountInc) {
+ fEntryBytes += amountInc;
+ if (entry->fIsExclusive) {
+ fClientDetachedBytes += amountInc;
+ }
+ this->purgeAsNeeded();
+}
+
+void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry, size_t amountDec) {
+ fEntryBytes -= amountDec;
+ if (entry->fIsExclusive) {
+ fClientDetachedBytes -= amountDec;
+ }
+#ifdef SK_DEBUG
+ this->validate();
+#endif
+}
+
/**
* Destroying a resource may potentially trigger the unlock of additional
* resources which in turn will trigger a nested purge. We block the nested
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index b2f91cd..1a81fe6 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -19,6 +19,7 @@
#include "SkTInternalLList.h"
class GrCacheable;
+class GrResourceCache;
class GrResourceCacheEntry;
class GrResourceKey {
@@ -128,12 +129,24 @@
void validate() const {}
#endif
+ /**
+ * Update the cached size for this entry and inform the resource cache that
+ * it has changed. Usually invoked from GrCacheable::didChangeGpuMemorySize,
+ * not directly from here.
+ */
+ void didChangeResourceSize();
+
private:
- GrResourceCacheEntry(const GrResourceKey& key, GrCacheable* resource);
+ GrResourceCacheEntry(GrResourceCache* resourceCache,
+ const GrResourceKey& key,
+ GrCacheable* resource);
~GrResourceCacheEntry();
+ GrResourceCache* fResourceCache;
GrResourceKey fKey;
GrCacheable* fResource;
+ size_t fCachedSize;
+ bool fIsExclusive;
// Linked list for the LRU ordering.
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrResourceCacheEntry);
@@ -272,6 +285,12 @@
void makeNonExclusive(GrResourceCacheEntry* entry);
/**
+ * Notify the cache that the size of a resource has changed.
+ */
+ void didIncreaseResourceSize(const GrResourceCacheEntry*, size_t amountInc);
+ void didDecreaseResourceSize(const GrResourceCacheEntry*, size_t amountDec);
+
+ /**
* Remove a resource from the cache and delete it!
*/
void deleteResource(GrResourceCacheEntry* entry);
diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp
index f851515..3186d89 100644
--- a/src/gpu/GrTexture.cpp
+++ b/src/gpu/GrTexture.cpp
@@ -44,6 +44,33 @@
this->INHERITED::internal_dispose();
}
+void GrTexture::dirtyMipMaps(bool mipMapsDirty) {
+ if (mipMapsDirty) {
+ if (kValid_MipMapsStatus == fMipMapsStatus) {
+ fMipMapsStatus = kAllocated_MipMapsStatus;
+ }
+ } else {
+ const bool sizeChanged = kNotAllocated_MipMapsStatus == fMipMapsStatus;
+ fMipMapsStatus = kValid_MipMapsStatus;
+ if (sizeChanged) {
+ // This must not be called until after changing fMipMapsStatus.
+ this->didChangeGpuMemorySize();
+ }
+ }
+}
+
+size_t GrTexture::gpuMemorySize() const {
+ size_t textureSize = (size_t) fDesc.fWidth *
+ fDesc.fHeight *
+ GrBytesPerPixel(fDesc.fConfig);
+ if (kNotAllocated_MipMapsStatus != fMipMapsStatus) {
+ // We don't have to worry about the mipmaps being a different size than
+ // we'd expect because we never change fDesc.fWidth/fHeight.
+ textureSize *= 2;
+ }
+ return textureSize;
+}
+
bool GrTexture::readPixels(int left, int top, int width, int height,
GrPixelConfig config, void* buffer,
size_t rowBytes, uint32_t pixelOpsFlags) {
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 7262b43..f6c8b1d 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -57,11 +57,14 @@
}
class TestResource : public GrCacheable {
+ static const size_t kDefaultSize = 100;
+
public:
SK_DECLARE_INST_COUNT(TestResource);
- TestResource()
+ TestResource(size_t size = kDefaultSize)
: fCache(NULL)
- , fToDelete(NULL) {
+ , fToDelete(NULL)
+ , fSize(size) {
++fAlive;
}
@@ -74,7 +77,12 @@
}
}
- size_t gpuMemorySize() const SK_OVERRIDE { return 100; }
+ void setSize(size_t size) {
+ fSize = size;
+ this->didChangeGpuMemorySize();
+ }
+
+ size_t gpuMemorySize() const SK_OVERRIDE { return fSize; }
bool isValidOnGpu() const SK_OVERRIDE { return true; }
@@ -88,6 +96,7 @@
private:
GrResourceCache* fCache;
TestResource* fToDelete;
+ size_t fSize;
static int fAlive;
typedef GrCacheable INHERITED;
@@ -174,6 +183,99 @@
}
}
+static void test_resource_size_changed(skiatest::Reporter* reporter,
+ GrContext* context) {
+ GrCacheID::Domain domain = GrCacheID::GenerateDomain();
+ GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
+
+ GrCacheID::Key key1Data;
+ key1Data.fData64[0] = 0;
+ key1Data.fData64[1] = 0;
+ GrResourceKey key1(GrCacheID(domain, key1Data), t, 0);
+
+ GrCacheID::Key key2Data;
+ key2Data.fData64[0] = 1;
+ key2Data.fData64[1] = 0;
+ GrResourceKey key2(GrCacheID(domain, key2Data), t, 0);
+
+ // Test changing resources sizes (both increase & decrease).
+ {
+ GrResourceCache cache(2, 300);
+
+ TestResource* a = new TestResource(0);
+ a->setSize(100); // Test didChangeGpuMemorySize() when not in the cache.
+ cache.addResource(key1, a);
+ a->unref();
+
+ TestResource* b = new TestResource(0);
+ b->setSize(100);
+ cache.addResource(key2, b);
+ b->unref();
+
+ REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+
+ static_cast<TestResource*>(cache.find(key2))->setSize(200);
+ static_cast<TestResource*>(cache.find(key1))->setSize(50);
+
+ REPORTER_ASSERT(reporter, 250 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+ }
+
+ // Test increasing a resources size beyond the cache budget.
+ {
+ GrResourceCache cache(2, 300);
+
+ TestResource* a = new TestResource(100);
+ cache.addResource(key1, a);
+ a->unref();
+
+ TestResource* b = new TestResource(100);
+ cache.addResource(key2, b);
+ b->unref();
+
+ REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+
+ static_cast<TestResource*>(cache.find(key2))->setSize(201);
+ REPORTER_ASSERT(reporter, NULL == cache.find(key1));
+
+ REPORTER_ASSERT(reporter, 201 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 1 == cache.getCachedResourceCount());
+ }
+
+ // Test changing the size of an exclusively-held resource.
+ {
+ GrResourceCache cache(2, 300);
+
+ TestResource* a = new TestResource(100);
+ cache.addResource(key1, a);
+ cache.makeExclusive(a->getCacheEntry());
+
+ TestResource* b = new TestResource(100);
+ cache.addResource(key2, b);
+ b->unref();
+
+ REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+ REPORTER_ASSERT(reporter, NULL == cache.find(key1));
+
+ a->setSize(200);
+
+ REPORTER_ASSERT(reporter, 300 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+ // Internal resource cache validation will test the detached size (debug mode only).
+
+ cache.makeNonExclusive(a->getCacheEntry());
+ a->unref();
+
+ REPORTER_ASSERT(reporter, 300 == cache.getCachedResourceBytes());
+ REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
+ REPORTER_ASSERT(reporter, NULL != cache.find(key1));
+ // Internal resource cache validation will test the detached size (debug mode only).
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
DEF_GPUTEST(ResourceCache, reporter, factory) {
for (int type = 0; type < GrContextFactory::kLastGLContextType; ++type) {
@@ -199,6 +301,7 @@
test_cache(reporter, context, &canvas);
test_purge_invalidated(reporter, context);
test_cache_delete_on_destruction(reporter, context);
+ test_resource_size_changed(reporter, context);
}
}