Add mechanism to proactively purge old resources in GrResourceCache.
This change leaves the feature turned off by default. Review URL: https://codereview.chromium.org/1032873002
This commit is contained in:
parent
b7133bed55
commit
3f324321cd
@ -33,8 +33,15 @@ class GrResourceCache;
|
||||
*
|
||||
* The latter two ref types are private and intended only for Gr core code.
|
||||
*
|
||||
* When an item is purgeable DERIVED:notifyIsPurgeable() will be called (static poly morphism using
|
||||
* CRTP). GrIORef and GrGpuResource are separate classes for organizational reasons and to be
|
||||
* When all the ref/io counts reach zero DERIVED::notifyAllCntsAreZero() will be called (static poly
|
||||
* morphism using CRTP). Similarly when the ref (but not necessarily pending read/write) count
|
||||
* reaches 0 DERIVED::notifyRefCountIsZero() will be called. In the case when an unref() causes both
|
||||
* the ref cnt to reach zero and the other counts are zero, notifyRefCountIsZero() will be called
|
||||
* before notifyIsPurgeable(). Moreover, if notifyRefCountIsZero() returns false then
|
||||
* notifyAllRefCntsAreZero() won't be called at all. notifyRefCountIsZero() must return false if the
|
||||
* object may be deleted after notifyRefCntIsZero() returns.
|
||||
*
|
||||
* GrIORef and GrGpuResource are separate classes for organizational reasons and to be
|
||||
* able to give access via friendship to only the functions related to pending IO operations.
|
||||
*/
|
||||
template <typename DERIVED> class GrIORef : public SkNoncopyable {
|
||||
@ -52,8 +59,14 @@ public:
|
||||
|
||||
void unref() const {
|
||||
this->validate();
|
||||
--fRefCnt;
|
||||
this->didUnref();
|
||||
|
||||
if (!(--fRefCnt)) {
|
||||
if (!static_cast<const DERIVED*>(this)->notifyRefCountIsZero()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->didRemoveRefOrPendingIO(kRef_CntType);
|
||||
}
|
||||
|
||||
void validate() const {
|
||||
@ -68,6 +81,12 @@ public:
|
||||
protected:
|
||||
GrIORef() : fRefCnt(1), fPendingReads(0), fPendingWrites(0) { }
|
||||
|
||||
enum CntType {
|
||||
kRef_CntType,
|
||||
kPendingRead_CntType,
|
||||
kPendingWrite_CntType,
|
||||
};
|
||||
|
||||
bool isPurgeable() const { return !this->internalHasRef() && !this->internalHasPendingIO(); }
|
||||
|
||||
bool internalHasPendingRead() const { return SkToBool(fPendingReads); }
|
||||
@ -85,7 +104,7 @@ private:
|
||||
void completedRead() const {
|
||||
this->validate();
|
||||
--fPendingReads;
|
||||
this->didUnref();
|
||||
this->didRemoveRefOrPendingIO(kPendingRead_CntType);
|
||||
}
|
||||
|
||||
void addPendingWrite() const {
|
||||
@ -96,13 +115,13 @@ private:
|
||||
void completedWrite() const {
|
||||
this->validate();
|
||||
--fPendingWrites;
|
||||
this->didUnref();
|
||||
this->didRemoveRefOrPendingIO(kPendingWrite_CntType);
|
||||
}
|
||||
|
||||
private:
|
||||
void didUnref() const {
|
||||
void didRemoveRefOrPendingIO(CntType cntTypeRemoved) const {
|
||||
if (0 == fPendingReads && 0 == fPendingWrites && 0 == fRefCnt) {
|
||||
static_cast<const DERIVED*>(this)->notifyIsPurgeable();
|
||||
static_cast<const DERIVED*>(this)->notifyAllCntsAreZero(cntTypeRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,7 +290,8 @@ private:
|
||||
// See comments in CacheAccess and ResourcePriv.
|
||||
void setUniqueKey(const GrUniqueKey&);
|
||||
void removeUniqueKey();
|
||||
void notifyIsPurgeable() const;
|
||||
void notifyAllCntsAreZero(CntType) const;
|
||||
bool notifyRefCountIsZero() const;
|
||||
void removeScratchKey();
|
||||
void makeBudgeted();
|
||||
void makeUnbudgeted();
|
||||
@ -304,7 +324,7 @@ private:
|
||||
SkAutoTUnref<const SkData> fData;
|
||||
|
||||
typedef GrIORef<GrGpuResource> INHERITED;
|
||||
friend class GrIORef<GrGpuResource>; // to access notifyIsPurgeable.
|
||||
friend class GrIORef<GrGpuResource>; // to access notifyAllCntsAreZero and notifyRefCntIsZero.
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1477,6 +1477,7 @@ void GrContext::flush(int flagsBitfield) {
|
||||
} else {
|
||||
fDrawBuffer->flush();
|
||||
}
|
||||
fResourceCache->notifyFlushOccurred();
|
||||
fFlushToReduceCacheSize = false;
|
||||
}
|
||||
|
||||
|
@ -105,14 +105,39 @@ void GrGpuResource::setUniqueKey(const GrUniqueKey& key) {
|
||||
get_resource_cache(fGpu)->resourceAccess().changeUniqueKey(this, key);
|
||||
}
|
||||
|
||||
void GrGpuResource::notifyIsPurgeable() const {
|
||||
void GrGpuResource::notifyAllCntsAreZero(CntType lastCntTypeToReachZero) const {
|
||||
if (this->wasDestroyed()) {
|
||||
// We've already been removed from the cache. Goodbye cruel world!
|
||||
SkDELETE(this);
|
||||
} else {
|
||||
GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
|
||||
get_resource_cache(fGpu)->resourceAccess().notifyPurgeable(mutableThis);
|
||||
return;
|
||||
}
|
||||
|
||||
// We should have already handled this fully in notifyRefCntIsZero().
|
||||
SkASSERT(kRef_CntType != lastCntTypeToReachZero);
|
||||
|
||||
GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
|
||||
static const uint32_t kFlag =
|
||||
GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
|
||||
get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, kFlag);
|
||||
}
|
||||
|
||||
bool GrGpuResource::notifyRefCountIsZero() const {
|
||||
if (this->wasDestroyed()) {
|
||||
// handle this in notifyAllCntsAreZero().
|
||||
return true;
|
||||
}
|
||||
|
||||
GrGpuResource* mutableThis = const_cast<GrGpuResource*>(this);
|
||||
uint32_t flags =
|
||||
GrResourceCache::ResourceAccess::kRefCntReachedZero_RefNotificationFlag;
|
||||
if (!this->internalHasPendingIO()) {
|
||||
flags |= GrResourceCache::ResourceAccess::kAllCntsReachedZero_RefNotificationFlag;
|
||||
}
|
||||
get_resource_cache(fGpu)->resourceAccess().notifyCntReachedZero(mutableThis, flags);
|
||||
|
||||
// There is no need to call our notifyAllCntsAreZero function at this point since we already
|
||||
// told the cache about the state of cnts.
|
||||
return false;
|
||||
}
|
||||
|
||||
void GrGpuResource::setScratchKey(const GrScratchKey& scratchKey) {
|
||||
|
@ -40,6 +40,7 @@ GrUniqueKey::Domain GrUniqueKey::GenerateDomain() {
|
||||
|
||||
return static_cast<Domain>(domain);
|
||||
}
|
||||
|
||||
uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
|
||||
return SkChecksum::Compute(data, size);
|
||||
}
|
||||
@ -56,13 +57,12 @@ private:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const int kDefaultMaxCount = 2 * (1 << 12);
|
||||
static const size_t kDefaultMaxSize = 96 * (1 << 20);
|
||||
|
||||
GrResourceCache::GrResourceCache()
|
||||
: fTimestamp(0)
|
||||
, fMaxCount(kDefaultMaxCount)
|
||||
, fMaxBytes(kDefaultMaxSize)
|
||||
, fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
|
||||
#if GR_CACHE_STATS
|
||||
, fHighWaterCount(0)
|
||||
, fHighWaterBytes(0)
|
||||
@ -73,20 +73,49 @@ GrResourceCache::GrResourceCache()
|
||||
, fBudgetedCount(0)
|
||||
, fBudgetedBytes(0)
|
||||
, fOverBudgetCB(NULL)
|
||||
, fOverBudgetData(NULL) {
|
||||
, fOverBudgetData(NULL)
|
||||
, fFlushTimestamps(NULL)
|
||||
, fLastFlushTimestampIndex(0){
|
||||
SkDEBUGCODE(fCount = 0;)
|
||||
SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL;)
|
||||
this->resetFlushTimestamps();
|
||||
}
|
||||
|
||||
GrResourceCache::~GrResourceCache() {
|
||||
this->releaseAll();
|
||||
SkDELETE(fFlushTimestamps);
|
||||
}
|
||||
|
||||
void GrResourceCache::setLimits(int count, size_t bytes) {
|
||||
void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
|
||||
fMaxCount = count;
|
||||
fMaxBytes = bytes;
|
||||
fMaxUnusedFlushes = maxUnusedFlushes;
|
||||
this->resetFlushTimestamps();
|
||||
this->purgeAsNeeded();
|
||||
}
|
||||
|
||||
void GrResourceCache::resetFlushTimestamps() {
|
||||
SkDELETE(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 = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
fFlushTimestamps = SkNEW_ARRAY(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));
|
||||
@ -247,8 +276,8 @@ void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
|
||||
}
|
||||
|
||||
void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
|
||||
// Someone has a ref to this resource in order to invalidate it. When the ref count reaches
|
||||
// zero we will get a notifyPurgable() and figure out what to do with it.
|
||||
// Someone has a ref to this resource in order to have removed the key. When the ref count
|
||||
// reaches zero we will get a ref cnt notification and figure out what to do with it.
|
||||
if (resource->getUniqueKey().isValid()) {
|
||||
SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
|
||||
fUniqueHash.remove(resource->getUniqueKey());
|
||||
@ -307,11 +336,34 @@ void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void GrResourceCache::notifyPurgeable(GrGpuResource* resource) {
|
||||
void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
|
||||
SkASSERT(resource);
|
||||
SkASSERT(!resource->wasDestroyed());
|
||||
SkASSERT(flags);
|
||||
SkASSERT(this->isInCache(resource));
|
||||
SkASSERT(resource->isPurgeable());
|
||||
// This resource should always be in the nonpurgeable array when this function is called. It
|
||||
// will be moved to the queue if it is newly purgeable.
|
||||
SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
|
||||
|
||||
if (SkToBool(ResourceAccess::kRefCntReachedZero_RefNotificationFlag & flags)) {
|
||||
#ifdef SK_DEBUG
|
||||
// When the timestamp overflows validate() is called. validate() checks that resources in
|
||||
// the nonpurgeable array are indeed not purgeable. However, the movement from the array to
|
||||
// the purgeable queue happens just below in this function. So we mark it as an exception.
|
||||
if (resource->isPurgeable()) {
|
||||
fNewlyPurgeableResourceForValidation = resource;
|
||||
}
|
||||
#endif
|
||||
resource->cacheAccess().setTimestamp(this->getNextTimestamp());
|
||||
SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL);
|
||||
}
|
||||
|
||||
if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
|
||||
SkASSERT(!resource->isPurgeable());
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(resource->isPurgeable());
|
||||
this->removeFromNonpurgeableArray(resource);
|
||||
fPurgeableQueue.insert(resource);
|
||||
|
||||
@ -391,25 +443,43 @@ void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void GrResourceCache::internalPurgeAsNeeded() {
|
||||
SkASSERT(this->overBudget());
|
||||
void GrResourceCache::purgeAsNeeded() {
|
||||
SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
|
||||
fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
|
||||
if (invalidKeyMsgs.count()) {
|
||||
this->processInvalidUniqueKeys(invalidKeyMsgs);
|
||||
}
|
||||
|
||||
bool stillOverbudget = true;
|
||||
while (fPurgeableQueue.count()) {
|
||||
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;
|
||||
}
|
||||
GrGpuResource* resource = fPurgeableQueue.peek();
|
||||
SkASSERT(resource->isPurgeable());
|
||||
resource->cacheAccess().release();
|
||||
}
|
||||
}
|
||||
|
||||
bool stillOverbudget = this->overBudget();
|
||||
while (stillOverbudget && fPurgeableQueue.count()) {
|
||||
GrGpuResource* resource = fPurgeableQueue.peek();
|
||||
SkASSERT(resource->isPurgeable());
|
||||
resource->cacheAccess().release();
|
||||
if (!this->overBudget()) {
|
||||
stillOverbudget = false;
|
||||
break;
|
||||
}
|
||||
stillOverbudget = this->overBudget();
|
||||
}
|
||||
|
||||
this->validate();
|
||||
|
||||
if (stillOverbudget) {
|
||||
// Despite the purge we're still over budget. Call our over budget callback. If this frees
|
||||
// any resources then we'll get notifyPurgeable() calls and take appropriate action.
|
||||
// any resources then we'll get notified and take appropriate action.
|
||||
(*fOverBudgetCB)(fOverBudgetData);
|
||||
this->validate();
|
||||
}
|
||||
@ -433,7 +503,7 @@ void GrResourceCache::processInvalidUniqueKeys(
|
||||
GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
|
||||
if (resource) {
|
||||
resource->resourcePriv().removeUniqueKey();
|
||||
resource->unref(); // will call notifyPurgeable, if it is indeed now purgeable.
|
||||
resource->unref(); // If this resource is now purgeable, the cache will be notified.
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -518,11 +588,26 @@ uint32_t GrResourceCache::getNextTimestamp() {
|
||||
|
||||
// count should be the next timestamp we return.
|
||||
SkASSERT(fTimestamp == SkToU32(count));
|
||||
|
||||
// The historical timestamps of flushes are now invalid.
|
||||
this->resetFlushTimestamps();
|
||||
}
|
||||
}
|
||||
return fTimestamp++;
|
||||
}
|
||||
|
||||
void GrResourceCache::notifyFlushOccurred() {
|
||||
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;
|
||||
this->purgeAsNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
void GrResourceCache::validate() const {
|
||||
// Reduce the frequency of validations for large resource counts.
|
||||
@ -586,7 +671,8 @@ void GrResourceCache::validate() const {
|
||||
Stats stats(this);
|
||||
|
||||
for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
|
||||
SkASSERT(!fNonpurgeableResources[i]->isPurgeable());
|
||||
SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
|
||||
fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
|
||||
SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
|
||||
SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
|
||||
stats.update(fNonpurgeableResources[i]);
|
||||
@ -615,7 +701,7 @@ void GrResourceCache::validate() const {
|
||||
SkASSERT(stats.fContent == fUniqueHash.count());
|
||||
SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
|
||||
|
||||
// This assertion is not currently valid because we can be in recursive notifyIsPurgeable()
|
||||
// This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
|
||||
// calls. This will be fixed when subresource registration is explicit.
|
||||
// bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
|
||||
// SkASSERT(!overBudget || locked == count || fPurging);
|
||||
|
@ -38,20 +38,39 @@ class SkString;
|
||||
* 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:
|
||||
GrResourceCache();
|
||||
~GrResourceCache();
|
||||
|
||||
// Default maximum number of budgeted resources in the cache.
|
||||
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 = 1024;
|
||||
|
||||
/** Used to access functionality needed by GrGpuResource for lifetime management. */
|
||||
class ResourceAccess;
|
||||
ResourceAccess resourceAccess();
|
||||
|
||||
/**
|
||||
* Sets the cache limits in terms of number of resources and max gpu memory byte size.
|
||||
* 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.
|
||||
*/
|
||||
void setLimits(int count, size_t bytes);
|
||||
void setLimits(int count, size_t bytes, int maxUnusedFlushes = kDefaultMaxUnusedFlushes);
|
||||
|
||||
/**
|
||||
* Returns the number of resources.
|
||||
@ -136,17 +155,7 @@ public:
|
||||
|
||||
/** Purges resources to become under budget and processes resources with invalidated unique
|
||||
keys. */
|
||||
void purgeAsNeeded() {
|
||||
SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
|
||||
fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
|
||||
if (invalidKeyMsgs.count()) {
|
||||
this->processInvalidUniqueKeys(invalidKeyMsgs);
|
||||
}
|
||||
if (fBudgetedCount <= fMaxCount && fBudgetedBytes <= fMaxBytes) {
|
||||
return;
|
||||
}
|
||||
this->internalPurgeAsNeeded();
|
||||
}
|
||||
void purgeAsNeeded();
|
||||
|
||||
/** Purges all resources that don't have external owners. */
|
||||
void purgeAllUnlocked();
|
||||
@ -167,6 +176,8 @@ public:
|
||||
fOverBudgetData = data;
|
||||
}
|
||||
|
||||
void notifyFlushOccurred();
|
||||
|
||||
#if GR_GPU_STATS
|
||||
void dumpStats(SkString*) const;
|
||||
#endif
|
||||
@ -180,7 +191,7 @@ private:
|
||||
////
|
||||
void insertResource(GrGpuResource*);
|
||||
void removeResource(GrGpuResource*);
|
||||
void notifyPurgeable(GrGpuResource*);
|
||||
void notifyCntReachedZero(GrGpuResource*, uint32_t flags);
|
||||
void didChangeGpuMemorySize(const GrGpuResource*, size_t oldSize);
|
||||
void changeUniqueKey(GrGpuResource*, const GrUniqueKey&);
|
||||
void removeUniqueKey(GrGpuResource*);
|
||||
@ -189,7 +200,7 @@ private:
|
||||
void refAndMakeResourceMRU(GrGpuResource*);
|
||||
/// @}
|
||||
|
||||
void internalPurgeAsNeeded();
|
||||
void resetFlushTimestamps();
|
||||
void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
|
||||
void addToNonpurgeableArray(GrGpuResource*);
|
||||
void removeFromNonpurgeableArray(GrGpuResource*);
|
||||
@ -251,6 +262,7 @@ private:
|
||||
// our budget, used in purgeAsNeeded()
|
||||
int fMaxCount;
|
||||
size_t fMaxBytes;
|
||||
int fMaxUnusedFlushes;
|
||||
|
||||
#if GR_CACHE_STATS
|
||||
int fHighWaterCount;
|
||||
@ -270,7 +282,16 @@ private:
|
||||
PFOverBudgetCB fOverBudgetCB;
|
||||
void* fOverBudgetData;
|
||||
|
||||
// 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;
|
||||
|
||||
InvalidUniqueKeyInbox fInvalidUniqueKeyInbox;
|
||||
|
||||
// This resource is allowed to be in the nonpurgeable array for the sake of validate() because
|
||||
// we're in the midst of converting it to purgeable status.
|
||||
SkDEBUGCODE(GrGpuResource* fNewlyPurgeableResourceForValidation;)
|
||||
};
|
||||
|
||||
class GrResourceCache::ResourceAccess {
|
||||
@ -290,9 +311,26 @@ private:
|
||||
void removeResource(GrGpuResource* resource) { fCache->removeResource(resource); }
|
||||
|
||||
/**
|
||||
* Called by GrGpuResources when they detects that they are newly purgeable.
|
||||
* Notifications that should be sent to the cache when the ref/io cnt status of resources
|
||||
* changes.
|
||||
*/
|
||||
void notifyPurgeable(GrGpuResource* resource) { fCache->notifyPurgeable(resource); }
|
||||
enum RefNotificationFlags {
|
||||
/** All types of refs on the resource have reached zero. */
|
||||
kAllCntsReachedZero_RefNotificationFlag = 0x1,
|
||||
/** The normal (not pending IO type) ref cnt has reached zero. */
|
||||
kRefCntReachedZero_RefNotificationFlag = 0x2,
|
||||
};
|
||||
/**
|
||||
* Called by GrGpuResources when they detect that their ref/io cnts have reached zero. When the
|
||||
* normal ref cnt reaches zero the flags that are set should be:
|
||||
* a) kRefCntReachedZero if a pending IO cnt is still non-zero.
|
||||
* b) (kRefCntReachedZero | kAllCntsReachedZero) when all pending IO cnts are also zero.
|
||||
* kAllCntsReachedZero is set by itself if a pending IO cnt is decremented to zero and all the
|
||||
* the other cnts are already zero.
|
||||
*/
|
||||
void notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
|
||||
fCache->notifyCntReachedZero(resource, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by GrGpuResources when their sizes change.
|
||||
|
@ -5,6 +5,9 @@
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
// Include here to ensure SK_SUPPORT_GPU is set correctly before it is examined.
|
||||
#include "SkTypes.h"
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
|
||||
#include "GrContext.h"
|
||||
@ -1023,6 +1026,88 @@ static void test_timestamp_wrap(skiatest::Reporter* reporter) {
|
||||
}
|
||||
}
|
||||
|
||||
static void test_flush(skiatest::Reporter* reporter) {
|
||||
Mock mock(1000000, 1000000);
|
||||
GrContext* context = mock.context();
|
||||
GrResourceCache* cache = mock.cache();
|
||||
|
||||
// The current cache impl will round the max flush count to the next power of 2. So we choose a
|
||||
// power of two here to keep things simpler.
|
||||
static const int kFlushCount = 16;
|
||||
cache->setLimits(1000000, 1000000, kFlushCount);
|
||||
|
||||
{
|
||||
// Insert a resource and send a flush notification kFlushCount times.
|
||||
for (int i = 0; i < kFlushCount; ++i) {
|
||||
TestResource* r = SkNEW_ARGS(TestResource, (context->getGpu()));
|
||||
GrUniqueKey k;
|
||||
make_unique_key<1>(&k, i);
|
||||
r->resourcePriv().setUniqueKey(k);
|
||||
r->unref();
|
||||
cache->notifyFlushOccurred();
|
||||
}
|
||||
|
||||
// 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.
|
||||
REPORTER_ASSERT(reporter, kFlushCount - i - 1 == cache->getResourceCount());
|
||||
for (int j = 0; j < i; ++j) {
|
||||
GrUniqueKey k;
|
||||
make_unique_key<1>(&k, j);
|
||||
GrGpuResource* r = cache->findAndRefUniqueResource(k);
|
||||
REPORTER_ASSERT(reporter, !SkToBool(r));
|
||||
SkSafeUnref(r);
|
||||
}
|
||||
cache->notifyFlushOccurred();
|
||||
}
|
||||
|
||||
REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
|
||||
cache->purgeAllUnlocked();
|
||||
}
|
||||
|
||||
// Do a similar test but where we leave refs on some resources to prevent them from being
|
||||
// purged.
|
||||
{
|
||||
GrGpuResource* refedResources[kFlushCount >> 1];
|
||||
for (int i = 0; i < kFlushCount; ++i) {
|
||||
TestResource* r = SkNEW_ARGS(TestResource, (context->getGpu()));
|
||||
GrUniqueKey k;
|
||||
make_unique_key<1>(&k, i);
|
||||
r->resourcePriv().setUniqueKey(k);
|
||||
// Leave a ref on every other resource, beginning with the first.
|
||||
if (SkToBool(i & 0x1)) {
|
||||
refedResources[i/2] = r;
|
||||
} else {
|
||||
r->unref();
|
||||
}
|
||||
cache->notifyFlushOccurred();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Unref all the resources that we kept refs on in the first loop.
|
||||
for (int i = 0; i < kFlushCount >> 1; ++i) {
|
||||
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) {
|
||||
REPORTER_ASSERT(reporter, kFlushCount >> 1 == cache->getResourceCount());
|
||||
cache->notifyFlushOccurred();
|
||||
}
|
||||
REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
|
||||
|
||||
cache->purgeAllUnlocked();
|
||||
}
|
||||
|
||||
REPORTER_ASSERT(reporter, 0 == cache->getResourceCount());
|
||||
}
|
||||
|
||||
static void test_large_resource_count(skiatest::Reporter* reporter) {
|
||||
// Set the cache size to double the resource count because we're going to create 2x that number
|
||||
// resources, using two different key domains. Add a little slop to the bytes because we resize
|
||||
@ -1118,6 +1203,7 @@ DEF_GPUTEST(ResourceCache, reporter, factory) {
|
||||
test_cache_chained_purge(reporter);
|
||||
test_resource_size_changed(reporter);
|
||||
test_timestamp_wrap(reporter);
|
||||
test_flush(reporter);
|
||||
test_large_resource_count(reporter);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user