Change implementation of flush-count based GrGpuResource purging
Change default to approx 30seconds (given some API usage assumptions)
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2361093002
Review-Url: https://codereview.chromium.org/2361093002
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 9305c16..46e48fc 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -287,6 +287,7 @@
// This value reflects how recently this resource was accessed in the cache. This is maintained
// by the cache.
uint32_t fTimestamp;
+ uint32_t fExternalFlushCntWhenBecamePurgeable;
static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
GrScratchKey fScratchKey;
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index e64339b..c1578b5 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -20,7 +20,8 @@
}
GrGpuResource::GrGpuResource(GrGpu* gpu)
- : fGpu(gpu)
+ : fExternalFlushCntWhenBecamePurgeable(0)
+ , fGpu(gpu)
, fGpuMemorySize(kInvalidGpuMemorySize)
, fBudgeted(SkBudgeted::kNo)
, fRefsWrappedObjects(false)
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index b09dcc1..e91f899 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -58,6 +58,20 @@
uint32_t timestamp() const { return fResource->fTimestamp; }
void setTimestamp(uint32_t ts) { fResource->fTimestamp = ts; }
+ /** Called by the cache to record when this became purgeable. */
+ void setFlushCntWhenResourceBecamePurgeable(uint32_t cnt) {
+ SkASSERT(fResource->isPurgeable());
+ fResource->fExternalFlushCntWhenBecamePurgeable = cnt;
+ }
+ /**
+ * Called by the cache to determine whether this resource has been puregable for more than
+ * a threshold number of external flushes.
+ */
+ uint32_t flushCntWhenResourceBecamePurgeable() {
+ SkASSERT(fResource->isPurgeable());
+ return fResource->fExternalFlushCntWhenBecamePurgeable;
+ }
+
int* accessCacheIndex() const { return &fResource->fCacheArrayIndex; }
CacheAccess(GrGpuResource* resource) : fResource(resource) {}
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 529f87c..9462a73 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -74,49 +74,23 @@
, fBudgetedCount(0)
, fBudgetedBytes(0)
, fRequestFlush(false)
- , fFlushTimestamps(nullptr)
- , fLastFlushTimestampIndex(0)
+ , fExternalFlushCnt(0)
, fPreferVRAMUseOverFlushes(caps->preferVRAMUseOverFlushes()) {
SkDEBUGCODE(fCount = 0;)
SkDEBUGCODE(fNewlyPurgeableResourceForValidation = nullptr;)
- this->resetFlushTimestamps();
}
GrResourceCache::~GrResourceCache() {
this->releaseAll();
- delete[] fFlushTimestamps;
}
void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
fMaxCount = count;
fMaxBytes = bytes;
fMaxUnusedFlushes = maxUnusedFlushes;
- this->resetFlushTimestamps();
this->purgeAsNeeded();
}
-void GrResourceCache::resetFlushTimestamps() {
- delete[] fFlushTimestamps;
-
- // We assume this number is a power of two when wrapping indices into the timestamp array.
- fMaxUnusedFlushes = SkNextPow2(fMaxUnusedFlushes);
-
- // Since our implementation is to store the timestamps of the last fMaxUnusedFlushes flush calls
- // we just turn the feature off if that array would be large.
- static const int kMaxSupportedTimestampHistory = 128;
-
- if (fMaxUnusedFlushes > kMaxSupportedTimestampHistory) {
- fFlushTimestamps = nullptr;
- return;
- }
-
- fFlushTimestamps = new uint32_t[fMaxUnusedFlushes];
- fLastFlushTimestampIndex = 0;
- // Set all the historical flush timestamps to initially be at the beginning of time (timestamp
- // 0).
- sk_bzero(fFlushTimestamps, fMaxUnusedFlushes * sizeof(uint32_t));
-}
-
void GrResourceCache::insertResource(GrGpuResource* resource) {
SkASSERT(resource);
SkASSERT(!this->isInCache(resource));
@@ -390,6 +364,7 @@
SkASSERT(resource->isPurgeable());
this->removeFromNonpurgeableArray(resource);
fPurgeableQueue.insert(resource);
+ resource->cacheAccess().setFlushCntWhenResourceBecamePurgeable(fExternalFlushCnt);
if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
// Check whether this resource could still be used as a scratch resource.
@@ -474,20 +449,29 @@
this->processInvalidUniqueKeys(invalidKeyMsgs);
}
- if (fFlushTimestamps) {
- // Assuming kNumFlushesToDeleteUnusedResource is a power of 2.
- SkASSERT(SkIsPow2(fMaxUnusedFlushes));
- int oldestFlushIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
-
- uint32_t oldestAllowedTimestamp = fFlushTimestamps[oldestFlushIndex];
- while (fPurgeableQueue.count()) {
- uint32_t oldestResourceTimestamp = fPurgeableQueue.peek()->cacheAccess().timestamp();
- if (oldestAllowedTimestamp < oldestResourceTimestamp) {
- break;
+ if (fMaxUnusedFlushes > 0) {
+ // We want to know how many complete flushes have occurred without the resource being used.
+ // If the resource was tagged when fExternalFlushCnt was N then this means it became
+ // purgeable during activity that became the N+1th flush. So when the flush count is N+2
+ // it has sat in the purgeable queue for one entire flush.
+ uint32_t oldestAllowedFlushCnt = fExternalFlushCnt - fMaxUnusedFlushes - 1;
+ // check for underflow
+ if (oldestAllowedFlushCnt < fExternalFlushCnt) {
+ while (fPurgeableQueue.count()) {
+ uint32_t flushWhenResourceBecamePurgeable =
+ fPurgeableQueue.peek()->cacheAccess().flushCntWhenResourceBecamePurgeable();
+ if (oldestAllowedFlushCnt < flushWhenResourceBecamePurgeable) {
+ // Resources were given both LRU timestamps and tagged with a flush cnt when
+ // they first became purgeable. The LRU timestamp won't change again until the
+ // resource is made non-purgeable again. So, at this point all the remaining
+ // resources in the timestamp-sorted queue will have a flush count >= to this
+ // one.
+ break;
+ }
+ GrGpuResource* resource = fPurgeableQueue.peek();
+ SkASSERT(resource->isPurgeable());
+ resource->cacheAccess().release();
}
- GrGpuResource* resource = fPurgeableQueue.peek();
- SkASSERT(resource->isPurgeable());
- resource->cacheAccess().release();
}
}
@@ -566,13 +550,8 @@
fPurgeableQueue.pop();
}
- struct Less {
- bool operator()(GrGpuResource* a, GrGpuResource* b) {
- return CompareTimestamp(a,b);
- }
- };
- Less less;
- SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1, less);
+ SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1,
+ CompareTimestamp);
// Pick resources out of the purgeable and non-purgeable arrays based on lowest
// timestamp and assign new timestamps.
@@ -611,9 +590,6 @@
// count should be the next timestamp we return.
SkASSERT(fTimestamp == SkToU32(count));
-
- // The historical timestamps of flushes are now invalid.
- this->resetFlushTimestamps();
}
}
return fTimestamp++;
@@ -628,13 +604,12 @@
fRequestFlush = false;
break;
case FlushType::kExternal:
- if (fFlushTimestamps) {
- SkASSERT(SkIsPow2(fMaxUnusedFlushes));
- fLastFlushTimestampIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
- // get the timestamp before accessing fFlushTimestamps because getNextTimestamp will
- // reallocate fFlushTimestamps on timestamp overflow.
- uint32_t timestamp = this->getNextTimestamp();
- fFlushTimestamps[fLastFlushTimestampIndex] = timestamp;
+ ++fExternalFlushCnt;
+ if (0 == fExternalFlushCnt) {
+ // When this wraps just reset all the purgeable resources' last used flush state.
+ for (int i = 0; i < fPurgeableQueue.count(); ++i) {
+ fPurgeableQueue.at(i)->cacheAccess().setFlushCntWhenResourceBecamePurgeable(0);
+ }
}
break;
}
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index bf7b237..ae9a4e7 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -40,12 +40,6 @@
* A unique key always takes precedence over a scratch key when a resource has both types of keys.
* If a resource has neither key type then it will be deleted as soon as the last reference to it
* is dropped.
- *
- * When proactive purging is enabled, on every flush, the timestamp of that flush is stored in a
- * n-sized ring buffer. When purging occurs each purgeable resource's timestamp is compared to the
- * timestamp of the n-th prior flush. If the resource's last use timestamp is older than the old
- * flush then the resource is proactively purged even when the cache is under budget. By default
- * this feature is disabled, though it can be enabled by calling GrResourceCache::setLimits.
*/
class GrResourceCache {
public:
@@ -56,11 +50,12 @@
static const int kDefaultMaxCount = 2 * (1 << 12);
// Default maximum number of bytes of gpu memory of budgeted resources in the cache.
static const size_t kDefaultMaxSize = 96 * (1 << 20);
- // Default number of flushes a budgeted resources can go unused in the cache before it is
- // purged. Large values disable the feature (as the ring buffer of flush timestamps would be
- // large). This is currently the default until we decide to enable this feature
- // of the cache by default.
- static const int kDefaultMaxUnusedFlushes = 64;
+ // Default number of external flushes a budgeted resources can go unused in the cache before it
+ // is purged. Using a value <= 0 disables this feature.
+ static const int kDefaultMaxUnusedFlushes =
+ 1 * /* flushes per frame */
+ 60 * /* fps */
+ 30; /* seconds */
/** Used to access functionality needed by GrGpuResource for lifetime management. */
class ResourceAccess;
@@ -68,9 +63,9 @@
/**
* Sets the cache limits in terms of number of resources, max gpu memory byte size, and number
- * of GrContext flushes that a resource can be unused before it is evicted. The latter value is
- * a suggestion and there is no promise that a resource will be purged immediately after it
- * hasn't been used in maxUnusedFlushes flushes.
+ * of external GrContext flushes that a resource can be unused before it is evicted. The latter
+ * value is a suggestion and there is no promise that a resource will be purged immediately
+ * after it hasn't been used in maxUnusedFlushes flushes.
*/
void setLimits(int count, size_t bytes, int maxUnusedFlushes = kDefaultMaxUnusedFlushes);
@@ -237,7 +232,6 @@
void refAndMakeResourceMRU(GrGpuResource*);
/// @}
- void resetFlushTimestamps();
void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
void addToNonpurgeableArray(GrGpuResource*);
void removeFromNonpurgeableArray(GrGpuResource*);
@@ -321,11 +315,7 @@
size_t fBudgetedBytes;
bool fRequestFlush;
-
- // We keep track of the "timestamps" of the last n flushes. If a resource hasn't been used in
- // that time then we well preemptively purge it to reduce memory usage.
- uint32_t* fFlushTimestamps;
- int fLastFlushTimestampIndex;
+ uint32_t fExternalFlushCnt;
InvalidUniqueKeyInbox fInvalidUniqueKeyInbox;
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index b568485..3176366 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -1137,8 +1137,8 @@
}
// Send flush notifications to the cache. Each flush should purge the oldest resource.
- for (int i = 0; i < kFlushCount - 1; ++i) {
- // The first resource was purged after the last flush in the initial loop, hence the -1.
+ for (int i = 0; i < kFlushCount; ++i) {
+ cache->notifyFlushOccurred(GrResourceCache::kExternal);
REPORTER_ASSERT(reporter, kFlushCount - i - 1 == cache->getResourceCount());
for (int j = 0; j < i; ++j) {
GrUniqueKey k;
@@ -1147,7 +1147,6 @@
REPORTER_ASSERT(reporter, !SkToBool(r));
SkSafeUnref(r);
}
- cache->notifyFlushOccurred(GrResourceCache::kExternal);
}
REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
@@ -1174,8 +1173,8 @@
for (int i = 0; i < kFlushCount; ++i) {
// Should get a resource purged every other flush.
- REPORTER_ASSERT(reporter, kFlushCount - i/2 - 1 == cache->getResourceCount());
cache->notifyFlushOccurred(GrResourceCache::kExternal);
+ REPORTER_ASSERT(reporter, kFlushCount - i/2 - 1 == cache->getResourceCount());
}
// Unref all the resources that we kept refs on in the first loop.
@@ -1183,9 +1182,9 @@
refedResources[i]->unref();
}
- // When we unref'ed them their timestamps got updated. So nothing should be purged until we
- // get kFlushCount additional flushes. Then everything should be purged.
- for (int i = 0; i < kFlushCount; ++i) {
+ // After kFlushCount + 1 flushes they all will have sat in the purgeable queue for
+ // kFlushCount full flushes.
+ for (int i = 0; i < kFlushCount + 1; ++i) {
REPORTER_ASSERT(reporter, kFlushCount >> 1 == cache->getResourceCount());
cache->notifyFlushOccurred(GrResourceCache::kExternal);
}