Back SkSpecialImage_Gpu with a GrTextureProxy

This is split out of https://codereview.chromium.org/2215323003/ (Start using RenderTargetProxy (omnibus))

The addition of the gpuMemorySize methods is for the SkSpecialImage cache.

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=4227

Change-Id: Ia9b9d42fb2a0caf61bbfa3ebcc84308c56f541fc
Reviewed-on: https://skia-review.googlesource.com/4227
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/include/private/GrRenderTargetProxy.h b/include/private/GrRenderTargetProxy.h
index 7d36bf6..27e0492 100644
--- a/include/private/GrRenderTargetProxy.h
+++ b/include/private/GrRenderTargetProxy.h
@@ -72,6 +72,8 @@
     // Wrapped version
     GrRenderTargetProxy(const GrCaps&, sk_sp<GrRenderTarget> rt);
 
+    size_t onGpuMemorySize() const override;
+
     // For wrapped render targets the actual GrRenderTarget is stored in the GrIORefProxy class.
     // For deferred proxies that pointer is filled in when we need to instantiate the
     // deferred resource.
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index d57c2f5..37669fe 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -112,6 +112,25 @@
     void setLastOpList(GrOpList* opList);
     GrOpList* getLastOpList() { return fLastOpList; }
 
+    /**
+     * Retrieves the amount of GPU memory that will be or currently is used by this resource 
+     * in bytes. It is approximate since we aren't aware of additional padding or copies made
+     * by the driver.
+     *
+     * @return the amount of GPU memory used in bytes
+     */
+    size_t gpuMemorySize() const {
+        if (fTarget) {
+            return fTarget->gpuMemorySize();
+        }
+
+        if (kInvalidGpuMemorySize == fGpuMemorySize) {
+            fGpuMemorySize = this->onGpuMemorySize();
+            SkASSERT(kInvalidGpuMemorySize != fGpuMemorySize);
+        }
+        return fGpuMemorySize;
+    }
+
 protected:
     // Deferred version
     GrSurfaceProxy(const GrSurfaceDesc& desc, SkBackingFit fit, SkBudgeted budgeted)
@@ -119,6 +138,7 @@
         , fFit(fit)
         , fBudgeted(budgeted)
         , fUniqueID(GrGpuResource::CreateUniqueID())
+        , fGpuMemorySize(kInvalidGpuMemorySize)
         , fLastOpList(nullptr) {
     }
 
@@ -133,7 +153,16 @@
     const SkBudgeted    fBudgeted; // set from the backing resource for wrapped resources
     const uint32_t      fUniqueID; // set from the backing resource for wrapped resources
 
+    static const size_t kInvalidGpuMemorySize = ~static_cast<size_t>(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
+    // the instantiation method.
+    mutable size_t      fGpuMemorySize;
+
 private:
+    virtual size_t onGpuMemorySize() const = 0;
+
     // The last opList that wrote to or is currently going to write to this surface
     // The opList can be closed (e.g., no render target context is currently bound
     // to this renderTarget).
diff --git a/include/private/GrTextureProxy.h b/include/private/GrTextureProxy.h
index 5b4eeef..b85302f 100644
--- a/include/private/GrTextureProxy.h
+++ b/include/private/GrTextureProxy.h
@@ -18,7 +18,8 @@
 public:
     // TODO: need to refine ownership semantics of 'srcData' if we're in completely
     // deferred mode
-    static sk_sp<GrTextureProxy> Make(const GrSurfaceDesc&, SkBackingFit, SkBudgeted,
+    static sk_sp<GrTextureProxy> Make(GrTextureProvider*, const GrSurfaceDesc&,
+                                      SkBackingFit, SkBudgeted,
                                       const void* srcData = nullptr, size_t rowBytes = 0);
     static sk_sp<GrTextureProxy> Make(sk_sp<GrTexture>);
 
@@ -27,7 +28,7 @@
     const GrTextureProxy* asTextureProxy() const override { return this; }
 
     // Actually instantiate the backing texture, if necessary
-    GrTexture* instantiate(GrTextureProvider* texProvider);
+    GrTexture* instantiate(GrTextureProvider*);
 
 private:
     // Deferred version
@@ -36,6 +37,8 @@
     // Wrapped version
     GrTextureProxy(sk_sp<GrTexture> tex);
 
+    size_t onGpuMemorySize() const override;
+
     // For wrapped proxies the GrTexture pointer is stored in GrIORefProxy.
     // For deferred proxies that pointer will be filled n when we need to instantiate
     // the deferred resource
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
index eeb00c9..e750571 100644
--- a/src/core/SkSpecialImage.cpp
+++ b/src/core/SkSpecialImage.cpp
@@ -19,6 +19,7 @@
 #include "GrContext.h"
 #include "GrTexture.h"
 #include "GrTextureParams.h"
+#include "GrTextureProxy.h"
 #include "SkGr.h"
 #include "SkGrPriv.h"
 #endif
@@ -42,12 +43,13 @@
 
     virtual bool onGetROPixels(SkBitmap*) const = 0;
 
-    virtual GrTexture* onPeekTexture() const { return nullptr; }
+    virtual GrContext* onGetContext() const { return nullptr; }
 
     virtual SkColorSpace* onGetColorSpace() const = 0;
 
 #if SK_SUPPORT_GPU
     virtual sk_sp<GrTexture> onAsTextureRef(GrContext* context) const = 0;
+    virtual sk_sp<GrTextureProxy> onAsTextureProxy(GrContext* context) const = 0;
 #endif
 
     virtual sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const = 0;
@@ -82,8 +84,8 @@
     if (!context) {
         return nullptr;
     }
-    if (GrTexture* peek = as_SIB(this)->onPeekTexture()) {
-        return peek->getContext() == context ? sk_sp<SkSpecialImage>(SkRef(this)) : nullptr;
+    if (GrContext* curContext = as_SIB(this)->onGetContext()) {
+        return curContext == context ? sk_sp<SkSpecialImage>(SkRef(this)) : nullptr;
     }
 
     SkBitmap bmp;
@@ -125,22 +127,11 @@
 }
 
 bool SkSpecialImage::isTextureBacked() const {
-#if SK_SUPPORT_GPU
-    return as_SIB(this)->onPeekTexture() && as_SIB(this)->onPeekTexture()->getContext();
-#else
-    return false;
-#endif
+    return SkToBool(as_SIB(this)->onGetContext());
 }
 
 GrContext* SkSpecialImage::getContext() const {
-#if SK_SUPPORT_GPU
-    GrTexture* texture = as_SIB(this)->onPeekTexture();
-    
-    if (texture) {
-        return texture->getContext();
-    }
-#endif
-    return nullptr;
+    return as_SIB(this)->onGetContext();
 }
 
 SkColorSpace* SkSpecialImage::getColorSpace() const {
@@ -151,6 +142,10 @@
 sk_sp<GrTexture> SkSpecialImage::asTextureRef(GrContext* context) const {
     return as_SIB(this)->onAsTextureRef(context);
 }
+
+sk_sp<GrTextureProxy> SkSpecialImage::asTextureProxy(GrContext* context) const {
+    return as_SIB(this)->onAsTextureProxy(context);
+}
 #endif
 
 sk_sp<SkSpecialSurface> SkSpecialImage::makeSurface(const SkImageFilter::OutputProperties& outProps,
@@ -254,6 +249,19 @@
 
         return nullptr;
     }
+
+    sk_sp<GrTextureProxy> onAsTextureProxy(GrContext* context) const override {
+        if (context) {
+            sk_sp<GrTexture> tex(sk_ref_sp(GrRefCachedBitmapTexture(
+                                                              context,
+                                                              fBitmap,
+                                                              GrTextureParams::ClampNoFilter(),
+                                                              SkSourceGammaTreatment::kRespect)));
+            return GrTextureProxy::Make(tex);
+        }
+
+        return nullptr;
+    }
 #endif
 
 // TODO: The raster implementations of image filters all currently assume that the pixels are
@@ -349,7 +357,19 @@
                        uint32_t uniqueID, sk_sp<GrTexture> tex, SkAlphaType at,
                        sk_sp<SkColorSpace> colorSpace, const SkSurfaceProps* props)
         : INHERITED(subset, uniqueID, props)
-        , fTexture(std::move(tex))
+        , fContext(tex->getContext())
+        , fAlphaType(at)
+        , fColorSpace(std::move(colorSpace))
+        , fAddedRasterVersionToCache(false) {
+        fTextureProxy = GrTextureProxy::Make(std::move(tex));
+    }
+
+    SkSpecialImage_Gpu(GrContext* context, const SkIRect& subset,
+                       uint32_t uniqueID, sk_sp<GrTextureProxy> proxy, SkAlphaType at,
+                       sk_sp<SkColorSpace> colorSpace, const SkSurfaceProps* props)
+        : INHERITED(subset, uniqueID, props)
+        , fContext(context)
+        , fTextureProxy(std::move(proxy))
         , fAlphaType(at)
         , fColorSpace(std::move(colorSpace))
         , fAddedRasterVersionToCache(false) {
@@ -363,23 +383,33 @@
 
     SkAlphaType alphaType() const override { return fAlphaType; }
 
-    size_t getSize() const override { return fTexture->gpuMemorySize(); }
+    size_t getSize() const override { return fTextureProxy->gpuMemorySize(); }
 
     void onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPaint* paint) const override {
         SkRect dst = SkRect::MakeXYWH(x, y,
                                       this->subset().width(), this->subset().height());
 
-        auto img = sk_sp<SkImage>(new SkImage_Gpu(fTexture->width(), fTexture->height(),
-                                                  this->uniqueID(), fAlphaType, fTexture,
+        // TODO: add GrTextureProxy-backed SkImage_Gpus
+        sk_sp<GrTexture> tex = sk_ref_sp(fTextureProxy->instantiate(fContext->textureProvider()));
+
+        auto img = sk_sp<SkImage>(new SkImage_Gpu(fTextureProxy->width(), fTextureProxy->height(),
+                                                  this->uniqueID(), fAlphaType, std::move(tex),
                                                   fColorSpace, SkBudgeted::kNo));
 
         canvas->drawImageRect(img, this->subset(),
                                dst, paint, SkCanvas::kStrict_SrcRectConstraint);
     }
 
-    GrTexture* onPeekTexture() const override { return fTexture.get(); }
+    GrContext* onGetContext() const override { return fContext; }
 
-    sk_sp<GrTexture> onAsTextureRef(GrContext*) const override { return fTexture; }
+    // This entry point should go away in favor of asTextureProxy
+    sk_sp<GrTexture> onAsTextureRef(GrContext* context) const override {
+        return sk_ref_sp(fTextureProxy->instantiate(context->textureProvider()));
+    }
+
+    sk_sp<GrTextureProxy> onAsTextureProxy(GrContext*) const override {
+        return fTextureProxy;
+    }
 
     bool onGetROPixels(SkBitmap* dst) const override {
         if (SkBitmapCache::Find(this->uniqueID(), dst)) {
@@ -396,8 +426,11 @@
             return false;
         }
 
-        if (!fTexture->readPixels(0, 0, dst->width(), dst->height(), kSkia8888_GrPixelConfig,
-                                  dst->getPixels(), dst->rowBytes())) {
+        // Reading back to an SkBitmap ends deferral
+        GrTexture* texture = fTextureProxy->instantiate(fContext->textureProvider());
+
+        if (!texture->readPixels(0, 0, dst->width(), dst->height(), kSkia8888_GrPixelConfig,
+                                 dst->getPixels(), dst->rowBytes())) {
             return false;
         }
 
@@ -413,46 +446,49 @@
 
     sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
                                           const SkISize& size, SkAlphaType at) const override {
-        if (!fTexture->getContext()) {
+        if (!fContext) {
             return nullptr;
         }
 
         SkColorSpace* colorSpace = outProps.colorSpace();
         return SkSpecialSurface::MakeRenderTarget(
-            fTexture->getContext(), size.width(), size.height(),
+            fContext, size.width(), size.height(),
             GrRenderableConfigForColorSpace(colorSpace), sk_ref_sp(colorSpace));
     }
 
     sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const override {
-        return SkSpecialImage::MakeFromGpu(subset,
-                                           this->uniqueID(),
-                                           fTexture,
-                                           fColorSpace,
-                                           &this->props(),
-                                           fAlphaType);
+        return SkSpecialImage::MakeDeferredFromGpu(fContext,
+                                                   subset,
+                                                   this->uniqueID(),
+                                                   fTextureProxy,
+                                                   fColorSpace,
+                                                   &this->props(),
+                                                   fAlphaType);
     }
 
     sk_sp<SkImage> onMakeTightSubset(const SkIRect& subset) const override {
+        // TODO: add GrTextureProxy-backed SkImage_Gpus
+        sk_sp<GrTexture> tex = sk_ref_sp(fTextureProxy->instantiate(fContext->textureProvider()));
+
         if (0 == subset.fLeft && 0 == subset.fTop &&
-            fTexture->width() == subset.width() &&
-            fTexture->height() == subset.height()) {
+            fTextureProxy->width() == subset.width() &&
+            fTextureProxy->height() == subset.height()) {
             // The existing GrTexture is already tight so reuse it in the SkImage
-            return sk_make_sp<SkImage_Gpu>(fTexture->width(), fTexture->height(),
+            return sk_make_sp<SkImage_Gpu>(tex->width(), tex->height(),
                                            kNeedNewImageUniqueID,
-                                           fAlphaType, fTexture, fColorSpace,
+                                           fAlphaType, std::move(tex), fColorSpace,
                                            SkBudgeted::kYes);
         }
 
-        GrContext* ctx = fTexture->getContext();
-        GrSurfaceDesc desc = fTexture->desc();
+        GrSurfaceDesc desc = fTextureProxy->desc();
         desc.fWidth = subset.width();
         desc.fHeight = subset.height();
 
-        sk_sp<GrTexture> subTx(ctx->textureProvider()->createTexture(desc, SkBudgeted::kYes));
+        sk_sp<GrTexture> subTx(fContext->textureProvider()->createTexture(desc, SkBudgeted::kYes));
         if (!subTx) {
             return nullptr;
         }
-        ctx->copySurface(subTx.get(), fTexture.get(), subset, SkIPoint::Make(0, 0));
+        fContext->copySurface(subTx.get(), tex.get(), subset, SkIPoint::Make(0, 0));
         return sk_make_sp<SkImage_Gpu>(desc.fWidth, desc.fHeight, kNeedNewImageUniqueID,
                                        fAlphaType, std::move(subTx), fColorSpace, SkBudgeted::kYes);
     }
@@ -464,11 +500,12 @@
             ? kRGBA_F16_SkColorType : kRGBA_8888_SkColorType;
         SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), colorType, at,
                                              sk_ref_sp(colorSpace));
-        return SkSurface::MakeRenderTarget(fTexture->getContext(), SkBudgeted::kYes, info);
+        return SkSurface::MakeRenderTarget(fContext, SkBudgeted::kYes, info);
     }
 
 private:
-    sk_sp<GrTexture>        fTexture;
+    GrContext*              fContext;
+    sk_sp<GrTextureProxy>   fTextureProxy;
     const SkAlphaType       fAlphaType;
     sk_sp<SkColorSpace>     fColorSpace;
     mutable SkAtomic<bool>  fAddedRasterVersionToCache;
@@ -487,4 +524,15 @@
                                           std::move(colorSpace), props);
 }
 
+sk_sp<SkSpecialImage> SkSpecialImage::MakeDeferredFromGpu(GrContext* context,
+                                                          const SkIRect& subset,
+                                                          uint32_t uniqueID,
+                                                          sk_sp<GrTextureProxy> proxy,
+                                                          sk_sp<SkColorSpace> colorSpace,
+                                                          const SkSurfaceProps* props,
+                                                          SkAlphaType at) {
+    SkASSERT(rect_fits(subset, proxy->width(), proxy->height()));
+    return sk_make_sp<SkSpecialImage_Gpu>(context, subset, uniqueID, std::move(proxy), at,
+                                          std::move(colorSpace), props);
+}
 #endif
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
index c1f3791..99e3489 100644
--- a/src/core/SkSpecialImage.h
+++ b/src/core/SkSpecialImage.h
@@ -17,6 +17,7 @@
 
 class GrContext;
 class GrTexture;
+class GrTextureProxy;
 class SkBitmap;
 class SkCanvas;
 class SkImage;
@@ -82,6 +83,14 @@
                                              sk_sp<SkColorSpace>,
                                              const SkSurfaceProps* = nullptr,
                                              SkAlphaType at = kPremul_SkAlphaType);
+
+    static sk_sp<SkSpecialImage> MakeDeferredFromGpu(GrContext*,
+                                                     const SkIRect& subset,
+                                                     uint32_t uniqueID,
+                                                     sk_sp<GrTextureProxy>,
+                                                     sk_sp<SkColorSpace>,
+                                                     const SkSurfaceProps* = nullptr,
+                                                     SkAlphaType at = kPremul_SkAlphaType);
 #endif
 
     /**
@@ -129,6 +138,11 @@
      *  The active portion of the texture can be retrieved via 'subset'.
      */
     sk_sp<GrTexture> asTextureRef(GrContext*) const;
+
+    /**
+     *  The same as above but return the contents as a GrTextureProxy.
+     */
+    sk_sp<GrTextureProxy> asTextureProxy(GrContext*) const;
 #endif
 
     // TODO: hide this whe the imagefilter all have a consistent draw path (see skbug.com/5063)
diff --git a/src/gpu/GrRenderTargetProxy.cpp b/src/gpu/GrRenderTargetProxy.cpp
index 50d79f4..3809848 100644
--- a/src/gpu/GrRenderTargetProxy.cpp
+++ b/src/gpu/GrRenderTargetProxy.cpp
@@ -53,6 +53,12 @@
         return nullptr;
     }
 
+#ifdef SK_DEBUG
+    if (kInvalidGpuMemorySize != fGpuMemorySize) {
+        SkASSERT(fTarget->gpuMemorySize() <= fGpuMemorySize);    
+    }
+#endif
+
     // Check that our a priori computation matched the ultimate reality
     SkASSERT(fFlags == fTarget->asRenderTarget()->renderTargetPriv().flags());
 
@@ -70,6 +76,20 @@
 }
 #endif
 
+size_t GrRenderTargetProxy::onGpuMemorySize() const {
+    if (fTarget) {
+        return fTarget->gpuMemorySize();
+    }
+
+    SkASSERT(kUnknown_GrPixelConfig != fDesc.fConfig);
+    SkASSERT(!GrPixelConfigIsCompressed(fDesc.fConfig));
+    size_t colorBytes = GrBytesPerPixel(fDesc.fConfig);
+    SkASSERT(colorBytes > 0);
+
+    // TODO: do we have enough information to improve this worst case estimate?
+    return (fDesc.fSampleCnt + 1) * fDesc.fWidth * fDesc.fHeight * colorBytes;
+}
+
 sk_sp<GrRenderTargetProxy> GrRenderTargetProxy::Make(const GrCaps& caps,
                                                      const GrSurfaceDesc& desc,
                                                      SkBackingFit fit,
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index 094eaa3..c5afd0f 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -16,6 +16,7 @@
     , fFit(fit)
     , fBudgeted(fTarget->resourcePriv().isBudgeted())
     , fUniqueID(fTarget->uniqueID())
+    , fGpuMemorySize(kInvalidGpuMemorySize)
     , fLastOpList(nullptr) {
 }
 
diff --git a/src/gpu/GrTextureProxy.cpp b/src/gpu/GrTextureProxy.cpp
index 41775b1..be09bd4 100644
--- a/src/gpu/GrTextureProxy.cpp
+++ b/src/gpu/GrTextureProxy.cpp
@@ -10,9 +10,9 @@
 #include "GrTextureProvider.h"
 
 GrTextureProxy::GrTextureProxy(const GrSurfaceDesc& srcDesc, SkBackingFit fit, SkBudgeted budgeted,
-                               const void* /*srcData*/, size_t /*rowBytes*/)
+                               const void* srcData, size_t /*rowBytes*/)
     : INHERITED(srcDesc, fit, budgeted) {
-    // TODO: Handle 'srcData' here
+    SkASSERT(!srcData);   // currently handled in Make()
 }
 
 GrTextureProxy::GrTextureProxy(sk_sp<GrTexture> tex)
@@ -30,17 +30,47 @@
         fTarget = texProvider->createTexture(fDesc, fBudgeted);
     }
 
+#ifdef SK_DEBUG
+    if (kInvalidGpuMemorySize != fGpuMemorySize) {
+        SkASSERT(fTarget->gpuMemorySize() <= fGpuMemorySize);
+    }
+#endif
+
     return fTarget->asTexture();
 }
 
-sk_sp<GrTextureProxy> GrTextureProxy::Make(const GrSurfaceDesc& desc,
+size_t GrTextureProxy::onGpuMemorySize() const {
+    size_t textureSize;
+
+    if (GrPixelConfigIsCompressed(fDesc.fConfig)) {
+        textureSize = GrCompressedFormatDataSize(fDesc.fConfig, fDesc.fWidth, fDesc.fHeight);
+    } else {
+        textureSize = (size_t) fDesc.fWidth * fDesc.fHeight * GrBytesPerPixel(fDesc.fConfig);
+    }
+
+    // TODO: add tracking of mipmap state to improve the estimate
+    textureSize += textureSize/3;
+
+    SkASSERT(!SkToBool(fDesc.fFlags & kRenderTarget_GrSurfaceFlag));
+    SkASSERT(textureSize <= GrSurface::WorstCaseSize(fDesc));
+
+    return textureSize;
+}
+
+sk_sp<GrTextureProxy> GrTextureProxy::Make(GrTextureProvider* texProvider,
+                                           const GrSurfaceDesc& desc,
                                            SkBackingFit fit,
                                            SkBudgeted budgeted,
                                            const void* srcData,
                                            size_t rowBytes) {
-    // TODO: handle 'srcData' (we could use the wrapped version if there is data)
-    SkASSERT(!srcData && !rowBytes);
-    return sk_sp<GrTextureProxy>(new GrTextureProxy(desc, fit, budgeted, srcData, rowBytes));
+    if (srcData) {
+        // If we have srcData, for now, we create a wrapped GrTextureProxy
+        sk_sp<GrTexture> tex = sk_ref_sp(texProvider->createTexture(desc, budgeted,
+                                                                    srcData, rowBytes));
+        return GrTextureProxy::Make(std::move(tex));
+    }
+
+    return sk_sp<GrTextureProxy>(new GrTextureProxy(desc, fit, budgeted, nullptr, 0));
 }
 
 sk_sp<GrTextureProxy> GrTextureProxy::Make(sk_sp<GrTexture> tex) {
diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp
index 9a7697c..b69e410 100644
--- a/tests/ProxyTest.cpp
+++ b/tests/ProxyTest.cpp
@@ -116,7 +116,8 @@
 
                             desc.fSampleCnt = 0;
 
-                            sk_sp<GrTextureProxy> texProxy(GrTextureProxy::Make(desc,
+                            sk_sp<GrTextureProxy> texProxy(GrTextureProxy::Make(provider,
+                                                                                desc,
                                                                                 fit,
                                                                                 budgeted));
                             check_surface(reporter, texProxy.get(), origin,
diff --git a/tests/SpecialImageTest.cpp b/tests/SpecialImageTest.cpp
index e992177..23e8d5b 100644
--- a/tests/SpecialImageTest.cpp
+++ b/tests/SpecialImageTest.cpp
@@ -17,6 +17,7 @@
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
+#include "GrTextureProxy.h"
 #endif
 
 
@@ -290,4 +291,44 @@
     }
 }
 
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SpecialImage_DeferredGpu, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    SkBitmap bm = create_bm();
+
+    GrSurfaceDesc desc;
+    desc.fConfig = kSkia8888_GrPixelConfig;
+    desc.fFlags  = kNone_GrSurfaceFlags;
+    desc.fWidth  = kFullSize;
+    desc.fHeight = kFullSize;
+
+    sk_sp<GrTextureProxy> proxy(GrTextureProxy::Make(context->textureProvider(), desc,
+                                                     SkBackingFit::kExact, SkBudgeted::kNo,
+                                                     bm.getPixels(), 0));
+    if (!proxy) {
+        return;
+    }
+
+    sk_sp<SkSpecialImage> fullSImg(SkSpecialImage::MakeDeferredFromGpu(
+                                                            context,
+                                                            SkIRect::MakeWH(kFullSize, kFullSize),
+                                                            kNeedNewImageUniqueID_SpecialImage,
+                                                            proxy, nullptr));
+
+    const SkIRect& subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    {
+        sk_sp<SkSpecialImage> subSImg1(SkSpecialImage::MakeDeferredFromGpu(
+                                                               context,
+                                                               subset,
+                                                               kNeedNewImageUniqueID_SpecialImage,
+                                                               proxy, nullptr));
+        test_image(subSImg1, reporter, context, true, kPad, kFullSize);
+    }
+
+    {
+        sk_sp<SkSpecialImage> subSImg2(fullSImg->makeSubset(subset));
+        test_image(subSImg2, reporter, context, true, kPad, kFullSize);
+    }
+}
+
 #endif