Add idle texture callback mechanism.

A proc can be registered with a GrTexture. The proc will be called when
it is safe to delete the texture is "idle." Idle means it referred to
outside of GrResourceCache and that the I/O operations on the GPU are
completed (this latter part applieas to Vulkan only).

The intended use case for this is to call promise image texture release
procs once we start caching GrTextures for deinstantiated promise
images.

Bug= skia:8613

Change-Id: Idce9a4292fef7b15370a053060d8878a9d6828fa
Reviewed-on: https://skia-review.googlesource.com/c/178937
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2018-12-19 15:42:06 -05:00 committed by Skia Commit-Bot
parent fde1875f50
commit 614c1a838a
17 changed files with 468 additions and 61 deletions

View File

@ -85,8 +85,6 @@ protected:
kPendingWrite_CntType,
};
bool isPurgeable() const { return !this->internalHasRef() && !this->internalHasPendingIO(); }
bool internalHasPendingRead() const { return SkToBool(fPendingReads); }
bool internalHasPendingWrite() const { return SkToBool(fPendingWrites); }
bool internalHasPendingIO() const { return SkToBool(fPendingWrites | fPendingReads); }
@ -301,6 +299,8 @@ protected:
private:
bool isPurgeable() const { return !this->internalHasRef() && !this->internalHasPendingIO(); }
/**
* Called by the registerWithCache if the resource is available to be used as scratch.
* Resource subclasses should override this if the instances should be recycled as scratch
@ -316,6 +316,11 @@ private:
virtual size_t onGpuMemorySize() const = 0;
/**
* Called by GrResourceCache when a resource transitions from being unpurgeable to purgeable.
*/
virtual void becamePurgeable() {}
// See comments in CacheAccess and ResourcePriv.
void setUniqueKey(const GrUniqueKey&);
void removeUniqueKey();

View File

@ -60,6 +60,17 @@ public:
this->setRelease(std::move(helper));
}
/**
* Installs a proc on this texture. It will be called when the texture becomes "idle". Idle is
* defined to mean that the texture has no refs or pending IOs and that GPU I/O operations on
* the texture are completed if the backend API disallows deletion of a texture before such
* operations occur (e.g. Vulkan). After the idle proc is called it is removed. The idle proc
* will always be called before the texture is destroyed, even in unusual shutdown scenarios
* (e.g. GrContext::abandonContext()).
*/
using IdleProc = void(void*);
virtual void setIdleProc(IdleProc, void* context) = 0;
/** Access methods that are only to be used within Skia code. */
inline GrTexturePriv texturePriv();
inline const GrTexturePriv texturePriv() const;

View File

@ -266,6 +266,9 @@ bool GrContext::abandoned() const {
void GrContext::releaseResourcesAndAbandonContext() {
ASSERT_SINGLE_OWNER
if (this->abandoned()) {
return;
}
fProxyProvider->abandon();
fResourceProvider->abandon();

View File

@ -34,6 +34,12 @@ private:
*/
bool shouldPurgeImmediately() const { return fResource->fShouldPurgeImmediately; }
/**
* Called by GrResourceCache when a resource becomes purgeable regardless of whether the cache
* has decided to keep the resource ot purge it immediately.
*/
void becamePurgeable() { fResource->becamePurgeable(); }
/**
* Called by the cache to delete the resource under normal circumstances.
*/

View File

@ -69,6 +69,8 @@ public:
*/
void removeScratchKey() const { fResource->removeScratchKey(); }
bool isPurgeable() const { return fResource->isPurgeable(); }
protected:
ResourcePriv(GrGpuResource* resource) : fResource(resource) { }
ResourcePriv(const ResourcePriv& that) : fResource(that.fResource) {}

View File

@ -6,10 +6,11 @@
*/
#include "GrResourceCache.h"
#include <atomic>
#include "GrCaps.h"
#include "GrSingleOwner.h"
#include "GrGpuResourceCacheAccess.h"
#include "GrProxyProvider.h"
#include "GrSingleOwner.h"
#include "GrTexture.h"
#include "GrTextureProxyCacheAccess.h"
#include "GrTracing.h"
@ -17,9 +18,9 @@
#include "SkMessageBus.h"
#include "SkOpts.h"
#include "SkRandom.h"
#include "SkScopeExit.h"
#include "SkTSort.h"
#include "SkTo.h"
#include <atomic>
DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage);
@ -109,7 +110,7 @@ void GrResourceCache::insertResource(GrGpuResource* resource) {
SkASSERT(resource);
SkASSERT(!this->isInCache(resource));
SkASSERT(!resource->wasDestroyed());
SkASSERT(!resource->isPurgeable());
SkASSERT(!resource->resourcePriv().isPurgeable());
// We must set the timestamp before adding to the array in case the timestamp wraps and we wind
// up iterating over all the resources that already have timestamps.
@ -149,7 +150,7 @@ void GrResourceCache::removeResource(GrGpuResource* resource) {
SkASSERT(this->isInCache(resource));
size_t size = resource->gpuMemorySize();
if (resource->isPurgeable()) {
if (resource->resourcePriv().isPurgeable()) {
fPurgeableQueue.remove(resource);
fPurgeableBytes -= size;
} else {
@ -186,6 +187,9 @@ void GrResourceCache::abandonAll() {
while (fNonpurgeableResources.count()) {
GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
SkASSERT(!back->wasDestroyed());
// If these resources we're relying on a purgeable notification to release something, notify
// them now. They aren't in the purgeable queue but they're getting purged anyway.
back->cacheAccess().becamePurgeable();
back->cacheAccess().abandon();
}
@ -223,9 +227,12 @@ void GrResourceCache::releaseAll() {
// they also have a raw pointer back to this class (which is presumably going away)!
fProxyProvider->removeAllUniqueKeys();
while(fNonpurgeableResources.count()) {
while (fNonpurgeableResources.count()) {
GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
SkASSERT(!back->wasDestroyed());
// If these resources we're relying on a purgeable notification to release something, notify
// them now. They aren't in the purgeable queue but they're getting purged anyway.
back->cacheAccess().becamePurgeable();
back->cacheAccess().release();
}
@ -329,7 +336,8 @@ void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey
if (newKey.isValid()) {
if (GrGpuResource* old = fUniqueHash.find(newKey)) {
// If the old resource using the key is purgeable and is unreachable, then remove it.
if (!old->resourcePriv().getScratchKey().isValid() && old->isPurgeable()) {
if (!old->resourcePriv().getScratchKey().isValid() &&
old->resourcePriv().isPurgeable()) {
old->cacheAccess().release();
} else {
this->removeUniqueKey(old);
@ -364,7 +372,7 @@ void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
SkASSERT(resource);
SkASSERT(this->isInCache(resource));
if (resource->isPurgeable()) {
if (resource->resourcePriv().isPurgeable()) {
// It's about to become unpurgeable.
fPurgeableBytes -= resource->gpuMemorySize();
fPurgeableQueue.remove(resource);
@ -391,7 +399,7 @@ void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t fla
// 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()) {
if (resource->resourcePriv().isPurgeable()) {
fNewlyPurgeableResourceForValidation = resource;
}
#endif
@ -400,11 +408,11 @@ void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t fla
}
if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
SkASSERT(!resource->isPurgeable());
SkASSERT(!resource->resourcePriv().isPurgeable());
return;
}
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
this->removeFromNonpurgeableArray(resource);
fPurgeableQueue.insert(resource);
resource->cacheAccess().setTimeWhenResourceBecomePurgeable();
@ -412,32 +420,34 @@ void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t fla
bool hasUniqueKey = resource->getUniqueKey().isValid();
if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
// We keep unbudgeted resources with a unique key in the purgable queue of the cache so they
// can be reused again by the image connected to the unique key.
if (hasUniqueKey && !resource->cacheAccess().shouldPurgeImmediately()) {
return;
}
// Check whether this resource could still be used as a scratch resource.
if (!resource->resourcePriv().refsWrappedObjects() &&
resource->resourcePriv().getScratchKey().isValid()) {
// We won't purge an existing resource to make room for this one.
if (fBudgetedCount < fMaxCount &&
fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
resource->resourcePriv().makeBudgeted();
{
SkScopeExit notifyPurgeable([resource] { resource->cacheAccess().becamePurgeable(); });
if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
// We keep unbudgeted resources with a unique key in the purgable queue of the cache so
// they can be reused again by the image connected to the unique key.
if (hasUniqueKey && !resource->cacheAccess().shouldPurgeImmediately()) {
return;
}
// Check whether this resource could still be used as a scratch resource.
if (!resource->resourcePriv().refsWrappedObjects() &&
resource->resourcePriv().getScratchKey().isValid()) {
// We won't purge an existing resource to make room for this one.
if (fBudgetedCount < fMaxCount &&
fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
resource->resourcePriv().makeBudgeted();
return;
}
}
} else {
// Purge the resource immediately if we're over budget
// Also purge if the resource has neither a valid scratch key nor a unique key.
bool hasKey = resource->resourcePriv().getScratchKey().isValid() || hasUniqueKey;
if (!this->overBudget() && hasKey) {
return;
}
}
} else {
// Purge the resource immediately if we're over budget
// Also purge if the resource has neither a valid scratch key nor a unique key.
bool hasKey = resource->resourcePriv().getScratchKey().isValid() ||
hasUniqueKey;
if (!this->overBudget() && hasKey) {
return;
}
}
SkDEBUGCODE(int beforeCount = this->getResourceCount();)
resource->cacheAccess().release();
// We should at least free this resource, perhaps dependent resources as well.
@ -482,7 +492,7 @@ void GrResourceCache::purgeAsNeeded() {
bool stillOverbudget = this->overBudget();
while (stillOverbudget && fPurgeableQueue.count()) {
GrGpuResource* resource = fPurgeableQueue.peek();
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
resource->cacheAccess().release();
stillOverbudget = this->overBudget();
}
@ -496,7 +506,7 @@ void GrResourceCache::purgeUnlockedResources(bool scratchResourcesOnly) {
// complexity. Moreover, this is rarely called.
while (fPurgeableQueue.count()) {
GrGpuResource* resource = fPurgeableQueue.peek();
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
resource->cacheAccess().release();
}
} else {
@ -507,7 +517,7 @@ void GrResourceCache::purgeUnlockedResources(bool scratchResourcesOnly) {
SkTDArray<GrGpuResource*> scratchResources;
for (int i = 0; i < fPurgeableQueue.count(); i++) {
GrGpuResource* resource = fPurgeableQueue.at(i);
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
if (!resource->getUniqueKey().isValid()) {
*scratchResources.append() = resource;
}
@ -536,7 +546,7 @@ void GrResourceCache::purgeResourcesNotUsedSince(GrStdSteadyClock::time_point pu
break;
}
GrGpuResource* resource = fPurgeableQueue.peek();
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
resource->cacheAccess().release();
}
}
@ -555,7 +565,7 @@ void GrResourceCache::purgeUnlockedResources(size_t bytesToPurge, bool preferScr
size_t scratchByteCount = 0;
for (int i = 0; i < fPurgeableQueue.count() && stillOverbudget; i++) {
GrGpuResource* resource = fPurgeableQueue.at(i);
SkASSERT(resource->isPurgeable());
SkASSERT(resource->resourcePriv().isPurgeable());
if (!resource->getUniqueKey().isValid()) {
*scratchResources.append() = resource;
scratchByteCount += resource->gpuMemorySize();
@ -738,7 +748,7 @@ void GrResourceCache::validate() const {
void update(GrGpuResource* resource) {
fBytes += resource->gpuMemorySize();
if (!resource->isPurgeable()) {
if (!resource->resourcePriv().isPurgeable()) {
++fLocked;
}
@ -794,14 +804,14 @@ void GrResourceCache::validate() const {
size_t purgeableBytes = 0;
for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() ||
fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
stats.update(fNonpurgeableResources[i]);
}
for (int i = 0; i < fPurgeableQueue.count(); ++i) {
SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
SkASSERT(fPurgeableQueue.at(i)->resourcePriv().isPurgeable());
SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
stats.update(fPurgeableQueue.at(i));

View File

@ -82,6 +82,11 @@ public:
fReleaseHelper = std::move(releaseHelper);
}
void setIdleProc(IdleProc proc, void* context) override {
fIdleProc = proc;
fIdleProcContext = context;
}
// These functions are used to track the texture parameters associated with the texture.
GrGpu::ResetTimestamp getCachedParamsTimestamp() const { return fParamsTimestamp; }
const SamplerParams& getCachedSamplerParams() const { return fSamplerParams; }
@ -127,10 +132,16 @@ protected:
private:
void invokeReleaseProc() {
if (fReleaseHelper) {
// Depending on the ref count of fReleaseHelper this may or may not actually trigger the
// ReleaseProc to be called.
fReleaseHelper.reset();
// Depending on the ref count of fReleaseHelper this may or may not actually trigger the
// ReleaseProc to be called.
fReleaseHelper.reset();
}
void becamePurgeable() override {
if (fIdleProc) {
fIdleProc(fIdleProcContext);
fIdleProc = nullptr;
fIdleProcContext = nullptr;
}
}
@ -138,6 +149,8 @@ private:
NonSamplerParams fNonSamplerParams;
GrGpu::ResetTimestamp fParamsTimestamp;
sk_sp<GrReleaseProcHelper> fReleaseHelper;
IdleProc* fIdleProc = nullptr;
void* fIdleProcContext = nullptr;
GrGLuint fID;
GrGLenum fFormat;
GrBackendObjectOwnership fTextureIDOwnership;

View File

@ -48,6 +48,11 @@ public:
fReleaseHelper = std::move(releaseHelper);
}
void setIdleProc(IdleProc proc, void* context) override {
fIdleProc = proc;
fIdleProcContext = context;
}
protected:
// constructor for subclasses
GrMockTexture(GrMockGpu* gpu, const GrSurfaceDesc& desc, GrMipMapsStatus mipMapsStatus,
@ -70,16 +75,27 @@ protected:
return false;
}
private:
void invokeReleaseProc() {
if (fReleaseHelper) {
// Depending on the ref count of fReleaseHelper this may or may not actually trigger the
// ReleaseProc to be called.
fReleaseHelper.reset();
// protected so that GrMockTextureRenderTarget can call this to avoid "inheritance via
// dominance" warning.
void becamePurgeable() override {
if (fIdleProc) {
fIdleProc(fIdleProcContext);
fIdleProc = nullptr;
fIdleProcContext = nullptr;
}
}
GrMockTextureInfo fInfo;
private:
void invokeReleaseProc() {
// Depending on the ref count of fReleaseHelper this may or may not actually trigger the
// ReleaseProc to be called.
fReleaseHelper.reset();
}
GrMockTextureInfo fInfo;
sk_sp<GrReleaseProcHelper> fReleaseHelper;
IdleProc* fIdleProc = nullptr;
void* fIdleProcContext = nullptr;
typedef GrTexture INHERITED;
};
@ -179,6 +195,9 @@ private:
GrMockTexture::onRelease();
}
// We implement this to avoid the inheritance via dominance warning.
void becamePurgeable() override { GrMockTexture::becamePurgeable(); }
size_t onGpuMemorySize() const override {
int numColorSamples = this->numColorSamples();
if (numColorSamples > 1) {

View File

@ -43,15 +43,22 @@ public:
fReleaseHelper = std::move(releaseHelper);
}
void setIdleProc(IdleProc proc, void* context) override {
fIdleProc = proc;
fIdleProcContext = context;
}
protected:
GrMtlTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>, GrMipMapsStatus);
GrMtlGpu* getMtlGpu() const;
void onAbandon() override {
this->invokeReleaseProc();
fTexture = nil;
}
void onRelease() override {
this->invokeReleaseProc();
fTexture = nil;
}
@ -61,6 +68,21 @@ protected:
private:
enum Wrapped { kWrapped };
void invokeReleaseProc() {
// Depending on the ref count of fReleaseHelper this may or may not actually trigger the
// ReleaseProc to be called.
fReleaseHelper.reset();
}
void becamePurgeable() override {
if (fIdleProc) {
fIdleProc(fIdleProcContext);
fIdleProc = nullptr;
fIdleProcContext = nullptr;
}
}
GrMtlTexture(GrMtlGpu*, SkBudgeted, const GrSurfaceDesc&, id<MTLTexture>,
GrMipMapsStatus);
@ -68,8 +90,9 @@ private:
GrIOType, bool purgeImmediately);
id<MTLTexture> fTexture;
sk_sp<GrReleaseProcHelper> fReleaseHelper;
sk_sp<GrReleaseProcHelper> fReleaseHelper;
IdleProc* fIdleProc = nullptr;
void* fIdleProcContext = nullptr;
typedef GrTexture INHERITED;
};

View File

@ -44,14 +44,17 @@ void GrVkCommandBuffer::invalidateState() {
void GrVkCommandBuffer::freeGPUData(GrVkGpu* gpu) const {
SkASSERT(!fIsActive);
for (int i = 0; i < fTrackedResources.count(); ++i) {
fTrackedResources[i]->notifyRemovedFromCommandBuffer();
fTrackedResources[i]->unref(gpu);
}
for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
fTrackedRecycledResources[i]->recycle(const_cast<GrVkGpu*>(gpu));
}
for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
fTrackedRecordingResources[i]->unref(gpu);
}
@ -64,15 +67,18 @@ void GrVkCommandBuffer::freeGPUData(GrVkGpu* gpu) const {
void GrVkCommandBuffer::abandonGPUData() const {
SkDEBUGCODE(fResourcesReleased = true;)
for (int i = 0; i < fTrackedResources.count(); ++i) {
fTrackedResources[i]->notifyRemovedFromCommandBuffer();
fTrackedResources[i]->unrefAndAbandon();
}
for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
// We don't recycle resources when abandoning them.
fTrackedRecycledResources[i]->unrefAndAbandon();
}
for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
fTrackedRecordingResources[i]->unrefAndAbandon();
}
@ -83,13 +89,16 @@ void GrVkCommandBuffer::releaseResources(GrVkGpu* gpu) {
SkDEBUGCODE(fResourcesReleased = true;)
SkASSERT(!fIsActive);
for (int i = 0; i < fTrackedResources.count(); ++i) {
fTrackedResources[i]->notifyRemovedFromCommandBuffer();
fTrackedResources[i]->unref(gpu);
}
for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
fTrackedRecycledResources[i]->recycle(const_cast<GrVkGpu*>(gpu));
}
for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
fTrackedRecordingResources[i]->unref(gpu);
}

View File

@ -109,6 +109,7 @@ public:
// execution
void addResource(const GrVkResource* resource) {
resource->ref();
resource->notifyAddedToCommandBuffer();
fTrackedResources.append(1, &resource);
}
@ -116,6 +117,7 @@ public:
// execution. When it is released, it will signal that the resource can be recycled for reuse.
void addRecycledResource(const GrVkRecycledResource* resource) {
resource->ref();
resource->notifyAddedToCommandBuffer();
fTrackedRecycledResources.append(1, &resource);
}
@ -123,6 +125,7 @@ public:
// recording.
void addRecordingResource(const GrVkResource* resource) {
resource->ref();
resource->notifyAddedToCommandBuffer();
fTrackedRecordingResources.append(1, &resource);
}

View File

@ -5,9 +5,11 @@
* found in the LICENSE file.
*/
#include "GrVkGpu.h"
#include "GrVkImage.h"
#include "GrGpuResourcePriv.h"
#include "GrVkGpu.h"
#include "GrVkMemory.h"
#include "GrVkTexture.h"
#include "GrVkUtil.h"
#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X)
@ -221,6 +223,7 @@ void GrVkImage::releaseImage(GrVkGpu* gpu) {
this->setImageLayout(gpu, this->currentLayout(), 0, 0, false, true);
}
if (fResource) {
fResource->removeOwningTexture();
fResource->unref(gpu);
fResource = nullptr;
}
@ -228,6 +231,7 @@ void GrVkImage::releaseImage(GrVkGpu* gpu) {
void GrVkImage::abandonImage() {
if (fResource) {
fResource->removeOwningTexture();
fResource->unrefAndAbandon();
fResource = nullptr;
}
@ -245,6 +249,37 @@ void GrVkImage::Resource::freeGPUData(GrVkGpu* gpu) const {
GrVkMemory::FreeImageMemory(gpu, isLinear, fAlloc);
}
void GrVkImage::Resource::setIdleProc(GrVkTexture* owner, GrTexture::IdleProc proc,
void* context) const {
fOwningTexture = owner;
fIdleProc = proc;
fIdleProcContext = context;
}
void GrVkImage::Resource::removeOwningTexture() const { fOwningTexture = nullptr; }
void GrVkImage::Resource::notifyAddedToCommandBuffer() const { ++fNumCommandBufferOwners; }
void GrVkImage::Resource::notifyRemovedFromCommandBuffer() const {
SkASSERT(fNumCommandBufferOwners);
if (--fNumCommandBufferOwners || !fIdleProc) {
return;
}
if (fOwningTexture && !fOwningTexture->resourcePriv().isPurgeable()) {
return;
}
fIdleProc(fIdleProcContext);
if (fOwningTexture) {
fOwningTexture->setIdleProc(nullptr, nullptr);
// Changing the texture's proc should change ours.
SkASSERT(!fIdleProc);
SkASSERT(!fIdleProc);
} else {
fIdleProc = nullptr;
fIdleProcContext = nullptr;
}
}
void GrVkImage::BorrowedResource::freeGPUData(GrVkGpu* gpu) const {
this->invokeReleaseProc();
}

View File

@ -10,16 +10,16 @@
#include "GrVkVulkan.h"
#include "GrVkResource.h"
#include "GrBackendSurface.h"
#include "GrTexture.h"
#include "GrTypesPriv.h"
#include "GrVkImageLayout.h"
#include "GrVkResource.h"
#include "SkTypes.h"
#include "vk/GrVkTypes.h"
class GrVkGpu;
class GrVkTexture;
class GrVkImage : SkNoncopyable {
private:
@ -151,6 +151,24 @@ private:
void setRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
fReleaseHelper = std::move(releaseHelper);
}
/**
* These are used to coordinate calling the idle proc between the GrVkTexture and the
* Resource. If the GrVkTexture becomes purgeable and if there are no command buffers
* referring to the Resource then it calls the proc. Otherwise, the Resource calls it
* when the last command buffer reference goes away and the GrVkTexture is purgeable.
*/
void setIdleProc(GrVkTexture* owner, GrTexture::IdleProc, void* context) const;
void removeOwningTexture() const;
/**
* We track how many outstanding references this Resource has in command buffers and
* when the count reaches zero we call the idle proc.
*/
void notifyAddedToCommandBuffer() const override;
void notifyRemovedFromCommandBuffer() const override;
bool isOwnedByCommandBuffer() const { return fNumCommandBufferOwners > 0; }
protected:
mutable sk_sp<GrReleaseProcHelper> fReleaseHelper;
@ -163,6 +181,10 @@ private:
VkImage fImage;
GrVkAlloc fAlloc;
VkImageTiling fImageTiling;
mutable int fNumCommandBufferOwners = 0;
mutable GrTexture::IdleProc* fIdleProc = nullptr;
mutable void* fIdleProcContext = nullptr;
mutable GrVkTexture* fOwningTexture = nullptr;
typedef GrVkResource INHERITED;
};

View File

@ -144,6 +144,13 @@ public:
}
}
// Called every time this resource is added to a command buffer.
virtual void notifyAddedToCommandBuffer() const {}
// Called every time this resource is removed from a command buffer (typically because
// the command buffer finished execution on the GPU but also when the command buffer
// is abandoned.)
virtual void notifyRemovedFromCommandBuffer() const {}
#ifdef SK_DEBUG
void validate() const {
SkASSERT(this->getRefCnt() > 0);

View File

@ -126,6 +126,11 @@ GrVkTexture::~GrVkTexture() {
}
void GrVkTexture::onRelease() {
// When there is an idle proc, the Resource will call the proc in releaseImage() so
// we clear it here.
fIdleProc = nullptr;
fIdleProcContext = nullptr;
// we create this and don't hand it off, so we should always destroy it
if (fTextureView) {
fTextureView->unref(this->getVkGpu());
@ -138,6 +143,11 @@ void GrVkTexture::onRelease() {
}
void GrVkTexture::onAbandon() {
// When there is an idle proc, the Resource will call the proc in abandonImage() so
// we clear it here.
fIdleProc = nullptr;
fIdleProcContext = nullptr;
// we create this and don't hand it off, so we should always destroy it
if (fTextureView) {
fTextureView->unrefAndAbandon();
fTextureView = nullptr;
@ -160,3 +170,27 @@ const GrVkImageView* GrVkTexture::textureView() {
return fTextureView;
}
void GrVkTexture::setIdleProc(IdleProc proc, void* context) {
fIdleProc = proc;
fIdleProcContext = context;
if (auto* resource = this->resource()) {
resource->setIdleProc(proc ? this : nullptr, proc, context);
}
}
void GrVkTexture::becamePurgeable() {
if (!fIdleProc) {
return;
}
// This is called when the GrTexture is purgeable. However, we need to check whether the
// Resource is still owned by any command buffers. If it is then it will call the proc.
auto* resource = this->resource();
SkASSERT(resource);
if (resource->isOwnedByCommandBuffer()) {
return;
}
fIdleProc(fIdleProcContext);
fIdleProc = nullptr;
fIdleProcContext = nullptr;
resource->setIdleProc(nullptr, nullptr, nullptr);
}

View File

@ -46,6 +46,8 @@ public:
this->setResourceRelease(std::move(releaseHelper));
}
void setIdleProc(IdleProc, void* context) override;
protected:
GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp<GrVkImageLayout>,
const GrVkImageView*, GrMipMapsStatus, GrBackendObjectOwnership);
@ -68,7 +70,11 @@ private:
sk_sp<GrVkImageLayout> layout, const GrVkImageView* imageView, GrMipMapsStatus,
GrBackendObjectOwnership, GrIOType ioType, bool purgeImmediately);
const GrVkImageView* fTextureView;
void becamePurgeable() override;
const GrVkImageView* fTextureView;
GrTexture::IdleProc* fIdleProc = nullptr;
void* fIdleProcContext = nullptr;
typedef GrTexture INHERITED;
};

View File

@ -5,6 +5,8 @@
* found in the LICENSE file.
*/
#include <set>
#include "GrClip.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
@ -15,7 +17,7 @@
#include "GrTexturePriv.h"
#include "SkAutoPixmapStorage.h"
#include "SkMipMap.h"
#include "SkTypes.h"
#include "SkSurface.h"
#include "Test.h"
// Tests that GrSurface::asTexture(), GrSurface::asRenderTarget(), and static upcasting of texture
@ -329,3 +331,200 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ReadOnlyTexture, reporter, context_info) {
}
}
}
DEF_GPUTEST(TextureIdleProcTest, reporter, options) {
GrContext* context;
static const int kS = 10;
// Helper to delete a backend texture in a GrTexture's release proc.
static const auto installBackendTextureReleaseProc = [](GrTexture* texture) {
auto backendTexture = texture->getBackendTexture();
auto context = texture->getContext();
struct ReleaseContext {
GrContext* fContext;
GrBackendTexture fBackendTexture;
};
auto release = [](void* rc) {
auto releaseContext = static_cast<ReleaseContext*>(rc);
if (!releaseContext->fContext->abandoned()) {
if (auto gpu = releaseContext->fContext->contextPriv().getGpu()) {
gpu->deleteTestingOnlyBackendTexture(releaseContext->fBackendTexture);
}
}
delete releaseContext;
};
texture->setRelease(sk_make_sp<GrReleaseProcHelper>(
release, new ReleaseContext{context, backendTexture}));
};
// Various ways of making textures.
auto makeWrapped = [](GrContext* context) {
auto backendTexture = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
nullptr, kS, kS, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
auto texture = context->contextPriv().resourceProvider()->wrapBackendTexture(
backendTexture, kBorrow_GrWrapOwnership, kRW_GrIOType);
installBackendTextureReleaseProc(texture.get());
return texture;
};
auto makeWrappedRenderable = [](GrContext* context) {
auto backendTexture = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
nullptr, kS, kS, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
auto texture = context->contextPriv().resourceProvider()->wrapRenderableBackendTexture(
backendTexture, 1, kBorrow_GrWrapOwnership);
installBackendTextureReleaseProc(texture.get());
return texture;
};
auto makeNormal = [](GrContext* context) {
GrSurfaceDesc desc;
desc.fConfig = kRGBA_8888_GrPixelConfig;
desc.fWidth = desc.fHeight = kS;
return context->contextPriv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
};
auto makeRenderable = [](GrContext* context) {
GrSurfaceDesc desc;
desc.fFlags = kRenderTarget_GrSurfaceFlag;
desc.fConfig = kRGBA_8888_GrPixelConfig;
desc.fWidth = desc.fHeight = kS;
return context->contextPriv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
};
std::function<sk_sp<GrTexture>(GrContext*)> makers[] = {makeWrapped, makeWrappedRenderable,
makeNormal, makeRenderable};
// Add a unique key, or not.
auto addKey = [](GrTexture* texture) {
static uint32_t gN = 0;
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
GrUniqueKey::Builder builder(&key, kDomain, 1);
builder[0] = gN++;
builder.finish();
texture->resourcePriv().setUniqueKey(key);
};
auto dontAddKey = [](GrTexture* texture) {};
std::function<void(GrTexture*)> keyAdders[] = {addKey, dontAddKey};
for (const auto& m : makers) {
for (const auto& keyAdder : keyAdders) {
for (int type = 0; type < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++type) {
sk_gpu_test::GrContextFactory factory;
auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(type);
context = factory.get(contextType);
if (!context) {
continue;
}
// The callback we add simply adds an integer to a set.
std::set<int> idleIDs;
struct Context {
std::set<int>* fIdleIDs;
int fNum;
};
auto proc = [](void* context) {
static_cast<Context*>(context)->fIdleIDs->insert(
static_cast<Context*>(context)->fNum);
delete static_cast<Context*>(context);
};
// Makes a texture, possibly adds a key, and sets the callback.
auto make = [&m, &keyAdder, &proc, &idleIDs](GrContext* context, int num) {
sk_sp<GrTexture> texture = m(context);
texture->setIdleProc(proc, new Context{&idleIDs, num});
keyAdder(texture.get());
return texture;
};
auto texture = make(context, 1);
REPORTER_ASSERT(reporter, idleIDs.find(1) == idleIDs.end());
bool isRT = SkToBool(texture->asRenderTarget());
auto backendFormat = texture->backendFormat();
texture.reset();
REPORTER_ASSERT(reporter, idleIDs.find(1) != idleIDs.end());
texture = make(context, 2);
SkImageInfo info =
SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
auto singleUseLazyCB = [&texture](GrResourceProvider* rp) {
return rp ? std::move(texture) : nullptr;
};
GrSurfaceDesc desc;
desc.fWidth = desc.fHeight = kS;
desc.fConfig = kRGBA_8888_GrPixelConfig;
if (isRT) {
desc.fFlags = kRenderTarget_GrSurfaceFlag;
}
SkBudgeted budgeted(texture->resourcePriv().isBudgeted());
auto proxy = context->contextPriv().proxyProvider()->createLazyProxy(
singleUseLazyCB, backendFormat, desc,
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
GrSurfaceProxy::LazyInstantiationType::kSingleUse);
rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest, SkPMColor4f(),
SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
SkMatrix::I(), nullptr);
// We still have the proxy, which should remain instantiated, thereby keeping the
// texture not purgeable.
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
context->flush();
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
// This time we move the proxy into the draw.
rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
SkMatrix::I(), nullptr);
REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
context->flush();
context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
// Now that the draw is fully consumed by the GPU, the texture should be idle.
REPORTER_ASSERT(reporter, idleIDs.find(2) != idleIDs.end());
// Make a proxy that should deinstantiate even if we keep a ref on it.
auto deinstantiateLazyCB = [&make, &context](GrResourceProvider* rp) {
return rp ? make(context, 3) : nullptr;
};
proxy = context->contextPriv().proxyProvider()->createLazyProxy(
deinstantiateLazyCB, backendFormat, desc,
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
SkMatrix::I(), nullptr);
// At this point the proxy shouldn't even be instantiated, there is no texture with
// id 3.
REPORTER_ASSERT(reporter, idleIDs.find(3) == idleIDs.end());
context->flush();
context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
// Now that the draw is fully consumed, we should have deinstantiated the proxy and
// the texture it made should be idle.
REPORTER_ASSERT(reporter, idleIDs.find(3) != idleIDs.end());
// Make sure we make the call during various shutdown scenarios.
texture = make(context, 4);
context->abandonContext();
REPORTER_ASSERT(reporter, idleIDs.find(4) != idleIDs.end());
factory.destroyContexts();
context = factory.get(contextType);
texture = make(context, 5);
factory.destroyContexts();
REPORTER_ASSERT(reporter, idleIDs.find(5) != idleIDs.end());
context = factory.get(contextType);
texture = make(context, 6);
factory.releaseResourcesAndAbandonContexts();
REPORTER_ASSERT(reporter, idleIDs.find(6) != idleIDs.end());
}
}
}
}