[graphite] Add validation checks to ResourceCache

Bug: skia:12754
Change-Id: Ie88290f18864d61138964eb618dc6ac2d6ff4fe0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/508916
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Greg Daniel 2022-03-03 09:19:43 -05:00 committed by SkCQ
parent 404c7e1b39
commit 5bec818c1f
11 changed files with 122 additions and 14 deletions

View File

@ -24,7 +24,10 @@ public:
protected:
Buffer(const Gpu* gpu, size_t size, BufferType type, PrioritizeGpuReads prioritizeGpuReads)
: Resource(gpu), fSize(size), fType(type), fPrioritizeGpuReads(prioritizeGpuReads) {}
: Resource(gpu, Ownership::kOwned)
, fSize(size)
, fType(type)
, fPrioritizeGpuReads(prioritizeGpuReads) {}
void* fMapPtr = nullptr;

View File

@ -9,7 +9,7 @@
namespace skgpu {
GraphicsPipeline::GraphicsPipeline(const Gpu* gpu) : Resource(gpu) {
GraphicsPipeline::GraphicsPipeline(const Gpu* gpu) : Resource(gpu, Ownership::kOwned) {
}
GraphicsPipeline::~GraphicsPipeline() {

View File

@ -11,11 +11,12 @@
namespace skgpu {
Resource::Resource(const Gpu* gpu)
Resource::Resource(const Gpu* gpu, Ownership ownership)
: fGpu(gpu)
, fUsageRefCnt(1)
, fCommandBufferRefCnt(0)
, fCacheRefCnt(0) {}
, fCacheRefCnt(0)
, fOwnership(ownership) {}
Resource::~Resource() {
// The cache should have released or destroyed this resource.

View File

@ -79,6 +79,8 @@ public:
}
}
Ownership ownership() const { return fOwnership; }
// Tests whether a object has been abandoned or released. All objects will be in this state
// after their creating Context is destroyed or abandoned.
//
@ -95,7 +97,7 @@ public:
void setKey(const GraphiteResourceKey& key) { fKey = key; }
protected:
Resource(const Gpu*);
Resource(const Gpu*, Ownership);
virtual ~Resource();
// Overridden to free GPU resources in the backend API.
@ -152,10 +154,15 @@ private:
}
}
#ifdef SK_DEBUG
bool isUsableAsScratch() const {
return fKey.shareable() == Shareable::kNo && !this->hasUsageRef() && fNonShareableInCache;
}
#endif
////////////////////////////////////////////////////////////////////////////
// The remaining calls are meant to be truely private
////////////////////////////////////////////////////////////////////////////
bool hasUsageRef() const {
if (0 == fUsageRefCnt.load(std::memory_order_acquire)) {
// The acquire barrier is only really needed if we return true. It
@ -217,12 +224,20 @@ private:
// be returned or not.
mutable int fReturnIndex = -1;
Ownership fOwnership;
// An index into a heap when this resource is purgeable or an array when not. This is maintained
// by the cache.
mutable int fCacheArrayIndex = -1;
// This value reflects how recently this resource was accessed in the cache. This is maintained
// by the cache.
uint32_t fTimestamp;
// This is only used during validation checking. Lots of the validation code depends on a
// resource being purgeable or not. However, purgeable itself just means having no refs. The
// refs can be removed before a Resource is returned to the cache (or even added to the
// ReturnQueue).
SkDEBUGCODE(mutable bool fNonShareableInCache = false);
};
} // namespace skgpu

View File

@ -10,6 +10,7 @@
#include "experimental/graphite/src/GraphiteResourceKey.h"
#include "experimental/graphite/src/Resource.h"
#include "include/private/SingleOwner.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkTMultiMap.h"
namespace skgpu {
@ -78,6 +79,8 @@ void ResourceCache::insertResource(Resource* resource) {
this->addToNonpurgeableArray(resource);
SkDEBUGCODE(fCount++;)
if (resource->key().shareable() == Shareable::kYes) {
fResourceMap.insert(resource->key(), resource);
}
@ -98,6 +101,7 @@ Resource* ResourceCache::findAndRefResource(const skgpu::GraphiteResourceKey& ke
// If a resource is not shareable (i.e. scratch resource) then we remove it from the map
// so that it isn't found again.
fResourceMap.remove(key, resource);
SkDEBUGCODE(resource->fNonShareableInCache = false;)
}
this->refAndMakeResourceMRU(resource);
this->validate();
@ -215,6 +219,7 @@ void ResourceCache::returnResourceToCache(Resource* resource, LastRemovedRef rem
// Shareable resources should still be in the cache
SkASSERT(fResourceMap.find(resource->key()));
} else {
SkDEBUGCODE(resource->fNonShareableInCache = true;)
fResourceMap.insert(resource->key(), resource);
}
}
@ -236,8 +241,11 @@ void ResourceCache::returnResourceToCache(Resource* resource, LastRemovedRef rem
return;
}
resource->setTimestamp(this->getNextTimestamp());
this->removeFromNonpurgeableArray(resource);
fPurgeableQueue.insert(resource);
this->validate();
}
void ResourceCache::addToNonpurgeableArray(Resource* resource) {
@ -357,7 +365,87 @@ int* ResourceCache::AccessResourceIndex(Resource* const& res) {
#ifdef SK_DEBUG
void ResourceCache::validate() const {
// TODO: fill this out
// Reduce the frequency of validations for large resource counts.
static SkRandom gRandom;
int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
if (~mask && (gRandom.nextU() & mask)) {
return;
}
struct Stats {
int fShareable;
int fScratch;
const ResourceMap* fResourceMap;
Stats(const ResourceCache* cache) {
memset(this, 0, sizeof(*this));
fResourceMap = &cache->fResourceMap;
}
void update(Resource* resource) {
const GraphiteResourceKey& key = resource->key();
SkASSERT(key.isValid());
// We should always have at least 1 cache ref
SkASSERT(resource->hasCacheRef());
// We track scratch (non-shareable, no usage refs, has been returned to cache) and
// shareable resources here as those should be the only things in the fResourceMap. A
// non-shareable resources that does meet the scratch criteria will not be able to be
// given back out from a cache requests. After processing all the resources we assert
// that the fScratch + fShareable equals the count in the fResourceMap.
if (resource->isUsableAsScratch()) {
SkASSERT(key.shareable() == Shareable::kNo);
SkASSERT(!resource->hasUsageRef());
++fScratch;
SkASSERT(fResourceMap->has(resource, key));
SkASSERT(resource->ownership() == Ownership::kOwned);
} else if (key.shareable() == Shareable::kNo) {
SkASSERT(resource->ownership() == Ownership::kOwned);
SkASSERT(!fResourceMap->has(resource, key));
} else {
SkASSERT(key.shareable() == Shareable::kYes);
++fShareable;
SkASSERT(fResourceMap->has(resource, key));
}
}
};
{
int count = 0;
fResourceMap.foreach([&](const Resource& resource) {
SkASSERT(resource.isUsableAsScratch() || resource.key().shareable() == Shareable::kYes);
count++;
});
SkASSERT(count == fResourceMap.count());
}
// In the below checks we can assert that anything in the purgeable queue is purgeable because
// we won't put a Resource into that queue unless all refs are zero. Thus there is no way for
// that resource to be made non-purgeable without going through the cache (which will switch
// queues back to non-purgeable).
//
// However, we can't say the same for things in the non-purgeable array. It is possible that
// Resources have removed all their refs (thus technically become purgeable) but have not been
// processed back into the cache yet. Thus we may not have moved resources to the purgeable
// queue yet. Its also possible that Resource hasn't been added to the ReturnQueue yet (thread
// paused between unref and adding to ReturnQueue) so we can't even make asserts like not
// purgeable or is in ReturnQueue.
Stats stats(this);
for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
SkASSERT(*fNonpurgeableResources[i]->accessCacheIndex() == i);
SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
SkASSERT(!this->inPurgeableQueue(fNonpurgeableResources[i]));
stats.update(fNonpurgeableResources[i]);
}
for (int i = 0; i < fPurgeableQueue.count(); ++i) {
SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
SkASSERT(*fPurgeableQueue.at(i)->accessCacheIndex() == i);
SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
stats.update(fPurgeableQueue.at(i));
}
SkASSERT((stats.fScratch + stats.fShareable) == fResourceMap.count());
}
bool ResourceCache::isInCache(const Resource* resource) const {

View File

@ -98,6 +98,8 @@ private:
PurgeableQueue fPurgeableQueue;
ResourceArray fNonpurgeableResources;
SkDEBUGCODE(int fCount = 0;)
ResourceMap fResourceMap;
SingleOwner* fSingleOwner = nullptr;

View File

@ -9,7 +9,7 @@
namespace skgpu {
Sampler::Sampler(const Gpu* gpu) : Resource(gpu) {}
Sampler::Sampler(const Gpu* gpu) : Resource(gpu, Ownership::kOwned) {}
Sampler::~Sampler() {}

View File

@ -10,7 +10,9 @@
namespace skgpu {
Texture::Texture(const Gpu* gpu, SkISize dimensions, const TextureInfo& info, Ownership ownership)
: Resource(gpu), fDimensions(dimensions), fInfo(info), fOwnership(ownership) {}
: Resource(gpu, ownership)
, fDimensions(dimensions)
, fInfo(info) {}
Texture::~Texture() {}

View File

@ -28,12 +28,9 @@ public:
protected:
Texture(const Gpu*, SkISize dimensions, const TextureInfo& info, Ownership);
Ownership ownership() const { return fOwnership; }
private:
SkISize fDimensions;
TextureInfo fInfo;
Ownership fOwnership;
};
} // namepsace skgpu

View File

@ -81,7 +81,7 @@ public:
private:
BlitCommandEncoder(const skgpu::Gpu* gpu, sk_cfp<id<MTLBlitCommandEncoder>> encoder)
: Resource(gpu), fCommandEncoder(std::move(encoder)) {}
: Resource(gpu, Ownership::kOwned), fCommandEncoder(std::move(encoder)) {}
void freeGpuData() override {
fCommandEncoder.reset();

View File

@ -246,7 +246,7 @@ private:
inline static constexpr int kMaxExpectedTextures = 16;
RenderCommandEncoder(const Gpu* gpu, sk_cfp<id<MTLRenderCommandEncoder>> encoder)
: Resource(gpu), fCommandEncoder(std::move(encoder)) {
: Resource(gpu, Ownership::kOwned), fCommandEncoder(std::move(encoder)) {
for (int i = 0; i < kMaxExpectedBuffers; i++) {
fCurrentVertexBuffer[i] = nil;
fCurrentFragmentBuffer[i] = nil;