Add DDL to SKPBench

Most of this CL is just repackaging the promise image and tile
code from ViaDDL for reuse by SKPBench.

Change-Id: Ie5003c36fe85cc5be9639552f9488b8e92dcdbbf
Reviewed-on: https://skia-review.googlesource.com/129805
Reviewed-by: Chris Dalton <csmartdalton@google.com>
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 9def615..ffe01db 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -9,6 +9,8 @@
 #include <cmath>
 #include <functional>
 #include "../src/jumper/SkJumper.h"
+#include "DDLPromiseImageHelper.h"
+#include "DDLTileHelper.h"
 #include "Resources.h"
 #include "SkAndroidCodec.h"
 #include "SkAutoMalloc.h"
@@ -2019,302 +2021,6 @@
     , fNumDivisions(numDivisions) {
 }
 
-// This class consolidates tracking & extraction of the original image data from the sources,
-// the upload of said data to the GPU and the fulfillment of promise images.
-//
-// The way this works is:
-//    the original skp is converted to SkData and all its image info is extracted into this
-//       class and only indices into this class are left in the SkData
-//    Prior to replaying in threads, all the images stored in this class are uploaded to the
-//       gpu and PromiseImageCallbackContexts are created for them
-//    Each thread reinflates the SkData into an SkPicture replacing all the indices w/
-//       promise images (all using the same GrBackendTexture and getting a ref to the
-//       appropriate PromiseImageCallbackContext) and then creates a DDL.
-//    This class is then reset - dropping all of its refs on the PromiseImageCallbackContexts
-//    Each done callback unrefs its PromiseImageCallbackContext so, once all the promise images
-//       are done the PromiseImageCallbackContext is freed and its GrBackendTexture removed
-//       from VRAM
-//
-class ViaDDL::PromiseImageHelper {
-public:
-    // This class acts as a proxy for the single GrBackendTexture representing an image.
-    // Whenever a promise image is created for the image the promise image receives a ref to
-    // this object. Once all the promise images receive their done callbacks this object
-    // is deleted - removing the GrBackendTexture from VRAM.
-    // Note that while the DDLs are being created in the threads, the PromiseImageHelper holds
-    // a ref on all the PromiseImageCallbackContexts. However, once all the threads are done
-    // it drops all of its refs (via "reset").
-    class PromiseImageCallbackContext : public SkRefCnt {
-    public:
-        PromiseImageCallbackContext(GrContext* context) : fContext(context) {}
-
-        ~PromiseImageCallbackContext() {
-            GrGpu* gpu = fContext->contextPriv().getGpu();
-
-            if (fBackendTexture.isValid()) {
-                gpu->deleteTestingOnlyBackendTexture(fBackendTexture);
-            }
-        }
-
-        void setBackendTexture(const GrBackendTexture& backendTexture) {
-            fBackendTexture = backendTexture;
-        }
-
-        const GrBackendTexture& backendTexture() const { return fBackendTexture; }
-
-    private:
-        GrContext*       fContext;
-        GrBackendTexture fBackendTexture;
-
-        typedef SkRefCnt INHERITED;
-    };
-
-    // This is the information extracted into this class from the parsing of the skp file.
-    // Once it has all been uploaded to the GPU and distributed to the promise images, it
-    // is all dropped via "reset".
-    class PromiseImageInfo {
-    public:
-        int                                fIndex;                // index in the 'fImageInfo' array
-        uint32_t                           fOriginalUniqueID;     // original ID for deduping
-        SkBitmap                           fBitmap;               // CPU-side cache of the contents
-        sk_sp<PromiseImageCallbackContext> fCallbackContext;
-    };
-
-    PromiseImageHelper() { }
-
-    void reset() { fImageInfo.reset(); }
-
-    bool isValidID(int id) const {
-        return id >= 0 && id < fImageInfo.count();
-    }
-
-    const PromiseImageInfo& getInfo(int id) const {
-        return fImageInfo[id];
-    }
-
-    // returns -1 on failure
-    int findOrDefineImage(SkImage* image) {
-        int preExistingID = this->findImage(image);
-        if (preExistingID >= 0) {
-            SkASSERT(this->isValidID(preExistingID));
-            return preExistingID;
-        }
-
-        int newID = this->addImage(image);
-        SkASSERT(this->isValidID(newID));
-        return newID;
-    }
-
-    void uploadAllToGPU(GrContext* context) {
-        GrGpu* gpu = context->contextPriv().getGpu();
-        SkASSERT(gpu);
-
-        for (int i = 0; i < fImageInfo.count(); ++i) {
-            sk_sp<PromiseImageCallbackContext> callbackContext(
-                                                    new PromiseImageCallbackContext(context));
-
-            const PromiseImageInfo& info = fImageInfo[i];
-
-            // DDL TODO: how can we tell if we need mipmapping!
-            callbackContext->setBackendTexture(gpu->createTestingOnlyBackendTexture(
-                                                                info.fBitmap.getPixels(),
-                                                                info.fBitmap.width(),
-                                                                info.fBitmap.height(),
-                                                                info.fBitmap.colorType(),
-                                                                info.fBitmap.colorSpace(),
-                                                                false, GrMipMapped::kNo));
-            // The GMs sometimes request too large an image
-            //SkAssertResult(callbackContext->backendTexture().isValid());
-
-            // The fImageInfo array gets the creation ref
-            fImageInfo[i].fCallbackContext = std::move(callbackContext);
-        }
-    }
-
-    static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
-        auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
-        SkASSERT(callbackContext->backendTexture().isValid());
-        *outTexture = callbackContext->backendTexture();
-    }
-
-    static void PromiseImageReleaseProc(void* textureContext) {
-#ifdef SK_DEBUG
-        auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
-        SkASSERT(callbackContext->backendTexture().isValid());
-#endif
-    }
-
-    static void PromiseImageDoneProc(void* textureContext) {
-        auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
-        callbackContext->unref();
-    }
-
-private:
-    // returns -1 if not found
-    int findImage(SkImage* image) const {
-        for (int i = 0; i < fImageInfo.count(); ++i) {
-            if (fImageInfo[i].fOriginalUniqueID == image->uniqueID()) {
-                SkASSERT(fImageInfo[i].fIndex == i);
-                SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].fIndex));
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    // returns -1 on failure
-    int addImage(SkImage* image) {
-        sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
-
-        SkImageInfo ii = SkImageInfo::Make(rasterImage->width(), rasterImage->height(),
-                                           rasterImage->colorType(), rasterImage->alphaType(),
-                                           rasterImage->refColorSpace());
-
-        SkBitmap bm;
-        bm.allocPixels(ii);
-
-        if (!rasterImage->readPixels(bm.pixmap(), 0, 0)) {
-            return -1;
-        }
-
-        bm.setImmutable();
-
-        PromiseImageInfo newImageInfo;
-        newImageInfo.fIndex = fImageInfo.count();
-        newImageInfo.fOriginalUniqueID = image->uniqueID();
-        newImageInfo.fBitmap = bm;
-        /* fCallbackContext is filled in by uploadAllToGPU */
-
-        fImageInfo.push_back(newImageInfo);
-        SkASSERT(newImageInfo.fIndex == fImageInfo.count()-1);
-        return fImageInfo.count()-1;
-    }
-
-    SkTArray<PromiseImageInfo> fImageInfo;
-};
-
-// TileData class encapsulates the information and behavior for a single tile/thread in
-// a DDL rendering.
-class ViaDDL::TileData {
-public:
-    // Note: we could just pass in surface characterization
-    TileData(sk_sp<SkSurface> surf, const SkIRect& clip)
-            : fSurface(std::move(surf))
-            , fClip(clip) {
-        SkAssertResult(fSurface->characterize(&fCharacterization));
-    }
-
-    // This method operates in parallel
-    // In each thread we will reconvert the compressedPictureData into an SkPicture
-    // replacing each image-index with a promise image.
-    void preprocess(SkData* compressedPictureData, const PromiseImageHelper& helper) {
-
-        SkDeferredDisplayListRecorder recorder(fCharacterization);
-
-        // DDL TODO: the DDLRecorder's GrContext isn't initialized until getCanvas is called.
-        // Maybe set it up in the ctor?
-        SkCanvas* subCanvas = recorder.getCanvas();
-
-        sk_sp<SkPicture> reconstitutedPicture;
-
-        {
-            PerRecorderContext perRecorderContext { &recorder, &helper };
-
-            SkDeserialProcs procs;
-            procs.fImageCtx = (void*) &perRecorderContext;
-            procs.fImageProc = PromiseImageCreator;
-
-            reconstitutedPicture = SkPicture::MakeFromData(compressedPictureData, &procs);
-            if (!reconstitutedPicture) {
-                return;
-            }
-        }
-
-        subCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
-        subCanvas->translate(-fClip.fLeft, -fClip.fTop);
-
-        // Note: in this use case we only render a picture to the deferred canvas
-        // but, more generally, clients will use arbitrary draw calls.
-        subCanvas->drawPicture(reconstitutedPicture);
-
-        fDisplayList = recorder.detach();
-    }
-
-    // This method operates serially and replays the recorded DDL into the tile surface.
-    void draw() {
-        fSurface->draw(fDisplayList.get());
-    }
-
-    // This method also operates serially and composes the results of replaying the DDL into
-    // the final destination surface.
-    void compose(SkCanvas* dst) {
-        sk_sp<SkImage> img = fSurface->makeImageSnapshot();
-        dst->save();
-        dst->clipRect(SkRect::Make(fClip));
-        dst->drawImage(std::move(img), fClip.fLeft, fClip.fTop);
-        dst->restore();
-    }
-
-private:
-    // This stack-based context allows each thread to re-inflate the image indices into
-    // promise images while still using the same GrBackendTexture.
-    struct PerRecorderContext {
-        SkDeferredDisplayListRecorder* fRecorder;
-        const PromiseImageHelper*      fHelper;
-    };
-
-    // 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.
-    static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) {
-        PerRecorderContext* perRecorderContext = static_cast<PerRecorderContext*>(ctxIn);
-        const PromiseImageHelper* helper = perRecorderContext->fHelper;
-        SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder;
-
-        SkASSERT(length == sizeof(int));
-
-        const int* indexPtr = static_cast<const int*>(rawData);
-        SkASSERT(helper->isValidID(*indexPtr));
-
-        const PromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
-
-        if (!curImage.fCallbackContext->backendTexture().isValid()) {
-            // 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.
-            // Note: we would like to share the same bitmap between all the threads but
-            // SkBitmap is not thread-safe.
-            return SkImage::MakeRasterCopy(curImage.fBitmap.pixmap());
-        }
-        SkASSERT(curImage.fIndex == *indexPtr);
-
-        GrBackendFormat backendFormat = curImage.fCallbackContext->backendTexture().format();
-
-        // Each DDL recorder gets its own ref on the promise callback context for the
-        // promise images it creates.
-        // DDL TODO: sort out mipmapping
-        sk_sp<SkImage> image = recorder->makePromiseTexture(
-                                                backendFormat,
-                                                curImage.fBitmap.width(),
-                                                curImage.fBitmap.height(),
-                                                GrMipMapped::kNo,
-                                                GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
-                                                curImage.fBitmap.colorType(),
-                                                curImage.fBitmap.alphaType(),
-                                                curImage.fBitmap.refColorSpace(),
-                                                PromiseImageHelper::PromiseImageFulfillProc,
-                                                PromiseImageHelper::PromiseImageReleaseProc,
-                                                PromiseImageHelper::PromiseImageDoneProc,
-                                                (void*) SkSafeRef(curImage.fCallbackContext.get()));
-        SkASSERT(image);
-        return image;
-    }
-
-    sk_sp<SkSurface>                       fSurface;
-    SkIRect                                fClip;    // in the device space of the dest canvas
-    std::unique_ptr<SkDeferredDisplayList> fDisplayList;
-    SkSurfaceCharacterization              fCharacterization;
-};
-
 Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     auto size = src.size();
     SkPictureRecorder recorder;
@@ -2328,30 +2034,10 @@
     // this is our ultimate final drawing area/rect
     SkIRect viewport = SkIRect::MakeWH(size.fWidth, size.fHeight);
 
-    PromiseImageHelper helper;
-    sk_sp<SkData> compressedPictureData;
-
-    // Convert the SkPicture into SkData replacing all the SkImages with an index.
-    {
-        SkSerialProcs procs;
-
-        procs.fImageCtx = &helper;
-        procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp<SkData> {
-            auto helper = static_cast<PromiseImageHelper*>(ctx);
-
-            int id = helper->findOrDefineImage(image);
-            if (id >= 0) {
-                SkASSERT(helper->isValidID(id));
-                return SkData::MakeWithCopy(&id, sizeof(id));
-            }
-
-            return nullptr;
-        };
-
-        compressedPictureData = inputPicture->serialize(&procs);
-        if (!compressedPictureData) {
-            return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
-        }
+    DDLPromiseImageHelper promiseImageHelper;
+    sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture.get());
+    if (!compressedPictureData) {
+        return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
     }
 
     return draw_to_canvas(fSink.get(), bitmap, stream, log, size,
@@ -2362,52 +2048,29 @@
                     }
 
                     // This is here bc this is the first point where we have access to the context
-                    helper.uploadAllToGPU(context);
+                    promiseImageHelper.uploadAllToGPU(context);
 
-                    int xTileSize = viewport.width()/fNumDivisions;
-                    int yTileSize = viewport.height()/fNumDivisions;
+                    // First, create all the tiles (including their individual dest surfaces)
+                    DDLTileHelper tiles(canvas, viewport, fNumDivisions);
 
-                    SkTArray<TileData> tileData;
-                    tileData.reserve(fNumDivisions*fNumDivisions);
+                    // Second, reinflate the compressed picture individually for each thread
+                    tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
 
-                    // First, create the destination tiles
-                    for (int y = 0, yOff = 0; y < fNumDivisions; ++y, yOff += yTileSize) {
-                        int ySize = (y < fNumDivisions-1) ? yTileSize : viewport.height()-yOff;
+                    // Third, create the DDLs in parallel
+                    tiles.createDDLsInParallel();
 
-                        for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) {
-                            int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff;
+                    // This drops the promiseImageHelper's refs on all the promise images
+                    promiseImageHelper.reset();
 
-                            SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize);
-
-                            SkASSERT(viewport.contains(clip));
-
-                            SkImageInfo tileII = SkImageInfo::MakeN32Premul(xSize, ySize);
-
-                            tileData.push_back(TileData(canvas->makeSurface(tileII), clip));
-                        }
-                    }
-
-                    // Second, run the cpu pre-processing in threads
-                    SkTaskGroup().batch(tileData.count(), [&](int i) {
-                        tileData[i].preprocess(compressedPictureData.get(), helper);
-                    });
-
-                    // This drops the helper's refs on all the promise images
-                    helper.reset();
-
-                    // Third, synchronously render the display lists into the dest tiles
+                    // Fourth, synchronously render the display lists into the dest tiles
                     // TODO: it would be cool to not wait until all the tiles are drawn to begin
                     // drawing to the GPU and composing to the final surface
-                    for (int i = 0; i < tileData.count(); ++i) {
-                        tileData[i].draw();
-                    }
+                    tiles.drawAllTilesAndFlush(context, false);
 
                     // Finally, compose the drawn tiles into the result
                     // Note: the separation between the tiles and the final composition better
                     // matches Chrome but costs us a copy
-                    for (int i = 0; i < tileData.count(); ++i) {
-                        tileData[i].compose(canvas);
-                    }
+                    tiles.composeAllTiles(canvas);
 
                     context->flush();
                     return "";