Revert "Revert "Reuse GrTexture instances when the same GrBackendTexture is used to""
This reverts commit 426ba463d54c7fdd6f3b94f88b66c10c0212dafd.
Bug: skia:8613
Change-Id: Iacaf40549369110aa95015e8d4579ec41db22d13
Reviewed-on: https://skia-review.googlesource.com/c/182963
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 1c013dd..73aecf4 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
+#include "SkImage_GpuBase.h"
#include "GrBackendSurface.h"
#include "GrClip.h"
#include "GrContext.h"
@@ -12,11 +13,11 @@
#include "GrRenderTargetContext.h"
#include "GrTexture.h"
#include "GrTextureAdjuster.h"
-#include "effects/GrYUVtoRGBEffect.h"
#include "SkBitmapCache.h"
#include "SkImage_Gpu.h"
-#include "SkImage_GpuBase.h"
+#include "SkPromiseImageTexture.h"
#include "SkReadPixelsRec.h"
+#include "effects/GrYUVtoRGBEffect.h"
SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
SkAlphaType at, sk_sp<SkColorSpace> cs)
@@ -391,9 +392,10 @@
sk_sp<GrTextureProxy> SkImage_GpuBase::MakePromiseImageLazyProxy(
GrContext* context, int width, int height, GrSurfaceOrigin origin, GrPixelConfig config,
GrBackendFormat backendFormat, GrMipMapped mipMapped,
- SkImage_GpuBase::TextureFulfillProc fulfillProc,
- SkImage_GpuBase::TextureReleaseProc releaseProc, SkImage_GpuBase::PromiseDoneProc doneProc,
- SkImage_GpuBase::TextureContext textureContext) {
+ PromiseImageTextureFulfillProc fulfillProc,
+ PromiseImageTextureReleaseProc releaseProc,
+ PromiseImageTextureDoneProc doneProc,
+ PromiseImageTextureContext textureContext) {
SkASSERT(context);
SkASSERT(width > 0 && height > 0);
SkASSERT(doneProc);
@@ -413,115 +415,169 @@
}
/**
- * This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise
- * images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak
- * ref allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the
- * GrTexture isn't done on the GPU) without needing to call FulfillProc again. It also holds a
- * hard ref on the DoneProc. The idea is that after every flush we may call the ReleaseProc so
- * that the client can free up their GPU memory if they want to. The life time of the DoneProc
- * matches that of any outstanding ReleaseProc as well as the PromiseLazyInstantiateCallback.
- * Thus we won't call the DoneProc until all ReleaseProcs are finished and we are finished with
- * the PromiseImageHelper (i.e. won't call FulfillProc again).
+ * This class is the lazy instantiation callback for promise images. It manages calling the
+ * client's Fulfill, Release, and Done procs. It attempts to reuse a GrTexture instance in
+ * cases where the client provides the same SkPromiseImageTexture for successive Fulfill calls.
+ * The created GrTexture is given a key based on a unique ID associated with the
+ * SkPromiseImageTexture. When the texture enters "idle" state (meaning it is not being used by
+ * the GPU and is at rest in the resource cache) the client's Release proc is called
+ * using GrTexture's idle proc mechanism. If the same SkPromiseImageTexture is provided for
+ * another fulfill we find the cached GrTexture. If the proxy, and therefore this object,
+ * is destroyed, we invalidate the GrTexture's key. Also if the client overwrites or
+ * destroys their SkPromiseImageTexture we invalidate the key.
+ *
+ * Currently a GrTexture is only reused for a given SkPromiseImageTexture if the
+ * SkPromiseImageTexture is reused in Fulfill for the same promise SkImage. However, we'd
+ * like to relax that so that a SkPromiseImageTexture can be reused with different promise
+ * SkImages that will reuse a single GrTexture.
*/
class PromiseLazyInstantiateCallback {
public:
- PromiseLazyInstantiateCallback(SkImage_GpuBase::TextureFulfillProc fulfillProc,
- SkImage_GpuBase::TextureReleaseProc releaseProc,
- SkImage_GpuBase::PromiseDoneProc doneProc,
- SkImage_GpuBase::TextureContext context,
+ PromiseLazyInstantiateCallback(PromiseImageTextureFulfillProc fulfillProc,
+ PromiseImageTextureReleaseProc releaseProc,
+ PromiseImageTextureDoneProc doneProc,
+ PromiseImageTextureContext context,
GrPixelConfig config)
: fFulfillProc(fulfillProc)
, fReleaseProc(releaseProc)
, fContext(context)
, fConfig(config) {
- fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
+ fDoneHelper = sk_make_sp<GrReleaseProcHelper>(doneProc, context);
+ static std::atomic<uint32_t> gUniqueID;
+ fUniqueID = gUniqueID.fetch_add(1) + 1;
+ }
+ ~PromiseLazyInstantiateCallback() {
+ // If we've already released the texture then it is safe to call done now. Here we may
+ // be on any thread.
+ if (fIdleContext && fIdleContext->fWasReleased.load()) {
+ // We still own a ref on fDoneHelper so no other thread can be calling the done
+ // proc.
+ fDoneHelper->callAndClear();
+ }
+ // Remove the key from the texture so that the texture will be removed from the cache.
+ // If we didn't just call the done proc above then it will get called when the texture
+ // is removed from the cache after this message is processed.
+ if (fLastFulfilledKey.isValid()) {
+ SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
+ GrUniqueKeyInvalidatedMessage(fLastFulfilledKey, fContextID));
+ }
}
sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
if (!resourceProvider) {
- this->reset();
+ return nullptr;
+ }
+
+ sk_sp<GrTexture> cachedTexture;
+ SkASSERT(fLastFulfilledKey.isValid() == (fLastFulfillID > 0));
+ if (fLastFulfilledKey.isValid()) {
+ auto surf = resourceProvider->findByUniqueKey<GrSurface>(fLastFulfilledKey);
+ if (surf) {
+ cachedTexture = sk_ref_sp(surf->asTexture());
+ SkASSERT(cachedTexture);
+ }
+ }
+ // If the release callback hasn't been called already by releasing the GrTexture
+ // then we can be sure that won't happen so long as we have a ref to the texture.
+ // Moreoever, only this thread should be able to change the atomic to true, hence the
+ // relaxed memory order.
+ if (cachedTexture && !fIdleContext->fWasReleased.load(std::memory_order_relaxed)) {
+ return std::move(cachedTexture);
+ }
+ GrBackendTexture backendTexture;
+ SkPromiseImageTexture* promiseTexture = fFulfillProc(fContext);
+ if (!promiseTexture) {
+ fReleaseProc(fContext, nullptr);
+ return sk_sp<GrTexture>();
+ }
+ bool same = promiseTexture->uniqueID() == fLastFulfillID;
+ SkASSERT(!same || fLastFulfilledKey.isValid());
+ if (same && cachedTexture) {
+ SkASSERT(fIdleContext->unique());
+ // Reset the purgeable context so that we balance the new fulfill with a release.
+ fIdleContext->ref();
+ SkASSERT(fIdleContext->fReleaseProc == fReleaseProc);
+ SkASSERT(fIdleContext->fTextureContext == fContext);
+ // Memory order relaxed because only this thread can change fWasReleased to true.
+ fIdleContext->fWasReleased.store(false, std::memory_order_relaxed);
+ cachedTexture->setIdleProc(IdleProc, fIdleContext.get());
+ return std::move(cachedTexture);
+ } else if (cachedTexture) {
+ cachedTexture->resourcePriv().removeUniqueKey();
+ // We don't want calling the client's done proc to be tied to the old texture.
+ cachedTexture->setRelease(nullptr);
+ }
+ fLastFulfillID = promiseTexture->uniqueID();
+
+ backendTexture = promiseTexture->backendTexture();
+ backendTexture.fConfig = fConfig;
+ if (!backendTexture.isValid()) {
+ // Even though the GrBackendTexture is not valid, we must call the release
+ // proc to keep our contract of always calling Fulfill and Release in pairs.
+ fReleaseProc(fContext, promiseTexture);
return sk_sp<GrTexture>();
}
- // Releases the promise helper if there are no outstanding hard refs. This means that we
- // don't have any ReleaseProcs waiting to be called so we will need to do a fulfill.
- if (fReleaseHelper && fReleaseHelper->weak_expired()) {
- this->resetReleaseHelper();
+ auto tex = resourceProvider->wrapBackendTexture(backendTexture, kBorrow_GrWrapOwnership,
+ kRead_GrIOType);
+ if (!tex) {
+ // Even though we failed to wrap the backend texture, we must call the release
+ // proc to keep our contract of always calling Fulfill and Release in pairs.
+ fReleaseProc(fContext, promiseTexture);
+ return sk_sp<GrTexture>();
}
-
- sk_sp<GrTexture> tex;
- if (!fReleaseHelper) {
- fFulfillProc(fContext, &fBackendTex);
- fBackendTex.fConfig = fConfig;
- if (!fBackendTex.isValid()) {
- // Even though the GrBackendTexture is not valid, we must call the release
- // proc to keep our contract of always calling Fulfill and Release in pairs.
- fReleaseProc(fContext);
- return sk_sp<GrTexture>();
- }
-
- tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
- kRead_GrIOType);
- if (!tex) {
- // Even though the GrBackendTexture is not valid, we must call the release
- // proc to keep our contract of always calling Fulfill and Release in pairs.
- fReleaseProc(fContext);
- return sk_sp<GrTexture>();
- }
- fReleaseHelper =
- new SkPromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper);
- // Take a weak ref
- fReleaseHelper->weak_ref();
- } else {
- SkASSERT(fBackendTex.isValid());
- tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
- kRead_GrIOType);
- if (!tex) {
- // We weren't able to make a texture here, but since we are in this branch
- // of the calls (promiseHelper.fReleaseHelper is valid) there is already a
- // texture out there which will call the release proc so we don't need to
- // call it here.
- return sk_sp<GrTexture>();
- }
-
- SkAssertResult(fReleaseHelper->try_ref());
- }
- SkASSERT(tex);
- // Pass the hard ref off to the texture
- tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
+ fIdleContext = sk_make_sp<IdleContext>(fReleaseProc, fContext, promiseTexture);
+ // The texture gets a ref, which is balanced when the idle callback is called.
+ fIdleContext->ref();
+ tex->setIdleProc(IdleProc, fIdleContext.get());
+ tex->setRelease(fDoneHelper);
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+ GrUniqueKey::Builder builder(&fLastFulfilledKey, kDomain, 2, "promise");
+ builder[0] = promiseTexture->uniqueID();
+ builder[1] = fUniqueID;
+ builder.finish();
+ tex->resourcePriv().setUniqueKey(fLastFulfilledKey);
+ SkASSERT(fContextID == SK_InvalidUniqueID ||
+ fContextID == tex->getContext()->uniqueID());
+ fContextID = tex->getContext()->uniqueID();
+ promiseTexture->addKeyToInvalidate(fContextID, fLastFulfilledKey);
return std::move(tex);
}
private:
- void reset() {
- this->resetReleaseHelper();
- fDoneHelper.reset();
+ struct IdleContext : public SkNVRefCnt<IdleContext> {
+ IdleContext(PromiseImageTextureReleaseProc proc, PromiseImageTextureContext context,
+ const SkPromiseImageTexture* texture)
+ : fReleaseProc(proc), fTextureContext(context), fPromiseImageTexture(texture) {}
+ PromiseImageTextureReleaseProc fReleaseProc;
+ PromiseImageTextureContext fTextureContext;
+ const SkPromiseImageTexture* fPromiseImageTexture;
+ std::atomic<bool> fWasReleased{false};
+ };
+ static void IdleProc(void* context) {
+ IdleContext* rc = static_cast<IdleContext*>(context);
+ SkASSERT(!rc->fWasReleased.load());
+ rc->fReleaseProc(rc->fTextureContext, rc->fPromiseImageTexture);
+ rc->fWasReleased.store(true);
+ // Drop the texture's implicit ref on the IdleContext.
+ rc->unref();
}
- // Weak unrefs fReleaseHelper and sets it to null
- void resetReleaseHelper() {
- if (fReleaseHelper) {
- fReleaseHelper->weak_unref();
- fReleaseHelper = nullptr;
- }
- }
-
- SkImage_GpuBase::TextureFulfillProc fFulfillProc;
- SkImage_GpuBase::TextureReleaseProc fReleaseProc;
- SkImage_GpuBase::TextureContext fContext;
+ PromiseImageTextureFulfillProc fFulfillProc;
+ PromiseImageTextureReleaseProc fReleaseProc;
+ PromiseImageTextureContext fContext;
GrPixelConfig fConfig;
- // We cache the GrBackendTexture so that if we deleted the GrTexture but the the release
- // proc has yet not been called (this can happen on Vulkan), then we can create a new
- // texture without needing to call the fulfill proc again.
- GrBackendTexture fBackendTex;
- // The fReleaseHelper is used to track a weak ref on the release proc. This helps us make
- // sure we are always pairing fulfill and release proc calls correctly.
- SkPromiseReleaseProcHelper* fReleaseHelper = nullptr;
- // We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and
- // all ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref
- // to each fReleaseHelper we make.
+ sk_sp<IdleContext> fIdleContext;
sk_sp<GrReleaseProcHelper> fDoneHelper;
+
+ // ID of the last SkPromiseImageTexture given to us by the client.
+ uint32_t fLastFulfillID = 0;
+ // ID of the GrContext that we are interacting with.
+ uint32_t fContextID = SK_InvalidUniqueID;
+ // Unique ID of this lazy instantiation callback.
+ uint32_t fUniqueID;
+ GrUniqueKey fLastFulfilledKey;
} callback(fulfillProc, releaseProc, doneProc, textureContext, config);
GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();