viewer can show offscreen layers in MSKPSlide

Also three fixes for drawing to offscreen layers in MSKPPlayer:
*Only play from last full redraw to next cmd
*Clear before full redraw
*Actually track current cmd in layer state.

Bug: skia:11900
Change-Id: I988afb61f96c8acb7e7554d65bfa6cd6020196c7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/407460
Reviewed-by: Nathaniel Nifong <nifong@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/tools/MSKPPlayer.cpp b/tools/MSKPPlayer.cpp
index 758c398..3ba6ab3 100644
--- a/tools/MSKPPlayer.cpp
+++ b/tools/MSKPPlayer.cpp
@@ -25,15 +25,33 @@
 // Base Cmd struct.
 struct MSKPPlayer::Cmd {
     virtual ~Cmd() = default;
+    virtual bool isFullRedraw(SkCanvas*) const = 0;
     virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0;
+    // If this command draws another layer, return it's ID. Otherwise, -1.
+    virtual int layerID() const { return -1; }
 };
 
 // Draws a SkPicture.
 struct MSKPPlayer::PicCmd : Cmd {
     sk_sp<SkPicture> fContent;
+    SkIRect fClipRect = SkIRect::MakeEmpty(); // clip for picture (no clip if empty).
+
+    bool isFullRedraw(SkCanvas* canvas) const override {
+        if (fClipRect.isEmpty()) {
+            return false;
+        }
+        return fClipRect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize()));
+    }
 
     void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override {
+        if (!fClipRect.isEmpty()) {
+            canvas->save();
+            canvas->clipIRect(fClipRect);
+        }
         canvas->drawPicture(fContent.get());
+        if (!fClipRect.isEmpty()) {
+            canvas->restore();
+        }
     }
 };
 
@@ -49,7 +67,9 @@
     SkCanvas::SrcRectConstraint fConstraint;
     SkTLazy<SkPaint>            fPaint;
 
+    bool isFullRedraw(SkCanvas* canvas) const override { return false; }
     void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override;
+    int layerID() const override { return fLayerId; }
 };
 
 void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas,
@@ -71,9 +91,21 @@
         cmd = 0;
     }
     SkCanvas* layerCanvas = layerState->fSurface->getCanvas();
+    // Check if there is a full redraw between cmd and fLayerCmdCnt and if so jump to it and ensure
+    // we clear the canvas if starting from a full redraw.
+    for (size_t checkCmd = fLayerCmdCnt - 1; checkCmd > cmd; --checkCmd) {
+        if (layer.fCmds[checkCmd]->isFullRedraw(layerCanvas)) {
+            cmd = checkCmd;
+            break;
+        }
+    }
     for (; cmd < fLayerCmdCnt; ++cmd) {
+        if (cmd == 0 || layer.fCmds[cmd]->isFullRedraw(layerCanvas)) {
+            layerState->fSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
+        }
         layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap);
     }
+    layerState->fCurrCmd = fLayerCmdCnt;
     const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr;
     canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(),
                           fSrcRect,
@@ -87,8 +119,11 @@
 
 class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
 public:
-    CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers)
+    CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers, const SkIRect* clipRect = nullptr)
             : fDst(dst), fOffscreenLayers(offscreenLayers) {
+        if (clipRect) {
+            fClipRect = *clipRect;
+        }
         fRecorder.beginRecording(SkRect::Make(dst->fDimensions));
     }
     ~CmdRecordCanvas() override { this->recordPicCmd(); }
@@ -263,15 +298,16 @@
                 // Indicates that the next drawPicture command contains the SkPicture to render
                 // to the layer identified by the ID. 'rect' indicates the dirty area to update
                 // (and indicates the layer size if this command is introducing a new layer id).
-                fNextDrawToLayerID = std::stoi(tokens[1].c_str());
-                if (fOffscreenLayers->find(fNextDrawToLayerID) == fOffscreenLayers->end()) {
-                    SkASSERT(rect.left() == 0 && rect.top() == 0);
-                    SkISize size = {SkScalarCeilToInt(rect.right()),
-                                    SkScalarCeilToInt(rect.bottom())};
-                    (*fOffscreenLayers)[fNextDrawToLayerID].fDimensions = size;
+                fNextDrawPictureToLayerID = std::stoi(tokens[1].c_str());
+                fNextDrawPictureToLayerClipRect = rect.roundOut();
+                if (fOffscreenLayers->find(fNextDrawPictureToLayerID) == fOffscreenLayers->end()) {
+                    SkASSERT(fNextDrawPictureToLayerClipRect.left() == 0 &&
+                             fNextDrawPictureToLayerClipRect.top()  == 0);
+                    (*fOffscreenLayers)[fNextDrawPictureToLayerID].fDimensions =
+                            fNextDrawPictureToLayerClipRect.size();
                 }
-                // The next draw picture will notice that fNextDrawLayerID is set and redirect
-                // the picture to the offscreen layer.
+                // The next draw picture will notice that fNextDrawPictureToLayerID is set and
+                // redirect the picture to the offscreen layer.
                 return;
             } else if (tokens[0].equals(kSurfaceID)) {
                 // Indicates that the following drawImageRect should draw an offscreen layer
@@ -293,12 +329,14 @@
     void onDrawPicture(const SkPicture* picture,
                        const SkMatrix* matrix,
                        const SkPaint* paint) override {
-        if (fNextDrawToLayerID != -1) {
+        if (fNextDrawPictureToLayerID != -1) {
             SkASSERT(!matrix);
             SkASSERT(!paint);
-            CmdRecordCanvas sc(&fOffscreenLayers->at(fNextDrawToLayerID), fOffscreenLayers);
+            Layer* layer = &fOffscreenLayers->at(fNextDrawPictureToLayerID);
+            CmdRecordCanvas sc(layer, fOffscreenLayers, &fNextDrawPictureToLayerClipRect);
             picture->playback(&sc);
-            fNextDrawToLayerID = -1;
+            fNextDrawPictureToLayerID = -1;
+            fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
             return;
         }
         if (paint) {
@@ -324,6 +362,7 @@
     void recordPicCmd() {
         auto cmd = std::make_unique<PicCmd>();
         cmd->fContent = fRecorder.finishRecordingAsPicture();
+        cmd->fClipRect = fClipRect;
         if (cmd->fContent) {
             fDst->fCmds.push_back(std::move(cmd));
         }
@@ -332,10 +371,12 @@
     }
 
     SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer.
-    Layer*            fDst                      = nullptr;
-    int               fNextDrawToLayerID        = -1;
-    int               fNextDrawImageFromLayerID = -1;
-    LayerMap*         fOffscreenLayers          = nullptr;
+    Layer*            fDst                            = nullptr;
+    SkIRect           fClipRect                       = SkIRect::MakeEmpty();
+    int               fNextDrawPictureToLayerID       = -1;
+    SkIRect           fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
+    int               fNextDrawImageFromLayerID       = -1;
+    LayerMap*         fOffscreenLayers                = nullptr;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -432,3 +473,40 @@
         }
     }
 }
+
+std::vector<int> MSKPPlayer::layerIDs(int frame) const {
+    std::vector<int> result;
+    if (frame < 0) {
+        result.reserve(fOffscreenLayers.size());
+        for (auto& [id, _] : fOffscreenLayers) {
+            result.push_back(id);
+        }
+        return result;
+    }
+    if (frame < static_cast<int>(fRootLayers.size())) {
+        this->collectReferencedLayers(fRootLayers[frame], &result);
+    }
+    return result;
+}
+
+sk_sp<SkImage> MSKPPlayer::layerSnapshot(int layerID) const {
+    auto iter = fOffscreenLayerStates.find(layerID);
+    if (iter == fOffscreenLayerStates.end() || !iter->second.fSurface) {
+        return nullptr;
+    }
+    return iter->second.fSurface->makeImageSnapshot();
+}
+
+void MSKPPlayer::collectReferencedLayers(const Layer& layer, std::vector<int>* out) const {
+    for (const auto& cmd : layer.fCmds) {
+        if (int id = cmd->layerID(); id >= 0) {
+            // Linear, but we'd need to have a lot of layers to actually care.
+            if (std::find(out->begin(), out->end(), id) == out->end()) {
+                out->push_back(id);
+                auto iter = fOffscreenLayers.find(id);
+                SkASSERT(iter != fOffscreenLayers.end());
+                this->collectReferencedLayers(iter->second, out);
+            }
+        }
+    }
+}
diff --git a/tools/MSKPPlayer.h b/tools/MSKPPlayer.h
index 5359f19..1b66852 100644
--- a/tools/MSKPPlayer.h
+++ b/tools/MSKPPlayer.h
@@ -15,6 +15,7 @@
 #include <vector>
 
 class SkCanvas;
+class SkImage;
 class SkStreamSeekable;
 class SkSurface;
 
@@ -60,6 +61,22 @@
      */
     void allocateLayers(SkCanvas*);
 
+    /**
+     * A set of IDs of offscreen layers in no particular order. If frame value >= 0 is specified
+     * then the layer set is filtered to layers used by that frame (or empty if >= numFrames). If
+     * < 0 then gathers all the layers across all frames.
+     */
+    std::vector<int> layerIDs(int frame = -1) const;
+
+    /**
+     * Gets the contents of an offscreen layer. It's contents will depend on current playback state
+     * (playFrame(), updateFrameLayers(), resetLayers()). If the layer currently has no backing
+     * store because it hasn't been drawn or resetLayers() was called then this will return nullptr.
+     * Layer contents are not affected by rewindLayers() as that simply lazily redraws the frame
+     * contents the next time it is required by playFrame*() or updateFrameLayers().
+     */
+    sk_sp<SkImage> layerSnapshot(int layerID) const;
+
 private:
     MSKPPlayer() = default;
     // noncopyable, nonmoveable.
@@ -93,6 +110,8 @@
 
     static sk_sp<SkSurface> MakeSurfaceForLayer(const Layer&, SkCanvas* rootCanvas);
 
+    void collectReferencedLayers(const Layer& layer, std::vector<int>*) const;
+
     // MSKP layer ID -> Layer
     using LayerMap = std::unordered_map<int, Layer>;
     // MSKP layer ID -> LayerState
diff --git a/tools/viewer/MSKPSlide.cpp b/tools/viewer/MSKPSlide.cpp
index abb34f7..1bc5f3a 100644
--- a/tools/viewer/MSKPSlide.cpp
+++ b/tools/viewer/MSKPSlide.cpp
@@ -58,6 +58,7 @@
     ImGui::Text("Frame:");
     ImGui::SameLine();
     ImGui::PushButtonRepeat(true);  // Enable click-and-hold for frame arrows.
+    int oldFrame = fFrame;
     if (ImGui::ArrowButton("-mksp_frame", ImGuiDir_Left)) {
         fFrame = (fFrame + fPlayer->numFrames() - 1)%fPlayer->numFrames();
     }
@@ -69,6 +70,11 @@
     if (ImGui::ArrowButton("+mskp_frame", ImGuiDir_Right)) {
         fFrame = (fFrame + 1)%fPlayer->numFrames();
     }
+    if (fFrame != oldFrame) {
+        // When manually adjusting frames force layers to redraw.
+        this->redrawLayers();
+    }
+
     ImGui::PopButtonRepeat();
     ImGui::EndGroup();
 
@@ -82,6 +88,16 @@
     }
     ImGui::EndGroup();
 
+    // UI for visualizing contents of offscreen layers.
+    ImGui::Text("Offscreen Layers "); ImGui::SameLine();
+    ImGui::Checkbox("List All Layers", &fListAllLayers);
+    ImGui::RadioButton("root", &fDrawLayerID, -1);
+    const std::vector<int>& layerIDs = fListAllLayers ? fAllLayerIDs : fFrameLayerIDs[fFrame];
+    fLayerIDStrings.resize(layerIDs.size());
+    for (size_t i = 0; i < layerIDs.size(); ++i) {
+        fLayerIDStrings[i] = SkStringPrintf("%d", layerIDs[i]);
+        ImGui::RadioButton(fLayerIDStrings[i].c_str(), &fDrawLayerID, layerIDs[i]);
+    }
     ImGui::End();
 
     auto bounds = SkIRect::MakeSize(fPlayer->frameDimensions(fFrame));
@@ -98,6 +114,11 @@
     }
 
     canvas->save();
+    if (fDrawLayerID >= 0) {
+        // clip out the root layer content, but still call playFrame so layer contents are updated
+        // to fFrame.
+        bounds = SkIRect::MakeEmpty();
+    }
     canvas->clipIRect(bounds);
     canvas->clear(SkColor4f{fBackgroundColor[0],
                             fBackgroundColor[1],
@@ -105,6 +126,20 @@
                             fBackgroundColor[3]});
     fPlayer->playFrame(canvas, fFrame);
     canvas->restore();
+
+    if (fDrawLayerID >= 0) {
+        if (sk_sp<SkImage> layerImage = fPlayer->layerSnapshot(fDrawLayerID)) {
+            canvas->save();
+            canvas->clipIRect(SkIRect::MakeSize(layerImage->dimensions()));
+            canvas->clear(SkColor4f{fBackgroundColor[0],
+                                    fBackgroundColor[1],
+                                    fBackgroundColor[2],
+                                    fBackgroundColor[3]});
+            canvas->drawImage(std::move(layerImage), 0, 0);
+            canvas->restore();
+        }
+        return;
+    }
 }
 
 bool MSKPSlide::animate(double nanos) {
@@ -126,10 +161,14 @@
     double elapsed = nanos - fLastFrameTime;
     double frameTime = 1E9/fFPS;
     int framesToAdvance = elapsed/frameTime;
-    fFrame = (fFrame + framesToAdvance)%fPlayer->numFrames();
+    fFrame = fFrame + framesToAdvance;
+    if (fFrame >= fPlayer->numFrames()) {
+        this->redrawLayers();
+    }
+    fFrame %= fPlayer->numFrames();
     // Instead of just adding elapsed, note the time when this frame should have begun.
     fLastFrameTime += framesToAdvance*frameTime;
-    return framesToAdvance%fPlayer->numFrames() != 0;
+    return framesToAdvance > 0;
 }
 
 void MSKPSlide::load(SkScalar, SkScalar) {
@@ -138,9 +177,31 @@
     }
     fStream->rewind();
     fPlayer = MSKPPlayer::Make(fStream.get());
+    if (!fPlayer) {
+        return;
+    }
+    fAllLayerIDs = fPlayer->layerIDs();
+    fFrameLayerIDs.clear();
+    fFrameLayerIDs.resize(fPlayer->numFrames());
+    for (int i = 0; i < fPlayer->numFrames(); ++i) {
+        fFrameLayerIDs[i] = fPlayer->layerIDs(i);
+    }
 }
 
 void MSKPSlide::unload() { fPlayer.reset(); }
 
 void MSKPSlide::gpuTeardown() { fPlayer->resetLayers(); }
 
+void MSKPSlide::redrawLayers() {
+    if (fDrawLayerID >= 0) {
+        // Completely reset the layers so that we won't see content from later frames on layers
+        // that haven't been visited from frames 0..fFrames.
+        fPlayer->resetLayers();
+    } else {
+        // Just rewind layers so that we redraw any layer from scratch on the next frame that
+        // updates it. Important for benchmarking/profiling as otherwise if a layer is only
+        // drawn once in the frame sequence then it will never be updated after the first play
+        // through. This doesn't reallocate the layer backing stores.
+        fPlayer->rewindLayers();
+    }
+}
diff --git a/tools/viewer/MSKPSlide.h b/tools/viewer/MSKPSlide.h
index a03fcd9..1e47f94 100644
--- a/tools/viewer/MSKPSlide.h
+++ b/tools/viewer/MSKPSlide.h
@@ -27,6 +27,9 @@
     void gpuTeardown() override;
 
 private:
+    // Call if layers need to be redrawn because we've looped playback or UI interaction.
+    void redrawLayers();
+
     std::unique_ptr<SkStreamSeekable> fStream;
     std::unique_ptr<MSKPPlayer>       fPlayer;
 
@@ -40,6 +43,12 @@
     // Default to transparent black, which is correct for Android MSKPS.
     float fBackgroundColor[4] = {0, 0, 0, 0};
 
+    std::vector<int>              fAllLayerIDs;
+    std::vector<std::vector<int>> fFrameLayerIDs;
+    std::vector<SkString>         fLayerIDStrings;
+    int                           fDrawLayerID = -1;  // -1 means just draw the root layer
+    bool                          fListAllLayers = true;
+
     using INHERITED = Slide;
 };