Revert "Revert "Distinguish between "flushed" and "finished" idle state callbacks on GrTexture.""
This reverts commit 88b8d1124b7280d379f7545eda4b9097a4d8a292.
Bug: skia:8800
Change-Id: I27f5da73b651b91af0c5440557f5986e493a1559
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/199080
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index e20ebb0..e8f8035 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -49,17 +49,31 @@
}
#endif
+ /** See addIdleProc. */
+ enum class IdleState {
+ kFlushed,
+ kFinished
+ };
/**
- * 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()).
+ * Installs a proc on this texture. It will be called when the texture becomes "idle". There
+ * are two types of idle states as indicated by IdleState. For managed backends (e.g. GL where
+ * a driver typically handles CPU/GPU synchronization of resource access) there is no difference
+ * between the two. They both mean "all work related to the resource has been flushed to the
+ * backend API and the texture is not owned outside the resource cache".
+ *
+ * If the API is unmanaged (e.g. Vulkan) then kFinished has the additional constraint that the
+ * work flushed to the GPU is finished.
*/
- virtual void addIdleProc(sk_sp<GrRefCntedCallback> callback) {
- callback->addChild(std::move(fIdleCallback));
- fIdleCallback = std::move(callback);
+ virtual void addIdleProc(sk_sp<GrRefCntedCallback> idleProc, IdleState) {
+ // This is the default implementation for the managed case where the IdleState can be
+ // ignored. Unmanaged backends, e.g. Vulkan, must override this to consider IdleState.
+ fIdleProcs.push_back(std::move(idleProc));
+ }
+ /** Helper version of addIdleProc that creates the ref-counted wrapper. */
+ void addIdleProc(GrRefCntedCallback::Callback callback,
+ GrRefCntedCallback::Context context,
+ IdleState state) {
+ this->addIdleProc(sk_make_sp<GrRefCntedCallback>(callback, context), state);
}
/** Access methods that are only to be used within Skia code. */
@@ -71,11 +85,11 @@
virtual bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) = 0;
- sk_sp<GrRefCntedCallback> fIdleCallback;
+ SkTArray<sk_sp<GrRefCntedCallback>> fIdleProcs;
void willRemoveLastRefOrPendingIO() override {
- // We're about to be idle in the resource cache. Do our part to trigger the idle callback.
- fIdleCallback.reset();
+ // We're about to be idle in the resource cache. Do our part to trigger the idle callbacks.
+ fIdleProcs.reset();
}
private:
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 49cce1d..86d29ef 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -1481,8 +1481,7 @@
}
/**
- * Ref-counted object that calls a callback from its destructor. These can be chained together. Any
- * owner can cancel calling the callback via abandon().
+ * Ref-counted object that calls a callback from its destructor.
*/
class GrRefCntedCallback : public SkRefCnt {
public:
@@ -1494,28 +1493,9 @@
}
~GrRefCntedCallback() override { fReleaseProc ? fReleaseProc(fReleaseCtx) : void(); }
- /**
- * After abandon is called the release proc will no longer be called in the destructor. This
- * does not recurse on child release procs or unref them.
- */
- void abandon() {
- fReleaseProc = nullptr;
- fReleaseCtx = nullptr;
- }
-
- /** Adds another GrRefCntedCallback that this will unref in its destructor. */
- void addChild(sk_sp<GrRefCntedCallback> next) {
- if (!fNext) {
- fNext = std::move(next);
- return;
- }
- fNext->addChild(std::move(next));
- }
-
Context context() const { return fReleaseCtx; }
private:
- sk_sp<GrRefCntedCallback> fNext;
Callback fReleaseProc;
Context fReleaseCtx;
};
diff --git a/src/gpu/vk/GrVkImage.cpp b/src/gpu/vk/GrVkImage.cpp
index c8d816e..b1180d5 100644
--- a/src/gpu/vk/GrVkImage.cpp
+++ b/src/gpu/vk/GrVkImage.cpp
@@ -270,28 +270,37 @@
GrVkMemory::FreeImageMemory(gpu, isLinear, fAlloc);
}
-void GrVkImage::Resource::replaceIdleProc(
- GrVkTexture* owner, sk_sp<GrRefCntedCallback> idleCallback) const {
- fOwningTexture = owner;
- fIdleCallback = std::move(idleCallback);
+void GrVkImage::Resource::addIdleProc(GrVkTexture* owningTexture,
+ sk_sp<GrRefCntedCallback> idleProc) const {
+ SkASSERT(!fOwningTexture || fOwningTexture == owningTexture);
+ fOwningTexture = owningTexture;
+ fIdleProcs.push_back(std::move(idleProc));
}
+int GrVkImage::Resource::idleProcCnt() const { return fIdleProcs.count(); }
+
+sk_sp<GrRefCntedCallback> GrVkImage::Resource::idleProc(int i) const { return fIdleProcs[i]; }
+
+void GrVkImage::Resource::resetIdleProcs() const { fIdleProcs.reset(); }
+
void GrVkImage::Resource::removeOwningTexture() const { fOwningTexture = nullptr; }
void GrVkImage::Resource::notifyAddedToCommandBuffer() const { ++fNumCommandBufferOwners; }
void GrVkImage::Resource::notifyRemovedFromCommandBuffer() const {
SkASSERT(fNumCommandBufferOwners);
- if (--fNumCommandBufferOwners || !fIdleCallback) {
+ if (--fNumCommandBufferOwners || !fIdleProcs.count()) {
return;
}
if (fOwningTexture) {
if (fOwningTexture->resourcePriv().hasRefOrPendingIO()) {
+ // Wait for the texture to become idle in the cache to call the procs.
return;
}
- fOwningTexture->removeIdleProc();
+ fOwningTexture->callIdleProcsOnBehalfOfResource();
+ } else {
+ fIdleProcs.reset();
}
- fIdleCallback.reset();
}
void GrVkImage::BorrowedResource::freeGPUData(GrVkGpu* gpu) const {
diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h
index 2da325d..ec3c15d 100644
--- a/src/gpu/vk/GrVkImage.h
+++ b/src/gpu/vk/GrVkImage.h
@@ -187,12 +187,15 @@
}
/**
- * 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.
+ * These are used to coordinate calling the "finished" idle procs 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 procs. Otherwise, the Resource calls
+ * them when the last command buffer reference goes away and the GrVkTexture is purgeable.
*/
- void replaceIdleProc(GrVkTexture* owner, sk_sp<GrRefCntedCallback>) const;
+ void addIdleProc(GrVkTexture*, sk_sp<GrRefCntedCallback>) const;
+ int idleProcCnt() const;
+ sk_sp<GrRefCntedCallback> idleProc(int) const;
+ void resetIdleProcs() const;
void removeOwningTexture() const;
/**
@@ -225,7 +228,7 @@
GrVkAlloc fAlloc;
VkImageTiling fImageTiling;
mutable int fNumCommandBufferOwners = 0;
- mutable sk_sp<GrRefCntedCallback> fIdleCallback;
+ mutable SkTArray<sk_sp<GrRefCntedCallback>> fIdleProcs;
mutable GrVkTexture* fOwningTexture = nullptr;
typedef GrVkResource INHERITED;
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 74f9b15..8610a86 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -120,11 +120,11 @@
}
void GrVkTexture::onRelease() {
- // We're about to be severed from our GrVkResource. If there is an idle proc we have to decide
- // who will handle it. If the resource is still tied to a command buffer we let it handle it.
- // Otherwise, we handle it.
+ // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to
+ // decide who will handle them. If the resource is still tied to a command buffer we let it
+ // handle them. Otherwise, we handle them.
if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) {
- fIdleCallback.reset();
+ this->removeFinishIdleProcs();
}
// we create this and don't hand it off, so we should always destroy it
@@ -139,11 +139,11 @@
}
void GrVkTexture::onAbandon() {
- // We're about to be severed from our GrVkResource. If there is an idle proc we have to decide
- // who will handle it. If the resource is still tied to a command buffer we let it handle it.
- // Otherwise, we handle it.
+ // We're about to be severed from our GrVkResource. If there are "finish" idle procs we have to
+ // decide who will handle them. If the resource is still tied to a command buffer we let it
+ // handle them. Otherwise, we handle them.
if (this->hasResource() && this->resource()->isOwnedByCommandBuffer()) {
- fIdleCallback.reset();
+ this->removeFinishIdleProcs();
}
// we create this and don't hand it off, so we should always destroy it
@@ -169,25 +169,70 @@
return fTextureView;
}
-void GrVkTexture::addIdleProc(sk_sp<GrRefCntedCallback> callback) {
- INHERITED::addIdleProc(callback);
- if (auto* resource = this->resource()) {
- resource->replaceIdleProc(this, fIdleCallback);
+void GrVkTexture::addIdleProc(sk_sp<GrRefCntedCallback> idleProc, IdleState type) {
+ INHERITED::addIdleProc(idleProc, type);
+ if (type == IdleState::kFinished) {
+ if (auto* resource = this->resource()) {
+ resource->addIdleProc(this, std::move(idleProc));
+ }
}
}
+void GrVkTexture::callIdleProcsOnBehalfOfResource() {
+ // If we got here then the resource is being removed from its last command buffer and the
+ // texture is idle in the cache. Any kFlush idle procs should already have been called. So
+ // the texture and resource should have the same set of procs.
+ SkASSERT(this->resource());
+ SkASSERT(this->resource()->idleProcCnt() == fIdleProcs.count());
+#ifdef SK_DEBUG
+ for (int i = 0; i < fIdleProcs.count(); ++i) {
+ SkASSERT(fIdleProcs[i] == this->resource()->idleProc(i));
+ }
+#endif
+ fIdleProcs.reset();
+ this->resource()->resetIdleProcs();
+}
+
void GrVkTexture::willRemoveLastRefOrPendingIO() {
- if (!fIdleCallback) {
+ if (!fIdleProcs.count()) {
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->hasResource() ? this->resource() : nullptr;
- if (resource) {
- if (resource->isOwnedByCommandBuffer()) {
- return;
+ bool callFinishProcs = !resource || !resource->isOwnedByCommandBuffer();
+ if (callFinishProcs) {
+ // Everything must go!
+ fIdleProcs.reset();
+ resource->resetIdleProcs();
+ } else {
+ // The procs that should be called on flush but not finish are those that are owned
+ // by the GrVkTexture and not the Resource. We do this by copying the resource's array
+ // and thereby dropping refs to procs we own but the resource does not.
+ SkASSERT(resource);
+ fIdleProcs.reset(resource->idleProcCnt());
+ for (int i = 0; i < fIdleProcs.count(); ++i) {
+ fIdleProcs[i] = resource->idleProc(i);
}
- resource->replaceIdleProc(this, nullptr);
}
- fIdleCallback.reset();
+}
+
+void GrVkTexture::removeFinishIdleProcs() {
+ // This should only be called by onRelease/onAbandon when we have already checked for a
+ // resource.
+ const auto* resource = this->resource();
+ SkASSERT(resource);
+ SkSTArray<4, sk_sp<GrRefCntedCallback>> procsToKeep;
+ int resourceIdx = 0;
+ // The idle procs that are common between the GrVkTexture and its Resource should be found in
+ // the same order.
+ for (int i = 0; i < fIdleProcs.count(); ++i) {
+ if (fIdleProcs[i] == resource->idleProc(resourceIdx)) {
+ ++resourceIdx;
+ } else {
+ procsToKeep.push_back(fIdleProcs[i]);
+ }
+ }
+ SkASSERT(resourceIdx == resource->idleProcCnt());
+ fIdleProcs = procsToKeep;
}
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index ffd4a3f..50d4cae 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -38,8 +38,8 @@
const GrVkImageView* textureView();
- void addIdleProc(sk_sp<GrRefCntedCallback>) override;
- void removeIdleProc() { fIdleCallback.reset(); }
+ void addIdleProc(sk_sp<GrRefCntedCallback>, IdleState) override;
+ void callIdleProcsOnBehalfOfResource();
protected:
GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp<GrVkImageLayout>,
@@ -71,6 +71,8 @@
this->setResourceRelease(std::move(releaseHelper));
}
+ void removeFinishIdleProcs();
+
const GrVkImageView* fTextureView;
typedef GrTexture INHERITED;
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 908be47..6e29471 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -426,10 +426,8 @@
PromiseImageTextureDoneProc doneProc,
PromiseImageTextureContext context,
GrPixelConfig config)
- : fFulfillProc(fulfillProc), fConfig(config) {
- auto doneHelper = sk_make_sp<GrRefCntedCallback>(doneProc, context);
- fIdleCallback = sk_make_sp<GrRefCntedCallback>(releaseProc, context);
- fIdleCallback->addChild(std::move(doneHelper));
+ : fFulfillProc(fulfillProc), fReleaseProc(releaseProc), fConfig(config) {
+ fDoneCallback = sk_make_sp<GrRefCntedCallback>(doneProc, context);
}
PromiseLazyInstantiateCallback(PromiseLazyInstantiateCallback&&) = default;
PromiseLazyInstantiateCallback(const PromiseLazyInstantiateCallback&) {
@@ -444,14 +442,8 @@
}
~PromiseLazyInstantiateCallback() {
- if (fIdleCallback) {
- SkASSERT(!fTexture);
- // We were never fulfilled. Pass false so done proc is still called.
- fIdleCallback->abandon();
- }
// Our destructor can run on any thread. We trigger the unref of fTexture by message.
if (fTexture) {
- SkASSERT(!fIdleCallback);
SkMessageBus<GrGpuResourceFreedMessage>::Post({fTexture, fTextureContextID});
}
}
@@ -462,22 +454,19 @@
if (fTexture) {
return sk_ref_sp(fTexture);
}
- SkASSERT(fIdleCallback);
- PromiseImageTextureContext textureContext = fIdleCallback->context();
+ SkASSERT(fDoneCallback);
+ PromiseImageTextureContext textureContext = fDoneCallback->context();
sk_sp<SkPromiseImageTexture> promiseTexture = fFulfillProc(textureContext);
// From here on out our contract is that the release proc must be called, even if
// the return from fulfill was invalid or we fail for some other reason.
+ auto releaseCallback = sk_make_sp<GrRefCntedCallback>(fReleaseProc, textureContext);
if (!promiseTexture) {
- // Make sure we explicitly reset this because our destructor assumes a non-null
- // fIdleCallback means fulfill was never called.
- fIdleCallback.reset();
return sk_sp<GrTexture>();
}
auto backendTexture = promiseTexture->backendTexture();
backendTexture.fConfig = fConfig;
if (!backendTexture.isValid()) {
- fIdleCallback.reset();
return sk_sp<GrTexture>();
}
@@ -500,11 +489,11 @@
kRead_GrIOType))) {
tex->resourcePriv().setUniqueKey(key);
} else {
- fIdleCallback.reset();
return sk_sp<GrTexture>();
}
}
- tex->addIdleProc(std::move(fIdleCallback));
+ tex->addIdleProc(std::move(releaseCallback), GrTexture::IdleState::kFinished);
+ tex->addIdleProc(std::move(fDoneCallback), GrTexture::IdleState::kFinished);
promiseTexture->addKeyToInvalidate(tex->getContext()->priv().contextID(), key);
fTexture = tex.get();
// We need to hold on to the GrTexture in case our proxy gets reinstantiated. However,
@@ -518,10 +507,11 @@
}
private:
+ PromiseImageTextureFulfillProc fFulfillProc;
+ PromiseImageTextureReleaseProc fReleaseProc;
+ sk_sp<GrRefCntedCallback> fDoneCallback;
GrTexture* fTexture = nullptr;
uint32_t fTextureContextID = SK_InvalidUniqueID;
- sk_sp<GrRefCntedCallback> fIdleCallback;
- PromiseImageTextureFulfillProc fFulfillProc;
GrPixelConfig fConfig;
} callback(fulfillProc, releaseProc, doneProc, textureContext, config);
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index 375d34c..a35e1ab 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -424,8 +424,8 @@
// 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->addIdleProc(
- sk_make_sp<GrRefCntedCallback>(proc, new Context{&idleIDs, num}));
+ texture->addIdleProc(proc, new Context{&idleIDs, num},
+ GrTexture::IdleState::kFinished);
keyAdder(texture.get());
return texture;
};
@@ -608,12 +608,15 @@
for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
for (const auto& otherMaker : {make_wrapped_texture, make_normal_texture}) {
- auto idleTexture = idleMaker(context, false);
- auto otherTexture = otherMaker(context, false);
- otherTexture->ref();
- idleTexture->addIdleProc(sk_make_sp<GrRefCntedCallback>(idleProc, otherTexture.get()));
- otherTexture.reset();
- idleTexture.reset();
+ for (auto idleState :
+ {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+ auto idleTexture = idleMaker(context, false);
+ auto otherTexture = otherMaker(context, false);
+ otherTexture->ref();
+ idleTexture->addIdleProc(idleProc, otherTexture.get(), idleState);
+ otherTexture.reset();
+ idleTexture.reset();
+ }
}
}
}
@@ -627,25 +630,27 @@
auto idleProc = [](void* context) { reinterpret_cast<GrContext*>(context)->flush(); };
for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
- auto idleTexture = idleMaker(context, false);
- idleTexture->addIdleProc(sk_make_sp<GrRefCntedCallback>(idleProc, context));
- auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
- auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 1, nullptr);
- // We'll draw two images to the canvas. One is a normal texture-backed image. The other is
- // a wrapped-texture backed image.
- surf->getCanvas()->clear(SK_ColorWHITE);
- auto img1 = surf->makeImageSnapshot();
- auto gpu = context->priv().getGpu();
- std::unique_ptr<uint32_t[]> pixels(new uint32_t[info.width() * info.height()]);
- auto backendTexture = gpu->createTestingOnlyBackendTexture(
- pixels.get(), info.width(), info.height(), kRGBA_8888_SkColorType, false,
- GrMipMapped::kNo);
- auto img2 = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
- info.colorType(), info.alphaType(), nullptr);
- surf->getCanvas()->drawImage(std::move(img1), 0, 0);
- surf->getCanvas()->drawImage(std::move(img2), 1, 1);
- idleTexture.reset();
- gpu->deleteTestingOnlyBackendTexture(backendTexture);
+ for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+ auto idleTexture = idleMaker(context, false);
+ idleTexture->addIdleProc(idleProc, context, idleState);
+ auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ auto surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 1, nullptr);
+ // We'll draw two images to the canvas. One is a normal texture-backed image. The other
+ // is a wrapped-texture backed image.
+ surf->getCanvas()->clear(SK_ColorWHITE);
+ auto img1 = surf->makeImageSnapshot();
+ auto gpu = context->priv().getGpu();
+ std::unique_ptr<uint32_t[]> pixels(new uint32_t[info.width() * info.height()]);
+ auto backendTexture = gpu->createTestingOnlyBackendTexture(
+ pixels.get(), info.width(), info.height(), kRGBA_8888_SkColorType, false,
+ GrMipMapped::kNo);
+ auto img2 = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
+ info.colorType(), info.alphaType(), nullptr);
+ surf->getCanvas()->drawImage(std::move(img1), 0, 0);
+ surf->getCanvas()->drawImage(std::move(img2), 1, 1);
+ idleTexture.reset();
+ gpu->deleteTestingOnlyBackendTexture(backendTexture);
+ }
}
}
@@ -655,17 +660,59 @@
auto idleProc = [](void* texture) { reinterpret_cast<GrTexture*>(texture)->ref(); };
// release proc to check whether the texture was released or not.
auto releaseProc = [](void* isReleased) { *reinterpret_cast<bool*>(isReleased) = true; };
- bool isReleased = false;
- auto idleTexture = make_normal_texture(context, false);
- // This test assumes the texture won't be cached (or else the release proc doesn't get
- // called).
- idleTexture->resourcePriv().removeScratchKey();
- context->flush();
- idleTexture->addIdleProc(sk_make_sp<GrRefCntedCallback>(idleProc, idleTexture.get()));
- idleTexture->setRelease(releaseProc, &isReleased);
- auto* raw = idleTexture.get();
- idleTexture.reset();
- REPORTER_ASSERT(reporter, !isReleased);
- raw->unref();
- REPORTER_ASSERT(reporter, isReleased);
+ for (auto idleState : {GrTexture::IdleState::kFlushed, GrTexture::IdleState::kFinished}) {
+ bool isReleased = false;
+ auto idleTexture = make_normal_texture(context, false);
+ // This test assumes the texture won't be cached (or else the release proc doesn't get
+ // called).
+ idleTexture->resourcePriv().removeScratchKey();
+ context->flush();
+ idleTexture->addIdleProc(idleProc, idleTexture.get(), idleState);
+ idleTexture->setRelease(releaseProc, &isReleased);
+ auto* raw = idleTexture.get();
+ idleTexture.reset();
+ REPORTER_ASSERT(reporter, !isReleased);
+ raw->unref();
+ REPORTER_ASSERT(reporter, isReleased);
+ }
+}
+
+DEF_GPUTEST_FOR_ALL_CONTEXTS(TextureIdleStateTest, reporter, contextInfo) {
+ GrContext* context = contextInfo.grContext();
+ for (const auto& idleMaker : {make_wrapped_texture, make_normal_texture}) {
+ auto idleTexture = idleMaker(context, false);
+
+ uint32_t flags = 0;
+ static constexpr uint32_t kFlushFlag = 0x1;
+ static constexpr uint32_t kFinishFlag = 0x2;
+ auto flushProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFlushFlag; };
+ auto finishProc = [](void* flags) { *static_cast<uint32_t*>(flags) |= kFinishFlag; };
+ idleTexture->addIdleProc(flushProc, &flags, GrTexture::IdleState::kFlushed);
+ idleTexture->addIdleProc(finishProc, &flags, GrTexture::IdleState::kFinished);
+
+ // Insert a copy from idleTexture to another texture so that we have some queued IO on
+ // idleTexture.
+ auto proxy = context->priv().proxyProvider()->testingOnly_createWrapped(
+ std::move(idleTexture), kTopLeft_GrSurfaceOrigin);
+ SkImageInfo info = SkImageInfo::Make(proxy->width(), proxy->height(),
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
+ auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
+ context->flush();
+ rtc->copy(proxy.get());
+ proxy.reset();
+ REPORTER_ASSERT(reporter, flags == 0);
+
+ // After a flush we expect idleTexture to have reached the kFlushed state on all backends.
+ // On "managed" backends we expect it to reach kFinished as well. On Vulkan, the only
+ // current "unmanaged" backend, we *may* need a sync to reach kFinished.
+ context->flush();
+ if (contextInfo.backend() == kVulkan_GrBackend) {
+ REPORTER_ASSERT(reporter, flags & kFlushFlag);
+ } else {
+ REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
+ }
+ context->priv().getGpu()->testingOnly_flushGpuAndSync();
+ REPORTER_ASSERT(reporter, flags == (kFlushFlag | kFinishFlag));
+ }
}