Add ability to uninstantiate lazy proxies after every flush.

Bug: skia:
Change-Id: Id32540cda54a9c5e3e6cb721776699be3cc8ac1a
Reviewed-on: https://skia-review.googlesource.com/113263
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 86e2a03..457b405 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -225,6 +225,8 @@
   "$_src/gpu/GrTextureRenderTargetProxy.h",
   "$_src/gpu/GrTextureStripAtlas.h",
   "$_src/gpu/GrTRecorder.h",
+  "$_src/gpu/GrUninstantiateProxyTracker.cpp",
+  "$_src/gpu/GrUninstantiateProxyTracker.h",
   "$_src/gpu/GrUserStencilSettings.h",
   "$_src/gpu/GrWindowRectangles.h",
   "$_src/gpu/GrWindowRectsState.h",
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 470ab7d..d90c408 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -58,7 +58,8 @@
 #endif
 
     void release() {
-        SkASSERT(1 == fRefCnt);
+        // The proxy itself may still have multiple refs. It can be owned by an SkImage and multiple
+        // SkDeferredDisplayLists at the same time if we are using DDLs.
         SkASSERT(0 == fPendingReads);
         SkASSERT(0 == fPendingWrites);
 
@@ -194,8 +195,10 @@
 class GrSurfaceProxy : public GrIORefProxy {
 public:
     enum class LazyInstantiationType {
-        kSingleUse,    // Instantiation callback is allowed to be called only once
-        kMultipleUse,  // Instantiation callback can be called multiple times.
+        kSingleUse,         // Instantiation callback is allowed to be called only once
+        kMultipleUse,       // Instantiation callback can be called multiple times.
+        kUninstantiate,     // Instantiation callback can be called multiple times,
+                            // but we will uninstantiate the proxy after every flush
     };
 
     enum class LazyState {
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 62db98e..63d1175 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -214,7 +214,8 @@
         }
 
         GrResourceAllocator::AssignError error = GrResourceAllocator::AssignError::kNoError;
-        while (alloc.assign(&startIndex, &stopIndex, &error)) {
+        while (alloc.assign(&startIndex, &stopIndex, flushState.uninstantiateProxyTracker(),
+                            &error)) {
             if (GrResourceAllocator::AssignError::kFailedProxyInstantiation == error) {
                 for (int i = startIndex; i < stopIndex; ++i) {
                     fOpLists[i]->purgeOpsWithUninstantiatedProxies();
@@ -231,6 +232,8 @@
 
     GrSemaphoresSubmitted result = gpu->finishFlush(numSemaphores, backendSemaphores);
 
+    flushState.uninstantiateProxyTracker()->uninstantiateAllProxies();
+
     // We always have to notify the cache when it requested a flush so it can reset its state.
     if (flushed || type == GrResourceCache::FlushType::kCacheRequested) {
         fContext->contextPriv().getResourceCache()->notifyFlushOccurred(type);
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index 9abd3f6..c33bc51 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -85,6 +85,8 @@
     auto resourceProvider = fDrawingMgr->getContext()->contextPriv().resourceProvider();
 
     if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
+        // DDL TODO: Decide if we ever plan to have these proxies use the GrUninstantiateTracker
+        // to support unistantiating them at the end of a flush.
         return proxy->priv().doLazyInstantiation(resourceProvider);
     }
 
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index 4f210a8..a6f0c26 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -12,6 +12,7 @@
 #include "GrAppliedClip.h"
 #include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
+#include "GrUninstantiateProxyTracker.h"
 #include "SkArenaAlloc.h"
 #include "SkArenaAllocList.h"
 #include "ops/GrMeshDrawOp.h"
@@ -96,6 +97,10 @@
     // permissible).
     GrAtlasManager* atlasManager() const final;
 
+    GrUninstantiateProxyTracker* uninstantiateProxyTracker() {
+        return &fUninstantiateProxyTracker;
+    }
+
 private:
     /** GrMeshDrawOp::Target override. */
     SkArenaAlloc* pipelineArena() override { return &fArena; }
@@ -150,6 +155,9 @@
     SkArenaAllocList<Draw>::Iter fCurrDraw;
     int fCurrMesh;
     SkArenaAllocList<InlineUpload>::Iter fCurrUpload;
+
+    // Used to track the proxies that need to be uninstantiated after we finish a flush
+    GrUninstantiateProxyTracker fUninstantiateProxyTracker;
 };
 
 #endif
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index f41169c..d389507 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -16,6 +16,7 @@
 #include "GrSurfaceProxy.h"
 #include "GrSurfaceProxyPriv.h"
 #include "GrTextureProxy.h"
+#include "GrUninstantiateProxyTracker.h"
 
 void GrResourceAllocator::Interval::assign(sk_sp<GrSurface> s) {
     SkASSERT(!fAssignedSurface);
@@ -200,7 +201,9 @@
     }
 }
 
-bool GrResourceAllocator::assign(int* startIndex, int* stopIndex, AssignError* outError) {
+bool GrResourceAllocator::assign(int* startIndex, int* stopIndex,
+                                 GrUninstantiateProxyTracker* uninstantiateTracker,
+                                 AssignError* outError) {
     SkASSERT(outError);
     *outError = AssignError::kNoError;
 
@@ -252,6 +255,11 @@
         if (GrSurfaceProxy::LazyState::kNot != cur->proxy()->lazyInstantiationState()) {
             if (!cur->proxy()->priv().doLazyInstantiation(fResourceProvider)) {
                 *outError = AssignError::kFailedProxyInstantiation;
+            } else {
+                if (GrSurfaceProxy::LazyInstantiationType::kUninstantiate ==
+                    cur->proxy()->priv().lazyInstantiationType()) {
+                    uninstantiateTracker->addProxy(cur->proxy());
+                }
             }
         } else if (sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil)) {
             // TODO: make getUniqueKey virtual on GrSurfaceProxy
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index f25bef3..427ff32 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -17,6 +17,7 @@
 #include "SkTMultiMap.h"
 
 class GrResourceProvider;
+class GrUninstantiateProxyTracker;
 
 /*
  * The ResourceAllocator explicitly distributes GPU resources at flush time. It operates by
@@ -68,7 +69,8 @@
     // If this happens, the caller should remove all ops which reference an uninstantiated proxy.
     // This is used to execute a portion of the queued opLists in order to reduce the total
     // amount of GPU resources required.
-    bool assign(int* startIndex, int* stopIndex, AssignError* outError);
+    bool assign(int* startIndex, int* stopIndex, GrUninstantiateProxyTracker*,
+                AssignError* outError);
 
     void markEndOfOpList(int opListIndex);
 
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index e5d1f90..10203b7 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -74,6 +74,12 @@
         return fProxy->fLazyInstantiationType;
     }
 
+    bool isSafeToUninstantiate() const {
+        return SkToBool(fProxy->fTarget) &&
+               SkToBool(fProxy->fLazyInstantiateCallback) &&
+               GrSurfaceProxy::LazyInstantiationType::kUninstantiate == lazyInstantiationType();
+    }
+
     void testingOnly_setLazyInstantiationType(GrSurfaceProxy::LazyInstantiationType lazyType) {
         fProxy->fLazyInstantiationType = lazyType;
     }
diff --git a/src/gpu/GrUninstantiateProxyTracker.cpp b/src/gpu/GrUninstantiateProxyTracker.cpp
new file mode 100644
index 0000000..c4da21b
--- /dev/null
+++ b/src/gpu/GrUninstantiateProxyTracker.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrUninstantiateProxyTracker.h"
+
+#include "GrSurfaceProxy.h"
+#include "GrSurfaceProxyPriv.h"
+
+void GrUninstantiateProxyTracker::addProxy(GrSurfaceProxy* proxy) {
+#ifdef SK_DEBUG
+    using LazyType = GrSurfaceProxy::LazyInstantiationType;
+    SkASSERT(LazyType::kUninstantiate == proxy->priv().lazyInstantiationType());
+    for (int i = 0; i < fProxies.count(); ++i) {
+        SkASSERT(proxy != fProxies[i]);
+    }
+#endif
+    fProxies.push_back(proxy);
+}
+
+void GrUninstantiateProxyTracker::uninstantiateAllProxies() {
+    for (int i = 0; i < fProxies.count(); ++i) {
+        GrSurfaceProxy* proxy = fProxies[i];
+        SkASSERT(proxy->priv().isSafeToUninstantiate());
+        proxy->deInstantiate();
+    }
+}
diff --git a/src/gpu/GrUninstantiateProxyTracker.h b/src/gpu/GrUninstantiateProxyTracker.h
new file mode 100644
index 0000000..a862bb6
--- /dev/null
+++ b/src/gpu/GrUninstantiateProxyTracker.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrUninstantiateProxyTracker_DEFINED
+#define GrUninstantiateProxyTracker_DEFINED
+
+#include "SkTArray.h"
+
+class GrSurfaceProxy;
+
+class GrUninstantiateProxyTracker {
+public:
+    GrUninstantiateProxyTracker() {}
+
+    // Adds a proxy which will be uninstantiated at the end of flush. The same proxy may not be
+    // added multiple times.
+    void addProxy(GrSurfaceProxy* proxy);
+
+    // Loops through all tracked proxies and uninstantiates them.
+    void uninstantiateAllProxies();
+
+private:
+    SkTArray<GrSurfaceProxy*> fProxies;
+};
+
+#endif
diff --git a/src/gpu/mock/GrMockGpu.cpp b/src/gpu/mock/GrMockGpu.cpp
index 9341472..e31a775 100644
--- a/src/gpu/mock/GrMockGpu.cpp
+++ b/src/gpu/mock/GrMockGpu.cpp
@@ -78,6 +78,22 @@
     return sk_sp<GrTexture>(new GrMockTexture(this, budgeted, desc, mipMapsStatus, info));
 }
 
+sk_sp<GrTexture> GrMockGpu::onWrapBackendTexture(const GrBackendTexture& tex,
+                                                 GrWrapOwnership ownership) {
+    GrSurfaceDesc desc;
+    desc.fWidth = tex.width();
+    desc.fHeight = tex.height();
+    SkASSERT(tex.getMockTextureInfo());
+    GrMockTextureInfo info = *tex.getMockTextureInfo();
+    desc.fConfig = info.fConfig;
+
+    GrMipMapsStatus mipMapsStatus = tex.hasMipMaps() ? GrMipMapsStatus::kValid
+                                                     : GrMipMapsStatus::kNotAllocated;
+
+    return sk_sp<GrTexture>(new GrMockTexture(this, GrMockTexture::kWrapped, desc, mipMapsStatus,
+                                              info));
+}
+
 GrBuffer* GrMockGpu::onCreateBuffer(size_t sizeInBytes, GrBufferType type,
                                     GrAccessPattern accessPattern, const void*) {
     return new GrMockBuffer(this, sizeInBytes, type, accessPattern);
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index d3e665c..00b19ac 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -58,9 +58,7 @@
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc&, SkBudgeted, const GrMipLevel[],
                                      int mipLevelCount) override;
 
-    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership) override {
-        return nullptr;
-    }
+    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership) override;
 
     sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
                                                     int sampleCnt,
diff --git a/src/gpu/mock/GrMockTexture.h b/src/gpu/mock/GrMockTexture.h
index 8a5a175..5ed785e 100644
--- a/src/gpu/mock/GrMockTexture.h
+++ b/src/gpu/mock/GrMockTexture.h
@@ -20,6 +20,14 @@
             : GrMockTexture(gpu, desc, mipMapsStatus, info) {
         this->registerWithCache(budgeted);
     }
+
+    enum Wrapped { kWrapped };
+    GrMockTexture(GrMockGpu* gpu, Wrapped, const GrSurfaceDesc& desc,
+                  GrMipMapsStatus mipMapsStatus, const GrMockTextureInfo& info)
+            : GrMockTexture(gpu, desc, mipMapsStatus, info) {
+        this->registerWithCacheWrapped();
+    }
+
     ~GrMockTexture() override {}
 
     GrBackendObject getTextureHandle() const override {
@@ -44,11 +52,28 @@
                         mipMapsStatus)
             , fInfo(info) {}
 
+    void onRelease() override {
+        this->invokeReleaseProc();
+        INHERITED::onRelease();
+    }
+
+    void onAbandon() override {
+        this->invokeReleaseProc();
+        INHERITED::onAbandon();
+    }
+
     bool onStealBackendTexture(GrBackendTexture*, SkImage::BackendTextureReleaseProc*) override {
         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();
+        }
+    }
     GrMockTextureInfo          fInfo;
     sk_sp<GrReleaseProcHelper> fReleaseHelper;
 
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index 9253678..88cbd01 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -22,6 +22,7 @@
 #include "GrTextureProxyPriv.h"
 #include "SkMakeUnique.h"
 #include "SkRectPriv.h"
+#include "mock/GrMockGpu.h"
 #include "mock/GrMockTypes.h"
 
 // This test verifies that lazy proxy callbacks get invoked during flush, after onFlush callbacks,
@@ -341,7 +342,127 @@
             REPORTER_ASSERT(reporter, 2 == executeTestValue);
         }
     }
+}
 
+class LazyUninstantiateTestOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    LazyUninstantiateTestOp(sk_sp<GrTextureProxy> proxy)
+            : INHERITED(ClassID())
+            , fLazyProxy(std::move(proxy)) {
+
+        this->setBounds(SkRect::MakeIWH(kSize, kSize),
+                        HasAABloat::kNo, IsZeroArea::kNo);
+    }
+
+    void visitProxies(const VisitProxyFunc& func) const override {
+        func(fLazyProxy.get());
+    }
+
+private:
+    const char* name() const override { return "LazyUninstantiateTestOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+    RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
+                                GrPixelConfigIsClamped) override {
+        return RequiresDstTexture::kNo;
+    }
+    bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
+    void onPrepare(GrOpFlushState*) override {}
+    void onExecute(GrOpFlushState* state) override {}
+
+    sk_sp<GrSurfaceProxy> fLazyProxy;
+
+    typedef GrDrawOp INHERITED;
+};
+
+static void UninstantiateReleaseProc(void* releaseValue) {
+    (*static_cast<int*>(releaseValue))++;
+}
+
+// Test that lazy proxies with the Uninstantiate LazyCallbackType are uninstantiated and released as
+// expected.
+DEF_GPUTEST(LazyProxyUninstantiateTest, reporter, /* options */) {
+    GrMockOptions mockOptions;
+    sk_sp<GrContext> ctx = GrContext::MakeMock(&mockOptions, GrContextOptions());
+    GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider();
+    GrGpu* gpu = ctx->contextPriv().getGpu();
+
+    using LazyType = GrSurfaceProxy::LazyInstantiationType;
+    for (auto lazyType : {LazyType::kSingleUse, LazyType::kMultipleUse, LazyType::kUninstantiate}) {
+        sk_sp<GrRenderTargetContext> rtc = ctx->contextPriv().makeDeferredRenderTargetContext(
+                SkBackingFit::kExact, 100, 100,
+                kRGBA_8888_GrPixelConfig, nullptr);
+        REPORTER_ASSERT(reporter, rtc);
+
+        rtc->clear(nullptr, 0xbaaaaaad, GrRenderTargetContext::CanClearFullscreen::kYes);
+
+        int instantiateTestValue = 0;
+        int releaseTestValue = 0;
+        int* instantiatePtr = &instantiateTestValue;
+        int* releasePtr = &releaseTestValue;
+        GrSurfaceDesc desc;
+        desc.fWidth = kSize;
+        desc.fHeight = kSize;
+        desc.fConfig = kRGBA_8888_GrPixelConfig;
+
+        GrBackendTexture backendTex = gpu->createTestingOnlyBackendTexture(
+                nullptr, kSize, kSize, kRGBA_8888_GrPixelConfig, false, GrMipMapped::kNo);
+
+        sk_sp<GrTextureProxy> lazyProxy = proxyProvider->createLazyProxy(
+                [instantiatePtr, releasePtr, backendTex](GrResourceProvider* rp) {
+                    if (!rp) {
+                        return sk_sp<GrTexture>();
+                    }
+
+                    sk_sp<GrTexture> texture = rp->wrapBackendTexture(backendTex);
+                    if (!texture) {
+                        return sk_sp<GrTexture>();
+                    }
+                    (*instantiatePtr)++;
+                    texture->setRelease(UninstantiateReleaseProc, releasePtr);
+                    return texture;
+                },
+                desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
+                SkBudgeted::kNo);
+
+        lazyProxy->priv().testingOnly_setLazyInstantiationType(lazyType);
+
+        rtc->priv().testingOnly_addDrawOp(skstd::make_unique<LazyUninstantiateTestOp>(lazyProxy));
+
+        ctx->flush();
+
+        REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
+        if (LazyType::kUninstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 1 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        }
+
+        // This should cause the uninstantiate proxies to be instantiated again but have no effect
+        // on the others
+        rtc->priv().testingOnly_addDrawOp(skstd::make_unique<LazyUninstantiateTestOp>(lazyProxy));
+        // Add a second op to make sure we only instantiate once.
+        rtc->priv().testingOnly_addDrawOp(skstd::make_unique<LazyUninstantiateTestOp>(lazyProxy));
+        ctx->flush();
+
+        if (LazyType::kUninstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 2 == instantiateTestValue);
+            REPORTER_ASSERT(reporter, 2 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
+            REPORTER_ASSERT(reporter, 0 == releaseTestValue);
+        }
+
+        lazyProxy.reset();
+        if (LazyType::kUninstantiate == lazyType) {
+            REPORTER_ASSERT(reporter, 2 == releaseTestValue);
+        } else {
+            REPORTER_ASSERT(reporter, 1 == releaseTestValue);
+        }
+
+        gpu->deleteTestingOnlyBackendTexture(&backendTex);
+    }
 }
 
 #endif
diff --git a/tests/ResourceAllocatorTest.cpp b/tests/ResourceAllocatorTest.cpp
index 7647e89..7718146 100644
--- a/tests/ResourceAllocatorTest.cpp
+++ b/tests/ResourceAllocatorTest.cpp
@@ -20,6 +20,7 @@
 #include "GrTest.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
+#include "GrUninstantiateProxyTracker.h"
 
 struct ProxyParams {
     int             fSize;
@@ -71,7 +72,8 @@
 
     int startIndex, stopIndex;
     GrResourceAllocator::AssignError error;
-    alloc.assign(&startIndex, &stopIndex, &error);
+    GrUninstantiateProxyTracker uninstantiateTracker;
+    alloc.assign(&startIndex, &stopIndex, &uninstantiateTracker, &error);
     REPORTER_ASSERT(reporter, GrResourceAllocator::AssignError::kNoError == error);
 
     REPORTER_ASSERT(reporter, p1->priv().peekSurface());
@@ -93,7 +95,8 @@
 
     int startIndex, stopIndex;
     GrResourceAllocator::AssignError error;
-    alloc.assign(&startIndex, &stopIndex, &error);
+    GrUninstantiateProxyTracker uninstantiateTracker;
+    alloc.assign(&startIndex, &stopIndex, &uninstantiateTracker, &error);
     REPORTER_ASSERT(reporter, GrResourceAllocator::AssignError::kNoError == error);
 
     REPORTER_ASSERT(reporter, p1->priv().peekSurface());