DeferredDisplayList API proposal

Chrome would like to perform cpu-side preprocessing for gpu draws in parallel. 
They do not want to go through a picture (since they have their own display list format).


The general idea is that we add a new SkDeferredDisplayListRecorder class to
perform all of Ganesh's cpu-side preprocessing ahead of time and in parallel.

The SkDDLRecorder operates like SkPictureRecorder. The user can get an SkCanvas
from the SkDDLRecorder and feed it draw operations. Once finished, the user
calls 'detach' to get an SkDeferredDisplayList. All the work up to and 
including the 'detach' call can be done in parallel and will not touch
the GPU. To actually get pixels the client must call SkSurface::draw(SkDDL)
on an SkSurface that is "compatible" with the surface characterization
initially given to the SkDDLMaker.

The surface characterization contains the minimum amount of information Ganesh needs 
to know about the ultimate destination in order to perform its cpu-side work
(i.e., caps, width, height, config).



Change-Id: I75faa483ab5a6b779c8de56ea56b9d90b990f43a
Reviewed-on: https://skia-review.googlesource.com/30140
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index e14e1f9..dc8a913 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -19,6 +19,7 @@
 #include "SkData.h"
 #include "SkDebugCanvas.h"
 #include "SkDeferredCanvas.h"
+#include "SkDeferredDisplayListRecorder.h"
 #include "SkDocument.h"
 #include "SkExecutor.h"
 #include "SkImageGenerator.h"
@@ -39,9 +40,11 @@
 #include "SkRandom.h"
 #include "SkRecordDraw.h"
 #include "SkRecorder.h"
+#include "SkSurfaceCharacterization.h"
 #include "SkSVGCanvas.h"
 #include "SkStream.h"
 #include "SkSwizzler.h"
+#include "SkTaskGroup.h"
 #include "SkTLogic.h"
 #include <cmath>
 #include <functional>
@@ -1140,37 +1143,51 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-static const SkRect kSKPViewport = {0,0, 1000,1000};
+static const SkRect kSKPViewport = {0, 0, 1000, 1000};
 
-SKPSrc::SKPSrc(Path path) : fPath(path) {}
+SKPSrc::SKPSrc(Path path) : fPath(path) { }
 
-Error SKPSrc::draw(SkCanvas* canvas) const {
-    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(fPath.c_str());
+static sk_sp<SkPicture> read_skp(const char* path) {
+    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(path);
     if (!stream) {
-        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+        return nullptr;
     }
     sk_sp<SkPicture> pic(SkPicture::MakeFromStream(stream.get()));
     if (!pic) {
-        return SkStringPrintf("Couldn't decode %s as a picture.", fPath.c_str());
+        return nullptr;
     }
     stream = nullptr;  // Might as well drop this when we're done with it.
 
+    return pic;
+}
+
+Error SKPSrc::draw(SkCanvas* canvas) const {
+    sk_sp<SkPicture> pic = read_skp(fPath.c_str());
+    if (!pic) {
+        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+    }
+
     canvas->clipRect(kSKPViewport);
     canvas->drawPicture(pic);
     return "";
 }
 
-SkISize SKPSrc::size() const {
-    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(fPath.c_str());
+static SkRect get_cull_rect_for_skp(const char* path) {
+    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(path);
     if (!stream) {
-        return {0, 0};
+        return SkRect::MakeEmpty();
     }
     SkPictInfo info;
     if (!SkPicture::InternalOnly_StreamIsSKP(stream.get(), &info)) {
-        return {0, 0};
+        return SkRect::MakeEmpty();
     }
-    SkRect viewport = kSKPViewport;
-    if (!viewport.intersect(info.fCullRect)) {
+
+    return info.fCullRect;
+}
+
+SkISize SKPSrc::size() const {
+    SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
+    if (!viewport.intersect(kSKPViewport)) {
         return {0, 0};
     }
     return viewport.roundOut().size();
@@ -1179,6 +1196,123 @@
 Name SKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
+static const int kNumDDLXTiles = 4;
+static const int kNumDDLYTiles = 4;
+static const int kDDLTileSize = 1024;
+static const SkRect kDDLSKPViewport = { 0, 0,
+                                        kNumDDLXTiles * kDDLTileSize,
+                                        kNumDDLYTiles * kDDLTileSize };
+
+DDLSKPSrc::DDLSKPSrc(Path path) : fPath(path) { }
+
+Error DDLSKPSrc::draw(SkCanvas* canvas) const {
+    class 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
+        void preprocess(SkPicture* pic) {
+            SkDeferredDisplayListRecorder recorder(fCharacterization);
+
+            SkCanvas* subCanvas = recorder.getCanvas();
+
+            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(pic);
+
+            fDisplayList = recorder.detach();
+        }
+
+        // This method operates serially
+        void draw() {
+            fSurface->draw(fDisplayList.get());
+        }
+
+        // This method also operates serially
+        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:
+        sk_sp<SkSurface> fSurface;
+        SkIRect          fClip;    // in the device space of the destination canvas
+        std::unique_ptr<SkDeferredDisplayList> fDisplayList;
+        SkSurfaceCharacterization              fCharacterization;
+    };
+
+    SkTArray<TileData> tileData;
+    tileData.reserve(16);
+
+    sk_sp<SkPicture> pic = read_skp(fPath.c_str());
+    if (!pic) {
+        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+    }
+
+    const SkRect cullRect = pic->cullRect();
+
+    // All the destination tiles are the same size
+    const SkImageInfo tileII = SkImageInfo::MakeN32Premul(kDDLTileSize, kDDLTileSize);
+
+    // First, create the destination tiles
+    for (int y = 0; y < kNumDDLYTiles; ++y) {
+        for (int x = 0; x < kNumDDLXTiles; ++x) {
+            SkRect clip = SkRect::MakeXYWH(x * kDDLTileSize, y * kDDLTileSize,
+                                           kDDLTileSize, kDDLTileSize);
+
+            if (!clip.intersect(cullRect)) {
+                continue;
+            }
+
+            tileData.push_back(TileData(canvas->makeSurface(tileII), clip.roundOut()));
+        }
+    }
+
+    // Second, run the cpu pre-processing in threads
+    SkTaskGroup().batch(tileData.count(), [&](int i) {
+        tileData[i].preprocess(pic.get());
+    });
+
+    // Third, 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
+    for (int i = 0; i < tileData.count(); ++i) {
+        tileData[i].draw();
+    }
+
+    // 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);
+    }
+
+    return "";
+}
+
+SkISize DDLSKPSrc::size() const {
+    SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
+    if (!viewport.intersect(kDDLSKPViewport)) {
+        return {0, 0};
+    }
+    return viewport.roundOut().size();
+}
+
+Name DDLSKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 #if defined(SK_XML)
 // Used when the image doesn't have an intrinsic size.
 static const SkSize kDefaultSVGSize = {1000, 1000};