Add "lazy" texture proxies

Adds ultra-deferred proxies that are instantiated by a user-supplied
callback during flush.

Bug: skia:7190
Change-Id: I75a7ac6dba953c3b0a99febc203a7f4d2f3789fc
Reviewed-on: https://skia-review.googlesource.com/76461
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 418ce01..23e4f83 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -124,6 +124,7 @@
   "$_tests/IsClosedSingleContourTest.cpp",
   "$_tests/LayerDrawLooperTest.cpp",
   "$_tests/LayerRasterizerTest.cpp",
+  "$_tests/LazyProxyTest.cpp",
   "$_tests/LListTest.cpp",
   "$_tests/LRUCacheTest.cpp",
   "$_tests/MallocPixelRefTest.cpp",
diff --git a/include/private/GrRenderTargetProxy.h b/include/private/GrRenderTargetProxy.h
index e52588b..a5f876b 100644
--- a/include/private/GrRenderTargetProxy.h
+++ b/include/private/GrRenderTargetProxy.h
@@ -67,6 +67,9 @@
     GrRenderTargetProxy(const GrCaps&, const GrSurfaceDesc&,
                         SkBackingFit, SkBudgeted, uint32_t flags);
 
+    // Lazy-callback version
+    GrRenderTargetProxy(LazyInstantiateCallback&&, GrPixelConfig);
+
     // Wrapped version
     GrRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
 
@@ -74,6 +77,7 @@
 
 private:
     size_t onUninstantiatedGpuMemorySize() const override;
+    SkDEBUGCODE(void validateLazyTexture(const GrTexture*) override { SkASSERT(0); })
 
     int                 fSampleCnt;
     bool                fNeedsStencil;
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 23f6219..387aecd 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -212,15 +212,34 @@
 
     static sk_sp<GrTextureProxy> MakeWrappedBackend(GrContext*, GrBackendTexture&, GrSurfaceOrigin);
 
+    using LazyInstantiateCallback = std::function<sk_sp<GrTexture>(GrResourceProvider*,
+                                                                   GrSurfaceOrigin* outOrigin)>;
+
+    enum class Renderable : bool {
+        kNo = false,
+        kYes = true
+    };
+
+    /**
+     * Creates a texture proxy that will be instantiated by a user-supplied callback during flush.
+     * (Mipmapping, MSAA, and stencil are not supported by this method.)
+     */
+    static sk_sp<GrTextureProxy> MakeLazy(LazyInstantiateCallback&&, Renderable, GrPixelConfig);
+
+    GrPixelConfig config() const { return fConfig; }
+    int width() const { SkASSERT(!this->isPendingLazyInstantiation()); return fWidth; }
+    int height() const { SkASSERT(!this->isPendingLazyInstantiation()); return fHeight; }
+    int worstCaseWidth() const;
+    int worstCaseHeight() const;
     GrSurfaceOrigin origin() const {
+        SkASSERT(!this->isPendingLazyInstantiation());
         SkASSERT(kTopLeft_GrSurfaceOrigin == fOrigin || kBottomLeft_GrSurfaceOrigin == fOrigin);
         return fOrigin;
     }
-    int width() const { return fWidth; }
-    int height() const { return fHeight; }
-    int worstCaseWidth() const;
-    int worstCaseHeight() const;
-    GrPixelConfig config() const { return fConfig; }
+
+    // If the client gave us a LazyInstantiateCallback (via MakeLazy), then we will invoke that
+    // callback during flush. fWidth, fHeight, and fOrigin will be undefined until that time.
+    bool isPendingLazyInstantiation() const { return SkToBool(fLazyInstantiateCallback); }
 
     class UniqueID {
     public:
@@ -230,7 +249,7 @@
 
         // wrapped
         explicit UniqueID(const GrGpuResource::UniqueID& id) : fID(id.asUInt()) { }
-        // deferred
+        // deferred and lazy-callback
         UniqueID() : fID(GrGpuResource::CreateUniqueID()) { }
 
         uint32_t asUInt() const { return fID; }
@@ -281,7 +300,10 @@
     /**
      * Helper that gets the width and height of the surface as a bounding rectangle.
      */
-    SkRect getBoundsRect() const { return SkRect::MakeIWH(this->width(), this->height()); }
+    SkRect getBoundsRect() const {
+        SkASSERT(!this->isPendingLazyInstantiation());
+        return SkRect::MakeIWH(this->width(), this->height());
+    }
 
     /**
      * @return the texture proxy associated with the surface proxy, may be NULL.
@@ -314,6 +336,7 @@
      * @return the amount of GPU memory used in bytes
      */
     size_t gpuMemorySize() const {
+        SkASSERT(!this->isPendingLazyInstantiation());
         if (fTarget) {
             return fTarget->gpuMemorySize();
         }
@@ -363,6 +386,9 @@
         // Note: this ctor pulls a new uniqueID from the same pool at the GrGpuResources
     }
 
+    // Lazy-callback version
+    GrSurfaceProxy(LazyInstantiateCallback&& callback, GrPixelConfig config);
+
     // Wrapped version
     GrSurfaceProxy(sk_sp<GrSurface> surface, GrSurfaceOrigin origin, SkBackingFit fit);
 
@@ -392,23 +418,28 @@
                          GrSurfaceFlags flags, GrMipMapped mipMapped,
                          SkDestinationSurfaceColorMode mipColorMode, const GrUniqueKey*);
 
+private:
     // For wrapped resources, 'fConfig', 'fWidth', 'fHeight', and 'fOrigin; will always be filled in
     // from the wrapped resource.
     GrPixelConfig        fConfig;
     int                  fWidth;
     int                  fHeight;
     GrSurfaceOrigin      fOrigin;
-    SkBackingFit         fFit;      // always exact for wrapped resources
-    mutable SkBudgeted   fBudgeted; // set from the backing resource for wrapped resources
+    SkBackingFit         fFit;      // always kApprox for lazy-callback resources
+                                    // always kExact for wrapped resources
+    mutable SkBudgeted   fBudgeted; // always kYes for lazy-callback resources
+                                    // set from the backing resource for wrapped resources
                                     // mutable bc of SkSurface/SkImage wishy-washiness
     const uint32_t       fFlags;
 
     const UniqueID       fUniqueID; // set from the backing resource for wrapped resources
 
+    LazyInstantiateCallback fLazyInstantiateCallback;
+    SkDEBUGCODE(virtual void validateLazyTexture(const GrTexture*) = 0;)
+
     static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(0);
     SkDEBUGCODE(size_t getRawGpuMemorySize_debugOnly() const { return fGpuMemorySize; })
 
-private:
     virtual size_t onUninstantiatedGpuMemorySize() const = 0;
 
     bool                 fNeedsClear;
diff --git a/include/private/GrTextureProxy.h b/include/private/GrTextureProxy.h
index cc1148a..a2f11d5 100644
--- a/include/private/GrTextureProxy.h
+++ b/include/private/GrTextureProxy.h
@@ -67,6 +67,10 @@
     // Deferred version
     GrTextureProxy(const GrSurfaceDesc& srcDesc, SkBackingFit, SkBudgeted,
                    const void* srcData, size_t srcRowBytes, uint32_t flags);
+
+    // Lazy-callback version
+    GrTextureProxy(LazyInstantiateCallback&&, GrPixelConfig);
+
     // Wrapped version
     GrTextureProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
 
@@ -94,6 +98,8 @@
     void setUniqueKey(GrResourceCache*, const GrUniqueKey&);
     void clearUniqueKey();
 
+    SkDEBUGCODE(void validateLazyTexture(const GrTexture*) override;)
+
     // For wrapped proxies the GrTexture pointer is stored in GrIORefProxy.
     // For deferred proxies that pointer will be filled in when we need to instantiate
     // the deferred resource
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 4689e41..4c93518 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -24,6 +24,8 @@
 using GrStdSteadyClock = std::chrono::steady_clock;
 #endif
 
+static constexpr GrSurfaceOrigin kGrUnknownSurfaceOrigin = static_cast<GrSurfaceOrigin>(-1);
+
 /** This enum is used to specify the load operation to be used when an
  *  opList/GrGpuCommandBuffer begins execution.
  */
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index d95a2c5..bf8d1ab 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -21,6 +21,8 @@
 #include "GrSurfaceProxyPriv.h"
 #include "GrTextureContext.h"
 #include "GrTextureOpList.h"
+#include "GrTextureProxy.h"
+#include "GrTextureProxyPriv.h"
 #include "SkSurface_Gpu.h"
 #include "SkTTopoSort.h"
 
@@ -150,10 +152,18 @@
                                       fFlushingOpListIDs.begin(), fFlushingOpListIDs.count(),
                                       &renderTargetContexts);
             for (const sk_sp<GrRenderTargetContext>& rtc : renderTargetContexts) {
-                sk_sp<GrOpList> onFlushOpList = sk_ref_sp(rtc->getOpList());
+                sk_sp<GrRenderTargetOpList> onFlushOpList = sk_ref_sp(rtc->getRTOpList());
                 if (!onFlushOpList) {
                     continue;   // Odd - but not a big deal
                 }
+#ifdef SK_DEBUG
+                // OnFlush callbacks are already invoked during flush, and are therefore expected to
+                // handle resource allocation & usage on their own. (No deferred or lazy proxies!)
+                onFlushOpList->visitProxies_debugOnly([](GrSurfaceProxy* p) {
+                    SkASSERT(!p->asTextureProxy() || !p->asTextureProxy()->texPriv().isDeferred());
+                    SkASSERT(!p->isPendingLazyInstantiation());
+                });
+#endif
                 onFlushOpList->makeClosed(*fContext->caps());
                 onFlushOpList->prepare(&fFlushState);
                 fOnFlushCBOpLists.push_back(std::move(onFlushOpList));
diff --git a/src/gpu/GrRenderTargetContextPriv.h b/src/gpu/GrRenderTargetContextPriv.h
index 0d73793..271badb 100644
--- a/src/gpu/GrRenderTargetContextPriv.h
+++ b/src/gpu/GrRenderTargetContextPriv.h
@@ -109,6 +109,7 @@
     }
 
     uint32_t testingOnly_addDrawOp(std::unique_ptr<GrDrawOp>);
+    uint32_t testingOnly_addDrawOp(const GrClip&, std::unique_ptr<GrDrawOp>);
 
     bool refsWrappedObjects() const {
         return fRenderTargetContext->fRenderTargetProxy->refsWrappedObjects();
diff --git a/src/gpu/GrRenderTargetOpList.cpp b/src/gpu/GrRenderTargetOpList.cpp
index 885723c..ff85a41 100644
--- a/src/gpu/GrRenderTargetOpList.cpp
+++ b/src/gpu/GrRenderTargetOpList.cpp
@@ -60,6 +60,12 @@
         }
     }
 }
+
+void GrRenderTargetOpList::visitProxies_debugOnly(const GrOp::VisitProxyFunc& func) const {
+    for (const RecordedOp& recordedOp : fRecordedOps) {
+        recordedOp.visitProxies(func);
+    }
+}
 #endif
 
 void GrRenderTargetOpList::onPrepare(GrOpFlushState* flushState) {
diff --git a/src/gpu/GrRenderTargetOpList.h b/src/gpu/GrRenderTargetOpList.h
index 7e600d4..93bab91 100644
--- a/src/gpu/GrRenderTargetOpList.h
+++ b/src/gpu/GrRenderTargetOpList.h
@@ -125,6 +125,7 @@
 
     SkDEBUGCODE(int numOps() const override { return fRecordedOps.count(); })
     SkDEBUGCODE(int numClips() const override { return fNumClips; })
+    SkDEBUGCODE(void visitProxies_debugOnly(const GrOp::VisitProxyFunc&) const;)
 
 private:
     friend class GrRenderTargetContextPriv; // for stencil clip state. TODO: this is invasive
diff --git a/src/gpu/GrRenderTargetProxy.cpp b/src/gpu/GrRenderTargetProxy.cpp
index 9453ce8..dea47e6 100644
--- a/src/gpu/GrRenderTargetProxy.cpp
+++ b/src/gpu/GrRenderTargetProxy.cpp
@@ -34,12 +34,20 @@
     }
 }
 
+// Lazy-callback version
+GrRenderTargetProxy::GrRenderTargetProxy(LazyInstantiateCallback&& callback, GrPixelConfig config)
+        : INHERITED(std::move(callback), config)
+        , fSampleCnt(0)
+        , fNeedsStencil(false)
+        , fRenderTargetFlags(GrRenderTargetFlags::kNone) {
+}
+
 // Wrapped version
 GrRenderTargetProxy::GrRenderTargetProxy(sk_sp<GrSurface> surf, GrSurfaceOrigin origin)
-    : INHERITED(std::move(surf), origin, SkBackingFit::kExact)
-    , fSampleCnt(fTarget->asRenderTarget()->numStencilSamples())
-    , fNeedsStencil(false)
-    , fRenderTargetFlags(fTarget->asRenderTarget()->renderTargetPriv().flags()) {
+        : INHERITED(std::move(surf), origin, SkBackingFit::kExact)
+        , fSampleCnt(fTarget->asRenderTarget()->numStencilSamples())
+        , fNeedsStencil(false)
+        , fRenderTargetFlags(fTarget->asRenderTarget()->renderTargetPriv().flags()) {
 }
 
 int GrRenderTargetProxy::maxWindowRectangles(const GrCaps& caps) const {
@@ -85,8 +93,8 @@
     int colorSamplesPerPixel = this->numColorSamples() + 1;
 
     // TODO: do we have enough information to improve this worst case estimate?
-    return GrSurface::ComputeSize(fConfig, fWidth, fHeight, colorSamplesPerPixel, GrMipMapped::kNo,
-                                  SkBackingFit::kApprox == fFit);
+    return GrSurface::ComputeSize(this->config(), this->width(), this->height(),
+                                  colorSamplesPerPixel, GrMipMapped::kNo, !this->priv().isExact());
 }
 
 bool GrRenderTargetProxy::refsWrappedObjects() const {
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index 0afc0ed..bf46c61 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -73,6 +73,13 @@
 
     fIntvlList.insertByIncreasingStart(newIntvl);
     fIntvlHash.add(newIntvl);
+
+#ifdef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
+    // FIXME: remove this once we can do the lazy instantiation from assign instead.
+    if (proxy->isPendingLazyInstantiation()) {
+        proxy->priv().doLazyInstantiation(fResourceProvider);
+    }
+#endif
 }
 
 GrResourceAllocator::Interval* GrResourceAllocator::IntervalList::popHead() {
@@ -224,8 +231,9 @@
             continue;
         }
 
-        sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil);
-        if (surface) {
+        if (cur->proxy()->isPendingLazyInstantiation()) {
+            cur->proxy()->priv().doLazyInstantiation(fResourceProvider);
+        } else if (sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil)) {
             // TODO: make getUniqueKey virtual on GrSurfaceProxy
             GrTextureProxy* tex = cur->proxy()->asTextureProxy();
             if (tex && tex->getUniqueKey().isValid()) {
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 71ff637..a5c2be6 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -21,6 +21,23 @@
 #include "SkMathPriv.h"
 #include "SkMipMap.h"
 
+// Lazy-callback version
+GrSurfaceProxy::GrSurfaceProxy(LazyInstantiateCallback&& callback, GrPixelConfig config)
+        : fConfig(config)
+        , fWidth(-1) // Width, height, and origin will be initialized upon lazy instantiation.
+        , fHeight(-1)
+        , fOrigin(kGrUnknownSurfaceOrigin)
+        , fFit(SkBackingFit::kApprox)
+        , fBudgeted(SkBudgeted::kYes)
+        , fFlags(GrResourceProvider::kNoPendingIO_Flag)
+        , fLazyInstantiateCallback(std::move(callback))
+        , fNeedsClear(false)
+        , fGpuMemorySize(kInvalidGpuMemorySize)
+        , fLastOpList(nullptr) {
+    // NOTE: the default fUniqueID ctor pulls a value from the same pool as the GrGpuResources.
+}
+
+// Wrapped version
 GrSurfaceProxy::GrSurfaceProxy(sk_sp<GrSurface> surface, GrSurfaceOrigin origin, SkBackingFit fit)
         : INHERITED(std::move(surface))
         , fConfig(fTarget->config())
@@ -64,6 +81,7 @@
                                                 int sampleCnt, bool needsStencil,
                                                 GrSurfaceFlags flags, GrMipMapped mipMapped,
                                                 SkDestinationSurfaceColorMode mipColorMode) const {
+    SkASSERT(!this->isPendingLazyInstantiation());
     SkASSERT(GrMipMapped::kNo == mipMapped);
     GrSurfaceDesc desc;
     desc.fFlags = flags;
@@ -96,6 +114,7 @@
 }
 
 void GrSurfaceProxy::assign(sk_sp<GrSurface> surface) {
+    SkASSERT(!this->isPendingLazyInstantiation());
     SkASSERT(!fTarget && surface);
     fTarget = surface.release();
     this->INHERITED::transferRefs();
@@ -111,6 +130,7 @@
                                      bool needsStencil, GrSurfaceFlags flags, GrMipMapped mipMapped,
                                      SkDestinationSurfaceColorMode mipColorMode,
                                      const GrUniqueKey* uniqueKey) {
+    SkASSERT(!this->isPendingLazyInstantiation());
     if (fTarget) {
         if (uniqueKey) {
             SkASSERT(fTarget->getUniqueKey() == *uniqueKey);
@@ -135,6 +155,7 @@
 }
 
 void GrSurfaceProxy::computeScratchKey(GrScratchKey* key) const {
+    SkASSERT(!this->isPendingLazyInstantiation());
     const GrRenderTargetProxy* rtp = this->asRenderTargetProxy();
     int sampleCount = 0;
     if (rtp) {
@@ -377,7 +398,15 @@
     return GrSurfaceProxy::MakeWrapped(std::move(tex), origin);
 }
 
+sk_sp<GrTextureProxy> GrSurfaceProxy::MakeLazy(LazyInstantiateCallback&& callback,
+                                               Renderable renderable, GrPixelConfig config) {
+    return sk_sp<GrTextureProxy>(Renderable::kYes == renderable ?
+                                 new GrTextureRenderTargetProxy(std::move(callback), config) :
+                                 new GrTextureProxy(std::move(callback), config));
+}
+
 int GrSurfaceProxy::worstCaseWidth() const {
+    SkASSERT(!this->isPendingLazyInstantiation());
     if (fTarget) {
         return fTarget->width();
     }
@@ -389,6 +418,7 @@
 }
 
 int GrSurfaceProxy::worstCaseHeight() const {
+    SkASSERT(!this->isPendingLazyInstantiation());
     if (fTarget) {
         return fTarget->height();
     }
@@ -414,6 +444,7 @@
                                            GrMipMapped mipMapped,
                                            SkIRect srcRect,
                                            SkBudgeted budgeted) {
+    SkASSERT(!src->isPendingLazyInstantiation());
     if (!srcRect.intersect(SkIRect::MakeWH(src->width(), src->height()))) {
         return nullptr;
     }
@@ -442,12 +473,13 @@
 
 sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrContext* context, GrSurfaceProxy* src,
                                            GrMipMapped mipMapped, SkBudgeted budgeted) {
+    SkASSERT(!src->isPendingLazyInstantiation());
     return Copy(context, src, mipMapped, SkIRect::MakeWH(src->width(), src->height()), budgeted);
 }
 
 sk_sp<GrSurfaceContext> GrSurfaceProxy::TestCopy(GrContext* context, const GrSurfaceDesc& dstDesc,
                                                  GrSurfaceProxy* srcProxy) {
-
+    SkASSERT(!srcProxy->isPendingLazyInstantiation());
     sk_sp<GrSurfaceContext> dstContext(context->contextPriv().makeDeferredSurfaceContext(
                                                                             dstDesc,
                                                                             GrMipMapped::kNo,
@@ -465,6 +497,7 @@
 }
 
 void GrSurfaceProxyPriv::exactify() {
+    SkASSERT(!fProxy->isPendingLazyInstantiation());
     if (this->isExact()) {
         return;
     }
@@ -491,3 +524,30 @@
     // exact amount.
 }
 
+void GrSurfaceProxyPriv::doLazyInstantiation(GrResourceProvider* resourceProvider) {
+    SkASSERT(fProxy->fLazyInstantiateCallback);
+    SkASSERT(!fProxy->fTarget);
+
+    sk_sp<GrTexture> texture = fProxy->fLazyInstantiateCallback(resourceProvider, &fProxy->fOrigin);
+
+    // Indicate we are no longer pending lazy instantiation.
+    fProxy->fLazyInstantiateCallback = nullptr;
+
+    if (!texture) {
+        fProxy->fWidth = 0;
+        fProxy->fHeight = 0;
+        fProxy->fOrigin = kTopLeft_GrSurfaceOrigin;
+        return;
+    }
+
+    fProxy->fWidth = texture->width();
+    fProxy->fHeight = texture->height();
+
+    SkASSERT(texture->config() == fProxy->fConfig);
+    SkASSERT(kGrUnknownSurfaceOrigin != fProxy->origin());
+    SkASSERT(kTopLeft_GrSurfaceOrigin == fProxy->fOrigin ||
+             kBottomLeft_GrSurfaceOrigin == fProxy->fOrigin);
+    SkDEBUGCODE(fProxy->validateLazyTexture(texture.get());)
+    this->assign(std::move(texture));
+}
+
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index a93a20c..b4811b6 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -68,6 +68,8 @@
     // Don't. Just don't.
     void exactify();
 
+    void doLazyInstantiation(GrResourceProvider*);
+
     static bool AttachStencilIfNeeded(GrResourceProvider*, GrSurface*, bool needsStencil);
 
 private:
diff --git a/src/gpu/GrTextureProxy.cpp b/src/gpu/GrTextureProxy.cpp
index 9afa63f..9b3dc9c 100644
--- a/src/gpu/GrTextureProxy.cpp
+++ b/src/gpu/GrTextureProxy.cpp
@@ -13,6 +13,7 @@
 #include "GrResourceCache.h"
 #include "GrTexturePriv.h"
 
+// Deferred version
 GrTextureProxy::GrTextureProxy(const GrSurfaceDesc& srcDesc, SkBackingFit fit, SkBudgeted budgeted,
                                const void* srcData, size_t /*rowBytes*/, uint32_t flags)
         : INHERITED(srcDesc, fit, budgeted, flags)
@@ -23,6 +24,16 @@
     SkASSERT(!srcData);  // currently handled in Make()
 }
 
+// Lazy-callback version
+GrTextureProxy::GrTextureProxy(LazyInstantiateCallback&& callback, GrPixelConfig config)
+        : INHERITED(std::move(callback), config)
+        , fMipMapped(GrMipMapped::kNo)
+        , fMipColorMode(SkDestinationSurfaceColorMode::kLegacy)
+        , fCache(nullptr)
+        , fDeferredUploader(nullptr) {
+}
+
+// Wrapped version
 GrTextureProxy::GrTextureProxy(sk_sp<GrSurface> surf, GrSurfaceOrigin origin)
         : INHERITED(std::move(surf), origin, SkBackingFit::kExact)
         , fMipMapped(fTarget->asTexture()->texturePriv().mipMapped())
@@ -109,8 +120,8 @@
 }
 
 size_t GrTextureProxy::onUninstantiatedGpuMemorySize() const {
-    return GrSurface::ComputeSize(fConfig, fWidth, fHeight, 1, this->mipMapped(),
-                                  SkBackingFit::kApprox == fFit);
+    return GrSurface::ComputeSize(this->config(), this->width(), this->height(), 1,
+                                  this->mipMapped(), !this->priv().isExact());
 }
 
 void GrTextureProxy::setUniqueKey(GrResourceCache* cache, const GrUniqueKey& key) {
@@ -131,3 +142,10 @@
     fCache = nullptr;
 }
 
+#ifdef SK_DEBUG
+void GrTextureProxy::validateLazyTexture(const GrTexture* texture) {
+    SkASSERT(!texture->asRenderTarget());
+    SkASSERT(GrMipMapped::kNo == this->mipMapped());
+}
+#endif
+
diff --git a/src/gpu/GrTextureRenderTargetProxy.cpp b/src/gpu/GrTextureRenderTargetProxy.cpp
index dd79bfe..9435231 100644
--- a/src/gpu/GrTextureRenderTargetProxy.cpp
+++ b/src/gpu/GrTextureRenderTargetProxy.cpp
@@ -7,6 +7,10 @@
 
 #include "GrTextureRenderTargetProxy.h"
 
+#include "GrTexture.h"
+#include "GrRenderTarget.h"
+#include "GrSurfaceProxyPriv.h"
+
 // Deferred version
 // This class is virtually derived from GrSurfaceProxy (via both GrTextureProxy and
 // GrRenderTargetProxy) so its constructor must be explicitly called.
@@ -15,10 +19,20 @@
                                                        SkBackingFit fit,
                                                        SkBudgeted budgeted,
                                                        uint32_t flags)
-    : GrSurfaceProxy(desc, fit, budgeted, flags)
-    // for now textures w/ data are always wrapped
-    , GrTextureProxy(desc, fit, budgeted, nullptr, 0, flags)
-    , GrRenderTargetProxy(caps, desc, fit, budgeted, flags) {
+        : GrSurfaceProxy(desc, fit, budgeted, flags)
+        // for now textures w/ data are always wrapped
+        , GrTextureProxy(desc, fit, budgeted, nullptr, 0, flags)
+        , GrRenderTargetProxy(caps, desc, fit, budgeted, flags) {
+}
+
+// Lazy-callback version
+GrTextureRenderTargetProxy::GrTextureRenderTargetProxy(LazyInstantiateCallback&& callback,
+                                                       GrPixelConfig config)
+        : GrSurfaceProxy(std::move(callback), config)
+        // Since we have virtual inheritance, we initialize GrSurfaceProxy directly. Send null
+        // callbacks to the texture and RT proxies simply to route to the appropriate constructors.
+        , GrTextureProxy(LazyInstantiateCallback(), config)
+        , GrRenderTargetProxy(LazyInstantiateCallback(), config) {
 }
 
 // Wrapped version
@@ -26,9 +40,9 @@
 // GrRenderTargetProxy) so its constructor must be explicitly called.
 GrTextureRenderTargetProxy::GrTextureRenderTargetProxy(sk_sp<GrSurface> surf,
                                                        GrSurfaceOrigin origin)
-    : GrSurfaceProxy(surf, origin, SkBackingFit::kExact)
-    , GrTextureProxy(surf, origin)
-    , GrRenderTargetProxy(surf, origin) {
+        : GrSurfaceProxy(surf, origin, SkBackingFit::kExact)
+        , GrTextureProxy(surf, origin)
+        , GrRenderTargetProxy(surf, origin) {
     SkASSERT(surf->asTexture());
     SkASSERT(surf->asRenderTarget());
 }
@@ -37,8 +51,8 @@
     int colorSamplesPerPixel = this->numColorSamples() + 1;
 
     // TODO: do we have enough information to improve this worst case estimate?
-    return GrSurface::ComputeSize(fConfig, fWidth, fHeight, colorSamplesPerPixel, this->mipMapped(),
-                                  SkBackingFit::kApprox == fFit);
+    return GrSurface::ComputeSize(this->config(), this->width(), this->height(),
+                                  colorSamplesPerPixel, this->mipMapped(), !this->priv().isExact());
 }
 
 bool GrTextureRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) {
@@ -77,3 +91,11 @@
     return surface;
 }
 
+#ifdef SK_DEBUG
+void GrTextureRenderTargetProxy::validateLazyTexture(const GrTexture* texture) {
+    SkASSERT(texture->asRenderTarget());
+    SkASSERT(texture->asRenderTarget()->numStencilSamples() == this->numStencilSamples());
+    SkASSERT(GrMipMapped::kNo == this->mipMapped());
+}
+#endif
+
diff --git a/src/gpu/GrTextureRenderTargetProxy.h b/src/gpu/GrTextureRenderTargetProxy.h
index 7005169..5719366 100644
--- a/src/gpu/GrTextureRenderTargetProxy.h
+++ b/src/gpu/GrTextureRenderTargetProxy.h
@@ -29,6 +29,9 @@
     GrTextureRenderTargetProxy(const GrCaps&, const GrSurfaceDesc&,
                                SkBackingFit, SkBudgeted, uint32_t flags);
 
+    // Lazy-callback version
+    GrTextureRenderTargetProxy(LazyInstantiateCallback&&, GrPixelConfig);
+
     // Wrapped version
     GrTextureRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
 
@@ -36,6 +39,8 @@
     sk_sp<GrSurface> createSurface(GrResourceProvider*) const override;
 
     size_t onUninstantiatedGpuMemorySize() const override;
+
+    SkDEBUGCODE(void validateLazyTexture(const GrTexture*) override;)
 };
 
 #ifdef SK_BUILD_FOR_WIN
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
new file mode 100644
index 0000000..8a620b6
--- /dev/null
+++ b/tests/LazyProxyTest.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrClip.h"
+#include "GrContextPriv.h"
+#include "GrOnFlushResourceProvider.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "GrSurfaceProxy.h"
+#include "GrTexture.h"
+#include "GrTextureProxy.h"
+#include "GrTextureProxyPriv.h"
+#include "SkMakeUnique.h"
+#include "mock/GrMockTypes.h"
+
+// This test verifies that lazy proxy callbacks get invoked during flush, after onFlush callbacks,
+// but before Ops are executed. It also ensures that lazy proxy callbacks are invoked both for
+// regular Ops and for clips.
+class LazyProxyTest final : public GrOnFlushCallbackObject {
+public:
+    LazyProxyTest(skiatest::Reporter* reporter)
+            : fReporter(reporter)
+            , fHasOpTexture(false)
+            , fHasClipTexture(false) {
+    }
+
+    ~LazyProxyTest() override {
+        REPORTER_ASSERT(fReporter, fHasOpTexture);
+        REPORTER_ASSERT(fReporter, fHasClipTexture);
+    }
+
+    void preFlush(GrOnFlushResourceProvider*, const uint32_t*, int,
+                  SkTArray<sk_sp<GrRenderTargetContext>>*) override {
+        REPORTER_ASSERT(fReporter, !fHasOpTexture);
+        REPORTER_ASSERT(fReporter, !fHasClipTexture);
+    }
+
+    void postFlush(GrDeferredUploadToken, const uint32_t* opListIDs, int numOpListIDs) override {
+        REPORTER_ASSERT(fReporter, fHasOpTexture);
+        REPORTER_ASSERT(fReporter, fHasClipTexture);
+    }
+
+    class Op final : public GrDrawOp {
+    public:
+        DEFINE_OP_CLASS_ID
+
+        Op(LazyProxyTest* test, bool nullTexture) : GrDrawOp(ClassID()), fTest(test) {
+            fProxy = GrSurfaceProxy::MakeLazy([this, nullTexture](GrResourceProvider* rp,
+                                                                  GrSurfaceOrigin* origin) {
+                REPORTER_ASSERT(fTest->fReporter, !fTest->fHasOpTexture);
+                fTest->fHasOpTexture = true;
+                *origin = kTopLeft_GrSurfaceOrigin;
+                if (nullTexture) {
+                    return sk_sp<GrTexture>();
+                } else {
+                    GrSurfaceDesc desc;
+                    desc.fWidth = 1234;
+                    desc.fHeight = 567;
+                    desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+                    desc.fConfig = kRGB_565_GrPixelConfig;
+                    sk_sp<GrTexture> texture = rp->createTexture(desc, SkBudgeted::kYes);
+                    REPORTER_ASSERT(fTest->fReporter, texture);
+                    return texture;
+                }
+            }, GrSurfaceProxy::Renderable::kNo, kRGB_565_GrPixelConfig);
+            this->setBounds(SkRect::MakeLargest(), GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
+        }
+
+        void visitProxies(const VisitProxyFunc& func) const override {
+            func(fProxy.get());
+        }
+
+        void onExecute(GrOpFlushState*) override {
+            REPORTER_ASSERT(fTest->fReporter, fTest->fHasOpTexture);
+            REPORTER_ASSERT(fTest->fReporter, fTest->fHasClipTexture);
+        }
+
+    private:
+        const char* name() const override { return "LazyProxyTest::Op"; }
+        FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+        RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
+                                    GrPixelConfigIsClamped) override {
+            return RequiresDstTexture::kNo;
+        }
+        void wasRecorded(GrRenderTargetOpList*) override {}
+        bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
+        void onPrepare(GrOpFlushState*) override {}
+
+        LazyProxyTest* const fTest;
+        sk_sp<GrTextureProxy> fProxy;
+    };
+
+    class ClipFP : public GrFragmentProcessor {
+    public:
+        ClipFP(LazyProxyTest* test, GrTextureProxy* atlas)
+                : GrFragmentProcessor(kTestFP_ClassID, kNone_OptimizationFlags)
+                , fTest(test)
+                , fAtlas(atlas) {
+            fLazyProxy = GrSurfaceProxy::MakeLazy([this](GrResourceProvider* rp,
+                                                         GrSurfaceOrigin* origin) {
+                REPORTER_ASSERT(fTest->fReporter, !fTest->fHasClipTexture);
+                fTest->fHasClipTexture = true;
+                *origin = kBottomLeft_GrSurfaceOrigin;
+                fAtlas->instantiate(rp);
+                return sk_ref_sp(fAtlas->priv().peekTexture());
+            }, GrSurfaceProxy::Renderable::kYes, kAlpha_half_GrPixelConfig);
+            fAccess.reset(fLazyProxy, GrSamplerState::Filter::kNearest,
+                          GrSamplerState::WrapMode::kClamp, kFragment_GrShaderFlag);
+            this->addTextureSampler(&fAccess);
+        }
+
+    private:
+        const char* name() const override { return "LazyProxyTest::ClipFP"; }
+        std::unique_ptr<GrFragmentProcessor> clone() const override {
+            return skstd::make_unique<ClipFP>(fTest, fAtlas);
+        }
+        GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return nullptr; }
+        void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
+        bool onIsEqual(const GrFragmentProcessor&) const override { return false; }
+
+        LazyProxyTest* const fTest;
+        GrTextureProxy* const fAtlas;
+        sk_sp<GrTextureProxy> fLazyProxy;
+        TextureSampler fAccess;
+    };
+
+
+    class Clip : public GrClip {
+    public:
+        Clip(LazyProxyTest* test, GrTextureProxy* atlas)
+                : fTest(test)
+                , fAtlas(atlas) {}
+
+    private:
+        bool apply(GrContext*, GrRenderTargetContext*, bool, bool, GrAppliedClip* out,
+                   SkRect* bounds) const override {
+            out->addCoverageFP(skstd::make_unique<ClipFP>(fTest, fAtlas));
+            return true;
+        }
+        bool quickContains(const SkRect&) const final { return false; }
+        bool isRRect(const SkRect& rtBounds, SkRRect* rr, GrAA*) const final { return false; }
+        void getConservativeBounds(int width, int height, SkIRect* rect, bool* iior) const final {
+            rect->set(0, 0, width, height);
+            if (iior) {
+                *iior = false;
+            }
+        }
+
+        LazyProxyTest* const fTest;
+        GrTextureProxy* fAtlas;
+    };
+
+private:
+    skiatest::Reporter* fReporter;
+    bool fHasOpTexture;
+    bool fHasClipTexture;
+};
+
+DEF_GPUTEST(LazyProxyTest, reporter, /* options */) {
+    GrMockOptions mockOptions;
+    mockOptions.fConfigOptions[kAlpha_half_GrPixelConfig].fRenderable[0] = true;
+    mockOptions.fConfigOptions[kAlpha_half_GrPixelConfig].fTexturable = true;
+    sk_sp<GrContext> ctx = GrContext::MakeMock(&mockOptions, GrContextOptions());
+    for (bool nullTexture : {false, true}) {
+        LazyProxyTest test(reporter);
+        ctx->contextPriv().addOnFlushCallbackObject(&test);
+        sk_sp<GrRenderTargetContext> rtc =
+                ctx->makeDeferredRenderTargetContext(SkBackingFit::kExact, 100, 100,
+                                                     kRGBA_8888_GrPixelConfig, nullptr);
+        REPORTER_ASSERT(reporter, rtc);
+        sk_sp<GrRenderTargetContext> mockAtlas =
+                ctx->makeDeferredRenderTargetContext(SkBackingFit::kExact, 10, 10,
+                                                     kAlpha_half_GrPixelConfig, nullptr);
+        REPORTER_ASSERT(reporter, mockAtlas);
+        rtc->priv().testingOnly_addDrawOp(LazyProxyTest::Clip(&test, mockAtlas->asTextureProxy()),
+                                         skstd::make_unique<LazyProxyTest::Op>(&test, nullTexture));
+        ctx->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(&test);
+    }
+}
+
+#endif
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 47f98a6..524cd63 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -274,7 +274,13 @@
 #define ASSERT_SINGLE_OWNER \
     SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fRenderTargetContext->singleOwner());)
 
+
 uint32_t GrRenderTargetContextPriv::testingOnly_addDrawOp(std::unique_ptr<GrDrawOp> op) {
+    return this->testingOnly_addDrawOp(GrNoClip(), std::move(op));
+}
+
+uint32_t GrRenderTargetContextPriv::testingOnly_addDrawOp(const GrClip& clip,
+                                                          std::unique_ptr<GrDrawOp> op) {
     ASSERT_SINGLE_OWNER
     if (fRenderTargetContext->drawingManager()->wasAbandoned()) {
         return SK_InvalidUniqueID;
@@ -282,7 +288,7 @@
     SkDEBUGCODE(fRenderTargetContext->validate());
     GR_AUDIT_TRAIL_AUTO_FRAME(fRenderTargetContext->fAuditTrail,
                               "GrRenderTargetContext::testingOnly_addDrawOp");
-    return fRenderTargetContext->addDrawOp(GrNoClip(), std::move(op));
+    return fRenderTargetContext->addDrawOp(clip, std::move(op));
 }
 
 #undef ASSERT_SINGLE_OWNER