Create DDL for final composition step in DDL test harness

Besides better matching Viz's behavior this also reduces a lot of choppiness in the composition RenderTask DAG.

In the previous approach DDL draws and compositing draws would be interleaved resulting in a lot of render target swaps.

This necessitated some reorganization bc I wanted to reuse PromiseImageCallbackContext to manage the tiles' promiseImages.

Change-Id: I513bf060a69ff2bfe0e7b82ae72f149dfede632e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/285056
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/tools/DDLTileHelper.cpp b/tools/DDLTileHelper.cpp
index 8477b1f..075ad75 100644
--- a/tools/DDLTileHelper.cpp
+++ b/tools/DDLTileHelper.cpp
@@ -19,16 +19,21 @@
 #include "tools/DDLPromiseImageHelper.h"
 
 void DDLTileHelper::TileData::init(int id,
-                                   sk_sp<SkSurface> dstSurface,
+                                   GrContext* context,
                                    const SkSurfaceCharacterization& dstSurfaceCharacterization,
                                    const SkIRect& clip) {
     fID = id;
-    fDstSurface = dstSurface;
     fClip = clip;
 
     fCharacterization = dstSurfaceCharacterization.createResized(clip.width(), clip.height());
     SkASSERT(fCharacterization.isValid());
-    SkASSERT(!fBackendTexture.isValid());
+
+    GrBackendFormat backendFormat = context->defaultBackendFormat(fCharacterization.colorType(),
+                                                                  GrRenderable::kYes);
+    SkDEBUGCODE(const GrCaps* caps = context->priv().caps());
+    SkASSERT(caps->isFormatTexturable(backendFormat));
+
+    fCallbackContext.reset(new PromiseImageCallbackContext(context, backendFormat));
 }
 
 DDLTileHelper::TileData::~TileData() {}
@@ -83,6 +88,29 @@
     fDisplayList = recorder.detach();
 }
 
+void DDLTileHelper::createComposeDDL() {
+    SkASSERT(!fComposeDDL);
+
+    SkDeferredDisplayListRecorder recorder(fDstCharacterization);
+
+    SkCanvas* recordingCanvas = recorder.getCanvas();
+
+    for (int i = 0; i < this->numTiles(); ++i) {
+        TileData* tile = &fTiles[i];
+
+        sk_sp<SkImage> promiseImage = tile->makePromiseImage(&recorder);
+
+        SkIRect clipRect = tile->clipRect();
+
+        SkASSERT(clipRect.width() == promiseImage->width() &&
+                 clipRect.height() == promiseImage->height());
+
+        recordingCanvas->drawImage(promiseImage, clipRect.fLeft, clipRect.fTop);
+    }
+
+    fComposeDDL = recorder.detach();
+}
+
 void DDLTileHelper::TileData::precompile(GrContext* context) {
     SkASSERT(fDisplayList);
 
@@ -93,12 +121,18 @@
 }
 
 sk_sp<SkSurface> DDLTileHelper::TileData::makeWrappedTileDest(GrContext* context) {
-    if (!fBackendTexture.isValid()) {
+    SkASSERT(fCallbackContext && fCallbackContext->promiseImageTexture());
+
+    auto promiseImageTexture = fCallbackContext->promiseImageTexture();
+    if (!promiseImageTexture->backendTexture().isValid()) {
         return nullptr;
     }
 
+    // Here we are, unfortunately, aliasing the backend texture held by the SkPromiseImageTexture.
+    // Both the tile's destination surface and the promise image used to draw the tile will be
+    // backed by the same backendTexture - unbeknownst to Ganesh.
     return SkSurface::MakeFromBackendTexture(context,
-                                             fBackendTexture,
+                                             promiseImageTexture->backendTexture(),
                                              fCharacterization.origin(),
                                              fCharacterization.sampleCount(),
                                              fCharacterization.colorType(),
@@ -126,7 +160,10 @@
 void DDLTileHelper::TileData::draw(GrContext* context) {
     SkASSERT(fDisplayList && !fTileSurface);
 
-    // The tile's surface needs to be held until after the DDL is flushed
+    // The tile's surface needs to be held until after the DDL is flushed bc the DDL doesn't take
+    // a ref on its destination proxy.
+    // TODO: make the DDL (or probably the drawing manager) take a ref on the destination proxy
+    // (maybe in GrDrawingManager::addDDLTarget).
     fTileSurface = this->makeWrappedTileDest(context);
     if (fTileSurface) {
         fTileSurface->draw(fDisplayList.get());
@@ -136,65 +173,67 @@
     }
 }
 
-// TODO: We should create a single DDL for the composition step and just add replaying it
-// as the last GPU task
-void DDLTileHelper::TileData::compose(GrContext* context) {
-    SkASSERT(context->priv().asDirectContext());
-    SkASSERT(fDstSurface);
-
-    if (!fBackendTexture.isValid()) {
-        return;
-    }
-
-    // Here we are, unfortunately, aliasing 'fBackendTexture'. It is backing both 'fTileSurface'
-    // and 'tmp'.
-    sk_sp<SkImage> tmp = SkImage::MakeFromTexture(context,
-                                                  fBackendTexture,
-                                                  fCharacterization.origin(),
-                                                  fCharacterization.colorType(),
-                                                  kPremul_SkAlphaType,
-                                                  fCharacterization.refColorSpace());
-
-    SkCanvas* canvas = fDstSurface->getCanvas();
-    canvas->save();
-    canvas->clipRect(SkRect::Make(fClip));
-    canvas->drawImage(tmp, fClip.fLeft, fClip.fTop);
-    canvas->restore();
-}
-
 void DDLTileHelper::TileData::reset() {
     // TODO: when DDLs are re-renderable we don't need to do this
     fDisplayList = nullptr;
+
     fTileSurface = nullptr;
 }
 
+sk_sp<SkImage> DDLTileHelper::TileData::makePromiseImage(SkDeferredDisplayListRecorder* recorder) {
+    SkASSERT(fCallbackContext);
+
+    // The promise image gets a ref on the promise callback context
+    sk_sp<SkImage> promiseImage = recorder->makePromiseTexture(
+                                    fCallbackContext->backendFormat(),
+                                    fClip.width(),
+                                    fClip.height(),
+                                    GrMipMapped::kNo,
+                                    GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin,
+                                    fCharacterization.colorType(),
+                                    kPremul_SkAlphaType,
+                                    fCharacterization.refColorSpace(),
+                                    PromiseImageCallbackContext::PromiseImageFulfillProc,
+                                    PromiseImageCallbackContext::PromiseImageReleaseProc,
+                                    PromiseImageCallbackContext::PromiseImageDoneProc,
+                                    (void*)this->refCallbackContext().release(),
+                                    SkDeferredDisplayListRecorder::PromiseImageApiVersion::kNew);
+    fCallbackContext->wasAddedToImage();
+
+    return promiseImage;
+}
+
 void DDLTileHelper::TileData::CreateBackendTexture(GrContext* context, TileData* tile) {
     SkASSERT(context->priv().asDirectContext());
-    SkASSERT(!tile->fBackendTexture.isValid());
+    SkASSERT(tile->fCallbackContext && !tile->fCallbackContext->promiseImageTexture());
 
-    tile->fBackendTexture = context->createBackendTexture(tile->fCharacterization);
-    // TODO: it seems that, on the Linux bots, backend texture creation is failing
-    // a lot (skbug.com/10142)
-    //SkASSERT(tile->fBackendTexture.isValid());
+    GrBackendTexture beTex = context->createBackendTexture(tile->fCharacterization);
+    tile->fCallbackContext->setBackendTexture(beTex);
 }
 
 void DDLTileHelper::TileData::DeleteBackendTexture(GrContext* context, TileData* tile) {
     SkASSERT(context->priv().asDirectContext());
+    SkASSERT(tile->fCallbackContext);
+
     // TODO: it seems that, on the Linux bots, backend texture creation is failing
     // a lot (skbug.com/10142)
-    //SkASSERT(tile->fBackendTexture.isValid());
+    SkASSERT(!tile->fCallbackContext->promiseImageTexture() ||
+             tile->fCallbackContext->promiseImageTexture()->backendTexture().isValid());
 
     tile->fTileSurface = nullptr;
-    context->deleteBackendTexture(tile->fBackendTexture);
+
+    SkASSERT(tile->fCallbackContext->unique());
+    tile->fCallbackContext.reset();
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-DDLTileHelper::DDLTileHelper(sk_sp<SkSurface> dstSurface,
+DDLTileHelper::DDLTileHelper(GrContext* context,
                              const SkSurfaceCharacterization& dstChar,
                              const SkIRect& viewport,
                              int numDivisions)
-        : fNumDivisions(numDivisions) {
+        : fNumDivisions(numDivisions)
+        , fDstCharacterization(dstChar) {
     SkASSERT(fNumDivisions > 0);
     fTiles = new TileData[this->numTiles()];
 
@@ -212,7 +251,7 @@
 
             SkASSERT(viewport.contains(clip));
 
-            fTiles[y*fNumDivisions+x].init(y*fNumDivisions+x, dstSurface, dstChar, clip);
+            fTiles[y*fNumDivisions+x].init(y*fNumDivisions+x, context, dstChar, clip);
         }
     }
 }
@@ -227,12 +266,14 @@
 void DDLTileHelper::createDDLsInParallel() {
 #if 1
     SkTaskGroup().batch(this->numTiles(), [&](int i) { fTiles[i].createDDL(); });
+    SkTaskGroup().add([this]{ this->createComposeDDL(); });
     SkTaskGroup().wait();
 #else
     // Use this code path to debug w/o threads
     for (int i = 0; i < this->numTiles(); ++i) {
         fTiles[i].createDDL();
     }
+    this->createComposeDDL();
 #endif
 }
 
@@ -246,10 +287,6 @@
     tile->precompile(context);
 
     tile->draw(context);
-
-    // TODO: we should actually have a separate DDL that does
-    // the final composition draw
-    tile->compose(context);
 }
 
 // We expect to have more than one recording thread but just one gpu thread
@@ -274,6 +311,8 @@
                                     });
                                 });
     }
+
+    recordingTaskGroup->add([this] { this->createComposeDDL(); });
 }
 
 void DDLTileHelper::precompileAndDrawAllTiles(GrContext* context) {
@@ -296,9 +335,9 @@
     }
 }
 
-void DDLTileHelper::composeAllTiles(GrContext* context) {
+void DDLTileHelper::dropCallbackContexts() {
     for (int i = 0; i < this->numTiles(); ++i) {
-        fTiles[i].compose(context);
+        fTiles[i].dropCallbackContext();
     }
 }
 
@@ -306,6 +345,7 @@
     for (int i = 0; i < this->numTiles(); ++i) {
         fTiles[i].reset();
     }
+    fComposeDDL.reset();
 }
 
 void DDLTileHelper::createBackendTextures(SkTaskGroup* taskGroup, GrContext* context) {