Copy on write for wrapped backend texture surfaces.

Makes SkImage_Gpu backed by two proxies, an original and a copy. The
image uses the original until a new render task is bound to it at which
point further uses of the image will use the copy. If the image is ever
used off a GrDirectContext we fall over to the copy. If the copy is
never used and never can be used by the next flush then the render
task that populates it is marked "skipped" and we don't perform the
copy.

Bug: skia:11208

Change-Id: Id255f4a733acc608c8a53c1a5633207aeafc404b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/366282
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrCopyRenderTask.cpp b/src/gpu/GrCopyRenderTask.cpp
index f2887e3..eb65dd5 100644
--- a/src/gpu/GrCopyRenderTask.cpp
+++ b/src/gpu/GrCopyRenderTask.cpp
@@ -29,13 +29,12 @@
         return nullptr;
     }
 
-    sk_sp<GrCopyRenderTask> task(new GrCopyRenderTask(drawingMgr,
-                                                      std::move(src),
-                                                      srcRect,
-                                                      std::move(dst),
-                                                      dstPoint,
-                                                      origin));
-    return std::move(task);
+    return sk_sp<GrRenderTask>(new GrCopyRenderTask(drawingMgr,
+                                                    std::move(src),
+                                                    srcRect,
+                                                    std::move(dst),
+                                                    dstPoint,
+                                                    origin));
 }
 
 GrCopyRenderTask::GrCopyRenderTask(GrDrawingManager* drawingMgr,
@@ -49,6 +48,10 @@
 }
 
 void GrCopyRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
+    if (!fSrc) {
+        alloc->incOps();
+        return;
+    }
     // This renderTask doesn't have "normal" ops. In this case we still need to add an interval (so
     // fEndOfOpsTaskOpIndices will remain in sync), so we create a fake op# to capture the fact that
     // we read fSrcView and copy to target view.
@@ -61,6 +64,8 @@
 
 GrRenderTask::ExpectedOutcome GrCopyRenderTask::onMakeClosed(const GrCaps&,
                                                              SkIRect* targetUpdateBounds) {
+    // We don't expect to be marked skippable before being closed.
+    SkASSERT(fSrc);
     *targetUpdateBounds = GrNativeRect::MakeIRectRelativeTo(
             fOrigin,
             this->target(0)->height(),
@@ -69,6 +74,10 @@
 }
 
 bool GrCopyRenderTask::onExecute(GrOpFlushState* flushState) {
+    if (!fSrc) {
+        // Did nothing, just like we're supposed to.
+        return true;
+    }
     GrSurfaceProxy* dstProxy = this->target(0);
     if (!fSrc->isInstantiated() || !dstProxy->isInstantiated()) {
         return false;
diff --git a/src/gpu/GrCopyRenderTask.h b/src/gpu/GrCopyRenderTask.h
index 515077e..6c6a743 100644
--- a/src/gpu/GrCopyRenderTask.h
+++ b/src/gpu/GrCopyRenderTask.h
@@ -31,6 +31,7 @@
                      SkIPoint dstPoint,
                      GrSurfaceOrigin);
 
+    void onCanSkip() override { fSrc.reset(); }
     bool onIsUsed(GrSurfaceProxy* proxy) const override { return proxy == fSrc.get(); }
     // If instantiation failed, at flush time we simply will skip doing the copy.
     void handleInternalAllocationFailure() override {}
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index cc4f761..628d0fe 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -798,11 +798,11 @@
     SkDEBUGCODE(this->validate());
 }
 
-bool GrDrawingManager::newCopyRenderTask(sk_sp<GrSurfaceProxy> src,
-                                         SkIRect srcRect,
-                                         sk_sp<GrSurfaceProxy> dst,
-                                         SkIPoint dstPoint,
-                                         GrSurfaceOrigin origin) {
+sk_sp<GrRenderTask> GrDrawingManager::newCopyRenderTask(sk_sp<GrSurfaceProxy> src,
+                                                        SkIRect srcRect,
+                                                        sk_sp<GrSurfaceProxy> dst,
+                                                        SkIPoint dstPoint,
+                                                        GrSurfaceOrigin origin) {
     SkDEBUGCODE(this->validate());
     SkASSERT(fContext);
 
@@ -813,21 +813,23 @@
     // task, then fail to make a copy task, the next active ops task may target the same proxy. This
     // will trip an assert related to unnecessary ops task splitting.
     if (src->framebufferOnly()) {
-        return false;
+        return nullptr;
     }
 
     this->closeActiveOpsTask();
 
-    GrRenderTask* task = this->appendTask(GrCopyRenderTask::Make(this,
-                                                                 src,
-                                                                 srcRect,
-                                                                 std::move(dst),
-                                                                 dstPoint,
-                                                                 origin));
+    sk_sp<GrRenderTask> task = GrCopyRenderTask::Make(this,
+                                                      src,
+                                                      srcRect,
+                                                      std::move(dst),
+                                                      dstPoint,
+                                                      origin);
     if (!task) {
-        return false;
+        return nullptr;
     }
 
+    this->appendTask(task);
+
     const GrCaps& caps = *fContext->priv().caps();
     // We always say GrMipmapped::kNo here since we are always just copying from the base layer to
     // another base layer. We don't need to make sure the whole mip map chain is valid.
@@ -838,7 +840,7 @@
     // shouldn't be an active one.
     SkASSERT(!fActiveOpsTask);
     SkDEBUGCODE(this->validate());
-    return true;
+    return task;
 }
 
 bool GrDrawingManager::newWritePixelsTask(sk_sp<GrSurfaceProxy> dst,
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 01d4b6b..3f8372c 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -76,12 +76,13 @@
     // dstProxy with top left at dstPoint. If the src rect is clipped by the src bounds then  pixel
     // values in the dst rect corresponding to the area clipped by the src rect are not overwritten.
     // This method is not guaranteed to succeed depending on the type of surface, formats, etc, and
-    // the backend-specific limitations.
-    bool newCopyRenderTask(sk_sp<GrSurfaceProxy> src,
-                           SkIRect srcRect,
-                           sk_sp<GrSurfaceProxy> dst,
-                           SkIPoint dstPoint,
-                           GrSurfaceOrigin);
+    // the backend-specific limitations. On success the task is returned so that the caller may
+    // mark it skippable if the copy is later deemed unnecessary.
+    sk_sp<GrRenderTask> newCopyRenderTask(sk_sp<GrSurfaceProxy> src,
+                                          SkIRect srcRect,
+                                          sk_sp<GrSurfaceProxy> dst,
+                                          SkIPoint dstPoint,
+                                          GrSurfaceOrigin);
 
     // Adds a task that writes the data from the passed GrMipLevels to dst. The lifetime of the
     // pixel data in the levels should be tied to the passed SkData or the caller must flush the
diff --git a/src/gpu/GrOpsTask.cpp b/src/gpu/GrOpsTask.cpp
index e9add24..c8d5f42 100644
--- a/src/gpu/GrOpsTask.cpp
+++ b/src/gpu/GrOpsTask.cpp
@@ -426,7 +426,6 @@
                    &dstProxyView, caps);
 }
 
-
 void GrOpsTask::endFlush(GrDrawingManager* drawingMgr) {
     fLastClipStackGenID = SK_InvalidUniqueID;
     this->deleteOps();
@@ -869,6 +868,13 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+void GrOpsTask::onCanSkip() {
+    this->deleteOps();
+    fDeferredProxies.reset();
+    fColorLoadOp = GrLoadOp::kLoad;
+    SkASSERT(this->isNoOp());
+}
+
 bool GrOpsTask::onIsUsed(GrSurfaceProxy* proxyToCheck) const {
     bool used = false;
 
diff --git a/src/gpu/GrOpsTask.h b/src/gpu/GrOpsTask.h
index 524306d..f55ee9e 100644
--- a/src/gpu/GrOpsTask.h
+++ b/src/gpu/GrOpsTask.h
@@ -228,6 +228,7 @@
         bool fSkipExecute = false;
     };
 
+    void onCanSkip() override;
 
     bool onIsUsed(GrSurfaceProxy*) const override;
 
diff --git a/src/gpu/GrRenderTask.cpp b/src/gpu/GrRenderTask.cpp
index 074f4a9..1437e4f 100644
--- a/src/gpu/GrRenderTask.cpp
+++ b/src/gpu/GrRenderTask.cpp
@@ -42,6 +42,11 @@
     }
 }
 
+void GrRenderTask::canSkip() {
+    SkASSERT(this->isClosed());
+    this->onCanSkip();
+}
+
 #ifdef SK_DEBUG
 GrRenderTask::~GrRenderTask() {
     SkASSERT(this->isSetFlag(kDisowned_Flag));
@@ -277,6 +282,7 @@
     SkASSERT(!fDrawingMgr || drawingMgr == fDrawingMgr);
     SkDEBUGCODE(fDrawingMgr = drawingMgr);
     drawingMgr->setLastRenderTask(proxy.get(), this);
+    proxy->isUsedAsTaskTarget();
     fTargets.emplace_back(std::move(proxy));
 }
 
diff --git a/src/gpu/GrRenderTask.h b/src/gpu/GrRenderTask.h
index 218267f..37ac2c1 100644
--- a/src/gpu/GrRenderTask.h
+++ b/src/gpu/GrRenderTask.h
@@ -52,6 +52,18 @@
 
     bool isClosed() const { return this->isSetFlag(kClosed_Flag); }
 
+    /**
+     * A task can be marked as able to be skipped. It must be used purely for optimization purposes
+     * at this point as not all tasks will actually skip their work. It would be better if we could
+     * detect tasks that can be skipped automatically. We'd need to support minimal flushes (i.e.,
+     * only flush that which is required for SkSurfaces/SkImages) and the ability to detect
+     * "orphaned tasks" and clean them out from the DAG so they don't indefinitely accumulate.
+     * Finally, we'd probably have to track whether a proxy's backing store was imported or ever
+     * exported to the client in case the client is doing direct reads outside of Skia and thus
+     * may require tasks targeting the proxy to execute even if our DAG contains no reads.
+     */
+    void canSkip();
+
     /*
      * Notify this GrRenderTask that it relies on the contents of 'dependedOn'
      */
@@ -239,6 +251,7 @@
         }
     };
 
+    virtual void onCanSkip() {}
     virtual void onPrePrepare(GrRecordingContext*) {} // Only the GrOpsTask currently overrides this
     virtual void onPrepare(GrOpFlushState*) {} // Only GrOpsTask and GrDDLTask override this virtual
     virtual bool onExecute(GrOpFlushState* flushState) = 0;
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 8c28765..8d38e0b 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -28,8 +28,9 @@
 #include "src/gpu/effects/GrBicubicEffect.h"
 #include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
 
-#define ASSERT_SINGLE_OWNER        GR_ASSERT_SINGLE_OWNER(this->singleOwner())
-#define RETURN_FALSE_IF_ABANDONED  if (this->fContext->abandoned()) { return false; }
+#define ASSERT_SINGLE_OWNER         GR_ASSERT_SINGLE_OWNER(this->singleOwner())
+#define RETURN_FALSE_IF_ABANDONED   if (this->fContext->abandoned()) { return false;   }
+#define RETURN_NULLPTR_IF_ABANDONED if (this->fContext->abandoned()) { return nullptr; }
 
 std::unique_ptr<GrSurfaceContext> GrSurfaceContext::Make(GrRecordingContext* context,
                                                          GrSurfaceProxyView readView,
@@ -1059,9 +1060,11 @@
                                   flushInfo);
 }
 
-bool GrSurfaceContext::copy(sk_sp<GrSurfaceProxy> src, SkIRect srcRect, SkIPoint dstPoint) {
+sk_sp<GrRenderTask> GrSurfaceContext::copy(sk_sp<GrSurfaceProxy> src,
+                                           SkIRect srcRect,
+                                           SkIPoint dstPoint) {
     ASSERT_SINGLE_OWNER
-    RETURN_FALSE_IF_ABANDONED
+    RETURN_NULLPTR_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
     GR_AUDIT_TRAIL_AUTO_FRAME(this->auditTrail(), "GrSurfaceContextPriv::copy");
 
@@ -1071,11 +1074,11 @@
     SkASSERT(src->backendFormat() == this->asSurfaceProxy()->backendFormat());
 
     if (this->asSurfaceProxy()->framebufferOnly()) {
-        return false;
+        return nullptr;
     }
 
     if (!caps->canCopySurface(this->asSurfaceProxy(), src.get(), srcRect, dstPoint)) {
-        return false;
+        return nullptr;
     }
 
     return this->drawingManager()->newCopyRenderTask(std::move(src),
diff --git a/src/gpu/GrSurfaceContext.h b/src/gpu/GrSurfaceContext.h
index 80e3fb1..b51d1af 100644
--- a/src/gpu/GrSurfaceContext.h
+++ b/src/gpu/GrSurfaceContext.h
@@ -18,6 +18,7 @@
 #include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrImageInfo.h"
 #include "src/gpu/GrPixmap.h"
+#include "src/gpu/GrRenderTask.h"
 #include "src/gpu/GrSurfaceProxy.h"
 #include "src/gpu/GrSurfaceProxyView.h"
 
@@ -181,12 +182,12 @@
 
 #if GR_TEST_UTILS
     bool testCopy(sk_sp<GrSurfaceProxy> src, const SkIRect& srcRect, const SkIPoint& dstPoint) {
-        return this->copy(std::move(src), srcRect, dstPoint);
+        return this->copy(std::move(src), srcRect, dstPoint) != nullptr;
     }
 
     bool testCopy(sk_sp<GrSurfaceProxy> src) {
         auto rect = SkIRect::MakeSize(src->dimensions());
-        return this->copy(std::move(src), rect, {0, 0});
+        return this->copy(std::move(src), rect, {0, 0}) != nullptr;
     }
 #endif
 
@@ -234,7 +235,8 @@
      * should go through GrSurfaceProxy::Copy.
      * @param src       src of pixels
      * @param dstPoint  the origin of the 'srcRect' in the destination coordinate space
-     * @return          true if the copy succeeded; false otherwise
+     * @return          a task (that may be skippable by calling canSkip) if successful and
+     *                  null otherwise.
      *
      * Note: Notionally, 'srcRect' is clipped to 'src's extent with 'dstPoint' being adjusted.
      *       Then the 'srcRect' offset by 'dstPoint' is clipped against the dst's extent.
@@ -242,7 +244,7 @@
      *       regions will not be shifted. The 'src' must have the same origin as the backing proxy
      *       of fSurfaceContext.
      */
-    bool copy(sk_sp<GrSurfaceProxy> src, SkIRect srcRect, SkIPoint dstPoint);
+    sk_sp<GrRenderTask> copy(sk_sp<GrSurfaceProxy> src, SkIRect srcRect, SkIPoint dstPoint);
 
     class AsyncReadResult;
 
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index aeb0227..855bd60 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -250,7 +250,8 @@
                                            SkIRect srcRect,
                                            SkBackingFit fit,
                                            SkBudgeted budgeted,
-                                           RectsMustMatch rectsMustMatch) {
+                                           RectsMustMatch rectsMustMatch,
+                                           sk_sp<GrRenderTask>* outTask) {
     SkASSERT(!src->isFullyLazy());
     int width;
     int height;
@@ -284,7 +285,11 @@
                                                  mipMapped,
                                                  src->isProtected(),
                                                  budgeted);
-        if (dstContext && dstContext->copy(src, srcRect, dstPoint)) {
+        sk_sp<GrRenderTask> copyTask;
+        if (dstContext && (copyTask = dstContext->copy(src, srcRect, dstPoint))) {
+            if (outTask) {
+                *outTask = std::move(copyTask);
+            }
             return dstContext->asSurfaceProxyRef();
         }
     }
@@ -304,6 +309,9 @@
                                                      budgeted);
         GrSurfaceProxyView view(std::move(src), origin, GrSwizzle::RGBA());
         if (dstContext && dstContext->blitTexture(std::move(view), srcRect, dstPoint)) {
+            if (outTask) {
+                *outTask = sk_ref_sp(dstContext->getOpsTask());
+            }
             return dstContext->asSurfaceProxyRef();
         }
     }
@@ -316,10 +324,19 @@
                                            GrSurfaceOrigin origin,
                                            GrMipmapped mipMapped,
                                            SkBackingFit fit,
-                                           SkBudgeted budgeted) {
+                                           SkBudgeted budgeted,
+                                           sk_sp<GrRenderTask>* outTask) {
     SkASSERT(!src->isFullyLazy());
     auto rect = SkIRect::MakeSize(src->dimensions());
-    return Copy(context, std::move(src), origin, mipMapped, rect, fit, budgeted);
+    return Copy(context,
+                std::move(src),
+                origin,
+                mipMapped,
+                rect,
+                fit,
+                budgeted,
+                RectsMustMatch::kNo,
+                outTask);
 }
 
 #if GR_TEST_UTILS
diff --git a/src/gpu/GrSurfaceProxy.h b/src/gpu/GrSurfaceProxy.h
index e987d12..02afea1 100644
--- a/src/gpu/GrSurfaceProxy.h
+++ b/src/gpu/GrSurfaceProxy.h
@@ -229,6 +229,12 @@
 
     bool isInstantiated() const { return SkToBool(fTarget); }
 
+    /** Called when this task becomes a target of a GrRenderTask. */
+    void isUsedAsTaskTarget() { ++fTaskTargetCount; }
+
+    /** How many render tasks has this proxy been the target of? */
+    int getTaskTargetCount() const { return fTaskTargetCount; }
+
     // If the proxy is already instantiated, return its backing GrTexture; if not, return null.
     GrSurface* peekSurface() const { return fTarget.get(); }
 
@@ -295,6 +301,10 @@
     // new one. Thus, there isn't a need for a swizzle when doing the copy. The format of the copy
     // will be the same as the src. Therefore, the copy can be used in a view with the same swizzle
     // as the original for use with a given color type.
+    //
+    // Optionally gets the render task that performs the copy. If it is later determined that the
+    // copy is not neccessaru then the task can be marked skippable using GrRenderTask::canSkip() and
+    // the copy will be elided.
     static sk_sp<GrSurfaceProxy> Copy(GrRecordingContext*,
                                       sk_sp<GrSurfaceProxy> src,
                                       GrSurfaceOrigin,
@@ -302,7 +312,8 @@
                                       SkIRect srcRect,
                                       SkBackingFit,
                                       SkBudgeted,
-                                      RectsMustMatch = RectsMustMatch::kNo);
+                                      RectsMustMatch = RectsMustMatch::kNo,
+                                      sk_sp<GrRenderTask>* outTask = nullptr);
 
     // Same as above Copy but copies the entire 'src'
     static sk_sp<GrSurfaceProxy> Copy(GrRecordingContext*,
@@ -310,7 +321,8 @@
                                       GrSurfaceOrigin,
                                       GrMipmapped,
                                       SkBackingFit,
-                                      SkBudgeted);
+                                      SkBudgeted,
+                                      sk_sp<GrRenderTask>* outTask = nullptr);
 
 #if GR_TEST_UTILS
     int32_t testingOnly_getBackingRefCnt() const;
@@ -435,6 +447,8 @@
     bool                   fIsPromiseProxy = false;
     GrProtected            fIsProtected;
 
+    int                     fTaskTargetCount = 0;
+
     // This entry is lazily evaluated so, when the proxy wraps a resource, the resource
     // will be called but, when the proxy is deferred, it will compute the answer itself.
     // If the proxy computes its own answer that answer is checked (in debug mode) in
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index c2093e4..1485854 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -99,6 +99,9 @@
 
     virtual bool isYUVA() const { return false; }
 
+    // If this image is the current cached image snapshot of a surface then this is called when the
+    // surface is destroyed to indicate no further writes may happen to surface backing store.
+    virtual void generatingSurfaceIsDeleted() {}
 #endif
 
     virtual bool onPinAsTexture(GrRecordingContext*) const { return false; }
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 09d9f03..60594a7 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -47,26 +47,198 @@
 #include <cstring>
 #include <type_traits>
 
-SkImage_Gpu::SkImage_Gpu(sk_sp<GrImageContext> context, uint32_t uniqueID, GrSurfaceProxyView view,
-                         SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> colorSpace)
-        : INHERITED(std::move(context), view.proxy()->backingStoreDimensions(), uniqueID,
-                    ct, at, colorSpace)
-        , fView(std::move(view)) {
+inline SkImage_Gpu::ProxyChooser::ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy)
+        : fStableProxy(std::move(stableProxy)) {
+    SkASSERT(fStableProxy);
+}
+
+inline SkImage_Gpu::ProxyChooser::ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy,
+                                               sk_sp<GrSurfaceProxy> volatileProxy,
+                                               sk_sp<GrRenderTask> copyTask,
+                                               int volatileProxyTargetCount)
+        : fStableProxy(std::move(stableProxy))
+        , fVolatileProxy(std::move(volatileProxy))
+        , fVolatileToStableCopyTask(std::move(copyTask))
+        , fVolatileProxyTargetCount(volatileProxyTargetCount) {
+    SkASSERT(fStableProxy);
+    SkASSERT(fVolatileProxy);
+    SkASSERT(fVolatileToStableCopyTask);
+}
+
+inline SkImage_Gpu::ProxyChooser::~ProxyChooser() {
+    // The image is being destroyed. If there is a stable copy proxy but we've been able to use
+    // the volatile proxy for all requests then we can skip the copy.
+    if (fVolatileToStableCopyTask) {
+        fVolatileToStableCopyTask->canSkip();
+    }
+}
+
+inline sk_sp<GrSurfaceProxy> SkImage_Gpu::ProxyChooser::chooseProxy(GrRecordingContext* context) {
+    SkAutoSpinlock hold(fLock);
+    if (fVolatileProxy) {
+        SkASSERT(fVolatileProxyTargetCount <= fVolatileProxy->getTaskTargetCount());
+        // If this image is used off the direct context it originated on, i.e. on a recording-only
+        // context, we don't know how the recording context's actions are ordered WRT direct context
+        // actions until the recording context's DAG is imported into the direct context.
+        if (context->asDirectContext() &&
+            fVolatileProxyTargetCount == fVolatileProxy->getTaskTargetCount()) {
+            return fVolatileProxy;
+        }
+        fVolatileProxy.reset();
+        fVolatileToStableCopyTask.reset();
+        return fStableProxy;
+    }
+    return fStableProxy;
+}
+
+inline sk_sp<GrSurfaceProxy> SkImage_Gpu::ProxyChooser::switchToStableProxy() {
+    SkAutoSpinlock hold(fLock);
+    fVolatileProxy.reset();
+    fVolatileToStableCopyTask.reset();
+    return fStableProxy;
+}
+
+inline sk_sp<GrSurfaceProxy> SkImage_Gpu::ProxyChooser::makeVolatileProxyStable() {
+    SkAutoSpinlock hold(fLock);
+    if (fVolatileProxy) {
+        fStableProxy = std::move(fVolatileProxy);
+        fVolatileToStableCopyTask->canSkip();
+        fVolatileToStableCopyTask.reset();
+    }
+    return fStableProxy;
+}
+
+inline bool SkImage_Gpu::ProxyChooser::surfaceMustCopyOnWrite(GrSurfaceProxy* surfaceProxy) const {
+    SkAutoSpinlock hold(fLock);
+    return surfaceProxy->underlyingUniqueID() == fStableProxy->underlyingUniqueID();
+}
+
+inline size_t SkImage_Gpu::ProxyChooser::gpuMemorySize() const {
+    SkAutoSpinlock hold(fLock);
+    size_t size = fStableProxy->gpuMemorySize();
+    if (fVolatileProxy) {
+        SkASSERT(fVolatileProxy->gpuMemorySize() == size);
+    }
+    return size;
+}
+
+inline GrMipmapped SkImage_Gpu::ProxyChooser::mipmapped() const {
+    SkAutoSpinlock hold(fLock);
+    GrMipmapped mipmapped = fStableProxy->asTextureProxy()->mipmapped();
+    if (fVolatileProxy) {
+        SkASSERT(fVolatileProxy->asTextureProxy()->mipmapped() == mipmapped);
+    }
+    return mipmapped;
+}
+
 #ifdef SK_DEBUG
-    const GrBackendFormat& format = fView.proxy()->backendFormat();
+inline GrBackendFormat SkImage_Gpu::ProxyChooser::backendFormat() {
+    SkAutoSpinlock hold(fLock);
+    if (fVolatileProxy) {
+        SkASSERT(fVolatileProxy->backendFormat() == fStableProxy->backendFormat());
+    }
+    return fStableProxy->backendFormat();
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+SkImage_Gpu::SkImage_Gpu(sk_sp<GrImageContext> context,
+                         uint32_t uniqueID,
+                         GrSurfaceProxyView view,
+                         SkColorType ct,
+                         SkAlphaType at,
+                         sk_sp<SkColorSpace> colorSpace)
+        : INHERITED(std::move(context),
+                    view.proxy()->backingStoreDimensions(),
+                    uniqueID,
+                    ct,
+                    at,
+                    std::move(colorSpace))
+        , fChooser(view.detachProxy())
+        , fSwizzle(view.swizzle())
+        , fOrigin(view.origin()) {
+#ifdef SK_DEBUG
+    const GrBackendFormat& format = fChooser.backendFormat();
     const GrCaps* caps = this->context()->priv().caps();
-    GrColorType grCT = SkColorTypeAndFormatToGrColorType(caps, ct, format);
+    GrColorType grCT = SkColorTypeAndFormatToGrColorType(caps, this->colorType(), format);
     SkASSERT(caps->isFormatCompressed(format) ||
              caps->areColorTypeAndFormatCompatible(grCT, format));
 #endif
 }
 
-SkImage_Gpu::~SkImage_Gpu() {}
+SkImage_Gpu::SkImage_Gpu(sk_sp<GrDirectContext> dContext,
+                         GrSurfaceProxyView volatileSrc,
+                         sk_sp<GrSurfaceProxy> stableCopy,
+                         sk_sp<GrRenderTask> copyTask,
+                         int volatileSrcTargetCount,
+                         SkColorInfo colorInfo)
+        : INHERITED(std::move(dContext),
+                    volatileSrc.proxy()->backingStoreDimensions(),
+                    kNeedNewImageUniqueID,
+                    colorInfo.colorType(),
+                    colorInfo.alphaType(),
+                    colorInfo.refColorSpace())
+        , fChooser(std::move(stableCopy),
+                   volatileSrc.detachProxy(),
+                   std::move(copyTask),
+                   volatileSrcTargetCount)
+        , fSwizzle(volatileSrc.swizzle())
+        , fOrigin(volatileSrc.origin()) {
+#ifdef SK_DEBUG
+    const GrBackendFormat& format = fChooser.backendFormat();
+    const GrCaps* caps = this->context()->priv().caps();
+    GrColorType grCT = SkColorTypeAndFormatToGrColorType(caps, this->colorType(), format);
+    SkASSERT(caps->isFormatCompressed(format) ||
+             caps->areColorTypeAndFormatCompatible(grCT, format));
+#endif
+}
+
+sk_sp<SkImage> SkImage_Gpu::MakeWithVolatileSrc(sk_sp<GrRecordingContext> rContext,
+                                                GrSurfaceProxyView volatileSrc,
+                                                SkColorInfo colorInfo) {
+    SkASSERT(rContext);
+    SkASSERT(volatileSrc);
+    SkASSERT(volatileSrc.proxy()->asTextureProxy());
+    GrMipmapped mm = volatileSrc.proxy()->asTextureProxy()->mipmapped();
+    sk_sp<GrRenderTask> copyTask;
+    auto copy = GrSurfaceProxy::Copy(rContext.get(),
+                                     volatileSrc.refProxy(),
+                                     volatileSrc.origin(),
+                                     mm,
+                                     SkBackingFit::kExact,
+                                     SkBudgeted::kYes,
+                                     &copyTask);
+    if (!copy) {
+        return nullptr;
+    }
+    // We only attempt to make a dual-proxy image on a direct context. This optimziation requires
+    // knowing how things are ordered and recording-only contexts are not well ordered WRT other
+    // recording contexts.
+    if (auto direct = sk_ref_sp(rContext->asDirectContext())) {
+        int targetCount = volatileSrc.proxy()->getTaskTargetCount();
+        return sk_sp<SkImage>(new SkImage_Gpu(std::move(direct),
+                                              std::move(volatileSrc),
+                                              std::move(copy),
+                                              std::move(copyTask),
+                                              targetCount,
+                                              std::move(colorInfo)));
+    }
+    GrSurfaceProxyView copyView(std::move(copy), volatileSrc.origin(), volatileSrc.swizzle());
+    return sk_make_sp<SkImage_Gpu>(std::move(rContext),
+                                   kNeedNewImageUniqueID,
+                                   std::move(copyView),
+                                   std::move(colorInfo));
+}
+
+SkImage_Gpu::~SkImage_Gpu() = default;
 
 bool SkImage_Gpu::surfaceMustCopyOnWrite(GrSurfaceProxy* surfaceProxy) const {
-    return surfaceProxy->underlyingUniqueID() == fView.proxy()->underlyingUniqueID();
+    return fChooser.surfaceMustCopyOnWrite(surfaceProxy);
 }
 
+bool SkImage_Gpu::onHasMipmaps() const { return fChooser.mipmapped() == GrMipmapped::kYes; }
+
 GrSemaphoresSubmitted SkImage_Gpu::onFlush(GrDirectContext* dContext, const GrFlushInfo& info) {
     if (!fContext->priv().matches(dContext) || dContext->abandoned()) {
         if (info.fSubmittedProc) {
@@ -78,7 +250,9 @@
         return GrSemaphoresSubmitted::kNo;
     }
 
-    return dContext->priv().flushSurface(fView.proxy(), SkSurface::BackendSurfaceAccess::kNoAccess,
+    sk_sp<GrSurfaceProxy> proxy = fChooser.chooseProxy(dContext);
+    return dContext->priv().flushSurface(proxy.get(),
+                                         SkSurface::BackendSurfaceAccess::kNoAccess,
                                          info);
 }
 
@@ -90,7 +264,9 @@
         return GrBackendTexture();  // invalid
     }
 
-    GrSurfaceProxy* proxy = fView.proxy();
+    // We don't know how client's use of the texture will be ordered WRT Skia's. Ensure the
+    // texture seen by the client won't be mutated by a SkSurface.
+    sk_sp<GrSurfaceProxy> proxy = fChooser.switchToStableProxy();
 
     if (!proxy->isInstantiated()) {
         auto resourceProvider = direct->priv().resourceProvider();
@@ -103,16 +279,18 @@
     GrTexture* texture = proxy->peekTexture();
     if (texture) {
         if (flushPendingGrContextIO) {
-            direct->priv().flushSurface(proxy);
+            direct->priv().flushSurface(proxy.get());
         }
         if (origin) {
-            *origin = fView.origin();
+            *origin = fOrigin;
         }
         return texture->getBackendTexture();
     }
     return GrBackendTexture();  // invalid
 }
 
+size_t SkImage_Gpu::onTextureSize() const { return fChooser.gpuMemorySize(); }
+
 sk_sp<SkImage> SkImage_Gpu::onMakeColorTypeAndColorSpace(SkColorType targetCT,
                                                          sk_sp<SkColorSpace> targetCS,
                                                          GrDirectContext* direct) const {
@@ -144,8 +322,15 @@
 }
 
 sk_sp<SkImage> SkImage_Gpu::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fView, this->colorType(),
-                                   this->alphaType(), std::move(newCS));
+    // It doesn't seem worth the complexity of trying to share the ProxyChooser among multiple
+    // images. Just fall back to the stable copy.
+    GrSurfaceProxyView view(fChooser.switchToStableProxy(), fOrigin, fSwizzle);
+    return sk_make_sp<SkImage_Gpu>(fContext,
+                                   kNeedNewImageUniqueID,
+                                   std::move(view),
+                                   this->colorType(),
+                                   this->alphaType(),
+                                   std::move(newCS));
 }
 
 void SkImage_Gpu::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
@@ -160,7 +345,9 @@
         callback(context, nullptr);
         return;
     }
-    auto ctx = GrSurfaceContext::Make(dContext, fView, this->imageInfo().colorInfo());
+    auto ctx = GrSurfaceContext::Make(dContext,
+                                      this->makeView(dContext),
+                                      this->imageInfo().colorInfo());
     if (!ctx) {
         callback(context, nullptr);
         return;
@@ -183,7 +370,9 @@
         callback(context, nullptr);
         return;
     }
-    auto ctx = GrSurfaceContext::Make(dContext, fView, this->imageInfo().colorInfo());
+    auto ctx = GrSurfaceContext::Make(dContext,
+                                      this->makeView(dContext),
+                                      this->imageInfo().colorInfo());
     if (!ctx) {
         callback(context, nullptr);
         return;
@@ -199,6 +388,8 @@
                                          context);
 }
 
+void SkImage_Gpu::generatingSurfaceIsDeleted() { fChooser.makeVolatileProxyStable(); }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static sk_sp<SkImage> new_wrapped_texture_common(GrRecordingContext* rContext,
@@ -636,13 +827,18 @@
         return {};
     }
     if (policy != GrImageTexGenPolicy::kDraw) {
-        return {CopyView(context, fView, mipmapped, policy),
+        return {CopyView(context, this->makeView(context), mipmapped, policy),
                 SkColorTypeToGrColorType(this->colorType())};
     }
+    GrSurfaceProxyView view = this->makeView(context);
     GrColorType ct = SkColorTypeAndFormatToGrColorType(context->priv().caps(),
                                                        this->colorType(),
-                                                       fView.proxy()->backendFormat());
+                                                       view.proxy()->backendFormat());
     GrColorInfo colorInfo(ct, this->alphaType(), this->refColorSpace());
-    GrTextureAdjuster adjuster(context, fView, colorInfo, this->uniqueID());
+    GrTextureAdjuster adjuster(context, std::move(view), colorInfo, this->uniqueID());
     return {adjuster.view(mipmapped), adjuster.colorType()};
 }
+
+GrSurfaceProxyView SkImage_Gpu::makeView(GrRecordingContext* rContext) const {
+    return {fChooser.chooseProxy(rContext), fOrigin, fSwizzle};
+}
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index 1421dcc..07bb8e0 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -8,6 +8,7 @@
 #ifndef SkImage_Gpu_DEFINED
 #define SkImage_Gpu_DEFINED
 
+#include "include/private/SkSpinlock.h"
 #include "src/core/SkImagePriv.h"
 #include "src/gpu/GrGpuResourcePriv.h"
 #include "src/gpu/GrSurfaceProxyPriv.h"
@@ -35,6 +36,10 @@
                           info.alphaType(),
                           info.refColorSpace()) {}
 
+    static sk_sp<SkImage> MakeWithVolatileSrc(sk_sp<GrRecordingContext> rContext,
+                                              GrSurfaceProxyView volatileSrc,
+                                              SkColorInfo colorInfo);
+
     ~SkImage_Gpu() override;
 
     // If this is image is a cached SkSurface snapshot then this method is called by the SkSurface
@@ -42,21 +47,16 @@
     // contents.
     bool surfaceMustCopyOnWrite(GrSurfaceProxy* surfaceProxy) const;
 
-    bool onHasMipmaps() const override {
-        return fView.asTextureProxy()->mipmapped() == GrMipmapped::kYes;
-    }
+    bool onHasMipmaps() const override;
 
     GrSemaphoresSubmitted onFlush(GrDirectContext*, const GrFlushInfo&) override;
 
     GrBackendTexture onGetBackendTexture(bool flushPendingGrContextIO,
                                          GrSurfaceOrigin* origin) const final;
 
-    bool onIsTextureBacked() const override {
-        SkASSERT(fView.proxy());
-        return true;
-    }
+    bool onIsTextureBacked() const override { return true; }
 
-    size_t onTextureSize() const override { return fView.proxy()->gpuMemorySize(); }
+    size_t onTextureSize() const override;
 
     sk_sp<SkImage> onMakeColorTypeAndColorSpace(SkColorType, sk_sp<SkColorSpace>,
                                                 GrDirectContext*) const final;
@@ -79,12 +79,69 @@
                                            ReadPixelsCallback,
                                            ReadPixelsContext) override;
 
+    void generatingSurfaceIsDeleted() override;
+
 private:
+    SkImage_Gpu(sk_sp<GrDirectContext>,
+                GrSurfaceProxyView volatileSrc,
+                sk_sp<GrSurfaceProxy> stableCopy,
+                sk_sp<GrRenderTask> copyTask,
+                int volatileSrcTargetCount,
+                SkColorInfo);
+
     std::tuple<GrSurfaceProxyView, GrColorType> onAsView(GrRecordingContext*,
                                                          GrMipmapped,
                                                          GrImageTexGenPolicy) const override;
 
-    GrSurfaceProxyView fView;
+    GrSurfaceProxyView makeView(GrRecordingContext*) const;
+
+    // Thread-safe wrapper around the proxies backing this image. Handles dynamically switching
+    // from a "volatile" proxy that may be overwritten (by an SkSurface that this image was snapped
+    // from) to a "stable" proxy that is a copy of the volatile proxy. It allows the image to cancel
+    // the copy if the stable proxy is never required because the contents of the volatile proxy
+    // were never mutated by the SkSurface during the image lifetime.
+    class ProxyChooser {
+    public:
+        ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy,
+                     sk_sp<GrSurfaceProxy> volatileProxy,
+                     sk_sp<GrRenderTask> copyTask,
+                     int volatileProxyTargetCount);
+
+        ProxyChooser(sk_sp<GrSurfaceProxy> stableProxy);
+
+        ~ProxyChooser();
+
+        // Checks if there is a volatile proxy that is safe to use. If so returns it, otherwise
+        // returns the stable proxy (and drops the volatile one if it exists).
+        sk_sp<GrSurfaceProxy> chooseProxy(GrRecordingContext* context) SK_EXCLUDES(fLock);
+        // Call when it is known copy is necessary.
+        sk_sp<GrSurfaceProxy> switchToStableProxy() SK_EXCLUDES(fLock);
+        // Call when it is known for sure copy won't be necessary.
+        sk_sp<GrSurfaceProxy> makeVolatileProxyStable() SK_EXCLUDES(fLock);
+
+        bool surfaceMustCopyOnWrite(GrSurfaceProxy* surfaceProxy) const SK_EXCLUDES(fLock);
+
+        // Queries that should be independent of which proxy is in use.
+        size_t gpuMemorySize() const SK_EXCLUDES(fLock);
+        GrMipmapped mipmapped() const SK_EXCLUDES(fLock);
+#ifdef SK_DEBUG
+        GrBackendFormat backendFormat() SK_EXCLUDES(fLock);
+#endif
+
+    private:
+        mutable SkSpinlock fLock;
+        sk_sp<GrSurfaceProxy> fStableProxy SK_GUARDED_BY(fLock);
+        sk_sp<GrSurfaceProxy> fVolatileProxy SK_GUARDED_BY(fLock);
+        sk_sp<GrRenderTask> fVolatileToStableCopyTask;
+        // The number of GrRenderTasks targeting the volatile proxy at creation time. If the
+        // proxy's target count increases it indicates additional writes and we must switch
+        // to using the stable copy.
+        const int fVolatileProxyTargetCount = 0;
+    };
+
+    mutable ProxyChooser fChooser;
+    GrSwizzle fSwizzle;
+    GrSurfaceOrigin fOrigin;
 
     using INHERITED = SkImage_GpuBase;
 };
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index f18a2bb..e7f0e4d 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -12,6 +12,7 @@
 #include "src/core/SkAutoPixmapStorage.h"
 #include "src/core/SkImagePriv.h"
 #include "src/core/SkPaintPriv.h"
+#include "src/image/SkImage_Base.h"
 #include "src/image/SkRescaleAndReadPixels.h"
 #include "src/image/SkSurface_Base.h"
 
@@ -39,6 +40,11 @@
     if (fCachedCanvas) {
         fCachedCanvas->setSurfaceBase(nullptr);
     }
+#if SK_SUPPORT_GPU
+    if (fCachedImage) {
+        as_IB(fCachedImage.get())->generatingSurfaceIsDeleted();
+    }
+#endif
 }
 
 GrRecordingContext* SkSurface_Base::onGetRecordingContext() {
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index a1d20e7..2963004 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -110,13 +110,21 @@
         return nullptr;
     }
 
+    GrSurfaceProxyView srcView = sdc->readSurfaceView();
+
     SkBudgeted budgeted = sdc->asSurfaceProxy()->isBudgeted();
 
-    GrSurfaceProxyView srcView = sdc->readSurfaceView();
     if (subset || !srcView.asTextureProxy() || sdc->refsWrappedObjects()) {
         // If the original render target is a buffer originally created by the client, then we don't
-        // want to ever retarget the SkSurface at another buffer we create. Force a copy now to
-        // avoid copy-on-write.
+        // want to ever retarget the SkSurface at another buffer we create. If the source is a
+        // texture (and the image is not subsetted) we make a dual-proxied SkImage that will
+        // attempt to share the backing store until the surface writes to the shared backing store
+        // at which point it uses a copy.
+        if (!subset && srcView.asTextureProxy()) {
+            return SkImage_Gpu::MakeWithVolatileSrc(sk_ref_sp(rContext),
+                                                    srcView,
+                                                    fDevice->imageInfo().colorInfo());
+        }
         auto rect = subset ? *subset : SkIRect::MakeSize(sdc->dimensions());
         srcView = GrSurfaceProxyView::Copy(rContext, std::move(srcView), sdc->mipmapped(), rect,
                                            SkBackingFit::kExact, budgeted);