Reland "Initiate MSAA resolves during DAG generation"

This is a reland of 804f6a0fe7f3c7df5ac69891841ee9cdf6d1121a

Original change's description:
> Initiate MSAA resolves during DAG generation
> 
> Adds an "fIsMSAADirty" flag to GrRenderTargetProxy and switches to
> resolving MSAA in GrTextureResolveRenderTask. This completes our push
> to resolve textures outside of render passes.
> 
> For the time being, we only store a dirty flag on the proxy and still
> rely on the GrRenderTarget itself to track the actual dirty rect. This
> will be followed by a CL that moves the dirty rect out of
> GrRenderTarget and into the proxy.
> 
> Bug: skia:
> Change-Id: I21219a58028bdb4590940210e565133093cd34b3
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/235672
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Robert Phillips <robertphillips@google.com>

Bug: skia:
Change-Id: I805b3af1404eb7919ae937cff3dfa97921e32c69
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/237482
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 7e05f0b..4776484 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -300,6 +300,10 @@
                     [](GrSurfaceProxy* p, GrMipMapped mipMapped) {
                 SkASSERT(!p->asTextureProxy() || !p->asTextureProxy()->texPriv().isDeferred());
                 SkASSERT(GrSurfaceProxy::LazyState::kNot == p->lazyInstantiationState());
+                if (p->requiresManualMSAAResolve()) {
+                    // The onFlush callback is responsible for ensuring MSAA gets resolved.
+                    SkASSERT(p->asRenderTargetProxy() && !p->asRenderTargetProxy()->isMSAADirty());
+                }
                 if (GrMipMapped::kYes == mipMapped) {
                     // The onFlush callback is responsible for regenerating mips if needed.
                     SkASSERT(p->asTextureProxy() && !p->asTextureProxy()->mipMapsAreDirty());
@@ -515,19 +519,30 @@
     GrSemaphoresSubmitted result = this->flush(proxies, numProxies, access, info,
                                                GrPrepareForExternalIORequests());
     for (int i = 0; i < numProxies; ++i) {
-        if (!proxies[i]->isInstantiated()) {
+        GrSurfaceProxy* proxy = proxies[i];
+        if (!proxy->isInstantiated()) {
             return result;
         }
-        GrSurface* surface = proxies[i]->peekSurface();
-        if (auto* rt = surface->asRenderTarget()) {
-            gpu->resolveRenderTarget(rt);
+        // In the flushSurfaces case, we need to resolve MSAA immediately after flush. This is
+        // because the client will call through to this method when drawing into a target created by
+        // wrapBackendTextureAsRenderTarget, and will expect the original texture to be fully
+        // resolved upon return.
+        if (proxy->requiresManualMSAAResolve()) {
+            auto* rtProxy = proxy->asRenderTargetProxy();
+            SkASSERT(rtProxy);
+            if (rtProxy->isMSAADirty()) {
+                SkASSERT(rtProxy->peekRenderTarget());
+                gpu->resolveRenderTarget(rtProxy->peekRenderTarget());
+                rtProxy->markMSAAResolved();
+            }
         }
         // If, after a flush, any of the proxies of interest have dirty mipmaps, regenerate them in
         // case their backend textures are being stolen.
         // (This special case is exercised by the ReimportImageTextureWithMipLevels test.)
         // FIXME: It may be more ideal to plumb down a "we're going to steal the backends" flag.
-        if (auto* textureProxy = proxies[i]->asTextureProxy()) {
+        if (auto* textureProxy = proxy->asTextureProxy()) {
             if (textureProxy->mipMapsAreDirty()) {
+                SkASSERT(textureProxy->peekTexture());
                 gpu->regenerateMipMapLevels(textureProxy->peekTexture());
                 textureProxy->markMipMapsClean();
             }
@@ -666,20 +681,20 @@
 }
 
 GrRenderTask* GrDrawingManager::newTextureResolveRenderTask(
-        sk_sp<GrTextureProxy> textureProxy, GrTextureResolveFlags flags, const GrCaps& caps) {
-    SkDEBUGCODE(auto* previousTaskBeforeMipsResolve = textureProxy->getLastRenderTask();)
+        sk_sp<GrSurfaceProxy> proxy, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) {
+    SkDEBUGCODE(auto* previousTaskBeforeMipsResolve = proxy->getLastRenderTask();)
 
     // Unlike in the "new opsTask" case, we do not want to close the active opsTask, nor (if we are
     // in sorting and opsTask reduction mode) the render tasks that depend on the proxy's current
     // state. This is because those opsTasks can still receive new ops and because if they refer to
-    // the mipmapped version of 'textureProxy', they will then come to depend on the render task
-    // being created here.
+    // the mipmapped version of 'proxy', they will then come to depend on the render task being
+    // created here.
     //
     // Add the new textureResolveTask before the fActiveOpsTask (if not in
     // sorting/opsTask-splitting-reduction mode) because it will depend upon this resolve task.
     // NOTE: Putting it here will also reduce the amount of work required by the topological sort.
     auto* resolveTask = static_cast<GrTextureResolveRenderTask*>(fDAG.addBeforeLast(
-            sk_make_sp<GrTextureResolveRenderTask>(std::move(textureProxy), flags)));
+            sk_make_sp<GrTextureResolveRenderTask>(std::move(proxy), flags)));
     resolveTask->init(caps);
 
     // GrTextureResolveRenderTask::init should have closed the texture proxy's previous task.
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 240c546..a1e3c6d 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -50,11 +50,11 @@
     sk_sp<GrOpsTask> newOpsTask(sk_sp<GrRenderTargetProxy>, bool managedOpsTask);
 
     // Create a new, specialized, render task that will regenerate mipmap levels and/or resolve
-    // MSAA (depending on GrTextureResolveFlags). This method will add the new render task to the
-    // list of render tasks and make it depend on the target texture proxy. It is up to the caller
-    // to add any dependencies on the new render task.
+    // MSAA (depending on ResolveFlags). This method will add the new render task to the list of
+    // render tasks and make it depend on the target texture proxy. It is up to the caller to add
+    // any dependencies on the new render task.
     GrRenderTask* newTextureResolveRenderTask(
-            sk_sp<GrTextureProxy>, GrTextureResolveFlags, const GrCaps&);
+            sk_sp<GrSurfaceProxy>, GrSurfaceProxy::ResolveFlags, const GrCaps&);
 
     // Create a new render task which copies the pixels from the srcProxy into the dstBuffer. This
     // is used to support the asynchronous readback API. The srcRect is the region of the srcProxy
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index 8d7c95c..74aa77b 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -14,6 +14,7 @@
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrSurfaceProxy.h"
+#include "src/gpu/GrTextureResolveRenderTask.h"
 
 std::unique_ptr<GrRenderTargetContext> GrOnFlushResourceProvider::makeRenderTargetContext(
         sk_sp<GrSurfaceProxy> proxy, GrColorType colorType, sk_sp<SkColorSpace> colorSpace,
@@ -40,6 +41,19 @@
     return renderTargetContext;
 }
 
+void GrOnFlushResourceProvider::addTextureResolveTask(sk_sp<GrTextureProxy> textureProxy,
+                                                      GrSurfaceProxy::ResolveFlags resolveFlags) {
+    // Since we are bypassing normal DAG operation, we need to ensure the textureProxy's last render
+    // task gets closed before making a texture resolve task. makeClosed is what will mark msaa and
+    // mipmaps dirty.
+    if (GrRenderTask* renderTask = textureProxy->getLastRenderTask()) {
+        renderTask->makeClosed(*this->caps());
+    }
+    auto task = static_cast<GrTextureResolveRenderTask*>(fDrawingMgr->fOnFlushRenderTasks.push_back(
+            sk_make_sp<GrTextureResolveRenderTask>(std::move(textureProxy), resolveFlags)).get());
+    task->init(*this->caps());
+}
+
 bool GrOnFlushResourceProvider::assignUniqueKeyToProxy(const GrUniqueKey& key,
                                                        GrTextureProxy* proxy) {
     auto proxyProvider = fDrawingMgr->getContext()->priv().proxyProvider();
diff --git a/src/gpu/GrOnFlushResourceProvider.h b/src/gpu/GrOnFlushResourceProvider.h
index e4fe6f1..7dd2847 100644
--- a/src/gpu/GrOnFlushResourceProvider.h
+++ b/src/gpu/GrOnFlushResourceProvider.h
@@ -63,10 +63,10 @@
 public:
     explicit GrOnFlushResourceProvider(GrDrawingManager* drawingMgr) : fDrawingMgr(drawingMgr) {}
 
-    std::unique_ptr<GrRenderTargetContext> makeRenderTargetContext(sk_sp<GrSurfaceProxy>,
-                                                                   GrColorType,
-                                                                   sk_sp<SkColorSpace>,
-                                                                   const SkSurfaceProps*);
+    std::unique_ptr<GrRenderTargetContext> makeRenderTargetContext(
+            sk_sp<GrSurfaceProxy>, GrColorType, sk_sp<SkColorSpace>, const SkSurfaceProps*);
+
+    void addTextureResolveTask(sk_sp<GrTextureProxy>, GrSurfaceProxy::ResolveFlags);
 
     // Proxy unique key management. See GrProxyProvider.h.
     bool assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*);
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index 3876a42..dcc53b6 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -17,6 +17,7 @@
 #include "src/gpu/GrPrimitiveProcessor.h"
 #include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrRenderTargetPriv.h"
+#include "src/gpu/GrTexturePriv.h"
 
 void GrOpsRenderPass::clear(const GrFixedClip& clip, const SkPMColor4f& color) {
     SkASSERT(fRenderTarget);
@@ -33,6 +34,51 @@
     this->onClearStencilClip(clip, insideStencilMask);
 }
 
+#ifdef SK_DEBUG
+static void assert_msaa_and_mips_are_resolved(
+        const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+        const GrPipeline::FixedDynamicState* fixedDynamicState,
+        const GrPipeline::DynamicStateArrays* dynamicStateArrays, int meshCount) {
+    auto assertResolved = [](GrTexture* tex, const GrSamplerState& sampler) {
+        SkASSERT(tex);
+
+        // Ensure msaa was resolved ahead of time by the DAG.
+        SkASSERT(!tex->asRenderTarget() || !tex->asRenderTarget()->needsResolve());
+
+        // Ensure mipmaps were all resolved ahead of time by the DAG.
+        if (GrSamplerState::Filter::kMipMap == sampler.filter() &&
+            (tex->width() != 1 || tex->height() != 1)) {
+            // There are some cases where we might be given a non-mipmapped texture with a mipmap
+            // filter. See skbug.com/7094.
+            SkASSERT(tex->texturePriv().mipMapped() != GrMipMapped::kYes ||
+                     !tex->texturePriv().mipMapsAreDirty());
+        }
+    };
+
+    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
+        for (int m = 0, i = 0; m < meshCount; ++m) {
+            for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
+                auto* tex = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
+                assertResolved(tex, primProc.textureSampler(s).samplerState());
+            }
+        }
+    } else {
+        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
+            auto* tex = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
+            assertResolved(tex, primProc.textureSampler(i).samplerState());
+        }
+    }
+
+    GrFragmentProcessor::Iter iter(pipeline);
+    while (const GrFragmentProcessor* fp = iter.next()) {
+        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
+            const auto& textureSampler = fp->textureSampler(i);
+            assertResolved(textureSampler.peekTexture(), textureSampler.samplerState());
+        }
+    }
+}
+#endif
+
 bool GrOpsRenderPass::draw(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
                            const GrPipeline::FixedDynamicState* fixedDynamicState,
                            const GrPipeline::DynamicStateArrays* dynamicStateArrays,
@@ -45,13 +91,12 @@
         SkASSERT(primProc.hasVertexAttributes() == meshes[i].hasVertexData());
         SkASSERT(primProc.hasInstanceAttributes() == meshes[i].hasInstanceData());
     }
-#endif
+
     SkASSERT(!pipeline.isScissorEnabled() || fixedDynamicState ||
              (dynamicStateArrays && dynamicStateArrays->fScissorRects));
 
     SkASSERT(!pipeline.isBad());
 
-#ifdef SK_DEBUG
     if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
         GrTextureProxy** processorProxies = fixedDynamicState->fPrimitiveProcessorTextures;
         for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
@@ -80,6 +125,9 @@
             }
         }
     }
+
+    assert_msaa_and_mips_are_resolved(
+            primProc, pipeline, fixedDynamicState, dynamicStateArrays, meshCount);
 #endif
 
     if (primProc.numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
diff --git a/src/gpu/GrRenderTargetProxy.h b/src/gpu/GrRenderTargetProxy.h
index 3371850..1d40efd 100644
--- a/src/gpu/GrRenderTargetProxy.h
+++ b/src/gpu/GrRenderTargetProxy.h
@@ -63,6 +63,19 @@
 
     bool wrapsVkSecondaryCB() const { return fWrapsVkSecondaryCB == WrapsVkSecondaryCB::kYes; }
 
+    void markMSAADirty() {
+        SkASSERT(this->requiresManualMSAAResolve());
+        fIsMSAADirty = true;
+    }
+    void markMSAAResolved() {
+        SkASSERT(this->requiresManualMSAAResolve());
+        fIsMSAADirty = false;
+    }
+    bool isMSAADirty() const {
+        SkASSERT(!fIsMSAADirty || this->requiresManualMSAAResolve());
+        return fIsMSAADirty;
+    }
+
     // TODO: move this to a priv class!
     bool refsWrappedObjects() const;
 
@@ -128,6 +141,10 @@
     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;
     // 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 9541408..91528f2 100644
--- a/src/gpu/GrRenderTask.cpp
+++ b/src/gpu/GrRenderTask.cpp
@@ -51,6 +51,10 @@
     }
 
     if (ExpectedOutcome::kTargetDirty == this->onMakeClosed(caps)) {
+        if (fTarget->requiresManualMSAAResolve()) {
+            SkASSERT(fTarget->asRenderTargetProxy());
+            fTarget->asRenderTargetProxy()->markMSAADirty();
+        }
         GrTextureProxy* textureProxy = fTarget->asTextureProxy();
         if (textureProxy && GrMipMapped::kYes == textureProxy->mipMapped()) {
             textureProxy->markMipMapsDirty();
@@ -60,7 +64,6 @@
     this->setFlag(kClosed_Flag);
 }
 
-
 void GrRenderTask::prepare(GrOpFlushState* flushState) {
     for (int i = 0; i < fDeferredProxies.count(); ++i) {
         fDeferredProxies[i]->texPriv().scheduleUpload(flushState);
@@ -93,8 +96,11 @@
     GrRenderTask* dependedOnTask = dependedOn->getLastRenderTask();
 
     if (dependedOnTask == this) {
-        // self-read - presumably for dst reads. We can't make it closed in the self-read case.
+        // self-read - presumably for dst reads. We don't need to do anything in this case. The
+        // XferProcessor will detect what is happening and insert a texture barrier.
         SkASSERT(GrMipMapped::kNo == mipMapped);
+        // We should never attempt a self-read on a surface that has a separate MSAA renderbuffer.
+        SkASSERT(!dependedOn->requiresManualMSAAResolve());
         SkASSERT(!dependedOn->asTextureProxy() ||
                  !dependedOn->asTextureProxy()->texPriv().isDeferred());
         return;
@@ -107,6 +113,16 @@
         dependedOnTask->makeClosed(caps);
     }
 
+    auto resolveFlags = GrSurfaceProxy::ResolveFlags::kNone;
+
+    if (dependedOn->requiresManualMSAAResolve()) {
+        auto* renderTargetProxy = dependedOn->asRenderTargetProxy();
+        SkASSERT(renderTargetProxy);
+        if (renderTargetProxy->isMSAADirty()) {
+            resolveFlags |= GrSurfaceProxy::ResolveFlags::kMSAA;
+        }
+    }
+
     GrTextureProxy* textureProxy = dependedOn->asTextureProxy();
     if (GrMipMapped::kYes == mipMapped) {
         SkASSERT(textureProxy);
@@ -114,26 +130,38 @@
             // There are some cases where we might be given a non-mipmapped texture with a mipmap
             // filter. See skbug.com/7094.
             mipMapped = GrMipMapped::kNo;
+        } else if (textureProxy->mipMapsAreDirty()) {
+            resolveFlags |= GrSurfaceProxy::ResolveFlags::kMipMaps;
         }
     }
 
-    // Does this proxy have mipmaps that need to be regenerated?
-    if (GrMipMapped::kYes == mipMapped && textureProxy->mipMapsAreDirty()) {
+    // Does this proxy have msaa to resolve and/or mipmaps to regenerate?
+    if (GrSurfaceProxy::ResolveFlags::kNone != resolveFlags) {
         // Create a renderTask that resolves the texture's mipmap data.
         GrRenderTask* textureResolveTask = textureResolveManager.newTextureResolveRenderTask(
-                sk_ref_sp(textureProxy), GrTextureResolveFlags::kMipMaps, caps);
+                sk_ref_sp(dependedOn), resolveFlags, caps);
 
+#ifdef SK_DEBUG
         // GrTextureResolveRenderTask::init should have called addDependency (in this instance,
-        // recursively) on the textureProxy.
-        SkASSERT(!dependedOnTask || textureResolveTask->dependsOn(dependedOnTask));
-        SkASSERT(!textureProxy->texPriv().isDeferred() ||
-                 textureResolveTask->fDeferredProxies.back() == textureProxy);
+        // recursively) on the textureResolveTask.
+        if (dependedOnTask) {
+            SkASSERT(textureResolveTask->dependsOn(dependedOnTask));
+        }
+        if (textureProxy && textureProxy->texPriv().isDeferred()) {
+            SkASSERT(textureResolveTask->fDeferredProxies.back() == textureProxy);
+        }
 
-        // The GrTextureResolveRenderTask factory should have also marked the mipmaps clean, set the
+        // The GrTextureResolveRenderTask factory should have also marked the proxy clean, set the
         // last renderTask on the textureProxy to textureResolveTask, and closed textureResolveTask.
-        SkASSERT(!textureProxy->mipMapsAreDirty());
-        SkASSERT(textureProxy->getLastRenderTask() == textureResolveTask);
+        if (GrRenderTargetProxy* renderTargetProxy = dependedOn->asRenderTargetProxy()) {
+            SkASSERT(!renderTargetProxy->isMSAADirty());
+        }
+        if (textureProxy) {
+            SkASSERT(!textureProxy->mipMapsAreDirty());
+        }
+        SkASSERT(dependedOn->getLastRenderTask() == textureResolveTask);
         SkASSERT(textureResolveTask->isClosed());
+#endif
 
         // Fall through and add textureResolveTask as a dependency of "this".
         dependedOnTask = textureResolveTask;
diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp
index 9ffe1b5..aae0171 100644
--- a/src/gpu/GrResourceProvider.cpp
+++ b/src/gpu/GrResourceProvider.cpp
@@ -370,6 +370,9 @@
         if (resource) {
             fGpu->stats()->incNumScratchTexturesReused();
             GrSurface* surface = static_cast<GrSurface*>(resource);
+            if (GrRenderTarget* rt = surface->asRenderTarget()) {
+                rt->flagAsResolved();  // Scratch textures always start without dirty MSAA.
+            }
             return sk_sp<GrTexture>(surface->asTexture());
         }
     }
diff --git a/src/gpu/GrSurfaceProxy.h b/src/gpu/GrSurfaceProxy.h
index 673e10e..e4bbe52 100644
--- a/src/gpu/GrSurfaceProxy.h
+++ b/src/gpu/GrSurfaceProxy.h
@@ -33,6 +33,16 @@
     virtual ~GrSurfaceProxy();
 
     /**
+     * Indicates "resolutions" that need to be done on a surface before its pixels can be accessed.
+     * If both types of resolve are requested, the MSAA resolve will happen first.
+     */
+    enum class ResolveFlags {
+        kNone = 0,
+        kMSAA = 1 << 0,  // Blit and resolve an internal MSAA render buffer into the texture.
+        kMipMaps = 1 << 1,  // Regenerate all mipmap levels.
+    };
+
+    /**
      * Some lazy proxy callbacks want to set their own (or no key) on the GrSurfaces they return.
      * Others want the GrSurface's key to be kept in sync with the proxy's key. This enum controls
      * the key relationship between proxies and their targets.
@@ -416,4 +426,6 @@
     GrRenderTask*          fLastRenderTask;
 };
 
+GR_MAKE_BITFIELD_CLASS_OPS(GrSurfaceProxy::ResolveFlags)
+
 #endif
diff --git a/src/gpu/GrTextureResolveManager.h b/src/gpu/GrTextureResolveManager.h
index c0da50a..a9b0182 100644
--- a/src/gpu/GrTextureResolveManager.h
+++ b/src/gpu/GrTextureResolveManager.h
@@ -14,7 +14,6 @@
 class GrCaps;
 class GrDrawingManager;
 class GrRenderTask;
-class GrTextureProxy;
 
 /*
  * This class is a shallow view of the drawing manager. It is passed to render tasks when setting up
@@ -26,10 +25,11 @@
     explicit GrTextureResolveManager(GrDrawingManager* drawingManager)
             : fDrawingManager(drawingManager) {}
 
-    GrRenderTask* newTextureResolveRenderTask(
-            sk_sp<GrTextureProxy> proxy, GrTextureResolveFlags flags, const GrCaps& caps) const {
+    GrRenderTask* newTextureResolveRenderTask(sk_sp<GrSurfaceProxy> proxy,
+                                              GrSurfaceProxy::ResolveFlags resolveFlags,
+                                              const GrCaps& caps) const {
         SkASSERT(fDrawingManager);
-        return fDrawingManager->newTextureResolveRenderTask(std::move(proxy), flags, caps);
+        return fDrawingManager->newTextureResolveRenderTask(std::move(proxy), resolveFlags, caps);
     }
 
 private:
diff --git a/src/gpu/GrTextureResolveRenderTask.cpp b/src/gpu/GrTextureResolveRenderTask.cpp
index 9a4d6b4..0b77383 100644
--- a/src/gpu/GrTextureResolveRenderTask.cpp
+++ b/src/gpu/GrTextureResolveRenderTask.cpp
@@ -10,10 +10,25 @@
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrResourceAllocator.h"
 #include "src/gpu/GrTexturePriv.h"
 
 void GrTextureResolveRenderTask::init(const GrCaps& caps) {
+    if (GrSurfaceProxy::ResolveFlags::kMSAA & fResolveFlags) {
+        GrRenderTargetProxy* renderTargetProxy = fTarget->asRenderTargetProxy();
+        SkASSERT(renderTargetProxy);
+        SkASSERT(renderTargetProxy->isMSAADirty());
+        renderTargetProxy->markMSAAResolved();
+    }
+
+    if (GrSurfaceProxy::ResolveFlags::kMipMaps & fResolveFlags) {
+        GrTextureProxy* textureProxy = fTarget->asTextureProxy();
+        SkASSERT(GrMipMapped::kYes == textureProxy->mipMapped());
+        SkASSERT(textureProxy->mipMapsAreDirty());
+        textureProxy->markMipMapsClean();
+    }
+
     // Add the target as a dependency: We will read the existing contents of this texture while
     // generating mipmap levels and/or resolving MSAA.
     //
@@ -23,13 +38,6 @@
 
     // We only resolve the texture; nobody should try to do anything else with this opsTask.
     this->makeClosed(caps);
-
-    if (GrTextureResolveFlags::kMipMaps & fResolveFlags) {
-        GrTextureProxy* textureProxy = fTarget->asTextureProxy();
-        SkASSERT(GrMipMapped::kYes == textureProxy->mipMapped());
-        SkASSERT(textureProxy->mipMapsAreDirty());
-        textureProxy->markMipMapsClean();
-    }
 }
 
 void GrTextureResolveRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
@@ -42,12 +50,21 @@
 }
 
 bool GrTextureResolveRenderTask::onExecute(GrOpFlushState* flushState) {
-    GrTexture* texture = fTarget->peekTexture();
-    SkASSERT(texture);
+    // Resolve msaa before regenerating mipmaps.
+    if (GrSurfaceProxy::ResolveFlags::kMSAA & fResolveFlags) {
+        GrRenderTarget* renderTarget = fTarget->peekRenderTarget();
+        SkASSERT(renderTarget);
+        if (renderTarget->needsResolve()) {
+            flushState->gpu()->resolveRenderTarget(renderTarget);
+        }
+    }
 
-    if ((GrTextureResolveFlags::kMipMaps & fResolveFlags) &&
-        texture->texturePriv().mipMapsAreDirty()) {
-        flushState->gpu()->regenerateMipMapLevels(texture);
+    if (GrSurfaceProxy::ResolveFlags::kMipMaps & fResolveFlags) {
+        GrTexture* texture = fTarget->peekTexture();
+        SkASSERT(texture);
+        if (texture->texturePriv().mipMapsAreDirty()) {
+            flushState->gpu()->regenerateMipMapLevels(texture);
+        }
     }
 
     return true;
diff --git a/src/gpu/GrTextureResolveRenderTask.h b/src/gpu/GrTextureResolveRenderTask.h
index 29d7eaa..86365dc 100644
--- a/src/gpu/GrTextureResolveRenderTask.h
+++ b/src/gpu/GrTextureResolveRenderTask.h
@@ -12,10 +12,14 @@
 
 class GrTextureResolveRenderTask final : public GrRenderTask {
 public:
-    GrTextureResolveRenderTask(sk_sp<GrTextureProxy> textureProxy, GrTextureResolveFlags flags)
-            : GrRenderTask(std::move(textureProxy))
-            , fResolveFlags(flags) {
-        SkASSERT(GrTextureResolveFlags::kNone != fResolveFlags);
+    GrTextureResolveRenderTask(sk_sp<GrSurfaceProxy> proxy,
+                               GrSurfaceProxy::ResolveFlags resolveFlags)
+            : GrRenderTask(std::move(proxy))
+            , fResolveFlags(resolveFlags) {
+        // Ensure the last render task that operated on the target is closed. That's where msaa and
+        // mipmaps should have been marked dirty.
+        SkASSERT(!fTarget->getLastRenderTask() || fTarget->getLastRenderTask()->isClosed());
+        SkASSERT(GrSurfaceProxy::ResolveFlags::kNone != fResolveFlags);
     }
 
     void init(const GrCaps&);
@@ -40,7 +44,7 @@
     void visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const override {}
 #endif
 
-    const GrTextureResolveFlags fResolveFlags;
+    const GrSurfaceProxy::ResolveFlags fResolveFlags;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 72ff29c..c5db56a 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -588,6 +588,10 @@
                         atlas->getStrokeBatchID(), atlas->drawBounds());
             }
             rtc->addDrawOp(GrNoClip(), std::move(op));
+            if (rtc->proxy()->requiresManualMSAAResolve()) {
+                onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->proxy()->asTextureProxy()),
+                                                 GrSurfaceProxy::ResolveFlags::kMSAA);
+            }
         }
 
         SkASSERT(atlas->getEndStencilResolveInstance() >= baseStencilResolveInstance);
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 4660793..ed722c8 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1868,51 +1868,6 @@
 #endif
 }
 
-void GrGLGpu::resolveAndGenerateMipMapsForProcessorTextures(
-        const GrPrimitiveProcessor& primProc,
-        const GrPipeline& pipeline,
-        const GrTextureProxy* const primProcTextures[],
-        int numPrimitiveProcessorTextureSets) {
-    auto genLevelsIfNeeded = [this](GrTexture* tex, const GrSamplerState& sampler) {
-        SkASSERT(tex);
-        auto* rt = tex->asRenderTarget();
-        if (rt && rt->needsResolve()) {
-            this->resolveRenderTarget(rt);
-            // TEMPORARY: MSAA resolve will have dirtied mipmaps. This goes away once we switch
-            // to resolving MSAA from the opsTask as well.
-            if (GrSamplerState::Filter::kMipMap == sampler.filter() &&
-                (tex->width() != 1 || tex->height() != 1)) {
-                SkASSERT(tex->texturePriv().mipMapped() == GrMipMapped::kYes);
-                SkASSERT(tex->texturePriv().mipMapsAreDirty());
-                this->regenerateMipMapLevels(tex);
-            }
-        }
-        // Ensure mipmaps were all resolved ahead of time by the opsTask.
-        if (GrSamplerState::Filter::kMipMap == sampler.filter() &&
-            (tex->width() != 1 || tex->height() != 1)) {
-            // There are some cases where we might be given a non-mipmapped texture with a mipmap
-            // filter. See skbug.com/7094.
-            SkASSERT(tex->texturePriv().mipMapped() != GrMipMapped::kYes ||
-                     !tex->texturePriv().mipMapsAreDirty());
-        }
-    };
-
-    for (int set = 0, tex = 0; set < numPrimitiveProcessorTextureSets; ++set) {
-        for (int sampler = 0; sampler < primProc.numTextureSamplers(); ++sampler, ++tex) {
-            GrTexture* texture = primProcTextures[tex]->peekTexture();
-            genLevelsIfNeeded(texture, primProc.textureSampler(sampler).samplerState());
-        }
-    }
-
-    GrFragmentProcessor::Iter iter(pipeline);
-    while (const GrFragmentProcessor* fp = iter.next()) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const auto& textureSampler = fp->textureSampler(i);
-            genLevelsIfNeeded(textureSampler.peekTexture(), textureSampler.samplerState());
-        }
-    }
-}
-
 bool GrGLGpu::flushGLState(GrRenderTarget* renderTarget,
                            GrSurfaceOrigin origin,
                            const GrPrimitiveProcessor& primProc,
@@ -1921,28 +1876,23 @@
                            const GrPipeline::DynamicStateArrays* dynamicStateArrays,
                            int dynamicStateArraysLength,
                            bool willDrawPoints) {
-    const GrTextureProxy* const* primProcProxiesForMipRegen = nullptr;
+    const GrTextureProxy* const* primProcProxies = nullptr;
     const GrTextureProxy* const* primProcProxiesToBind = nullptr;
-    int numPrimProcTextureSets = 1;  // number of texture per prim proc sampler.
     if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        primProcProxiesForMipRegen = dynamicStateArrays->fPrimitiveProcessorTextures;
-        numPrimProcTextureSets = dynamicStateArraysLength;
+        primProcProxies = dynamicStateArrays->fPrimitiveProcessorTextures;
     } else if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
-        primProcProxiesForMipRegen = fixedDynamicState->fPrimitiveProcessorTextures;
+        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
         primProcProxiesToBind = fixedDynamicState->fPrimitiveProcessorTextures;
     }
 
-    SkASSERT(SkToBool(primProcProxiesForMipRegen) == SkToBool(primProc.numTextureSamplers()));
+    SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
 
-    sk_sp<GrGLProgram> program(fProgramCache->refProgram(this, renderTarget, origin, primProc,
-                                                         primProcProxiesForMipRegen,
-                                                         pipeline, willDrawPoints));
+    sk_sp<GrGLProgram> program(fProgramCache->refProgram(
+            this, renderTarget, origin, primProc, primProcProxies, pipeline, willDrawPoints));
     if (!program) {
         GrCapsDebugf(this->caps(), "Failed to create program!\n");
         return false;
     }
-    this->resolveAndGenerateMipMapsForProcessorTextures(
-            primProc, pipeline, primProcProxiesForMipRegen, numPrimProcTextureSets);
 
     this->flushProgram(std::move(program));
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index d274a25..d494be3 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -270,15 +270,6 @@
     // binds texture unit in GL
     void setTextureUnit(int unitIdx);
 
-    /**
-     * primitiveProcessorTextures must contain GrPrimitiveProcessor::numTextureSamplers() *
-     * numPrimitiveProcessorTextureSets entries.
-     */
-    void resolveAndGenerateMipMapsForProcessorTextures(
-            const GrPrimitiveProcessor&, const GrPipeline&,
-            const GrTextureProxy* const primitiveProcessorTextures[],
-            int numPrimitiveProcessorTextureSets);
-
     // Flushes state from GrPipeline to GL. Returns false if the state couldn't be set.
     // willDrawPoints must be true if point primitives will be rendered after setting the GL state.
     // If DynamicStateArrays is not null then dynamicStateArraysLength is the number of dynamic
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index c0ab9c5..86efe99 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -115,7 +115,7 @@
 
     bool onRegenerateMipMapLevels(GrTexture*) override { return true; }
 
-    void onResolveRenderTarget(GrRenderTarget* target) override { return; }
+    void onResolveRenderTarget(GrRenderTarget* target) override { target->flagAsResolved(); }
 
     void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access,
                        const GrFlushInfo& info, const GrPrepareForExternalIORequests&) override {
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.mm b/src/gpu/mtl/GrMtlOpsRenderPass.mm
index ddcfe96..b630a3a 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.mm
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.mm
@@ -10,7 +10,6 @@
 #include "src/gpu/GrColor.h"
 #include "src/gpu/GrFixedClip.h"
 #include "src/gpu/GrRenderTargetPriv.h"
-#include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/mtl/GrMtlCommandBuffer.h"
 #include "src/gpu/mtl/GrMtlPipelineState.h"
 #include "src/gpu/mtl/GrMtlPipelineStateBuilder.h"
@@ -93,45 +92,6 @@
         return;
     }
 
-    auto prepareSampledImage = [&](GrTexture* texture, GrSamplerState::Filter filter) {
-        GrMtlTexture* mtlTexture = static_cast<GrMtlTexture*>(texture);
-        // We may need to resolve the texture first if it is also a render target
-        GrMtlRenderTarget* texRT = static_cast<GrMtlRenderTarget*>(mtlTexture->asRenderTarget());
-        if (texRT) {
-            fGpu->resolveRenderTargetNoFlush(texRT);
-        }
-
-        // Check if we need to regenerate any mip maps
-        if (GrSamplerState::Filter::kMipMap == filter &&
-            (texture->width() != 1 || texture->height() != 1)) {
-            SkASSERT(texture->texturePriv().mipMapped() == GrMipMapped::kYes);
-            if (texture->texturePriv().mipMapsAreDirty()) {
-                fGpu->regenerateMipMapLevels(texture);
-            }
-        }
-    };
-
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        for (int m = 0, i = 0; m < meshCount; ++m) {
-            for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
-                auto texture = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
-                prepareSampledImage(texture, primProc.textureSampler(s).samplerState().filter());
-            }
-        }
-    } else {
-        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            auto texture = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
-            prepareSampledImage(texture, primProc.textureSampler(i).samplerState().filter());
-        }
-    }
-    GrFragmentProcessor::Iter iter(pipeline);
-    while (const GrFragmentProcessor* fp = iter.next()) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
-            prepareSampledImage(sampler.peekTexture(), sampler.samplerState().filter());
-        }
-    }
-
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
     GrMtlPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline,
                                                                fixedDynamicState, primitiveType);
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index ba72061..d6ca67e 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -16,7 +16,6 @@
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrPipeline.h"
 #include "src/gpu/GrRenderTargetPriv.h"
-#include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/vk/GrVkCommandBuffer.h"
 #include "src/gpu/vk/GrVkCommandPool.h"
 #include "src/gpu/vk/GrVkGpu.h"
@@ -589,42 +588,16 @@
 
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
 
-    auto prepareSampledImage = [&](GrTexture* texture, GrSamplerState::Filter filter) {
-        GrVkTexture* vkTexture = static_cast<GrVkTexture*>(texture);
-        // We may need to resolve the texture first if it is also a render target
-        GrVkRenderTarget* texRT = static_cast<GrVkRenderTarget*>(vkTexture->asRenderTarget());
-        if (texRT && texRT->needsResolve()) {
-            fGpu->resolveRenderTargetNoFlush(texRT);
-            // TEMPORARY: MSAA resolve will have dirtied mipmaps. This goes away once we switch
-            // to resolving MSAA from the opsTask as well.
-            if (GrSamplerState::Filter::kMipMap == filter &&
-                (vkTexture->width() != 1 || vkTexture->height() != 1)) {
-                SkASSERT(vkTexture->texturePriv().mipMapped() == GrMipMapped::kYes);
-                SkASSERT(vkTexture->texturePriv().mipMapsAreDirty());
-                fGpu->regenerateMipMapLevels(vkTexture);
-            }
-        }
-
-        // Ensure mip maps were all resolved ahead of time by the opsTask.
-        if (GrSamplerState::Filter::kMipMap == filter &&
-            (vkTexture->width() != 1 || vkTexture->height() != 1)) {
-            SkASSERT(vkTexture->texturePriv().mipMapped() == GrMipMapped::kYes);
-            SkASSERT(!vkTexture->texturePriv().mipMapsAreDirty());
-        }
-    };
-
     if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
         for (int m = 0, i = 0; m < meshCount; ++m) {
             for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
                 auto texture = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
-                prepareSampledImage(texture, primProc.textureSampler(s).samplerState().filter());
                 this->appendSampledTexture(texture);
             }
         }
     } else {
         for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
             auto texture = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
-            prepareSampledImage(texture, primProc.textureSampler(i).samplerState().filter());
             this->appendSampledTexture(texture);
         }
     }
@@ -632,7 +605,6 @@
     while (const GrFragmentProcessor* fp = iter.next()) {
         for (int i = 0; i < fp->numTextureSamplers(); ++i) {
             const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
-            prepareSampledImage(sampler.peekTexture(), sampler.samplerState().filter());
             this->appendSampledTexture(sampler.peekTexture());
         }
     }