Improve DDLPromiseImageHelper

Split apart creation of the callback contexts and the backend textures
   This allows the texture upload to be done separately on the
   gpu-thread

Add a backendFormat member to the promise image callback context
   This allows the promise images to be created in CreatePromiseImages
   before the backend textures have been created (i.e., the backend
   textures can now be created on the gpu-thread so we have no
   guarantee they will be available when the SKP is being reinflated
   w/ promise images)

Change-Id: I1e21385e450a5ef27dd6950d9d6aee737aa7515d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/270939
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 9100c15..29def71 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1931,8 +1931,10 @@
         }
         sk_sp<SkSurface> surface = sk_ref_sp(tmp);
 
+        promiseImageHelper.createCallbackContexts(context);
+
         // This is here bc this is the first point where we have access to the context
-        promiseImageHelper.uploadAllToGPU(context);
+        promiseImageHelper.uploadAllToGPU(nullptr, context);
         // We draw N times, with a clear between.
         for (int replay = 0; replay < fNumReplays; ++replay) {
             if (replay > 0) {
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
index e612bc4..e068a0d 100644
--- a/tools/DDLPromiseImageHelper.cpp
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -14,6 +14,8 @@
 #include "include/core/SkYUVASizeInfo.h"
 #include "include/gpu/GrContext.h"
 #include "src/core/SkCachedData.h"
+#include "src/core/SkTaskGroup.h"
+#include "src/gpu/GrContextPriv.h"
 #include "src/image/SkImage_Base.h"
 #include "src/image/SkImage_GpuYUVA.h"
 
@@ -31,6 +33,7 @@
 void DDLPromiseImageHelper::PromiseImageCallbackContext::setBackendTexture(
         const GrBackendTexture& backendTexture) {
     SkASSERT(!fPromiseImageTexture);
+    SkASSERT(fBackendFormat == backendTexture.getBackendFormat());
     fPromiseImageTexture = SkPromiseImageTexture::Make(backendTexture);
 }
 
@@ -71,42 +74,107 @@
     return context->createBackendTexture(&pm, 1, GrRenderable::kNo, GrProtected::kNo);
 }
 
-void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
-    for (int i = 0; i < fImageInfo.count(); ++i) {
-        const PromiseImageInfo& info = fImageInfo[i];
+/*
+ * Create backend textures and upload data to them for all the textures required to satisfy
+ * a single promise image.
+ * For YUV textures this will result in up to 4 actual textures.
+ */
+void DDLPromiseImageHelper::CreateBETexturesForPromiseImage(GrContext* context,
+                                                            PromiseImageInfo* info) {
+    SkASSERT(context->priv().asDirectContext());
 
-        // DDL TODO: how can we tell if we need mipmapping!
+    // DDL TODO: how can we tell if we need mipmapping!
+    if (info->isYUV()) {
+        int numPixmaps;
+        SkAssertResult(SkYUVAIndex::AreValidIndices(info->yuvaIndices(), &numPixmaps));
+        for (int j = 0; j < numPixmaps; ++j) {
+            const SkPixmap& yuvPixmap = info->yuvPixmap(j);
+
+            PromiseImageCallbackContext* callbackContext = info->callbackContext(j);
+            SkASSERT(callbackContext);
+
+            callbackContext->setBackendTexture(create_yuva_texture(context, yuvPixmap,
+                                                                   info->yuvaIndices(), j));
+            SkASSERT(callbackContext->promiseImageTexture());
+        }
+    } else {
+        PromiseImageCallbackContext* callbackContext = info->callbackContext(0);
+        if (!callbackContext) {
+            // This texture would've been too large to fit on the GPU
+            return;
+        }
+
+        const SkBitmap& bm = info->normalBitmap();
+
+        GrBackendTexture backendTex = context->createBackendTexture(
+                                                    &bm.pixmap(), 1, GrRenderable::kNo,
+                                                    GrProtected::kNo);
+        SkASSERT(backendTex.isValid());
+
+        callbackContext->setBackendTexture(backendTex);
+    }
+}
+
+void DDLPromiseImageHelper::createCallbackContexts(GrContext* context) {
+    const GrCaps* caps = context->priv().caps();
+    const int maxDimension = caps->maxTextureSize();
+
+    for (int i = 0; i < fImageInfo.count(); ++i) {
+        PromiseImageInfo& info = fImageInfo[i];
+
         if (info.isYUV()) {
             int numPixmaps;
             SkAssertResult(SkYUVAIndex::AreValidIndices(info.yuvaIndices(), &numPixmaps));
+
             for (int j = 0; j < numPixmaps; ++j) {
                 const SkPixmap& yuvPixmap = info.yuvPixmap(j);
 
+                GrBackendFormat backendFormat = context->defaultBackendFormat(yuvPixmap.colorType(),
+                                                                              GrRenderable::kNo);
+
                 sk_sp<PromiseImageCallbackContext> callbackContext(
-                                                        new PromiseImageCallbackContext(context));
+                    new PromiseImageCallbackContext(context, backendFormat));
 
-                callbackContext->setBackendTexture(create_yuva_texture(context, yuvPixmap,
-                                                                       info.yuvaIndices(), j));
-                SkASSERT(callbackContext->promiseImageTexture());
-
-                fImageInfo[i].setCallbackContext(j, std::move(callbackContext));
+                info.setCallbackContext(j, std::move(callbackContext));
             }
         } else {
-            sk_sp<PromiseImageCallbackContext> callbackContext(
-                                                    new PromiseImageCallbackContext(context));
-
             const SkBitmap& bm = info.normalBitmap();
 
-            GrBackendTexture backendTex = context->createBackendTexture(
-                                                        &bm.pixmap(), 1, GrRenderable::kNo,
-                                                        GrProtected::kNo);
+            // TODO: explicitly mark the PromiseImageInfo as too big and check in uploadAllToGPU
+            if (maxDimension < std::max(bm.width(), bm.height())) {
+                // This won't fit on the GPU. Fallback to a raster-backed image per tile.
+                continue;
+            }
 
-            callbackContext->setBackendTexture(backendTex);
+            GrBackendFormat backendFormat = context->defaultBackendFormat(bm.pixmap().colorType(),
+                                                                          GrRenderable::kNo);
+            if (!caps->isFormatTexturable(backendFormat)) {
+                continue;
+            }
 
-            // The GMs sometimes request too large an image
-            //SkAssertResult(callbackContext->backendTexture().isValid());
 
-            fImageInfo[i].setCallbackContext(0, std::move(callbackContext));
+            sk_sp<PromiseImageCallbackContext> callbackContext(
+                new PromiseImageCallbackContext(context, backendFormat));
+
+            info.setCallbackContext(0, std::move(callbackContext));
+        }
+    }
+}
+
+void DDLPromiseImageHelper::uploadAllToGPU(SkTaskGroup* taskGroup, GrContext* context) {
+    SkASSERT(context->priv().asDirectContext());
+
+    if (taskGroup) {
+        for (int i = 0; i < fImageInfo.count(); ++i) {
+            PromiseImageInfo* info = &fImageInfo[i];
+
+            taskGroup->add([context, info]() {
+                              CreateBETexturesForPromiseImage(context, info);
+                           });
+        }
+    } else {
+        for (int i = 0; i < fImageInfo.count(); ++i) {
+            CreateBETexturesForPromiseImage(context, &fImageInfo[i]);
         }
     }
 }
@@ -119,7 +187,7 @@
 
     SkDeserialProcs procs;
     procs.fImageCtx = (void*) &perRecorderContext;
-    procs.fImageProc = PromiseImageCreator;
+    procs.fImageProc = CreatePromiseImages;
 
     return SkPicture::MakeFromData(compressedPictureData, &procs);
 }
@@ -127,7 +195,7 @@
 // This generates promise images to replace the indices in the compressed picture. This
 // reconstitution is performed separately in each thread so we end up with multiple
 // promise images referring to the same GrBackendTexture.
-sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
+sk_sp<SkImage> DDLPromiseImageHelper::CreatePromiseImages(const void* rawData,
                                                           size_t length, void* ctxIn) {
     PerRecorderContext* perRecorderContext = static_cast<PerRecorderContext*>(ctxIn);
     const DDLPromiseImageHelper* helper = perRecorderContext->fHelper;
@@ -142,13 +210,13 @@
 
     const DDLPromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
 
-    if (!curImage.promiseTexture(0)) {
-        SkASSERT(!curImage.isYUV());
-        // We weren't able to make a backend texture for this SkImage. In this case we create
-        // a separate bitmap-backed image for each thread.
+    // If there is no callback context that means 'createCallbackContexts' determined the
+    // texture wouldn't fit on the GPU. Create a separate bitmap-backed image for each thread.
+    if (!curImage.isYUV() && !curImage.callbackContext(0)) {
         SkASSERT(curImage.normalBitmap().isImmutable());
         return SkImage::MakeFromBitmap(curImage.normalBitmap());
     }
+
     SkASSERT(curImage.index() == *indexPtr);
 
     sk_sp<SkImage> image;
@@ -160,8 +228,7 @@
         int textureCount;
         SkAssertResult(SkYUVAIndex::AreValidIndices(curImage.yuvaIndices(), &textureCount));
         for (int i = 0; i < textureCount; ++i) {
-            const GrBackendTexture& backendTex = curImage.promiseTexture(i)->backendTexture();
-            backendFormats[i] = backendTex.getBackendFormat();
+            backendFormats[i] = curImage.backendFormat(i);
             SkASSERT(backendFormats[i].isValid());
             contexts[i] = curImage.refCallbackContext(i).release();
             sizes[i].set(curImage.yuvPixmap(i).width(), curImage.yuvPixmap(i).height());
@@ -198,8 +265,7 @@
         }
 #endif
     } else {
-        const GrBackendTexture& backendTex = curImage.promiseTexture(0)->backendTexture();
-        GrBackendFormat backendFormat = backendTex.getBackendFormat();
+        GrBackendFormat backendFormat = curImage.backendFormat(0);
         SkASSERT(backendFormat.isValid());
 
         // Each DDL recorder gets its own ref on the promise callback context for the
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
index 99d883e..7fff09e 100644
--- a/tools/DDLPromiseImageHelper.h
+++ b/tools/DDLPromiseImageHelper.h
@@ -53,7 +53,9 @@
     // Convert the SkPicture into SkData replacing all the SkImages with an index.
     sk_sp<SkData> deflateSKP(const SkPicture* inputPicture);
 
-    void uploadAllToGPU(GrContext* context);
+    void createCallbackContexts(GrContext*);
+
+    void uploadAllToGPU(SkTaskGroup*, GrContext*);
 
     // reinflate a deflated SKP, replacing all the indices with promise images.
     sk_sp<SkPicture> reinflateSKP(SkDeferredDisplayListRecorder*,
@@ -73,10 +75,14 @@
     // it drops all of its refs (via "reset").
     class PromiseImageCallbackContext : public SkRefCnt {
     public:
-        PromiseImageCallbackContext(GrContext* context) : fContext(context) {}
+        PromiseImageCallbackContext(GrContext* context, GrBackendFormat backendFormat)
+                : fContext(context)
+                , fBackendFormat(backendFormat) {}
 
         ~PromiseImageCallbackContext();
 
+        const GrBackendFormat& backendFormat() const { return fBackendFormat; }
+
         void setBackendTexture(const GrBackendTexture& backendTexture);
 
         sk_sp<SkPromiseImageTexture> fulfill() {
@@ -101,17 +107,18 @@
         void wasAddedToImage() { fNumImages++; }
 
         const SkPromiseImageTexture* promiseImageTexture() const {
-          return fPromiseImageTexture.get();
+            return fPromiseImageTexture.get();
         }
 
     private:
-        GrContext* fContext;
+        GrContext*                   fContext;
+        GrBackendFormat              fBackendFormat;
         sk_sp<SkPromiseImageTexture> fPromiseImageTexture;
-        int fNumImages = 0;
-        int fTotalFulfills = 0;
-        int fTotalReleases = 0;
-        int fUnreleasedFulfills = 0;
-        int fDoneCnt = 0;
+        int                          fNumImages = 0;
+        int                          fTotalFulfills = 0;
+        int                          fTotalReleases = 0;
+        int                          fUnreleasedFulfills = 0;
+        int                          fDoneCnt = 0;
 
         typedef SkRefCnt INHERITED;
     };
@@ -169,6 +176,10 @@
             return fCallbackContexts[index];
         }
 
+        const GrBackendFormat& backendFormat(int index) const {
+            SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
+            return fCallbackContexts[index]->backendFormat();
+        }
         const SkPromiseImageTexture* promiseTexture(int index) const {
             SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
             return fCallbackContexts[index]->promiseImageTexture();
@@ -216,6 +227,8 @@
         SkTArray<sk_sp<SkImage>>*      fPromiseImages;
     };
 
+    static void CreateBETexturesForPromiseImage(GrContext*, PromiseImageInfo*);
+
     static sk_sp<SkPromiseImageTexture> PromiseImageFulfillProc(void* textureContext) {
         auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
         return callbackContext->fulfill();
@@ -232,7 +245,7 @@
         callbackContext->unref();
     }
 
-    static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn);
+    static sk_sp<SkImage> CreatePromiseImages(const void* rawData, size_t length, void* ctxIn);
 
     bool isValidID(int id) const { return id >= 0 && id < fImageInfo.count(); }
     const PromiseImageInfo& getInfo(int id) const { return fImageInfo[id]; }
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index 1799bc6..31e01e3 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -238,7 +238,9 @@
         exitf(ExitErr::kUnavailable, "DDL: conversion of skp failed");
     }
 
-    promiseImageHelper.uploadAllToGPU(context);
+    promiseImageHelper.createCallbackContexts(context);
+
+    promiseImageHelper.uploadAllToGPU(nullptr, context);
 
     DDLTileHelper tiles(surface, viewport, FLAGS_ddlTilingWidthHeight);