Reorder msaa and mipmap resolves to happen all at once

Makes it so every renderTask has only one textureResolveTask, and
modifies GrTextureResolveTask to perform multiple resolves
back-to-back.

Bug: skia:9406
Change-Id: I93566cf4b23764bd846a1e0a0848642c9b3a507a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/241936
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 6f3dfdd..42db1eb 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -626,7 +626,11 @@
 
         for (int i = 0; i < fDAG.numRenderTasks(); ++i) {
             if (fActiveOpsTask != fDAG.renderTask(i)) {
-                SkASSERT(fDAG.renderTask(i)->isClosed());
+                // The resolveTask associated with the activeTask remains open for as long as the
+                // activeTask does.
+                bool isActiveResolveTask =
+                        fActiveOpsTask && fActiveOpsTask->fTextureResolveTask == fDAG.renderTask(i);
+                SkASSERT(isActiveResolveTask || fDAG.renderTask(i)->isClosed());
             }
         }
 
@@ -679,12 +683,9 @@
     return opsTask;
 }
 
-GrRenderTask* GrDrawingManager::newTextureResolveRenderTask(
-        sk_sp<GrSurfaceProxy> proxy, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) {
-    SkDEBUGCODE(auto* previousTaskBeforeMipsResolve = proxy->getLastRenderTask();)
-
+GrTextureResolveRenderTask* GrDrawingManager::newTextureResolveRenderTask(const GrCaps& caps) {
     // 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
+    // in sorting and opsTask reduction mode) the render tasks that depend on any proxy's current
     // state. This is because those opsTasks can still receive new ops and because if they refer to
     // the mipmapped version of 'proxy', they will then come to depend on the render task being
     // created here.
@@ -692,15 +693,8 @@
     // 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(proxy), flags)));
-    resolveTask->init(caps);
-
-    // GrTextureResolveRenderTask::init should have closed the texture proxy's previous task.
-    SkASSERT(!previousTaskBeforeMipsResolve || previousTaskBeforeMipsResolve->isClosed());
-    SkASSERT(resolveTask->fTarget->getLastRenderTask() == resolveTask);
-
-    return resolveTask;
+    return static_cast<GrTextureResolveRenderTask*>(fDAG.addBeforeLast(
+            sk_make_sp<GrTextureResolveRenderTask>()));
 }
 
 void GrDrawingManager::newWaitRenderTask(sk_sp<GrSurfaceProxy> proxy,
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 6c6d669..72f3211 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -27,6 +27,7 @@
 class GrRenderTargetProxy;
 class GrSoftwarePathRenderer;
 class GrTextureContext;
+class GrTextureResolveRenderTask;
 class SkDeferredDisplayList;
 
 class GrDrawingManager {
@@ -49,12 +50,10 @@
     // others). An unmanaged one is created and used by the onFlushCallback.
     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 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<GrSurfaceProxy>, GrSurfaceProxy::ResolveFlags, const GrCaps&);
+    // Create a render task that can resolve MSAA and/or regenerate mipmap levels on proxies. This
+    // method will only add the new render task to the list. It is up to the caller to call
+    // addProxy() on the returned object.
+    GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps&);
 
     // Create a new render task that will cause the gpu to wait on semaphores before executing any
     // more RenderTasks that target proxy. It is possible for this wait to also block additional
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index 781df12..2cf4af8 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -49,8 +49,9 @@
         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());
+            sk_make_sp<GrTextureResolveRenderTask>()).get());
+    task->addProxy(textureProxy, resolveFlags, *this->caps());
+    task->makeClosed(*this->caps());
 }
 
 bool GrOnFlushResourceProvider::assignUniqueKeyToProxy(const GrUniqueKey& key,
diff --git a/src/gpu/GrRenderTask.cpp b/src/gpu/GrRenderTask.cpp
index 42906dd..287fd5a 100644
--- a/src/gpu/GrRenderTask.cpp
+++ b/src/gpu/GrRenderTask.cpp
@@ -10,6 +10,7 @@
 #include "src/gpu/GrRenderTargetPriv.h"
 #include "src/gpu/GrStencilAttachment.h"
 #include "src/gpu/GrTextureProxyPriv.h"
+#include "src/gpu/GrTextureResolveRenderTask.h"
 
 uint32_t GrRenderTask::CreateUniqueID() {
     static std::atomic<uint32_t> nextID{1};
@@ -61,6 +62,12 @@
         }
     }
 
+    if (fTextureResolveTask) {
+        this->addDependency(fTextureResolveTask);
+        fTextureResolveTask->makeClosed(caps);
+        fTextureResolveTask = nullptr;
+    }
+
     this->setFlag(kClosed_Flag);
 }
 
@@ -146,18 +153,23 @@
 
     // 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(dependedOn), resolveFlags, caps);
+        if (!fTextureResolveTask) {
+            fTextureResolveTask = textureResolveManager.newTextureResolveRenderTask(caps);
+        }
+        fTextureResolveTask->addProxy(sk_ref_sp(dependedOn), resolveFlags, caps);
+
+        // addProxy() should have closed the texture proxy's previous task.
+        SkASSERT(!dependedOnTask || dependedOnTask->isClosed());
+        SkASSERT(dependedOn->getLastRenderTask() == fTextureResolveTask);
 
 #ifdef SK_DEBUG
-        // GrTextureResolveRenderTask::init should have called addDependency (in this instance,
-        // recursively) on the textureResolveTask.
+        // addProxy() should have called addDependency (in this instance, recursively) on
+        // fTextureResolveTask.
         if (dependedOnTask) {
-            SkASSERT(textureResolveTask->dependsOn(dependedOnTask));
+            SkASSERT(fTextureResolveTask->dependsOn(dependedOnTask));
         }
         if (textureProxy && textureProxy->texPriv().isDeferred()) {
-            SkASSERT(textureResolveTask->fDeferredProxies.back() == textureProxy);
+            SkASSERT(fTextureResolveTask->fDeferredProxies.back() == textureProxy);
         }
 
         // The GrTextureResolveRenderTask factory should have also marked the proxy clean, set the
@@ -168,13 +180,12 @@
         if (textureProxy) {
             SkASSERT(!textureProxy->mipMapsAreDirty());
         }
-        SkASSERT(dependedOn->getLastRenderTask() == textureResolveTask);
-        SkASSERT(textureResolveTask->isClosed());
+        SkASSERT(dependedOn->getLastRenderTask() == fTextureResolveTask);
 #endif
+        return;
+    }
 
-        // Fall through and add textureResolveTask as a dependency of "this".
-        dependedOnTask = textureResolveTask;
-    } else if (textureProxy && textureProxy->texPriv().isDeferred()) {
+    if (textureProxy && textureProxy->texPriv().isDeferred()) {
         fDeferredProxies.push_back(textureProxy);
     }
 
diff --git a/src/gpu/GrRenderTask.h b/src/gpu/GrRenderTask.h
index 2ad4656..0cb006f 100644
--- a/src/gpu/GrRenderTask.h
+++ b/src/gpu/GrRenderTask.h
@@ -17,6 +17,7 @@
 class GrOpFlushState;
 class GrOpsTask;
 class GrResourceAllocator;
+class GrTextureResolveRenderTask;
 
 // This class abstracts a task that targets a single GrSurfaceProxy, participates in the
 // GrDrawingManager's DAG, and implements the onExecute method to modify its target proxy's
@@ -77,7 +78,9 @@
 
     void visitTargetAndSrcProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const {
         this->visitProxies_debugOnly(fn);
-        fn(fTarget.get(), GrMipMapped::kNo);
+        if (fTarget) {
+            fn(fTarget.get(), GrMipMapped::kNo);
+        }
     }
 #endif
 
@@ -185,6 +188,11 @@
     SkSTArray<1, GrRenderTask*, true> fDependencies;
     // 'this' GrRenderTask's output is relied on by the GrRenderTasks in 'fDependents'
     SkSTArray<1, GrRenderTask*, true> fDependents;
+
+    // For performance reasons, we should perform texture resolves back-to-back as much as possible.
+    // (http://skbug.com/9406). To accomplish this, we make and reuse one single resolve task for
+    // each render task, then add it as a dependency during makeClosed().
+    GrTextureResolveRenderTask* fTextureResolveTask = nullptr;
 };
 
 #endif
diff --git a/src/gpu/GrTextureResolveManager.h b/src/gpu/GrTextureResolveManager.h
index a9b0182..f610c6b 100644
--- a/src/gpu/GrTextureResolveManager.h
+++ b/src/gpu/GrTextureResolveManager.h
@@ -25,11 +25,9 @@
     explicit GrTextureResolveManager(GrDrawingManager* drawingManager)
             : fDrawingManager(drawingManager) {}
 
-    GrRenderTask* newTextureResolveRenderTask(sk_sp<GrSurfaceProxy> proxy,
-                                              GrSurfaceProxy::ResolveFlags resolveFlags,
-                                              const GrCaps& caps) const {
+    GrTextureResolveRenderTask* newTextureResolveRenderTask(const GrCaps& caps) const {
         SkASSERT(fDrawingManager);
-        return fDrawingManager->newTextureResolveRenderTask(std::move(proxy), resolveFlags, caps);
+        return fDrawingManager->newTextureResolveRenderTask(caps);
     }
 
 private:
diff --git a/src/gpu/GrTextureResolveRenderTask.cpp b/src/gpu/GrTextureResolveRenderTask.cpp
index 0b77383..d97e5d5 100644
--- a/src/gpu/GrTextureResolveRenderTask.cpp
+++ b/src/gpu/GrTextureResolveRenderTask.cpp
@@ -14,58 +14,83 @@
 #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();
+GrTextureResolveRenderTask::~GrTextureResolveRenderTask() {
+    for (const auto& resolve : fResolves) {
+        // Ensure the proxy doesn't keep hold of a dangling back pointer.
+        resolve.fProxy->setLastRenderTask(nullptr);
+    }
+}
+
+void GrTextureResolveRenderTask::addProxy(
+        sk_sp<GrSurfaceProxy> proxy, GrSurfaceProxy::ResolveFlags flags, const GrCaps& caps) {
+    // 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());
+    SkASSERT(GrSurfaceProxy::ResolveFlags::kNone != flags);
+
+    if (GrSurfaceProxy::ResolveFlags::kMSAA & flags) {
+        GrRenderTargetProxy* renderTargetProxy = proxy->asRenderTargetProxy();
         SkASSERT(renderTargetProxy);
         SkASSERT(renderTargetProxy->isMSAADirty());
         renderTargetProxy->markMSAAResolved();
     }
 
-    if (GrSurfaceProxy::ResolveFlags::kMipMaps & fResolveFlags) {
-        GrTextureProxy* textureProxy = fTarget->asTextureProxy();
+    if (GrSurfaceProxy::ResolveFlags::kMipMaps & flags) {
+        GrTextureProxy* textureProxy = proxy->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
+    // Add the proxy as a dependency: We will read the existing contents of this texture while
     // generating mipmap levels and/or resolving MSAA.
-    //
-    // NOTE: This must be called before makeClosed.
-    this->addDependency(fTarget.get(), GrMipMapped::kNo, GrTextureResolveManager(nullptr), caps);
-    fTarget->setLastRenderTask(this);
+    this->addDependency(proxy.get(), GrMipMapped::kNo, GrTextureResolveManager(nullptr), caps);
+    proxy->setLastRenderTask(this);
 
-    // We only resolve the texture; nobody should try to do anything else with this opsTask.
-    this->makeClosed(caps);
+    fResolves.emplace_back(std::move(proxy), flags);
 }
 
 void GrTextureResolveRenderTask::gatherProxyIntervals(GrResourceAllocator* alloc) const {
-    // This renderTask doesn't have "normal" ops. In this case we still need to add an interval (so
-    // fEndOfOpsTaskOpIndices will remain in sync), so we create a fake op# to capture the fact that
-    // we manipulate fTarget.
-    alloc->addInterval(fTarget.get(), alloc->curOp(), alloc->curOp(),
-                       GrResourceAllocator::ActualUse::kYes);
+    // This renderTask doesn't have "normal" ops, however we still need to add intervals so
+    // fEndOfOpsTaskOpIndices will remain in sync. We create fake op#'s to capture the fact that we
+    // manipulate the resolve proxies.
+    auto fakeOp = alloc->curOp();
+    for (const auto& resolve : fResolves) {
+        alloc->addInterval(resolve.fProxy.get(), fakeOp, fakeOp,
+                           GrResourceAllocator::ActualUse::kYes);
+    }
     alloc->incOps();
 }
 
 bool GrTextureResolveRenderTask::onExecute(GrOpFlushState* flushState) {
-    // Resolve msaa before regenerating mipmaps.
-    if (GrSurfaceProxy::ResolveFlags::kMSAA & fResolveFlags) {
-        GrRenderTarget* renderTarget = fTarget->peekRenderTarget();
-        SkASSERT(renderTarget);
-        if (renderTarget->needsResolve()) {
-            flushState->gpu()->resolveRenderTarget(renderTarget);
+    // Resolve all msaa back-to-back, before regenerating mipmaps.
+    for (const auto& resolve : fResolves) {
+        if (GrSurfaceProxy::ResolveFlags::kMSAA & resolve.fFlags) {
+            GrRenderTarget* renderTarget = resolve.fProxy->peekRenderTarget();
+            SkASSERT(renderTarget);
+            if (renderTarget->needsResolve()) {
+                flushState->gpu()->resolveRenderTarget(renderTarget);
+            }
         }
     }
-
-    if (GrSurfaceProxy::ResolveFlags::kMipMaps & fResolveFlags) {
-        GrTexture* texture = fTarget->peekTexture();
-        SkASSERT(texture);
-        if (texture->texturePriv().mipMapsAreDirty()) {
-            flushState->gpu()->regenerateMipMapLevels(texture);
+    // Regenerate all mipmaps back-to-back.
+    for (const auto& resolve : fResolves) {
+        if (GrSurfaceProxy::ResolveFlags::kMipMaps & resolve.fFlags) {
+            GrTexture* texture = resolve.fProxy->peekTexture();
+            SkASSERT(texture);
+            if (texture->texturePriv().mipMapsAreDirty()) {
+                flushState->gpu()->regenerateMipMapLevels(texture);
+            }
         }
     }
 
     return true;
 }
+
+#ifdef SK_DEBUG
+void GrTextureResolveRenderTask::visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const {
+    for (const auto& resolve : fResolves) {
+        fn(resolve.fProxy.get(), GrMipMapped::kNo);
+    }
+}
+#endif
diff --git a/src/gpu/GrTextureResolveRenderTask.h b/src/gpu/GrTextureResolveRenderTask.h
index 86365dc..b175b0b 100644
--- a/src/gpu/GrTextureResolveRenderTask.h
+++ b/src/gpu/GrTextureResolveRenderTask.h
@@ -12,17 +12,10 @@
 
 class GrTextureResolveRenderTask final : public GrRenderTask {
 public:
-    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);
-    }
+    GrTextureResolveRenderTask() : GrRenderTask(nullptr) {}
+    ~GrTextureResolveRenderTask() override;
 
-    void init(const GrCaps&);
+    void addProxy(sk_sp<GrSurfaceProxy>, GrSurfaceProxy::ResolveFlags, const GrCaps&);
 
 private:
     void onPrepare(GrOpFlushState*) override {}
@@ -40,11 +33,17 @@
     bool onExecute(GrOpFlushState*) override;
 
 #ifdef SK_DEBUG
-    // No non-dst proxies.
-    void visitProxies_debugOnly(const VisitSurfaceProxyFunc& fn) const override {}
+    SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;)
 #endif
 
-    const GrSurfaceProxy::ResolveFlags fResolveFlags;
+    struct Resolve {
+        Resolve(sk_sp<GrSurfaceProxy> proxy, GrSurfaceProxy::ResolveFlags flags)
+                : fProxy(std::move(proxy)), fFlags(flags) {}
+        sk_sp<GrSurfaceProxy> fProxy;
+        GrSurfaceProxy::ResolveFlags fFlags;
+    };
+
+    SkSTArray<4, Resolve> fResolves;
 };
 
 #endif