Add GrCap check for whether we can do a copy or not, and implement in Vk backend

Today, we only know if we fail a copy during the flush so we have no
way to cleanly handle a failed copy. This will allow us to know if we'll
fail a copy during recording and allow us to do some appropriate fallback
and/or dropping of the draw.

Bug: skia:
Change-Id: I38f209dbd4ebb4e762210b4609147d4b0a1b01b1
Reviewed-on: https://skia-review.googlesource.com/123560
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Greg Daniel <egdaniel@google.com>
diff --git a/include/gpu/GrCaps.h b/include/gpu/GrCaps.h
index 02a51a0..b88146f 100644
--- a/include/gpu/GrCaps.h
+++ b/include/gpu/GrCaps.h
@@ -21,6 +21,7 @@
 struct GrContextOptions;
 class GrRenderTargetProxy;
 class GrSurface;
+class GrSurfaceProxy;
 class SkJSONWriter;
 
 /**
@@ -233,6 +234,14 @@
     bool crossContextTextureSupport() const { return fCrossContextTextureSupport; }
 
     /**
+     * Returns whether or not we will be able to do a copy given the passed in params
+     */
+    virtual bool canCopySurface(const GrSurfaceProxy* /*dst*/, const GrSurfaceProxy* /*src*/,
+                                const SkIRect& /*srcRect*/, const SkIPoint& /*dstPoint*/) const {
+        return true;
+    }
+
+    /**
      * This is can be called before allocating a texture to be a dst for copySurface. This is only
      * used for doing dst copies needed in blends, thus the src is always a GrRenderTargetProxy. It
      * will populate config and flags fields of the desc such that copySurface can efficiently
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 37d0b71..56d6d7d 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -85,7 +85,11 @@
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
-    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrSurfaceContext::onCopy");
+    GR_AUDIT_TRAIL_AUTO_FRAME(fAuditTrail, "GrSurfaceContext::copy");
+
+    if (!fContext->caps()->canCopySurface(this->asSurfaceProxy(), src, srcRect, dstPoint)) {
+        return false;
+    }
 
     return this->getOpList()->copySurface(*fContext->caps(),
                                           this->asSurfaceProxy(), src, srcRect, dstPoint);
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index ae1570c..1c36a14 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -74,6 +74,128 @@
     return true;
 }
 
+bool GrVkCaps::canCopyImage(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
+                            GrPixelConfig srcConfig, int srcSampleCnt,
+                            GrSurfaceOrigin srcOrigin) const {
+    if ((dstSampleCnt > 1 || srcSampleCnt > 1) && dstSampleCnt != srcSampleCnt) {
+        return false;
+    }
+
+    // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
+    // as image usage flags.
+    if (srcOrigin != dstOrigin || GrBytesPerPixel(srcConfig) != GrBytesPerPixel(dstConfig)) {
+        return false;
+    }
+
+    if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
+        this->shaderCaps()->configOutputSwizzle(dstConfig)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool GrVkCaps::canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCnt, bool dstIsLinear,
+                             GrPixelConfig srcConfig, int srcSampleCnt, bool srcIsLinear) const {
+    // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
+    // as image usage flags.
+    if (!this->configCanBeDstofBlit(dstConfig, dstIsLinear) ||
+        !this->configCanBeSrcofBlit(srcConfig, srcIsLinear)) {
+        return false;
+    }
+
+    if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
+        this->shaderCaps()->configOutputSwizzle(dstConfig)) {
+        return false;
+    }
+
+    // We cannot blit images that are multisampled. Will need to figure out if we can blit the
+    // resolved msaa though.
+    if (dstSampleCnt > 1 || srcSampleCnt > 1) {
+        return false;
+    }
+
+    return true;
+}
+
+bool GrVkCaps::canCopyAsResolve(GrPixelConfig dstConfig, int dstSampleCnt,
+                                GrSurfaceOrigin dstOrigin, GrPixelConfig srcConfig,
+                                int srcSampleCnt, GrSurfaceOrigin srcOrigin) const {
+    // The src surface must be multisampled.
+    if (srcSampleCnt <= 1) {
+        return false;
+    }
+
+    // The dst must not be multisampled.
+    if (dstSampleCnt > 1) {
+        return false;
+    }
+
+    // Surfaces must have the same format.
+    if (dstConfig != srcConfig) {
+        return false;
+    }
+
+    // Surfaces must have the same origin.
+    if (srcOrigin != dstOrigin) {
+        return false;
+    }
+
+    return true;
+}
+
+bool GrVkCaps::canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
+                             GrPixelConfig srcConfig, bool srcIsTextureable) const {
+    // TODO: Make copySurfaceAsDraw handle the swizzle
+    if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
+        this->shaderCaps()->configOutputSwizzle(dstConfig)) {
+        return false;
+    }
+
+    // Make sure the dst is a render target and the src is a texture.
+    if (!dstIsRenderable || !srcIsTextureable) {
+        return false;
+    }
+
+    return true;
+}
+
+bool GrVkCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                              const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+    GrSurfaceOrigin dstOrigin = dst->origin();
+    GrSurfaceOrigin srcOrigin = src->origin();
+
+    GrPixelConfig dstConfig = dst->config();
+    GrPixelConfig srcConfig = src->config();
+
+    // TODO: Figure out a way to track if we've wrapped a linear texture in a proxy (e.g.
+    // PromiseImage which won't get instantiated right away. Does this need a similar thing like the
+    // tracking of external or rectangle textures in GL? For now we don't create linear textures
+    // internally, and I don't believe anyone is wrapping them.
+    bool srcIsLinear = false;
+    bool dstIsLinear = false;
+
+    int dstSampleCnt = 0;
+    int srcSampleCnt = 0;
+    if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) {
+        dstSampleCnt = rtProxy->numColorSamples();
+    }
+    if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) {
+        srcSampleCnt = rtProxy->numColorSamples();
+    }
+    SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy()));
+    SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy()));
+
+    return this->canCopyImage(dstConfig, dstSampleCnt, dstOrigin,
+                              srcConfig, srcSampleCnt, srcOrigin) ||
+           this->canCopyAsBlit(dstConfig, dstSampleCnt, dstIsLinear,
+                               srcConfig, srcSampleCnt, srcIsLinear) ||
+           this->canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin,
+                                  srcConfig, srcSampleCnt, srcOrigin) ||
+           this->canCopyAsDraw(dstConfig, dstSampleCnt > 0,
+                               srcConfig, SkToBool(src->asTextureProxy()));
+}
+
 void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface* vkInterface,
                     VkPhysicalDevice physDev, uint32_t featureFlags, uint32_t extensionFlags) {
 
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index 881aacc..69298d7 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -110,6 +110,28 @@
         return fPreferedStencilFormat;
     }
 
+    /**
+     * Helpers used by canCopySurface. In all cases if the SampleCnt parameter is zero that means
+     * the surface is not a render target, otherwise it is the number of samples in the render
+     * target.
+     */
+    bool canCopyImage(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
+                      GrPixelConfig srcConfig, int srcSamplecnt, GrSurfaceOrigin srcOrigin) const;
+
+    bool canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCnt, bool dstIsLinear,
+                       GrPixelConfig srcConfig, int srcSampleCnt, bool srcIsLinear) const;
+
+    bool canCopyAsResolve(GrPixelConfig dstConfig, int dstSampleCnt, GrSurfaceOrigin dstOrigin,
+                          GrPixelConfig srcConfig, int srcSamplecnt,
+                          GrSurfaceOrigin srcOrigin) const;
+
+    bool canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
+                       GrPixelConfig srcConfig, bool srcIsTextureable) const;
+
+    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                        const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
+
+
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override;
 
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index bc47ae6..428a74b 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -503,6 +503,7 @@
         SkASSERT(dst->asTexture());
         dstImage = static_cast<GrVkTexture*>(dst->asTexture());
     }
+    SkASSERT(1 == dstImage->mipLevels());
     dstImage->setImageLayout(this,
                              VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                              VK_ACCESS_TRANSFER_WRITE_BIT,
@@ -1669,33 +1670,11 @@
     fCurrentCmdBuffer->clearDepthStencilImage(this, vkStencil, &vkStencilColor, 1, &subRange);
 }
 
-inline bool can_copy_image(const GrSurface* dst, GrSurfaceOrigin dstOrigin,
-                           const GrSurface* src, GrSurfaceOrigin srcOrigin,
-                           const GrVkGpu* gpu) {
-    const GrRenderTarget* dstRT = dst->asRenderTarget();
-    const GrRenderTarget* srcRT = src->asRenderTarget();
-    if (dstRT && srcRT) {
-        if (srcRT->numColorSamples() != dstRT->numColorSamples()) {
-            return false;
-        }
-    } else if (dstRT) {
-        if (dstRT->numColorSamples() > 1) {
-            return false;
-        }
-    } else if (srcRT) {
-        if (srcRT->numColorSamples() > 1) {
-            return false;
-        }
+static int get_surface_sample_cnt(GrSurface* surf) {
+    if (const GrRenderTarget* rt = surf->asRenderTarget()) {
+        return rt->numColorSamples();
     }
-
-    // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
-    // as image usage flags.
-    if (srcOrigin == dstOrigin &&
-        GrBytesPerPixel(src->config()) == GrBytesPerPixel(dst->config())) {
-        return true;
-    }
-
-    return false;
+    return 0;
 }
 
 void GrVkGpu::copySurfaceAsCopyImage(GrSurface* dst, GrSurfaceOrigin dstOrigin,
@@ -1704,7 +1683,13 @@
                                      GrVkImage* srcImage,
                                      const SkIRect& srcRect,
                                      const SkIPoint& dstPoint) {
-    SkASSERT(can_copy_image(dst, dstOrigin, src, srcOrigin, this));
+#ifdef SK_DEBUG
+    int dstSampleCnt = get_surface_sample_cnt(dst);
+    int srcSampleCnt = get_surface_sample_cnt(src);
+    SkASSERT(this->vkCaps().canCopyImage(dst->config(), dstSampleCnt, dstOrigin,
+                                         src->config(), srcSampleCnt, srcOrigin));
+
+#endif
 
     // These flags are for flushing/invalidating caches and for the dst image it doesn't matter if
     // the cache is flushed since it is only being written to.
@@ -1752,37 +1737,19 @@
     this->didWriteToSurface(dst, dstOrigin, &dstRect);
 }
 
-inline bool can_copy_as_blit(const GrSurface* dst,
-                             const GrSurface* src,
-                             const GrVkImage* dstImage,
-                             const GrVkImage* srcImage,
-                             const GrVkGpu* gpu) {
-    // We require that all vulkan GrSurfaces have been created with transfer_dst and transfer_src
-    // as image usage flags.
-    const GrVkCaps& caps = gpu->vkCaps();
-    if (!caps.configCanBeDstofBlit(dst->config(), dstImage->isLinearTiled()) ||
-        !caps.configCanBeSrcofBlit(src->config(), srcImage->isLinearTiled())) {
-        return false;
-    }
-
-    // We cannot blit images that are multisampled. Will need to figure out if we can blit the
-    // resolved msaa though.
-    if ((dst->asRenderTarget() && dst->asRenderTarget()->numColorSamples() > 1) ||
-        (src->asRenderTarget() && src->asRenderTarget()->numColorSamples() > 1)) {
-        return false;
-    }
-
-    return true;
-}
-
 void GrVkGpu::copySurfaceAsBlit(GrSurface* dst, GrSurfaceOrigin dstOrigin,
                                 GrSurface* src, GrSurfaceOrigin srcOrigin,
                                 GrVkImage* dstImage,
                                 GrVkImage* srcImage,
                                 const SkIRect& srcRect,
                                 const SkIPoint& dstPoint) {
-    SkASSERT(can_copy_as_blit(dst, src, dstImage, srcImage, this));
+#ifdef SK_DEBUG
+    int dstSampleCnt = get_surface_sample_cnt(dst);
+    int srcSampleCnt = get_surface_sample_cnt(src);
+    SkASSERT(this->vkCaps().canCopyAsBlit(dst->config(), dstSampleCnt, dstImage->isLinearTiled(),
+                                          src->config(), srcSampleCnt, srcImage->isLinearTiled()));
 
+#endif
     dstImage->setImageLayout(this,
                              VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                              VK_ACCESS_TRANSFER_WRITE_BIT,
@@ -1843,29 +1810,6 @@
     this->didWriteToSurface(dst, dstOrigin, &dstRect);
 }
 
-inline bool can_copy_as_resolve(const GrSurface* dst, GrSurfaceOrigin dstOrigin,
-                                const GrSurface* src, GrSurfaceOrigin srcOrigin,
-                                const GrVkGpu* gpu) {
-    // Our src must be a multisampled render target
-    if (!src->asRenderTarget() || 1 == src->asRenderTarget()->numColorSamples()) {
-        return false;
-    }
-
-    // The dst must not be a multisampled render target, expect in the case where the dst is the
-    // resolve texture connected to the msaa src. We check for this in case we are copying a part of
-    // a surface to a different region in the same surface.
-    if (dst->asRenderTarget() && dst->asRenderTarget()->numColorSamples() > 1 && dst != src) {
-        return false;
-    }
-
-    // Surfaces must have the same origin.
-    if (srcOrigin != dstOrigin) {
-        return false;
-    }
-
-    return true;
-}
-
 void GrVkGpu::copySurfaceAsResolve(GrSurface* dst, GrSurfaceOrigin dstOrigin, GrSurface* src,
                                    GrSurfaceOrigin srcOrigin, const SkIRect& origSrcRect,
                                    const SkIPoint& origDstPoint) {
@@ -1885,7 +1829,14 @@
                             GrSurface* src, GrSurfaceOrigin srcOrigin,
                             const SkIRect& srcRect, const SkIPoint& dstPoint,
                             bool canDiscardOutsideDstRect) {
-    if (can_copy_as_resolve(dst, dstOrigin, src, srcOrigin, this)) {
+    GrPixelConfig dstConfig = dst->config();
+    GrPixelConfig srcConfig = src->config();
+
+    int dstSampleCnt = get_surface_sample_cnt(dst);
+    int srcSampleCnt = get_surface_sample_cnt(src);
+
+    if (this->vkCaps().canCopyAsResolve(dstConfig, dstSampleCnt, dstOrigin,
+                                        srcConfig, srcSampleCnt, srcOrigin)) {
         this->copySurfaceAsResolve(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint);
         return true;
     }
@@ -1894,8 +1845,10 @@
         this->submitCommandBuffer(GrVkGpu::kSkip_SyncQueue);
     }
 
-    if (fCopyManager.copySurfaceAsDraw(this, dst, dstOrigin, src, srcOrigin, srcRect, dstPoint,
-                                       canDiscardOutsideDstRect)) {
+    if (this->vkCaps().canCopyAsDraw(dstConfig, SkToBool(dst->asRenderTarget()),
+                                     srcConfig, SkToBool(src->asTexture()))) {
+        SkAssertResult(fCopyManager.copySurfaceAsDraw(this, dst, dstOrigin, src, srcOrigin, srcRect,
+                                                      dstPoint, canDiscardOutsideDstRect));
         auto dstRect = srcRect.makeOffset(dstPoint.fX, dstPoint.fY);
         this->didWriteToSurface(dst, dstOrigin, &dstRect);
         return true;
@@ -1920,13 +1873,15 @@
         srcImage = static_cast<GrVkTexture*>(src->asTexture());
     }
 
-    if (can_copy_image(dst, dstOrigin, src, srcOrigin, this)) {
+    if (this->vkCaps().canCopyImage(dstConfig, dstSampleCnt, dstOrigin,
+                                    srcConfig, srcSampleCnt, srcOrigin)) {
         this->copySurfaceAsCopyImage(dst, dstOrigin, src, srcOrigin, dstImage, srcImage,
                                      srcRect, dstPoint);
         return true;
     }
 
-    if (can_copy_as_blit(dst, src, dstImage, srcImage, this)) {
+    if (this->vkCaps().canCopyAsBlit(dstConfig, dstSampleCnt, dstImage->isLinearTiled(),
+                                     srcConfig, srcSampleCnt, srcImage->isLinearTiled())) {
         this->copySurfaceAsBlit(dst, dstOrigin, src, srcOrigin, dstImage, srcImage,
                                 srcRect, dstPoint);
         return true;