Revert "Initiate MSAA resolves during DAG generation"

This reverts commit 804f6a0fe7f3c7df5ac69891841ee9cdf6d1121a.

Reason for revert: <INSERT REASONING HERE>

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>

TBR=egdaniel@google.com,bsalomon@google.com,robertphillips@google.com,csmartdalton@google.com

Change-Id: Ife557caa840edfb64cbcafc272dc3012cfb43702
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/237242
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index e47fb0c..a32240c 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -294,10 +294,6 @@
                     [](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());
@@ -513,30 +509,19 @@
     GrSemaphoresSubmitted result = this->flush(proxies, numProxies, access, info,
                                                GrPrepareForExternalIORequests());
     for (int i = 0; i < numProxies; ++i) {
-        GrSurfaceProxy* proxy = proxies[i];
-        if (!proxy->isInstantiated()) {
+        if (!proxies[i]->isInstantiated()) {
             return result;
         }
-        // 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();
-            }
+        GrSurface* surface = proxies[i]->peekSurface();
+        if (auto* rt = surface->asRenderTarget()) {
+            gpu->resolveRenderTarget(rt);
         }
         // 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 = proxy->asTextureProxy()) {
+        if (auto* textureProxy = proxies[i]->asTextureProxy()) {
             if (textureProxy->mipMapsAreDirty()) {
-                SkASSERT(textureProxy->peekTexture());
                 gpu->regenerateMipMapLevels(textureProxy->peekTexture());
                 textureProxy->markMipMapsClean();
             }
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index aa27b95..8d7c95c 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -14,7 +14,6 @@
 #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,
@@ -41,18 +40,6 @@
     return renderTargetContext;
 }
 
-void GrOnFlushResourceProvider::addTextureResolveTask(sk_sp<GrTextureProxy> textureProxy,
-                                                      GrTextureResolveFlags 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());
-    }
-    fDrawingMgr->fOnFlushRenderTasks.push_back(GrTextureResolveRenderTask::Make(
-            std::move(textureProxy), resolveFlags, *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 9e86629..e4fe6f1 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*);
-
-    void addTextureResolveTask(sk_sp<GrTextureProxy>, GrTextureResolveFlags);
+    std::unique_ptr<GrRenderTargetContext> makeRenderTargetContext(sk_sp<GrSurfaceProxy>,
+                                                                   GrColorType,
+                                                                   sk_sp<SkColorSpace>,
+                                                                   const SkSurfaceProps*);
 
     // 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 dcc53b6..3876a42 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -17,7 +17,6 @@
 #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);
@@ -34,51 +33,6 @@
     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,
@@ -91,12 +45,13 @@
         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) {
@@ -125,9 +80,6 @@
             }
         }
     }
-
-    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 fd7ae5c..2759a3d 100644
--- a/src/gpu/GrRenderTargetProxy.h
+++ b/src/gpu/GrRenderTargetProxy.h
@@ -63,19 +63,6 @@
 
     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;
 
@@ -141,10 +128,6 @@
     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 181a8dd..639f4cf 100644
--- a/src/gpu/GrRenderTask.cpp
+++ b/src/gpu/GrRenderTask.cpp
@@ -51,10 +51,6 @@
     }
 
     if (ExpectedOutcome::kTargetDirty == this->onMakeClosed(caps)) {
-        GrRenderTargetProxy* renderTargetProxy = fTarget->asRenderTargetProxy();
-        if (renderTargetProxy && renderTargetProxy->requiresManualMSAAResolve()) {
-            renderTargetProxy->markMSAADirty();
-        }
         GrTextureProxy* textureProxy = fTarget->asTextureProxy();
         if (textureProxy && GrMipMapped::kYes == textureProxy->mipMapped()) {
             textureProxy->markMipMapsDirty();
@@ -97,11 +93,8 @@
     GrRenderTask* dependedOnTask = dependedOn->getLastRenderTask();
 
     if (dependedOnTask == this) {
-        // 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.
+        // self-read - presumably for dst reads. We can't make it closed in the self-read case.
         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;
@@ -114,16 +107,6 @@
         dependedOnTask->makeClosed(caps);
     }
 
-    auto textureResolveFlags = GrTextureResolveFlags::kNone;
-
-    if (dependedOn->requiresManualMSAAResolve()) {
-        auto* renderTargetProxy = dependedOn->asRenderTargetProxy();
-        SkASSERT(renderTargetProxy);
-        if (renderTargetProxy->isMSAADirty()) {
-            textureResolveFlags |= GrTextureResolveFlags::kMSAA;
-        }
-    }
-
     GrTextureProxy* textureProxy = dependedOn->asTextureProxy();
     if (GrMipMapped::kYes == mipMapped) {
         SkASSERT(textureProxy);
@@ -131,16 +114,14 @@
             // 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()) {
-            textureResolveFlags |= GrTextureResolveFlags::kMipMaps;
         }
     }
 
-    // Does this proxy have msaa to resolve and/or mipmaps to regenerate?
-    if (GrTextureResolveFlags::kNone != textureResolveFlags) {
+    // Does this proxy have mipmaps that need to be regenerated?
+    if (GrMipMapped::kYes == mipMapped && textureProxy->mipMapsAreDirty()) {
         // Create a renderTask that resolves the texture's mipmap data.
         GrRenderTask* textureResolveTask = textureResolveManager.newTextureResolveRenderTask(
-                sk_ref_sp(textureProxy), textureResolveFlags, caps);
+                sk_ref_sp(textureProxy), GrTextureResolveFlags::kMipMaps, caps);
 
         // The GrTextureResolveRenderTask factory should have called addDependency (in this
         // instance, recursively) on the textureProxy.
diff --git a/src/gpu/GrTextureResolveRenderTask.cpp b/src/gpu/GrTextureResolveRenderTask.cpp
index 0064fe6..1e52b87 100644
--- a/src/gpu/GrTextureResolveRenderTask.cpp
+++ b/src/gpu/GrTextureResolveRenderTask.cpp
@@ -10,7 +10,6 @@
 #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"
 
@@ -20,24 +19,6 @@
     sk_sp<GrTextureResolveRenderTask> resolveTask(
             new GrTextureResolveRenderTask(std::move(textureProxy), flags));
 
-    // Ensure the last render task that operated on the textureProxy is closed. That's where msaa
-    // and mipmaps should have been marked dirty.
-    SkASSERT(!textureProxyPtr->getLastRenderTask() ||
-             textureProxyPtr->getLastRenderTask()->isClosed());
-
-    if (GrTextureResolveFlags::kMSAA & flags) {
-        GrRenderTargetProxy* renderTargetProxy = textureProxyPtr->asRenderTargetProxy();
-        SkASSERT(renderTargetProxy);
-        SkASSERT(renderTargetProxy->isMSAADirty());
-        renderTargetProxy->markMSAAResolved();
-    }
-
-    if (GrTextureResolveFlags::kMipMaps & flags) {
-        SkASSERT(GrMipMapped::kYes == textureProxyPtr->mipMapped());
-        SkASSERT(textureProxyPtr->mipMapsAreDirty());
-        textureProxyPtr->markMipMapsClean();
-    }
-
     // Add the target as a dependency: We will read the existing contents of this texture while
     // generating mipmap levels and/or resolving MSAA.
     //
@@ -49,7 +30,13 @@
     // We only resolve the texture; nobody should try to do anything else with this opsTask.
     resolveTask->makeClosed(caps);
 
-    return std::move(resolveTask);
+    if (GrTextureResolveFlags::kMipMaps & flags) {
+        SkASSERT(GrMipMapped::kYes == textureProxyPtr->mipMapped());
+        SkASSERT(textureProxyPtr->mipMapsAreDirty());
+        textureProxyPtr->markMipMapsClean();
+    }
+
+    return resolveTask;
 }
 
 void GrTextureResolveRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
@@ -62,17 +49,10 @@
 }
 
 bool GrTextureResolveRenderTask::onExecute(GrOpFlushState* flushState) {
-    // Resolve msaa before regenerating mipmaps.
-    if (GrTextureResolveFlags::kMSAA & fResolveFlags) {
-        GrRenderTarget* renderTarget = fTarget->peekRenderTarget();
-        SkASSERT(renderTarget);
-        SkASSERT(renderTarget->needsResolve());
-        flushState->gpu()->resolveRenderTarget(renderTarget);
-    }
+    GrTexture* texture = fTarget->peekTexture();
+    SkASSERT(texture);
 
     if (GrTextureResolveFlags::kMipMaps & fResolveFlags) {
-        GrTexture* texture = fTarget->peekTexture();
-        SkASSERT(texture);
         SkASSERT(texture->texturePriv().mipMapsAreDirty());
         flushState->gpu()->regenerateMipMapLevels(texture);
     }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 8f56592..72ff29c 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -588,10 +588,6 @@
                         atlas->getStrokeBatchID(), atlas->drawBounds());
             }
             rtc->addDrawOp(GrNoClip(), std::move(op));
-            if (rtc->proxy()->requiresManualMSAAResolve()) {
-                onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->proxy()->asTextureProxy()),
-                                                 GrTextureResolveFlags::kMSAA);
-            }
         }
 
         SkASSERT(atlas->getEndStencilResolveInstance() >= baseStencilResolveInstance);
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index ed722c8..4660793 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1868,6 +1868,51 @@
 #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,
@@ -1876,23 +1921,28 @@
                            const GrPipeline::DynamicStateArrays* dynamicStateArrays,
                            int dynamicStateArraysLength,
                            bool willDrawPoints) {
-    const GrTextureProxy* const* primProcProxies = nullptr;
+    const GrTextureProxy* const* primProcProxiesForMipRegen = nullptr;
     const GrTextureProxy* const* primProcProxiesToBind = nullptr;
+    int numPrimProcTextureSets = 1;  // number of texture per prim proc sampler.
     if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        primProcProxies = dynamicStateArrays->fPrimitiveProcessorTextures;
+        primProcProxiesForMipRegen = dynamicStateArrays->fPrimitiveProcessorTextures;
+        numPrimProcTextureSets = dynamicStateArraysLength;
     } else if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
-        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
+        primProcProxiesForMipRegen = fixedDynamicState->fPrimitiveProcessorTextures;
         primProcProxiesToBind = fixedDynamicState->fPrimitiveProcessorTextures;
     }
 
-    SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
+    SkASSERT(SkToBool(primProcProxiesForMipRegen) == SkToBool(primProc.numTextureSamplers()));
 
-    sk_sp<GrGLProgram> program(fProgramCache->refProgram(
-            this, renderTarget, origin, primProc, primProcProxies, pipeline, willDrawPoints));
+    sk_sp<GrGLProgram> program(fProgramCache->refProgram(this, renderTarget, origin, primProc,
+                                                         primProcProxiesForMipRegen,
+                                                         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 d494be3..d274a25 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -270,6 +270,15 @@
     // 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 86efe99..c0ab9c5 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 { target->flagAsResolved(); }
+    void onResolveRenderTarget(GrRenderTarget* target) override { return; }
 
     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 8b0bcde..f9754fe 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.mm
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.mm
@@ -10,6 +10,7 @@
 #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"
@@ -95,6 +96,45 @@
         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 db3d62b..6862436 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -16,6 +16,7 @@
 #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"
@@ -615,16 +616,42 @@
 
     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,6 +659,7 @@
     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());
         }
     }