Revert "Revert "Add mskp player, use in viewer slide""

This reverts commit 0d174586c46923098eca1684e21a7049d5b62116.

Use SkTLazy instead of std::optional (C++17 library feature)

Bug: skia:11900
Change-Id: Ia41caa9322d812f9ba6644dd14ede7d0015cf8b3
Cq-Include-Trybots: luci.skia.skia.primary:Housekeeper-PerCommit-CreateDockerImage_Skia_Release,Build-Debian10-Clang-x86_64-Release-CMake
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/402642
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index d352489..ed045cf 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1679,6 +1679,11 @@
                                   "trim string")) {
           data_sources += [ "skps" ]
         }
+        if ("True" == exec_script("//gn/checkdir.py",
+                                  [ rebase_path("mskps", root_build_dir) ],
+                                  "trim string")) {
+          data_sources += [ "mskps" ]
+        }
       }
     } else {
       # !is_ios
@@ -1877,6 +1882,8 @@
       "tools/DDLTileHelper.cpp",
       "tools/DDLTileHelper.h",
       "tools/LsanSuppressions.cpp",
+      "tools/MSKPPlayer.cpp",
+      "tools/MSKPPlayer.h",
       "tools/ProcStats.cpp",
       "tools/ProcStats.h",
       "tools/Resources.cpp",
@@ -2708,6 +2715,8 @@
         "tools/viewer/ImGuiLayer.h",
         "tools/viewer/ImageSlide.cpp",
         "tools/viewer/ImageSlide.h",
+        "tools/viewer/MSKPSlide.cpp",
+        "tools/viewer/MSKPSlide.h",
         "tools/viewer/ParticlesSlide.cpp",
         "tools/viewer/ParticlesSlide.h",
         "tools/viewer/SKPSlide.cpp",
diff --git a/src/utils/SkMultiPictureDocument.cpp b/src/utils/SkMultiPictureDocument.cpp
index 471a2b3..c9a15fd 100644
--- a/src/utils/SkMultiPictureDocument.cpp
+++ b/src/utils/SkMultiPictureDocument.cpp
@@ -199,6 +199,9 @@
     }
 
     auto picture = SkPicture::MakeFromStream(stream, procs);
+    if (!picture) {
+        return false;
+    }
 
     PagerCanvas canvas(joined.toCeil(), dstArray, dstArrayCount);
     // Must call playback(), not drawPicture() to reach
diff --git a/tools/MSKPPlayer.cpp b/tools/MSKPPlayer.cpp
new file mode 100644
index 0000000..89bae48
--- /dev/null
+++ b/tools/MSKPPlayer.cpp
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tools/MSKPPlayer.h"
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkCanvasVirtualEnforcer.h"
+#include "include/core/SkPicture.h"
+#include "include/core/SkPictureRecorder.h"
+#include "include/core/SkSurface.h"
+#include "include/private/SkTArray.h"
+#include "include/utils/SkNoDrawCanvas.h"
+#include "src/core/SkCanvasPriv.h"
+#include "src/core/SkTLazy.h"
+#include "src/utils/SkMultiPictureDocument.h"
+#include "tools/SkSharingProc.h"
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Base Cmd struct.
+struct MSKPPlayer::Cmd {
+    virtual ~Cmd() = default;
+    virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0;
+};
+
+// Draws a SkPicture.
+struct MSKPPlayer::PicCmd : Cmd {
+    sk_sp<SkPicture> fContent;
+
+    void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override {
+        canvas->drawPicture(fContent.get());
+    }
+};
+
+// Draws another layer. Stores the ID of the layer to draw and what command index on that
+// layer should be current when the layer is drawn. The layer contents are updated to the
+// stored command index before the layer is drawn.
+struct MSKPPlayer::DrawLayerCmd : Cmd {
+    int                         fLayerId;
+    size_t                      fLayerCmdCnt;
+    SkRect                      fSrcRect;
+    SkRect                      fDstRect;
+    SkSamplingOptions           fSampling;
+    SkCanvas::SrcRectConstraint fConstraint;
+    SkTLazy<SkPaint>            fPaint;
+
+    void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override;
+};
+
+void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas,
+                                    const LayerMap& layerMap,
+                                    LayerStateMap* layerStateMap) const {
+    const Layer& layer = layerMap.at(fLayerId);
+    LayerState* layerState = &(*layerStateMap)[fLayerId];
+    if (!layerState->fSurface) {
+        layerState->fCurrCmd = 0;
+        // Assume layer has same surface props and info as this (mskp doesn't currently record this
+        // data).
+        SkSurfaceProps props;
+        canvas->getProps(&props);
+        layerState->fSurface =
+                canvas->makeSurface(canvas->imageInfo().makeDimensions(layer.fDimensions), &props);
+        if (!layerState->fSurface) {
+            SkDebugf("Couldn't create surface for layer");
+            return;
+        }
+    }
+    size_t cmd = layerState->fCurrCmd;
+    if (cmd > fLayerCmdCnt) {
+        // If the layer contains contents from later commands then replay from the beginning.
+        cmd = 0;
+    }
+    SkCanvas* layerCanvas = layerState->fSurface->getCanvas();
+    for (; cmd < fLayerCmdCnt; ++cmd) {
+        layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap);
+    }
+    const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr;
+    canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(),
+                          fSrcRect,
+                          fDstRect,
+                          fSampling,
+                          paint,
+                          fConstraint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
+public:
+    CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers)
+            : fDst(dst), fOffscreenLayers(offscreenLayers) {
+        fRecorder.beginRecording(SkRect::Make(dst->fDimensions));
+    }
+    ~CmdRecordCanvas() override { this->recordPicCmd(); }
+
+protected:
+    void onDrawPaint(const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawPaint(paint);
+    }
+
+    void onDrawBehind(const SkPaint& paint) override {
+        SkCanvasPriv::DrawBehind(fRecorder.getRecordingCanvas(), paint);
+    }
+
+    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawRect(rect, paint);
+    }
+
+    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawRRect(rrect, paint);
+    }
+
+    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawDRRect(outer, inner, paint);
+    }
+
+    void onDrawOval(const SkRect& rect, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawOval(rect, paint);
+    }
+
+    void onDrawArc(const SkRect& rect,
+                   SkScalar startAngle,
+                   SkScalar sweepAngle,
+                   bool useCenter,
+                   const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawArc(rect, startAngle, sweepAngle, useCenter, paint);
+    }
+
+    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawPath(path, paint);
+    }
+
+    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawRegion(region, paint);
+    }
+
+    void onDrawTextBlob(const SkTextBlob* blob,
+                        SkScalar x,
+                        SkScalar y,
+                        const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawTextBlob(blob, x, y, paint);
+    }
+
+    void onDrawPatch(const SkPoint cubics[12],
+                     const SkColor colors[4],
+                     const SkPoint texCoords[4],
+                     SkBlendMode mode,
+                     const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawPatch(cubics, colors, texCoords, mode, paint);
+    }
+
+    void onDrawPoints(SkCanvas::PointMode mode,
+                      size_t count,
+                      const SkPoint pts[],
+                      const SkPaint& paint) override {
+        fRecorder.getRecordingCanvas()->drawPoints(mode, count, pts, paint);
+    }
+
+    void onDrawImage2(const SkImage* image,
+                      SkScalar dx,
+                      SkScalar dy,
+                      const SkSamplingOptions& sampling,
+                      const SkPaint* paint) override {
+        fRecorder.getRecordingCanvas()->drawImage(image, dx, dy, sampling, paint);
+    }
+
+    void onDrawImageRect2(const SkImage* image,
+                          const SkRect& src,
+                          const SkRect& dst,
+                          const SkSamplingOptions& sampling,
+                          const SkPaint* paint,
+                          SrcRectConstraint constraint) override {
+        if (fNextDrawImageFromLayerID != -1) {
+            this->recordPicCmd();
+            auto drawLayer = std::make_unique<DrawLayerCmd>();
+            drawLayer->fLayerId = fNextDrawImageFromLayerID;
+            drawLayer->fLayerCmdCnt = fOffscreenLayers->at(fNextDrawImageFromLayerID).fCmds.size();
+            drawLayer->fSrcRect = src;
+            drawLayer->fDstRect = dst;
+            drawLayer->fSampling = sampling;
+            drawLayer->fConstraint = constraint;
+            if (paint) {
+                drawLayer->fPaint.init(*paint);
+            }
+            fDst->fCmds.push_back(std::move(drawLayer));
+            fNextDrawImageFromLayerID = -1;
+            return;
+        }
+        fRecorder.getRecordingCanvas()->drawImageRect(image, src, dst, sampling, paint, constraint);
+    }
+
+    void onDrawImageLattice2(const SkImage* image,
+                             const Lattice& lattice,
+                             const SkRect& dst,
+                             SkFilterMode mode,
+                             const SkPaint* paint) override {
+        fRecorder.getRecordingCanvas()->drawImageLattice(image, lattice, dst, mode, paint);
+    }
+
+    void onDrawAtlas2(const SkImage* image,
+                      const SkRSXform rsxForms[],
+                      const SkRect src[],
+                      const SkColor colors[],
+                      int count,
+                      SkBlendMode mode,
+                      const SkSamplingOptions& sampling,
+                      const SkRect* cull,
+                      const SkPaint* paint) override {
+        fRecorder.getRecordingCanvas()->drawAtlas(image,
+                                                  rsxForms,
+                                                  src,
+                                                  colors,
+                                                  count,
+                                                  mode,
+                                                  sampling,
+                                                  cull,
+                                                  paint);
+    }
+
+    void onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[],
+                               int count,
+                               const SkPoint dstClips[],
+                               const SkMatrix preViewMatrices[],
+                               const SkSamplingOptions& sampling,
+                               const SkPaint* paint,
+                               SrcRectConstraint constraint) override {
+        fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAImageSet(imageSet,
+                                                                        count,
+                                                                        dstClips,
+                                                                        preViewMatrices,
+                                                                        sampling,
+                                                                        paint,
+                                                                        constraint);
+    }
+
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    void onDrawEdgeAAQuad(const SkRect& rect,
+                          const SkPoint clip[4],
+                          SkCanvas::QuadAAFlags aaFlags,
+                          const SkColor4f& color,
+                          SkBlendMode mode) override {}
+#else
+    void onDrawEdgeAAQuad(const SkRect& rect,
+                          const SkPoint clip[4],
+                          SkCanvas::QuadAAFlags aaFlags,
+                          const SkColor4f& color,
+                          SkBlendMode mode) override {
+        fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAQuad(rect,
+                                                                    clip,
+                                                                    aaFlags,
+                                                                    color,
+                                                                    mode);
+    }
+#endif
+
+    void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override {
+        static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw";
+        static constexpr char kSurfaceID[] = "SurfaceID";
+        SkTArray<SkString> tokens;
+        SkStrSplit(key, "|", kStrict_SkStrSplitMode, &tokens);
+        if (tokens.size() == 2) {
+            if (tokens[0].equals(kOffscreenLayerDraw)) {
+                // 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;
+                }
+                // The next draw picture will notice that fNextDrawLayerID 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
+                // to this layer.
+                fNextDrawImageFromLayerID = std::stoi(tokens[1].c_str());
+                return;
+            }
+        }
+    }
+
+    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
+        fRecorder.getRecordingCanvas()->private_draw_shadow_rec(path, rec);
+    }
+
+    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
+        fRecorder.getRecordingCanvas()->drawDrawable(drawable, matrix);
+    }
+
+    void onDrawPicture(const SkPicture* picture,
+                       const SkMatrix* matrix,
+                       const SkPaint* paint) override {
+        if (fNextDrawToLayerID != -1) {
+            SkASSERT(!matrix);
+            SkASSERT(!paint);
+            CmdRecordCanvas sc(&fOffscreenLayers->at(fNextDrawToLayerID), fOffscreenLayers);
+            picture->playback(&sc);
+            fNextDrawToLayerID = -1;
+            return;
+        }
+        if (paint) {
+            this->saveLayer(nullptr, paint);
+        }
+        if (matrix) {
+            this->save();
+            this->concat(*matrix);
+        }
+
+        picture->playback(this);
+
+        if (matrix) {
+            this->restore();
+        }
+        if (paint) {
+            this->restore();
+        }
+        fRecorder.getRecordingCanvas()->drawPicture(picture, matrix, paint);
+    }
+
+private:
+    void recordPicCmd() {
+        auto cmd = std::make_unique<PicCmd>();
+        cmd->fContent = fRecorder.finishRecordingAsPicture();
+        if (cmd->fContent) {
+            fDst->fCmds.push_back(std::move(cmd));
+        }
+        // Set up to accumulate again.
+        fRecorder.beginRecording(SkRect::Make(fDst->fDimensions));
+    }
+
+    SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer.
+    Layer*            fDst                      = nullptr;
+    int               fNextDrawToLayerID        = -1;
+    int               fNextDrawImageFromLayerID = -1;
+    LayerMap*         fOffscreenLayers          = nullptr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<MSKPPlayer> MSKPPlayer::Make(SkStreamSeekable* stream) {
+    auto deserialContext = std::make_unique<SkSharingDeserialContext>();
+    SkDeserialProcs procs;
+    procs.fImageProc = SkSharingDeserialContext::deserializeImage;
+    procs.fImageCtx = deserialContext.get();
+
+    int pageCount = SkMultiPictureDocumentReadPageCount(stream);
+    if (!pageCount) {
+        return nullptr;
+    }
+    std::vector<SkDocumentPage> pages(pageCount);
+    if (!SkMultiPictureDocumentRead(stream, pages.data(), pageCount, &procs)) {
+        return nullptr;
+    }
+    std::unique_ptr<MSKPPlayer> result(new MSKPPlayer);
+    result->fRootLayers.reserve(pages.size());
+    for (const auto& page : pages) {
+        SkISize dims = {SkScalarCeilToInt(page.fSize.width()),
+                        SkScalarCeilToInt(page.fSize.height())};
+        result->fRootLayers.emplace_back();
+        result->fRootLayers.back().fDimensions = dims;
+        result->fMaxDimensions.fWidth  = std::max(dims.width() , result->fMaxDimensions.width() );
+        result->fMaxDimensions.fHeight = std::max(dims.height(), result->fMaxDimensions.height());
+        CmdRecordCanvas sc(&result->fRootLayers.back(), &result->fOffscreenLayers);
+        page.fPicture->playback(&sc);
+    }
+    return result;
+}
+
+MSKPPlayer::~MSKPPlayer() = default;
+
+SkISize MSKPPlayer::frameDimensions(int i) const {
+    if (i < 0 || i >= this->numFrames()) {
+        return {-1, -1};
+    }
+    return fRootLayers[i].fDimensions;
+}
+
+bool MSKPPlayer::playFrame(SkCanvas* canvas, int i) {
+    if (i < 0 || i >= this->numFrames()) {
+        return false;
+    }
+
+    // Find the first offscreen layer that has a valid surface. If it's recording context
+    // differs from the passed canvas's then reset all the layers. Playback will
+    // automatically allocate new surfaces for offscreen layers as they're encountered.
+    for (const auto& ols : fOffscreenLayerStates) {
+        const LayerState& state = ols.second;
+        if (state.fSurface) {
+            if (state.fSurface->recordingContext() != canvas->recordingContext()) {
+                this->resetLayers();
+            }
+            break;
+        }
+    }
+
+    // Replay all the commands for this frame to the caller's canvas.
+    const Layer& layer = fRootLayers[i];
+    for (const auto& cmd : layer.fCmds) {
+        cmd->draw(canvas, fOffscreenLayers, &fOffscreenLayerStates);
+    }
+    return true;
+}
+
+void MSKPPlayer::resetLayers() { fOffscreenLayerStates.clear(); }
diff --git a/tools/MSKPPlayer.h b/tools/MSKPPlayer.h
new file mode 100644
index 0000000..6c77574
--- /dev/null
+++ b/tools/MSKPPlayer.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef MSKPPlayer_DEFINED
+#define MSKPPlayer_DEFINED
+
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSize.h"
+
+#include <unordered_map>
+#include <vector>
+
+class SkCanvas;
+class SkStreamSeekable;
+class SkSurface;
+
+/**
+ * Plays frames/pages of a MSKP to a canvas. This class uses the term "frame" as though the MSKP
+ * contains an animation, though it could indeed contain pages of a static document.
+ */
+class MSKPPlayer {
+public:
+    ~MSKPPlayer();
+
+    /** Make a player from a MSKP stream, or null if stream can't be read as MSKP. */
+    static std::unique_ptr<MSKPPlayer> Make(SkStreamSeekable* stream);
+
+    /** Maximum width and height across all frames. */
+    SkISize maxDimensions() const { return fMaxDimensions; }
+
+    /** Total number of frames. */
+    int numFrames() const { return static_cast<int>(fRootLayers.size()); }
+
+    /** Size of an individual frame. */
+    SkISize frameDimensions(int i) const;
+
+    /**
+     * Plays a frame into the passed canvas. Frames can be randomly accessed. Offscreen layers are
+     * incrementally updated from their current state to the state required for the frame
+     * (redrawing from scratch if their current state is ahead of the passed frame index).
+     */
+    bool playFrame(SkCanvas* canvas, int i);
+
+    /** Destroys any cached offscreen layers. */
+    void resetLayers();
+
+private:
+    MSKPPlayer() = default;
+    // noncopyable, nonmoveable.
+    MSKPPlayer(const MSKPPlayer&) = delete;
+    MSKPPlayer(MSKPPlayer&&) = delete;
+    MSKPPlayer& operator=(const MSKPPlayer&) = delete;
+    MSKPPlayer& operator=(MSKPPlayer&&) = delete;
+
+    // Cmds are used to draw content to the frame root layer and to offscreen layers.
+    struct Cmd;
+    // Draws a SkPicture.
+    struct PicCmd;
+    // Draws another layer. Stores the ID of the layer to draw and what command index on that
+    // layer should be current when the layer is drawn. The layer contents are updated to the
+    // stored command index before the layer is drawn.
+    struct DrawLayerCmd;
+
+    // The commands for a root/offscreen layer and dimensions of the layer.
+    struct Layer {
+        Layer() = default;
+        Layer(Layer&&) = default;
+        SkISize fDimensions;
+        std::vector<std::unique_ptr<Cmd>> fCmds;
+    };
+
+    // Playback state of layer: the last command index drawn to it and the SkSurface with contents.
+    struct LayerState {
+        size_t fCurrCmd = -1;
+        sk_sp<SkSurface> fSurface;
+    };
+
+    // MSKP layer ID -> Layer
+    using LayerMap = std::unordered_map<int, Layer>;
+    // MSKP layer ID -> LayerState
+    using LayerStateMap = std::unordered_map<int, LayerState>;
+
+    /**
+     * A SkCanvas that consumes the SkPicture and records Cmds into a Layer. It will spawn
+     * additional Layers and record nested SkPictures into those using additional CmdRecordCanvas
+     * CmdRecordCanvas instances. It needs access to fOffscreenLayers to create and update Layer
+     * structs for offscreen layers.
+     */
+    class CmdRecordCanvas;
+
+    SkISize            fMaxDimensions = {0, 0};  // Max dimensions across all frames.
+    LayerMap           fOffscreenLayers;         // All the offscreen layers for all frames.
+    LayerStateMap      fOffscreenLayerStates;    // Current surfaces and command idx for offscreen
+                                                 // layers
+    std::vector<Layer> fRootLayers;              // One root layer for each frame.
+};
+
+#endif
diff --git a/tools/viewer/MSKPSlide.cpp b/tools/viewer/MSKPSlide.cpp
new file mode 100644
index 0000000..d8019b2
--- /dev/null
+++ b/tools/viewer/MSKPSlide.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tools/viewer/MSKPSlide.h"
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkStream.h"
+#include "src/core/SkOSFile.h"
+
+MSKPSlide::MSKPSlide(const SkString& name, const SkString& path)
+        : MSKPSlide(name, SkStream::MakeFromFile(path.c_str())) {}
+
+MSKPSlide::MSKPSlide(const SkString& name, std::unique_ptr<SkStreamSeekable> stream)
+        : fStream(std::move(stream)) {
+    fName = name;
+}
+
+SkISize MSKPSlide::getDimensions() const {
+    return fPlayer ? fPlayer->maxDimensions() : SkISize{0, 0};
+}
+
+void MSKPSlide::draw(SkCanvas* canvas) {
+    if (fPlayer) {
+        fPlayer->playFrame(canvas, fFrame);
+    }
+}
+
+bool MSKPSlide::animate(double nanos) {
+    if (!fPlayer) {
+        return false;
+    }
+    double elapsed = nanos - fLastFrameTime;
+    double frameTime = 1E9/fFPS;
+    int framesToAdvance = elapsed/frameTime;
+    fFrame = (fFrame + framesToAdvance)%fPlayer->numFrames();
+    // Instead of just adding elapsed, note the time when this frame should have begun.
+    fLastFrameTime += framesToAdvance*frameTime;
+    return framesToAdvance%fPlayer->numFrames() != 0;
+}
+
+void MSKPSlide::load(SkScalar, SkScalar) {
+    if (!fStream) {
+        SkDebugf("No skp stream for slide %s.\n", fName.c_str());
+        return;
+    }
+    fStream->rewind();
+    fPlayer = MSKPPlayer::Make(fStream.get());
+    if (!fPlayer) {
+        SkDebugf("Could parse MSKP from stream for slide %s.\n", fName.c_str());
+        return;
+    }
+}
+
+void MSKPSlide::unload() { fPlayer.reset(); }
+
+void MSKPSlide::gpuTeardown() { fPlayer->resetLayers(); }
+
diff --git a/tools/viewer/MSKPSlide.h b/tools/viewer/MSKPSlide.h
new file mode 100644
index 0000000..41b3c9c
--- /dev/null
+++ b/tools/viewer/MSKPSlide.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef MSKPSlide_DEFINED
+#define MSKPSlide_DEFINED
+
+#include "tools/MSKPPlayer.h"
+#include "tools/viewer/Slide.h"
+
+class SkStreamSeekable;
+
+class MSKPSlide : public Slide {
+public:
+    MSKPSlide(const SkString& name, const SkString& path);
+    MSKPSlide(const SkString& name, std::unique_ptr<SkStreamSeekable>);
+
+    SkISize getDimensions() const override;
+
+    void draw(SkCanvas* canvas) override;
+    bool animate(double nanos) override;
+    void load(SkScalar winWidth, SkScalar winHeight) override;
+    void unload() override;
+    void gpuTeardown() override;
+
+private:
+    std::unique_ptr<SkStreamSeekable> fStream;
+    std::unique_ptr<MSKPPlayer> fPlayer;
+    int fFrame = 0;
+    int fFPS = 15;  // TODO: make this adjustable. This happens to work well for calendar.mskp
+    double fLastFrameTime = 0;
+
+    using INHERITED = Slide;
+};
+
+#endif
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index c30615c..82a005e 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -5,6 +5,8 @@
 * found in the LICENSE file.
 */
 
+#include "tools/viewer/Viewer.h"
+
 #include "include/core/SkCanvas.h"
 #include "include/core/SkData.h"
 #include "include/core/SkGraphics.h"
@@ -43,13 +45,13 @@
 #include "tools/viewer/BisectSlide.h"
 #include "tools/viewer/GMSlide.h"
 #include "tools/viewer/ImageSlide.h"
+#include "tools/viewer/MSKPSlide.h"
 #include "tools/viewer/ParticlesSlide.h"
 #include "tools/viewer/SKPSlide.h"
 #include "tools/viewer/SampleSlide.h"
 #include "tools/viewer/SkSLSlide.h"
 #include "tools/viewer/SlideDir.h"
 #include "tools/viewer/SvgSlide.h"
-#include "tools/viewer/Viewer.h"
 
 #include <cstdlib>
 #include <map>
@@ -150,19 +152,18 @@
                "it is skipped unless some list entry starts with ~");
 
 #if defined(SK_BUILD_FOR_ANDROID)
-    static DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from.");
-    static DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from.");
-    static DEFINE_string(lotties, "/data/local/tmp/lotties",
-                         "Directory to read (Bodymovin) jsons from.");
-    static DEFINE_string(rives, "/data/local/tmp/rives",
-                         "Directory to read Rive (Flare) files from.");
+#   define PATH_PREFIX "/data/local/tmp/"
 #else
-    static DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
-    static DEFINE_string(skps, "skps", "Directory to read skps from.");
-    static DEFINE_string(lotties, "lotties", "Directory to read (Bodymovin) jsons from.");
-    static DEFINE_string(rives, "rives", "Directory to read Rive (Flare) files from.");
+#   define PATH_PREFIX ""
 #endif
 
+static DEFINE_string(jpgs   , PATH_PREFIX "jpgs"   , "Directory to read jpgs from.");
+static DEFINE_string(skps   , PATH_PREFIX "skps"   , "Directory to read skps from.");
+static DEFINE_string(mskps  , PATH_PREFIX "mskps"  , "Directory to read mskps from.");
+static DEFINE_string(lotties, PATH_PREFIX "lotties", "Directory to read (Bodymovin) jsons from.");
+static DEFINE_string(rives  , PATH_PREFIX "rives"  , "Directory to read Rive (Flare) files from.");
+#undef PATH_PREFIX
+
 static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
 
 static DEFINE_int_2(threads, j, -1,
@@ -735,6 +736,10 @@
         const CommandLineFlags::StringArray&   fFlags;
         const SlideFactory                     fFactory;
     } gExternalSlidesInfo[] = {
+        { ".mskp", "mskp-dir", FLAGS_mskps,
+          [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
+            return sk_make_sp<MSKPSlide>(name, path);}
+        },
         { ".skp", "skp-dir", FLAGS_skps,
             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
                 return sk_make_sp<SKPSlide>(name, path);}