Move the msaa dirty rect to GrRenderTargetProxy

Bug: skia:
Change-Id: I01d7932bce23dffafb86e4eb124c27420acefdd3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/239192
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrCopyRenderTask.h b/src/gpu/GrCopyRenderTask.h
index 13f502a..8093e69 100644
--- a/src/gpu/GrCopyRenderTask.h
+++ b/src/gpu/GrCopyRenderTask.h
@@ -32,7 +32,9 @@
     // If instantiation failed, at flush time we simply will skip doing the copy.
     void handleInternalAllocationFailure() override {}
     void gatherProxyIntervals(GrResourceAllocator*) const override;
-    ExpectedOutcome onMakeClosed(const GrCaps&) override {
+    ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect* targetUpdateBounds) override {
+        targetUpdateBounds->setXYWH(fDstPoint.x(), fDstPoint.y(), fSrcRect.width(),
+                                    fSrcRect.height());
         return ExpectedOutcome::kTargetDirty;
     }
     bool onExecute(GrOpFlushState*) override;
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 8a24682..07d2066 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -531,7 +531,8 @@
             SkASSERT(rtProxy);
             if (rtProxy->isMSAADirty()) {
                 SkASSERT(rtProxy->peekRenderTarget());
-                gpu->resolveRenderTarget(rtProxy->peekRenderTarget(), GrGpu::ForExternalIO::kYes);
+                gpu->resolveRenderTarget(rtProxy->peekRenderTarget(), rtProxy->msaaDirtyRect(),
+                                         rtProxy->origin(), GrGpu::ForExternalIO::kYes);
                 rtProxy->markMSAAResolved();
             }
         }
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index c7e004d..cb80efa 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -19,6 +19,7 @@
 #include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrGpuResourcePriv.h"
 #include "src/gpu/GrMesh.h"
+#include "src/gpu/GrNativeRect.h"
 #include "src/gpu/GrPathRendering.h"
 #include "src/gpu/GrPipeline.h"
 #include "src/gpu/GrRenderTargetPriv.h"
@@ -580,8 +581,14 @@
     SkASSERT(texture);
     SkASSERT(this->caps()->mipMapSupport());
     SkASSERT(texture->texturePriv().mipMapped() == GrMipMapped::kYes);
-    SkASSERT(texture->texturePriv().mipMapsAreDirty());
     SkASSERT(!texture->asRenderTarget() || !texture->asRenderTarget()->needsResolve());
+    if (!texture->texturePriv().mipMapsAreDirty()) {
+        // This can happen when the proxy expects mipmaps to be dirty, but they are not dirty on the
+        // actual target. This may be caused by things that the drawingManager could not predict,
+        // i.e., ops that don't draw anything, aborting a draw for exceptional circumstances, etc.
+        // NOTE: This goes away once we quit tracking mipmap state on the actual texture.
+        return true;
+    }
     if (texture->readOnly()) {
         return false;
     }
@@ -597,10 +604,24 @@
     this->onResetTextureBindings();
 }
 
-void GrGpu::resolveRenderTarget(GrRenderTarget* target, ForExternalIO forExternalIO) {
+void GrGpu::resolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                                GrSurfaceOrigin origin, ForExternalIO forExternalIO) {
     SkASSERT(target);
+    if (!target->needsResolve()) {
+        // This can happen when the proxy expects MSAA to be dirty, but it is not dirty on the
+        // actual target. This may be caused by things that the drawingManager could not predict,
+        // i.e., ops that don't draw anything, aborting a draw for exceptional circumstances, etc.
+        // NOTE: This goes away once we quit tracking dirty state on the actual render target.
+        return;
+    }
+#ifdef SK_DEBUG
+    auto nativeResolveRect = GrNativeRect::MakeRelativeTo(origin, target->height(), resolveRect);
+    // The proxy will often track a tighter resolve rect than GrRenderTarget, but it should never be
+    // the other way around.
+    SkASSERT(target->getResolveRect().contains(nativeResolveRect.asSkIRect()));
+#endif
     this->handleDirtyContext();
-    this->onResolveRenderTarget(target, forExternalIO);
+    this->onResolveRenderTarget(target, resolveRect, origin, forExternalIO);
 }
 
 void GrGpu::didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 625a10a..19a8f9a 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -189,7 +189,8 @@
     /**
      * Resolves MSAA.
      */
-    void resolveRenderTarget(GrRenderTarget*, ForExternalIO);
+    void resolveRenderTarget(GrRenderTarget*, const SkIRect& resolveRect, GrSurfaceOrigin,
+                             ForExternalIO);
 
     /**
      * Uses the base of the texture to recompute the contents of the other levels.
@@ -634,7 +635,8 @@
                                       GrGpuBuffer* transferBuffer, size_t offset) = 0;
 
     // overridden by backend-specific derived class to perform the resolve
-    virtual void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO) = 0;
+    virtual void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                                       GrSurfaceOrigin resolveOrigin, ForExternalIO) = 0;
 
     // overridden by backend specific derived class to perform mip map level regeneration.
     virtual bool onRegenerateMipMapLevels(GrTexture*) = 0;
diff --git a/src/gpu/GrOpsTask.cpp b/src/gpu/GrOpsTask.cpp
index cab3e8e..b621dd4 100644
--- a/src/gpu/GrOpsTask.cpp
+++ b/src/gpu/GrOpsTask.cpp
@@ -504,6 +504,9 @@
 void GrOpsTask::setColorLoadOp(GrLoadOp op, const SkPMColor4f& color) {
     fColorLoadOp = op;
     fLoadClearColor = color;
+    if (GrLoadOp::kClear == fColorLoadOp) {
+        fTotalBounds.setWH(fTarget->width(), fTarget->height());
+    }
 }
 
 bool GrOpsTask::resetForFullscreenClear(CanDiscardPreviousOps canDiscardPreviousOps) {
@@ -534,6 +537,7 @@
     if (this->isEmpty()) {
         fColorLoadOp = GrLoadOp::kDiscard;
         fStencilLoadOp = GrLoadOp::kDiscard;
+        fTotalBounds.setEmpty();
     }
 }
 
@@ -671,6 +675,10 @@
         return;
     }
 
+    // Account for this op's bounds before we attempt to combine.
+    // NOTE: The caller should have already called "op->setClippedBounds()" by now, if applicable.
+    fTotalBounds.join(op->bounds());
+
     // Check if there is an op we can combine with by linearly searching back until we either
     // 1) check every op
     // 2) intersect with something
@@ -747,3 +755,15 @@
     }
 }
 
+GrRenderTask::ExpectedOutcome GrOpsTask::onMakeClosed(
+        const GrCaps& caps, SkIRect* targetUpdateBounds) {
+    this->forwardCombine(caps);
+    if (!this->isNoOp()) {
+        SkRect clippedContentBounds = SkRect::MakeIWH(fTarget->width(), fTarget->height());
+        if (clippedContentBounds.intersect(fTotalBounds)) {
+            clippedContentBounds.roundOut(targetUpdateBounds);
+            return ExpectedOutcome::kTargetDirty;
+        }
+    }
+    return ExpectedOutcome::kTargetUnchanged;
+}
diff --git a/src/gpu/GrOpsTask.h b/src/gpu/GrOpsTask.h
index e2f2682..0539eab 100644
--- a/src/gpu/GrOpsTask.h
+++ b/src/gpu/GrOpsTask.h
@@ -229,10 +229,7 @@
 
     void forwardCombine(const GrCaps&);
 
-    ExpectedOutcome onMakeClosed(const GrCaps& caps) override {
-        this->forwardCombine(caps);
-        return (this->isNoOp()) ? ExpectedOutcome::kTargetUnchanged : ExpectedOutcome::kTargetDirty;
-    }
+    ExpectedOutcome onMakeClosed(const GrCaps& caps, SkIRect* targetUpdateBounds) override;
 
     friend class GrRenderTargetContextPriv; // for stencil clip state. TODO: this is invasive
 
@@ -270,6 +267,8 @@
     // TODO: We could look into this being a set if we find we're adding a lot of duplicates that is
     // causing slow downs.
     SkTArray<GrTextureProxy*, true> fSampledProxies;
+
+    SkRect fTotalBounds = SkRect::MakeEmpty();
 };
 
 #endif
diff --git a/src/gpu/GrRenderTargetProxy.h b/src/gpu/GrRenderTargetProxy.h
index 50ebcd7e..f0b8d27 100644
--- a/src/gpu/GrRenderTargetProxy.h
+++ b/src/gpu/GrRenderTargetProxy.h
@@ -63,17 +63,22 @@
 
     bool wrapsVkSecondaryCB() const { return fWrapsVkSecondaryCB == WrapsVkSecondaryCB::kYes; }
 
-    void markMSAADirty() {
+    void markMSAADirty(const SkIRect& dirtyRect) {
+        SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(dirtyRect));
         SkASSERT(this->requiresManualMSAAResolve());
-        fIsMSAADirty = true;
+        fMSAADirtyRect.join(dirtyRect);
     }
     void markMSAAResolved() {
         SkASSERT(this->requiresManualMSAAResolve());
-        fIsMSAADirty = false;
+        fMSAADirtyRect.setEmpty();
     }
     bool isMSAADirty() const {
-        SkASSERT(!fIsMSAADirty || this->requiresManualMSAAResolve());
-        return fIsMSAADirty;
+        SkASSERT(fMSAADirtyRect.isEmpty() || this->requiresManualMSAAResolve());
+        return this->requiresManualMSAAResolve() && !fMSAADirtyRect.isEmpty();
+    }
+    const SkIRect& msaaDirtyRect() const {
+        SkASSERT(this->requiresManualMSAAResolve());
+        return fMSAADirtyRect;
     }
 
     // TODO: move this to a priv class!
@@ -160,10 +165,7 @@
     int8_t             fNumStencilSamples = 0;
     WrapsVkSecondaryCB fWrapsVkSecondaryCB;
     GrSwizzle          fOutputSwizzle;
-    // Indicates whether some sub-rectangle of the render target requires MSAA resolve. We currently
-    // rely on the GrRenderTarget itself to track the actual dirty rect.
-    // TODO: In the future, convert the flag to a dirty rect and quit tracking it in GrRenderTarget.
-    bool               fIsMSAADirty = false;
+    SkIRect            fMSAADirtyRect = SkIRect::MakeEmpty();
     // This is to fix issue in large comment above. Without the padding we end 6 bytes into a 16
     // byte range, so the GrTextureProxy ends up starting 8 byte aligned by not 16. We add the
     // padding here to get us right up to the 16 byte alignment (technically any padding of 3-10
diff --git a/src/gpu/GrRenderTask.cpp b/src/gpu/GrRenderTask.cpp
index db7cd4f..a69c48b 100644
--- a/src/gpu/GrRenderTask.cpp
+++ b/src/gpu/GrRenderTask.cpp
@@ -51,10 +51,12 @@
         return;
     }
 
-    if (ExpectedOutcome::kTargetDirty == this->onMakeClosed(caps)) {
+    SkIRect targetUpdateBounds;
+    if (ExpectedOutcome::kTargetDirty == this->onMakeClosed(caps, &targetUpdateBounds)) {
+        SkASSERT(SkIRect::MakeWH(fTarget->width(), fTarget->height()).contains(targetUpdateBounds));
         if (fTarget->requiresManualMSAAResolve()) {
             SkASSERT(fTarget->asRenderTargetProxy());
-            fTarget->asRenderTargetProxy()->markMSAADirty();
+            fTarget->asRenderTargetProxy()->markMSAADirty(targetUpdateBounds);
         }
         GrTextureProxy* textureProxy = fTarget->asTextureProxy();
         if (textureProxy && GrMipMapped::kYes == textureProxy->mipMapped()) {
diff --git a/src/gpu/GrRenderTask.h b/src/gpu/GrRenderTask.h
index 0cb006f..801713c 100644
--- a/src/gpu/GrRenderTask.h
+++ b/src/gpu/GrRenderTask.h
@@ -96,7 +96,12 @@
         kTargetDirty,
     };
 
-    virtual ExpectedOutcome onMakeClosed(const GrCaps&) = 0;
+    // Performs any work to finalize this renderTask prior to execution. If returning
+    // ExpectedOutcome::kTargetDiry, the caller is also responsible to fill out the area it will
+    // modify in targetUpdateBounds.
+    //
+    // targetUpdateBounds must not extend beyond the proxy bounds.
+    virtual ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect* targetUpdateBounds) = 0;
 
     sk_sp<GrSurfaceProxy> fTarget;
 
diff --git a/src/gpu/GrSurfaceProxy.h b/src/gpu/GrSurfaceProxy.h
index ad29e0a..d2c6a98 100644
--- a/src/gpu/GrSurfaceProxy.h
+++ b/src/gpu/GrSurfaceProxy.h
@@ -392,12 +392,12 @@
 private:
     // For wrapped resources, 'fFormat', 'fConfig', 'fWidth', 'fHeight', and 'fOrigin; will always
     // be filled in from the wrapped resource.
-    GrBackendFormat        fFormat;
-    GrPixelConfig          fConfig;
+    const GrBackendFormat  fFormat;
+    const GrPixelConfig    fConfig;
     int                    fWidth;
     int                    fHeight;
-    GrSurfaceOrigin        fOrigin;
-    GrSwizzle              fTextureSwizzle;
+    const GrSurfaceOrigin  fOrigin;
+    const GrSwizzle        fTextureSwizzle;
 
     SkBackingFit           fFit;      // always kApprox for lazy-callback resources
                                       // always kExact for wrapped resources
diff --git a/src/gpu/GrTextureResolveRenderTask.cpp b/src/gpu/GrTextureResolveRenderTask.cpp
index 39eb68f..92e505e 100644
--- a/src/gpu/GrTextureResolveRenderTask.cpp
+++ b/src/gpu/GrTextureResolveRenderTask.cpp
@@ -22,7 +22,10 @@
 }
 
 void GrTextureResolveRenderTask::addProxy(
-        sk_sp<GrSurfaceProxy> proxy, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) {
+        sk_sp<GrSurfaceProxy> proxyHolder, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) {
+    fResolves.emplace_back(std::move(proxyHolder), flags);
+    GrSurfaceProxy* proxy = fResolves.back().fProxy.get();
+
     // Ensure the last render task that operated on the proxy is closed. That's where msaa and
     // mipmaps should have been marked dirty.
     SkASSERT(!proxy->getLastRenderTask() || proxy->getLastRenderTask()->isClosed());
@@ -32,6 +35,7 @@
         GrRenderTargetProxy* renderTargetProxy = proxy->asRenderTargetProxy();
         SkASSERT(renderTargetProxy);
         SkASSERT(renderTargetProxy->isMSAADirty());
+        fResolves.back().fMSAAResolveRect = renderTargetProxy->msaaDirtyRect();
         renderTargetProxy->markMSAAResolved();
     }
 
@@ -44,10 +48,8 @@
 
     // Add the proxy as a dependency: We will read the existing contents of this texture while
     // generating mipmap levels and/or resolving MSAA.
-    this->addDependency(proxy.get(), GrMipMapped::kNo, GrTextureResolveManager(nullptr), caps);
+    this->addDependency(proxy, GrMipMapped::kNo, GrTextureResolveManager(nullptr), caps);
     proxy->setLastRenderTask(this);
-
-    fResolves.emplace_back(std::move(proxy), flags);
 }
 
 void GrTextureResolveRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
@@ -69,7 +71,10 @@
             // peekRenderTarget might be null if there was an instantiation error.
             GrRenderTarget* renderTarget = resolve.fProxy->peekRenderTarget();
             if (renderTarget && renderTarget->needsResolve()) {
-                flushState->gpu()->resolveRenderTarget(renderTarget, GrGpu::ForExternalIO::kNo);
+                flushState->gpu()->resolveRenderTarget(renderTarget, resolve.fMSAAResolveRect,
+                                                       resolve.fProxy->origin(),
+                                                       GrGpu::ForExternalIO::kNo);
+                SkASSERT(!renderTarget->needsResolve());
             }
         }
     }
@@ -80,6 +85,7 @@
             GrTexture* texture = resolve.fProxy->peekTexture();
             if (texture && texture->texturePriv().mipMapsAreDirty()) {
                 flushState->gpu()->regenerateMipMapLevels(texture);
+                SkASSERT(!texture->texturePriv().mipMapsAreDirty());
             }
         }
     }
diff --git a/src/gpu/GrTextureResolveRenderTask.h b/src/gpu/GrTextureResolveRenderTask.h
index 0a511c6..913f250 100644
--- a/src/gpu/GrTextureResolveRenderTask.h
+++ b/src/gpu/GrTextureResolveRenderTask.h
@@ -28,7 +28,7 @@
     }
     void gatherProxyIntervals(GrResourceAllocator*) const override;
 
-    ExpectedOutcome onMakeClosed(const GrCaps&) override {
+    ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override {
         return ExpectedOutcome::kTargetUnchanged;
     }
 
@@ -43,6 +43,7 @@
                 : fProxy(std::move(proxy)), fFlags(flags) {}
         sk_sp<GrSurfaceProxy> fProxy;
         GrSurfaceProxy::ResolveFlags fFlags;
+        SkIRect fMSAAResolveRect;
     };
 
     SkSTArray<4, Resolve> fResolves;
diff --git a/src/gpu/GrTransferFromRenderTask.h b/src/gpu/GrTransferFromRenderTask.h
index 421206e..08437c7 100644
--- a/src/gpu/GrTransferFromRenderTask.h
+++ b/src/gpu/GrTransferFromRenderTask.h
@@ -36,7 +36,7 @@
     void handleInternalAllocationFailure() override {}
     void gatherProxyIntervals(GrResourceAllocator*) const override;
 
-    ExpectedOutcome onMakeClosed(const GrCaps&) override {
+    ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override {
         return ExpectedOutcome::kTargetUnchanged;
     }
 
diff --git a/src/gpu/GrWaitRenderTask.h b/src/gpu/GrWaitRenderTask.h
index fa6b02c..46c2edf 100644
--- a/src/gpu/GrWaitRenderTask.h
+++ b/src/gpu/GrWaitRenderTask.h
@@ -28,7 +28,7 @@
     void handleInternalAllocationFailure() override {}
     void gatherProxyIntervals(GrResourceAllocator*) const override;
 
-    ExpectedOutcome onMakeClosed(const GrCaps&) override {
+    ExpectedOutcome onMakeClosed(const GrCaps&, SkIRect*) override {
         return ExpectedOutcome::kTargetUnchanged;
     }
 
diff --git a/src/gpu/dawn/GrDawnGpu.h b/src/gpu/dawn/GrDawnGpu.h
index 2f1b26c..7b17424 100644
--- a/src/gpu/dawn/GrDawnGpu.h
+++ b/src/gpu/dawn/GrDawnGpu.h
@@ -154,8 +154,8 @@
                               GrColorType surfaceColorType, GrColorType bufferColorType,
                               GrGpuBuffer* transferBuffer, size_t offset) override;
 
-    void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO) override {
-    }
+    void onResolveRenderTarget(GrRenderTarget*, const SkIRect&, GrSurfaceOrigin,
+                               ForExternalIO) override {}
 
     bool onRegenerateMipMapLevels(GrTexture*) override;
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index ea5e87d..632f82f 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1957,7 +1957,7 @@
                 this->flushRenderTargetNoColorWrites(renderTarget);
                 break;
             case GrGLRenderTarget::kCanResolve_ResolveType:
-                this->onResolveRenderTarget(renderTarget, ForExternalIO::kNo);
+                SkASSERT(!renderTarget->needsResolve());
                 // we don't track the state of the READ FBO ID.
                 this->bindFramebuffer(GR_GL_READ_FRAMEBUFFER, renderTarget->textureFBOID());
                 break;
@@ -2281,7 +2281,8 @@
     }
 }
 
-void GrGLGpu::onResolveRenderTarget(GrRenderTarget* target, ForExternalIO) {
+void GrGLGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                                    GrSurfaceOrigin resolveOrigin, ForExternalIO) {
     GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(target);
     if (rt->needsResolve()) {
         // Some extensions automatically resolves the texture when it is read.
@@ -2294,15 +2295,11 @@
             // make sure we go through flushRenderTarget() since we've modified
             // the bound DRAW FBO ID.
             fHWBoundRenderTargetUniqueID.makeInvalid();
-            const SkIRect dirtyRect = rt->getResolveRect();
-            // The dirty rect tracked on the RT is always stored in the native coordinates of the
-            // surface. Choose kTopLeft so no adjustments are made
-            static constexpr auto kDirtyRectOrigin = kTopLeft_GrSurfaceOrigin;
             if (GrGLCaps::kES_Apple_MSFBOType == this->glCaps().msFBOType()) {
                 // Apple's extension uses the scissor as the blit bounds.
                 GrScissorState scissorState;
-                scissorState.set(dirtyRect);
-                this->flushScissor(scissorState, rt->width(), rt->height(), kDirtyRectOrigin);
+                scissorState.set(resolveRect);
+                this->flushScissor(scissorState, rt->width(), rt->height(), resolveOrigin);
                 this->disableWindowRectangles();
                 GL_CALL(ResolveMultisampleFramebuffer());
             } else {
@@ -2315,7 +2312,7 @@
                     t = target->height();
                 } else {
                     auto rect = GrNativeRect::MakeRelativeTo(
-                            kDirtyRectOrigin, rt->height(), dirtyRect);
+                            resolveOrigin, rt->height(), resolveRect);
                     l = rect.fX;
                     b = rect.fY;
                     r = rect.fX + rect.fWidth;
@@ -2536,6 +2533,7 @@
 void GrGLGpu::bindTexture(int unitIdx, GrSamplerState samplerState, const GrSwizzle& swizzle,
                           GrGLTexture* texture) {
     SkASSERT(texture);
+    SkASSERT(!texture->asRenderTarget() || !texture->asRenderTarget()->needsResolve());
 
 #ifdef SK_DEBUG
     if (!this->caps()->npotTextureTileSupport()) {
@@ -2547,14 +2545,6 @@
     }
 #endif
 
-    // If we created a rt/tex and rendered to it without using a texture and now we're texturing
-    // from the rt it will still be the last bound texture, but it needs resolving. So keep this
-    // out of the "last != next" check.
-    GrGLRenderTarget* texRT = static_cast<GrGLRenderTarget*>(texture->asRenderTarget());
-    if (texRT) {
-        this->onResolveRenderTarget(texRT, ForExternalIO::kNo);
-    }
-
     GrGpuResource::UniqueID textureID = texture->uniqueID();
     GrGLenum target = texture->target();
     if (fHWTextureUnitBindings[unitIdx].boundID(target) != textureID) {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index d20909c..30bc989 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -258,7 +258,8 @@
     // PIXEL_UNPACK_BUFFER is unbound.
     void unbindCpuToGpuXferBuffer();
 
-    void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO) override;
+    void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                               GrSurfaceOrigin resolveOrigin, ForExternalIO) override;
 
     bool onRegenerateMipMapLevels(GrTexture*) override;
 
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index 9d6f2f2..3dd2682 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -118,7 +118,8 @@
 
     bool onRegenerateMipMapLevels(GrTexture*) override { return true; }
 
-    void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO) override {
+    void onResolveRenderTarget(GrRenderTarget* target, const SkIRect&, GrSurfaceOrigin,
+                               ForExternalIO) override {
         target->flagAsResolved();
     }
 
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index 6cb4bb8..d3067ee 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -108,10 +108,6 @@
         this->didWriteToSurface(surface, origin, bounds);
     }
 
-    void resolveRenderTargetNoFlush(GrRenderTarget* target) {
-        this->internalResolveRenderTarget(target, false);
-    }
-
 private:
     GrMtlGpu(GrContext* context, const GrContextOptions& options,
              id<MTLDevice> device, id<MTLCommandQueue> queue, MTLFeatureSet featureSet);
@@ -185,11 +181,9 @@
 
     bool onRegenerateMipMapLevels(GrTexture*) override;
 
-    void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO forExternalIO) override {
-        this->internalResolveRenderTarget(target, ForExternalIO::kYes == forExternalIO);
-    }
+    void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                               GrSurfaceOrigin resolveOrigin, ForExternalIO) override;
 
-    void internalResolveRenderTarget(GrRenderTarget* target, bool requiresSubmit);
     void resolveTexture(id<MTLTexture> colorTexture, id<MTLTexture> resolveTexture);
 
     void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access,
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index e99abf1..10666a4 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -1053,7 +1053,7 @@
                 mtlTexture = rt->mtlColorTexture();
                 break;
             case GrMtlRenderTarget::kCanResolve_ResolveType:
-                this->resolveRenderTargetNoFlush(rt);
+                SkASSERT(!rt->needsResolve());
                 mtlTexture = rt->mtlResolveTexture();
                 break;
             default:
@@ -1188,13 +1188,17 @@
 #endif
 }
 
-void GrMtlGpu::internalResolveRenderTarget(GrRenderTarget* target, bool requiresSubmit) {
+void GrMtlGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect&, GrSurfaceOrigin,
+                                     ForExternalIO forExternalIO) {
     if (target->needsResolve()) {
         this->resolveTexture(static_cast<GrMtlRenderTarget*>(target)->mtlResolveTexture(),
                              static_cast<GrMtlRenderTarget*>(target)->mtlColorTexture());
         target->flagAsResolved();
 
-        if (requiresSubmit) {
+        if (ForExternalIO::kYes == forExternalIO) {
+            // This resolve is called when we are preparing an msaa surface for external I/O. It is
+            // called after flushing, so we need to make sure we submit the command buffer after
+            // doing the resolve so that the resolve actually happens.
             this->submitCommandBuffer(kSkip_SyncQueue);
         }
     }
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 47f9573..a5577a8 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -535,18 +535,6 @@
         if (rt->wrapsSecondaryCommandBuffer()) {
             return false;
         }
-        // resolve the render target if necessary
-        switch (rt->getResolveType()) {
-            case GrVkRenderTarget::kCantResolve_ResolveType:
-                return false;
-            case GrVkRenderTarget::kAutoResolves_ResolveType:
-                break;
-            case GrVkRenderTarget::kCanResolve_ResolveType:
-                this->resolveRenderTargetNoFlush(rt);
-                break;
-            default:
-                SK_ABORT("Unknown resolve type");
-        }
         srcImage = rt;
     } else {
         srcImage = static_cast<GrVkTexture*>(surface->asTexture());
@@ -617,21 +605,24 @@
     fCurrentCmdBuffer->resolveImage(this, *src->msaaImage(), *dstImage, 1, &resolveInfo);
 }
 
-void GrVkGpu::internalResolveRenderTarget(GrRenderTarget* target, bool requiresSubmit) {
-    if (target->needsResolve()) {
-        SkASSERT(target->numSamples() > 1);
-        GrVkRenderTarget* rt = static_cast<GrVkRenderTarget*>(target);
-        SkASSERT(rt->msaaImage());
+void GrVkGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                                    GrSurfaceOrigin resolveOrigin, ForExternalIO forExternalIO) {
+    SkASSERT(target->numSamples() > 1);
+    GrVkRenderTarget* rt = static_cast<GrVkRenderTarget*>(target);
+    SkASSERT(rt->msaaImage());
 
-        const SkIRect& srcRect = rt->getResolveRect();
+    auto nativeResolveRect = GrNativeRect::MakeRelativeTo(
+            resolveOrigin, target->height(), resolveRect);
+    this->resolveImage(target, rt, nativeResolveRect.asSkIRect(),
+                       SkIPoint::Make(nativeResolveRect.fX, nativeResolveRect.fY));
 
-        this->resolveImage(target, rt, srcRect, SkIPoint::Make(srcRect.fLeft, srcRect.fTop));
+    rt->flagAsResolved();
 
-        rt->flagAsResolved();
-
-        if (requiresSubmit) {
-            this->submitCommandBuffer(kSkip_SyncQueue);
-        }
+    if (ForExternalIO::kYes == forExternalIO) {
+        // This resolve is called when we are preparing an msaa surface for external I/O. It is
+        // called after flushing, so we need to make sure we submit the command buffer after doing
+        // the resolve so that the resolve actually happens.
+        this->submitCommandBuffer(kSkip_SyncQueue);
     }
 }
 
@@ -2232,18 +2223,6 @@
         if (rt->wrapsSecondaryCommandBuffer()) {
             return false;
         }
-        // resolve the render target if necessary
-        switch (rt->getResolveType()) {
-            case GrVkRenderTarget::kCantResolve_ResolveType:
-                return false;
-            case GrVkRenderTarget::kAutoResolves_ResolveType:
-                break;
-            case GrVkRenderTarget::kCanResolve_ResolveType:
-                this->resolveRenderTargetNoFlush(rt);
-                break;
-            default:
-                SK_ABORT("Unknown resolve type");
-        }
         image = rt;
     } else {
         image = static_cast<GrVkTexture*>(surface->asTexture());
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index f041cab..d0b7787 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -119,13 +119,8 @@
 
     bool onRegenerateMipMapLevels(GrTexture* tex) override;
 
-    void resolveRenderTargetNoFlush(GrRenderTarget* target) {
-        this->internalResolveRenderTarget(target, false);
-    }
-
-    void onResolveRenderTarget(GrRenderTarget* target, ForExternalIO forExternalIO) override {
-        this->internalResolveRenderTarget(target, ForExternalIO::kYes == forExternalIO);
-    }
+    void onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect,
+                               GrSurfaceOrigin resolveOrigin, ForExternalIO) override;
 
     void submitSecondaryCommandBuffer(std::unique_ptr<GrVkSecondaryCommandBuffer>);
 
@@ -248,8 +243,6 @@
     void submitCommandBuffer(SyncQueue sync, GrGpuFinishedProc finishedProc = nullptr,
                              GrGpuFinishedContext finishedContext = nullptr);
 
-    void internalResolveRenderTarget(GrRenderTarget*, bool requiresSubmit);
-
     void copySurfaceAsCopyImage(GrSurface* dst, GrSurface* src, GrVkImage* dstImage,
                                 GrVkImage* srcImage, const SkIRect& srcRect,
                                 const SkIPoint& dstPoint);