skeletal animation support added to API and software backend

SkCanvas::drawVertices now supports overloads that take an array of bone deformation matrices.
SkVertices::MakeCopy and SkVertices::Builder now support two additional optional attributes, boneIndices and boneWeights.

Bug: skia:
Change-Id: I30a3b11691e7cdb13924907cc1401ff86d127aea
Reviewed-on: https://skia-review.googlesource.com/137221
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Ruiqi Mao <ruiqimao@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 97cbf73..f10d232 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1551,9 +1551,13 @@
         ":xml",
         "modules/sksg:samples",
         "modules/skshaper",
-        "//third_party/Nima-Cpp",
       ]
 
+      # NIMA does not build on Windows clang
+      if (!is_win || !is_clang) {
+        deps += [ "//third_party/Nima-Cpp" ]
+      }
+
       if (skia_use_lua) {
         sources += [ "samplecode/SampleLua.cpp" ]
         deps += [
@@ -1581,10 +1585,14 @@
         ":tool_utils",
         "modules/skottie",
         "modules/sksg",
-        "//third_party/Nima-Cpp",
         "//third_party/jsoncpp",
         "//third_party/libpng",
       ]
+
+      # NIMA does not build on Windows clang
+      if (!is_win || !is_clang) {
+        deps += [ "//third_party/Nima-Cpp" ]
+      }
     }
   }
 
@@ -2005,9 +2013,14 @@
       ":views",
       "modules/skottie",
       "modules/sksg",
-      "//third_party/Nima-Cpp",
       "//third_party/imgui",
     ]
+
+    # NIMA does not build on Windows clang
+    if (!is_win || !is_clang) {
+      sources += [ "tools/viewer/NIMASlide.cpp" ]
+      deps += [ "//third_party/Nima-Cpp" ]
+    }
   }
 
   if (is_android) {
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 5a307c8..15ed034 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -2149,6 +2149,40 @@
     */
     void drawVertices(const sk_sp<SkVertices>& vertices, SkBlendMode mode, const SkPaint& paint);
 
+    /** Draw SkVertices vertices, a triangle mesh, using clip and SkMatrix. Bone data is used to
+        deform vertices with bone weights.
+        If vertices texs and vertices colors are defined in vertices, and SkPaint paint
+        contains SkShader, SkBlendMode mode combines vertices colors with SkShader.
+        The first element of bones should be an object to world space transformation matrix that
+        will be applied before performing mesh deformations. If no such transformation is needed,
+        it should be the identity matrix.
+
+        @param vertices  triangle mesh to draw
+        @param bones     bone matrix data
+        @param boneCount number of bone matrices
+        @param mode      combines vertices colors with SkShader, if both are present
+        @param paint     specifies the SkShader, used as SkVertices texture, may be nullptr
+    */
+    void drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                      SkBlendMode mode, const SkPaint& paint);
+
+    /** Draw SkVertices vertices, a triangle mesh, using clip and SkMatrix. Bone data is used to
+        deform vertices with bone weights.
+        If vertices texs and vertices colors are defined in vertices, and SkPaint paint
+        contains SkShader, SkBlendMode mode combines vertices colors with SkShader.
+        The first element of bones should be an object to world space transformation matrix that
+        will be applied before performing mesh deformations. If no such transformation is needed,
+        it should be the identity matrix.
+
+        @param vertices  triangle mesh to draw
+        @param bones     bone matrix data
+        @param boneCount number of bone matrices
+        @param mode      combines vertices colors with SkShader, if both are present
+        @param paint     specifies the SkShader, used as SkVertices texture, may be nullptr
+    */
+    void drawVertices(const sk_sp<SkVertices> vertices, const SkMatrix* bones, int boneCount,
+                      SkBlendMode mode, const SkPaint& paint);
+
     /** Draws a Coons patch: the interpolation of four cubics with shared corners,
         associating a color, and optionally a texture SkPoint, with each corner.
 
@@ -2472,8 +2506,14 @@
                            const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint);
     virtual void onDrawPoints(PointMode mode, size_t count, const SkPoint pts[],
                               const SkPaint& paint);
+
+    // TODO: Remove old signature
     virtual void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
-                                      const SkPaint& paint);
+                                      const SkPaint& paint) {
+        this->onDrawVerticesObject(vertices, nullptr, 0, mode, paint);
+    }
+    virtual void onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                      int boneCount, SkBlendMode mode, const SkPaint& paint);
 
     virtual void onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy, const SkPaint* paint);
     virtual void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
diff --git a/include/core/SkCanvasVirtualEnforcer.h b/include/core/SkCanvasVirtualEnforcer.h
index 1999802..84b209d 100644
--- a/include/core/SkCanvasVirtualEnforcer.h
+++ b/include/core/SkCanvasVirtualEnforcer.h
@@ -48,8 +48,8 @@
                      const SkPaint& paint) override = 0;
     void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
                       const SkPaint& paint) override = 0;
-    void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
-                              const SkPaint& paint) override = 0;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override = 0;
 
     void onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy,
                      const SkPaint* paint) override = 0;
diff --git a/include/core/SkOverdrawCanvas.h b/include/core/SkOverdrawCanvas.h
index 47b5e04..380f9eb 100644
--- a/include/core/SkOverdrawCanvas.h
+++ b/include/core/SkOverdrawCanvas.h
@@ -39,7 +39,8 @@
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
     void onDrawRRect(const SkRRect&, const SkPaint&) override;
     void onDrawPoints(PointMode, size_t, const SkPoint[], const SkPaint&) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
                      int, SkBlendMode, const SkRect*, const SkPaint*) override;
     void onDrawPath(const SkPath&, const SkPaint&) override;
diff --git a/include/core/SkVertices.h b/include/core/SkVertices.h
index d5e97e1..6c4fb39 100644
--- a/include/core/SkVertices.h
+++ b/include/core/SkVertices.h
@@ -19,6 +19,20 @@
  */
 class SK_API SkVertices : public SkNVRefCnt<SkVertices> {
 public:
+    // BoneIndices indicates which (of a maximum of 4 bones) a given vertex will interpolate
+    // between. To indicate that a slot is not used, the convention is to assign the bone index
+    // to 0.
+    struct BoneIndices {
+        uint32_t indices[4];
+    };
+
+    // BoneWeights stores the interpolation weight for each of the (maximum of 4) bones a given
+    // vertex interpolates between. To indicate that a slot is not used, the weight for that
+    // slot should be 0.
+    struct BoneWeights {
+        float weights[4];
+    };
+
     enum VertexMode {
         kTriangles_VertexMode,
         kTriangleStrip_VertexMode,
@@ -28,21 +42,60 @@
     };
 
     /**
-     *  Create a vertices by copying the specified arrays. texs and colors may be nullptr,
-     *  and indices is ignored if indexCount == 0.
+     *  Create a vertices by copying the specified arrays. texs, colors, boneIndices, and
+     *  boneWeights may be nullptr, and indices is ignored if indexCount == 0.
+     *
+     *  boneIndices and boneWeights must either both be nullptr or both point to valid data.
+     *  If specified, they must both contain 'vertexCount' entries.
      */
     static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
                                       const SkPoint positions[],
                                       const SkPoint texs[],
                                       const SkColor colors[],
+                                      const BoneIndices boneIndices[],
+                                      const BoneWeights boneWeights[],
                                       int indexCount,
                                       const uint16_t indices[]);
 
     static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
                                       const SkPoint positions[],
                                       const SkPoint texs[],
+                                      const SkColor colors[],
+                                      const BoneIndices boneIndices[],
+                                      const BoneWeights boneWeights[]) {
+        return MakeCopy(mode,
+                        vertexCount,
+                        positions,
+                        texs,
+                        colors,
+                        boneIndices,
+                        boneWeights,
+                        0,
+                        nullptr);
+    }
+
+    static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
+                                      const SkPoint positions[],
+                                      const SkPoint texs[],
+                                      const SkColor colors[],
+                                      int indexCount,
+                                      const uint16_t indices[]) {
+        return MakeCopy(mode,
+                        vertexCount,
+                        positions,
+                        texs,
+                        colors,
+                        nullptr,
+                        nullptr,
+                        indexCount,
+                        indices);
+    }
+
+    static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
+                                      const SkPoint positions[],
+                                      const SkPoint texs[],
                                       const SkColor colors[]) {
-        return MakeCopy(mode, vertexCount, positions, texs, colors, 0, nullptr);
+        return MakeCopy(mode, vertexCount, positions, texs, colors, nullptr, nullptr);
     }
 
     struct Sizes;
@@ -50,6 +103,7 @@
     enum BuilderFlags {
         kHasTexCoords_BuilderFlag   = 1 << 0,
         kHasColors_BuilderFlag      = 1 << 1,
+        kHasBones_BuilderFlag       = 1 << 2,
     };
     class Builder {
     public:
@@ -61,9 +115,11 @@
         int vertexCount() const;
         int indexCount() const;
         SkPoint* positions();
-        SkPoint* texCoords();   // returns null if there are no texCoords
-        SkColor* colors();      // returns null if there are no colors
-        uint16_t* indices();    // returns null if there are no indices
+        SkPoint* texCoords();       // returns null if there are no texCoords
+        SkColor* colors();          // returns null if there are no colors
+        BoneIndices* boneIndices(); // returns null if there are no bone indices
+        BoneWeights* boneWeights(); // returns null if there are no bone weights
+        uint16_t* indices();        // returns null if there are no indices
 
         // Detach the built vertices object. After the first call, this will always return null.
         sk_sp<SkVertices> detach();
@@ -88,6 +144,7 @@
 
     bool hasColors() const { return SkToBool(this->colors()); }
     bool hasTexCoords() const { return SkToBool(this->texCoords()); }
+    bool hasBones() const { return SkToBool(this->boneIndices()); }
     bool hasIndices() const { return SkToBool(this->indices()); }
 
     int vertexCount() const { return fVertexCnt; }
@@ -95,6 +152,9 @@
     const SkPoint* texCoords() const { return fTexs; }
     const SkColor* colors() const { return fColors; }
 
+    const BoneIndices* boneIndices() const { return fBoneIndices; }
+    const BoneWeights* boneWeights() const { return fBoneWeights; }
+
     int indexCount() const { return fIndexCnt; }
     const uint16_t* indices() const { return fIndices; }
 
@@ -128,10 +188,12 @@
     uint32_t fUniqueID;
 
     // these point inside our allocation, so none of these can be "freed"
-    SkPoint*    fPositions;
-    SkPoint*    fTexs;
-    SkColor*    fColors;
-    uint16_t*   fIndices;
+    SkPoint*     fPositions;
+    SkPoint*     fTexs;
+    SkColor*     fColors;
+    BoneIndices* fBoneIndices;
+    BoneWeights* fBoneWeights;
+    uint16_t*    fIndices;
 
     SkRect  fBounds;    // computed to be the union of the fPositions[]
     int     fVertexCnt;
diff --git a/include/utils/SkLuaCanvas.h b/include/utils/SkLuaCanvas.h
index 2fef2c0..e29e0b5 100644
--- a/include/utils/SkLuaCanvas.h
+++ b/include/utils/SkLuaCanvas.h
@@ -58,7 +58,8 @@
                          const SkPaint*, SrcRectConstraint) override;
     void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst,
                           const SkPaint*) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
 
     void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
     void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
diff --git a/include/utils/SkNWayCanvas.h b/include/utils/SkNWayCanvas.h
index e6a6b99..188c0a1 100644
--- a/include/utils/SkNWayCanvas.h
+++ b/include/utils/SkNWayCanvas.h
@@ -78,7 +78,8 @@
                          const SkPaint*) override;
     void onDrawBitmapNine(const SkBitmap&, const SkIRect& center, const SkRect& dst,
                           const SkPaint*) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
                      int, SkBlendMode, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
diff --git a/include/utils/SkNoDrawCanvas.h b/include/utils/SkNoDrawCanvas.h
index 518d090..0e86ed6 100644
--- a/include/utils/SkNoDrawCanvas.h
+++ b/include/utils/SkNoDrawCanvas.h
@@ -75,7 +75,8 @@
                             const SkPaint*) override {}
     void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&,
                              const SkPaint*) override {}
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override {}
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix*, int, SkBlendMode,
+                              const SkPaint&) override {}
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
                      int, SkBlendMode, const SkRect*, const SkPaint*) override {}
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override {}
diff --git a/include/utils/SkPaintFilterCanvas.h b/include/utils/SkPaintFilterCanvas.h
index 72c9784..4728792 100644
--- a/include/utils/SkPaintFilterCanvas.h
+++ b/include/utils/SkPaintFilterCanvas.h
@@ -88,7 +88,8 @@
                          const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&,
                             const SkPaint*) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkBlendMode,
                              const SkPaint& paint) override;
diff --git a/resources/nima/Robot.nima b/resources/nima/Robot.nima
index af035ff..c9c6af5 100644
--- a/resources/nima/Robot.nima
+++ b/resources/nima/Robot.nima
Binary files differ
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index 2361468..0d1ec8d 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -570,11 +570,12 @@
                 nullptr)
 }
 
-void SkBitmapDevice::drawVertices(const SkVertices* vertices, SkBlendMode bmode,
-                                  const SkPaint& paint) {
+void SkBitmapDevice::drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                                  SkBlendMode bmode, const SkPaint& paint) {
     BDDraw(this).drawVertices(vertices->mode(), vertices->vertexCount(), vertices->positions(),
-                              vertices->texCoords(), vertices->colors(), bmode,
-                              vertices->indices(), vertices->indexCount(), paint);
+                              vertices->texCoords(), vertices->colors(), vertices->boneIndices(),
+                              vertices->boneWeights(), bmode, vertices->indices(),
+                              vertices->indexCount(), paint, bones, boneCount);
 }
 
 void SkBitmapDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint& origPaint) {
diff --git a/src/core/SkBitmapDevice.h b/src/core/SkBitmapDevice.h
index ba7846c..e2cedaa 100644
--- a/src/core/SkBitmapDevice.h
+++ b/src/core/SkBitmapDevice.h
@@ -113,7 +113,8 @@
      */
     void drawPosText(const void* text, size_t len, const SkScalar pos[],
                      int scalarsPerPos, const SkPoint& offset, const SkPaint& paint) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint& paint) override;
     void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override;
 
     ///////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index b297f98..bf32aaf 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1702,13 +1702,27 @@
     RETURN_ON_NULL(vertices);
     // We expect fans to be converted to triangles when building or deserializing SkVertices.
     SkASSERT(vertices->mode() != SkVertices::kTriangleFan_VertexMode);
-    this->onDrawVerticesObject(vertices.get(), mode, paint);
+    this->onDrawVerticesObject(vertices.get(), nullptr, 0, mode, paint);
 }
 
 void SkCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     RETURN_ON_NULL(vertices);
-    this->onDrawVerticesObject(vertices, mode, paint);
+    this->onDrawVerticesObject(vertices, nullptr, 0, mode, paint);
+}
+
+void SkCanvas::drawVertices(const sk_sp<SkVertices> vertices, const SkMatrix* bones, int boneCount,
+                            SkBlendMode mode, const SkPaint& paint) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    RETURN_ON_NULL(vertices);
+    this->onDrawVerticesObject(vertices.get(), bones, boneCount, mode, paint);
+}
+
+void SkCanvas::drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                            SkBlendMode mode, const SkPaint& paint) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    RETURN_ON_NULL(vertices);
+    this->onDrawVerticesObject(vertices, bones, boneCount, mode, paint);
 }
 
 void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
@@ -2597,13 +2611,13 @@
     this->onDrawTextBlob(blob, x, y, paint);
 }
 
-void SkCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
-                                    const SkPaint& paint) {
+void SkCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                    int boneCount, SkBlendMode bmode, const SkPaint& paint) {
     LOOPER_BEGIN(paint, SkDrawFilter::kPath_Type, nullptr)
 
     while (iter.next()) {
         // In the common case of one iteration we could std::move vertices here.
-        iter.fDevice->drawVertices(vertices, bmode, looper.paint());
+        iter.fDevice->drawVertices(vertices, bones, boneCount, bmode, looper.paint());
     }
 
     LOOPER_END
diff --git a/src/core/SkColorSpaceXformCanvas.cpp b/src/core/SkColorSpaceXformCanvas.cpp
index 3ef4d6d..caf07fb 100644
--- a/src/core/SkColorSpaceXformCanvas.cpp
+++ b/src/core/SkColorSpaceXformCanvas.cpp
@@ -90,8 +90,8 @@
                       const SkPaint& paint) override {
         fTarget->drawPoints(mode, count, pts, fXformer->apply(paint));
     }
-    void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
-                              const SkPaint& paint) override {
+    void onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                              SkBlendMode mode, const SkPaint& paint) override {
         sk_sp<SkVertices> copy;
         if (vertices->hasColors()) {
             int count = vertices->vertexCount();
@@ -99,11 +99,12 @@
             fXformer->apply(xformed.begin(), vertices->colors(), count);
             copy = SkVertices::MakeCopy(vertices->mode(), count, vertices->positions(),
                                         vertices->texCoords(), xformed.begin(),
+                                        vertices->boneIndices(), vertices->boneWeights(),
                                         vertices->indexCount(), vertices->indices());
             vertices = copy.get();
         }
 
-        fTarget->drawVertices(vertices, mode, fXformer->apply(paint));
+        fTarget->drawVertices(vertices, bones, boneCount, mode, fXformer->apply(paint));
     }
 
     void onDrawText(const void* ptr, size_t len,
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 46404b2..08fc3e2 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -137,7 +137,7 @@
     auto vertices = SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height(),
                                                interpColorsLinearly);
     if (vertices) {
-        this->drawVertices(vertices.get(), bmode, paint);
+        this->drawVertices(vertices.get(), nullptr, 0, bmode, paint);
     }
 }
 
@@ -311,7 +311,7 @@
     }
     SkPaint p(paint);
     p.setShader(atlas->makeShader());
-    this->drawVertices(builder.detach().get(), mode, p);
+    this->drawVertices(builder.detach().get(), nullptr, 0, mode, p);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkDevice.h b/src/core/SkDevice.h
index f840cf6..93ed3e1 100644
--- a/src/core/SkDevice.h
+++ b/src/core/SkDevice.h
@@ -225,7 +225,8 @@
      *  Decorations (underline and stike-thru) will be handled by SkCanvas.
      */
     virtual void drawGlyphRun(const SkPaint& paint, SkGlyphRun* glyphRun);
-    virtual void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) = 0;
+    virtual void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) = 0;
     virtual void drawShadow(const SkPath&, const SkDrawShadowRec&);
 
     // default implementation unrolls the blob runs.
@@ -434,7 +435,8 @@
     void drawPosText(const void*, size_t, const SkScalar[], int, const SkPoint&,
                      const SkPaint&) override {}
     void drawDevice(SkBaseDevice*, int, int, const SkPaint&) override {}
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override {}
+    void drawVertices(const SkVertices*, const SkMatrix*, int, SkBlendMode,
+                      const SkPaint&) override {}
 
 private:
     typedef SkBaseDevice INHERITED;
diff --git a/src/core/SkDraw.h b/src/core/SkDraw.h
index 8032ad9..afd50ea 100644
--- a/src/core/SkDraw.h
+++ b/src/core/SkDraw.h
@@ -65,11 +65,12 @@
     void    drawPosText(const char text[], size_t byteLength,
                         const SkScalar pos[], int scalarsPerPosition,
                         const SkPoint& offset, const SkPaint&, const SkSurfaceProps*) const;
-    void    drawVertices(SkVertices::VertexMode mode, int count,
+    void    drawVertices(SkVertices::VertexMode mode, int vertexCount,
                          const SkPoint vertices[], const SkPoint textures[],
-                         const SkColor colors[], SkBlendMode bmode,
+                         const SkColor colors[], const SkVertices::BoneIndices boneIndices[],
+                         const SkVertices::BoneWeights boneWeights[], SkBlendMode bmode,
                          const uint16_t indices[], int ptCount,
-                         const SkPaint& paint) const;
+                         const SkPaint& paint, const SkMatrix* bones, int boneCount) const;
 
     /**
      *  Overwrite the target with the path's coverage (i.e. its mask).
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index de6eaea..f918269 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -161,15 +161,16 @@
     return SkColorGetA(c) == 0xFF;
 }
 
-void SkDraw::drawVertices(SkVertices::VertexMode vmode, int count,
+void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount,
                           const SkPoint vertices[], const SkPoint textures[],
-                          const SkColor colors[], SkBlendMode bmode,
+                          const SkColor colors[], const SkVertices::BoneIndices boneIndices[],
+                          const SkVertices::BoneWeights boneWeights[], SkBlendMode bmode,
                           const uint16_t indices[], int indexCount,
-                          const SkPaint& paint) const {
-    SkASSERT(0 == count || vertices);
+                          const SkPaint& paint, const SkMatrix* bones, int boneCount) const {
+    SkASSERT(0 == vertexCount || vertices);
 
     // abort early if there is nothing to draw
-    if (count < 3 || (indices && indexCount < 3) || fRC->isEmpty()) {
+    if (vertexCount < 3 || (indices && indexCount < 3) || fRC->isEmpty()) {
         return;
     }
     SkMatrix ctmInv;
@@ -204,25 +205,81 @@
         shader = nullptr;
     }
 
-    constexpr size_t defCount = 16;
-    constexpr size_t outerSize = sizeof(SkTriColorShader) +
+    constexpr size_t kDefVertexCount = 16;
+    constexpr size_t kDefBoneCount = 8;
+    constexpr size_t kOuterSize = sizeof(SkTriColorShader) +
                                  sizeof(SkComposeShader) +
-                                 (sizeof(SkPoint) + sizeof(SkPM4f)) * defCount;
-    SkSTArenaAlloc<outerSize> outerAlloc;
+                                 (2 * sizeof(SkPoint) + sizeof(SkPM4f)) * kDefVertexCount +
+                                 sizeof(SkMatrix) * kDefBoneCount;
+    SkSTArenaAlloc<kOuterSize> outerAlloc;
 
-    SkPoint* devVerts = outerAlloc.makeArray<SkPoint>(count);
-    fMatrix->mapPoints(devVerts, vertices, count);
+    // deform vertices using the skeleton if it is passed in
+    if (bones && boneCount) {
+        // allocate space for the deformed vertices
+        SkPoint* deformed = outerAlloc.makeArray<SkPoint>(vertexCount);
+
+        // get the bone matrices
+        SkMatrix* transformedBones = outerAlloc.makeArray<SkMatrix>(boneCount);
+
+        // transform the bone matrices by the world transform
+        transformedBones[0] = bones[0];
+        for (int i = 1; i < boneCount; i ++) {
+            transformedBones[i] = SkMatrix::Concat(bones[i], bones[0]);
+        }
+
+        // deform the vertices
+        if (boneIndices && boneWeights) {
+            for (int i = 0; i < vertexCount; i ++) {
+                const SkVertices::BoneIndices& indices = boneIndices[i];
+                const SkVertices::BoneWeights& weights = boneWeights[i];
+
+                // apply bone deformations
+                SkPoint result = SkPoint::Make(0.0f, 0.0f);
+                SkPoint transformed;
+                for (uint32_t j = 0; j < 4; j ++) {
+                    // get the attachment data
+                    uint32_t index = indices.indices[j];
+                    float weight = weights.weights[j];
+
+                    // skip the bone if there is no weight
+                    if (weight == 0.0f) {
+                        continue;
+                    }
+                    SkASSERT(index != 0);
+
+                    // transformed = M * v
+                    transformedBones[index].mapPoints(&transformed, &vertices[i], 1);
+
+                    // result += transformed * w
+                    result += transformed * weight;
+                }
+
+                // set the deformed point
+                deformed[i] = result;
+            }
+        } else {
+            // no bones, so only apply world transform
+            const SkMatrix& worldTransform = bones[0];
+            worldTransform.mapPoints(deformed, vertices, vertexCount);
+        }
+
+        // change the vertices to point to deformed
+        vertices = deformed;
+    }
+
+    SkPoint* devVerts = outerAlloc.makeArray<SkPoint>(vertexCount);
+    fMatrix->mapPoints(devVerts, vertices, vertexCount);
 
     {
         SkRect bounds;
         // this also sets bounds to empty if we see a non-finite value
-        bounds.set(devVerts, count);
+        bounds.set(devVerts, vertexCount);
         if (bounds.isEmpty()) {
             return;
         }
     }
 
-    VertState       state(count, indices, indexCount);
+    VertState       state(vertexCount, indices, indexCount);
     VertState::Proc vertProc = state.chooseProc(vmode);
 
     if (colors || textures) {
@@ -230,10 +287,11 @@
         Matrix43*   matrix43 = nullptr;
 
         if (colors) {
-            dstColors = convert_colors(colors, count, fDst.colorSpace(), &outerAlloc);
+            dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), &outerAlloc);
 
             SkTriColorShader* triShader = outerAlloc.make<SkTriColorShader>(
-                                                                compute_is_opaque(colors, count));
+                                                                compute_is_opaque(colors,
+                                                                                  vertexCount));
             matrix43 = triShader->getMatrix43();
             if (shader) {
                 shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
diff --git a/src/core/SkLiteDL.cpp b/src/core/SkLiteDL.cpp
index ce6f0d3..4240055 100644
--- a/src/core/SkLiteDL.cpp
+++ b/src/core/SkLiteDL.cpp
@@ -459,13 +459,17 @@
     };
     struct DrawVertices final : Op {
         static const auto kType = Type::DrawVertices;
-        DrawVertices(const SkVertices* v, SkBlendMode m, const SkPaint& p)
-            : vertices(sk_ref_sp(const_cast<SkVertices*>(v))), mode(m), paint(p) {}
+        DrawVertices(const SkVertices* v, int bc, SkBlendMode m, const SkPaint& p)
+            : vertices(sk_ref_sp(const_cast<SkVertices*>(v)))
+            , boneCount(bc)
+            , mode(m)
+            , paint(p) {}
         sk_sp<SkVertices> vertices;
+        int boneCount;
         SkBlendMode mode;
         SkPaint paint;
         void draw(SkCanvas* c, const SkMatrix&) const {
-            c->drawVertices(vertices, mode, paint);
+            c->drawVertices(vertices, pod<SkMatrix>(this), boneCount, mode, paint);
         }
     };
     struct DrawAtlas final : Op {
@@ -676,8 +680,14 @@
     void* pod = this->push<DrawPoints>(count*sizeof(SkPoint), mode, count, paint);
     copy_v(pod, points,count);
 }
-void SkLiteDL::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
-    this->push<DrawVertices>(0, vertices, mode, paint);
+void SkLiteDL::drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                            SkBlendMode mode, const SkPaint& paint) {
+    void* pod = this->push<DrawVertices>(boneCount * sizeof(SkMatrix),
+                                         vertices,
+                                         boneCount,
+                                         mode,
+                                         paint);
+    copy_v(pod, bones, boneCount);
 }
 void SkLiteDL::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
                          const SkColor colors[], int count, SkBlendMode xfermode,
diff --git a/src/core/SkLiteDL.h b/src/core/SkLiteDL.h
index 6d63657..b63ed0d 100644
--- a/src/core/SkLiteDL.h
+++ b/src/core/SkLiteDL.h
@@ -76,7 +76,8 @@
     void drawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4],
                    SkBlendMode, const SkPaint&);
     void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&);
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&);
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint&);
     void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int,
                    SkBlendMode, const SkRect*, const SkPaint*);
     void drawShadowRec(const SkPath&, const SkDrawShadowRec&);
diff --git a/src/core/SkLiteRecorder.cpp b/src/core/SkLiteRecorder.cpp
index bd41ff3..0891508 100644
--- a/src/core/SkLiteRecorder.cpp
+++ b/src/core/SkLiteRecorder.cpp
@@ -182,9 +182,9 @@
                                   const SkPaint& paint) {
     fDL->drawPoints(mode, count, pts, paint);
 }
-void SkLiteRecorder::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
-                                          const SkPaint& paint) {
-    fDL->drawVertices(vertices, mode, paint);
+void SkLiteRecorder::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                          int boneCount, SkBlendMode mode, const SkPaint& paint) {
+    fDL->drawVertices(vertices, bones, boneCount, mode, paint);
 }
 void SkLiteRecorder::onDrawAtlas(const SkImage* atlas,
                                  const SkRSXform xforms[],
diff --git a/src/core/SkLiteRecorder.h b/src/core/SkLiteRecorder.h
index 6b21c89..af538ee 100644
--- a/src/core/SkLiteRecorder.h
+++ b/src/core/SkLiteRecorder.h
@@ -77,7 +77,8 @@
     void onDrawPatch(const SkPoint[12], const SkColor[4],
                      const SkPoint[4], SkBlendMode, const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
                      int, SkBlendMode, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
diff --git a/src/core/SkOverdrawCanvas.cpp b/src/core/SkOverdrawCanvas.cpp
index 25bc7fb..b86f9a3 100644
--- a/src/core/SkOverdrawCanvas.cpp
+++ b/src/core/SkOverdrawCanvas.cpp
@@ -219,9 +219,14 @@
     fList[0]->onDrawPoints(mode, count, points, this->overdrawPaint(paint));
 }
 
-void SkOverdrawCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode blendMode,
+void SkOverdrawCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                            int boneCount, SkBlendMode blendMode,
                                             const SkPaint& paint) {
-    fList[0]->onDrawVerticesObject(vertices, blendMode, this->overdrawPaint(paint));
+    fList[0]->onDrawVerticesObject(vertices,
+                                   bones,
+                                   boneCount,
+                                   blendMode,
+                                   this->overdrawPaint(paint));
 }
 
 void SkOverdrawCanvas::onDrawAtlas(const SkImage* image, const SkRSXform xform[],
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 780cb13..f63abe7 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -615,11 +615,15 @@
         case DRAW_VERTICES_OBJECT: {
             const SkPaint* paint = fPictureData->getPaint(reader);
             const SkVertices* vertices = fPictureData->getVertices(reader);
+            const int boneCount = reader->readInt();
+            const SkMatrix* bones = boneCount ?
+                                    (const SkMatrix*) reader->skip(boneCount, sizeof(SkMatrix)) :
+                                    nullptr;
             SkBlendMode bmode = reader->read32LE(SkBlendMode::kLastMode);
             BREAK_ON_READ_ERROR(reader);
 
             if (paint && vertices) {
-                canvas->drawVertices(vertices, bmode, *paint);
+                canvas->drawVertices(vertices, bones, boneCount, bmode, *paint);
             }
         } break;
         case RESTORE:
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index 87c8c4d..922fb6a 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -682,14 +682,16 @@
     this->validate(initialOffset, size);
 }
 
-void SkPictureRecord::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
-                                           const SkPaint& paint) {
-    // op + paint index + vertices index + mode
-    size_t size = 4 * kUInt32Size;
+void SkPictureRecord::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                           int boneCount, SkBlendMode mode, const SkPaint& paint) {
+    // op + paint index + vertices index + number of bones + bone matrices + mode
+    size_t size = 5 * kUInt32Size + boneCount * sizeof(SkMatrix);
     size_t initialOffset = this->addDraw(DRAW_VERTICES_OBJECT, &size);
 
     this->addPaint(paint);
     this->addVertices(vertices);
+    this->addInt(boneCount);
+    fWriter.write(bones, boneCount * sizeof(SkMatrix));
     this->addInt(static_cast<uint32_t>(mode));
 
     this->validate(initialOffset, size);
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index ad2dbdf..dc9f4d9 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -196,7 +196,8 @@
     void onDrawImageLattice(const SkImage*, const SkCanvas::Lattice& lattice, const SkRect& dst,
                             const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
 
     void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
     void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp
index f712c0e..9c58456 100644
--- a/src/core/SkRecordDraw.cpp
+++ b/src/core/SkRecordDraw.cpp
@@ -126,7 +126,7 @@
 DRAW(DrawTextRSXform, drawTextRSXform(r.text, r.byteLength, r.xforms, r.cull, r.paint));
 DRAW(DrawAtlas, drawAtlas(r.atlas.get(),
                           r.xforms, r.texs, r.colors, r.count, r.mode, r.cull, r.paint));
-DRAW(DrawVertices, drawVertices(r.vertices, r.bmode, r.paint));
+DRAW(DrawVertices, drawVertices(r.vertices, r.bones, r.boneCount, r.bmode, r.paint));
 DRAW(DrawShadowRec, private_draw_shadow_rec(r.path, r.rec));
 DRAW(DrawAnnotation, drawAnnotation(r.rect, r.key.c_str(), r.value.get()));
 #undef DRAW
diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp
index 65dcd7b..1abd582 100644
--- a/src/core/SkRecorder.cpp
+++ b/src/core/SkRecorder.cpp
@@ -317,9 +317,13 @@
     }
 }
 
-void SkRecorder::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
-                                      const SkPaint& paint) {
-    this->append<SkRecords::DrawVertices>(paint, sk_ref_sp(const_cast<SkVertices*>(vertices)), bmode);
+void SkRecorder::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                      int boneCount, SkBlendMode bmode, const SkPaint& paint) {
+    this->append<SkRecords::DrawVertices>(paint,
+                                          sk_ref_sp(const_cast<SkVertices*>(vertices)),
+                                          this->copy(bones, boneCount),
+                                          boneCount,
+                                          bmode);
 }
 
 void SkRecorder::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h
index 6a7a83b..7fed8ea 100644
--- a/src/core/SkRecorder.h
+++ b/src/core/SkRecorder.h
@@ -121,7 +121,8 @@
                             const SkPaint*) override;
     void onDrawBitmapLattice(const SkBitmap&, const Lattice& lattice, const SkRect& dst,
                              const SkPaint*) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
                      int count, SkBlendMode, const SkRect* cull, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
diff --git a/src/core/SkRecords.h b/src/core/SkRecords.h
index 67424ac..4ec2ae6 100644
--- a/src/core/SkRecords.h
+++ b/src/core/SkRecords.h
@@ -344,6 +344,8 @@
 RECORD(DrawVertices, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         sk_sp<SkVertices> vertices;
+        PODArray<SkMatrix> bones;
+        int boneCount;
         SkBlendMode bmode);
 RECORD(DrawShadowRec, kDraw_Tag,
        PreCachedPath path;
diff --git a/src/core/SkThreadedBMPDevice.cpp b/src/core/SkThreadedBMPDevice.cpp
index 10cd824..de2b6c0 100644
--- a/src/core/SkThreadedBMPDevice.cpp
+++ b/src/core/SkThreadedBMPDevice.cpp
@@ -222,15 +222,16 @@
     });
 }
 
-void SkThreadedBMPDevice::drawVertices(const SkVertices* vertices, SkBlendMode bmode,
-        const SkPaint& paint) {
+void SkThreadedBMPDevice::drawVertices(const SkVertices* vertices, const SkMatrix* bones,
+                                       int boneCount, SkBlendMode bmode, const SkPaint& paint) {
     const sk_sp<SkVertices> verts = sk_ref_sp(vertices);  // retain vertices until flush
     SkRect drawBounds = SkRectPriv::MakeLargest(); // TODO tighter drawBounds
     fQueue.push(drawBounds, [=](SkArenaAlloc*, const DrawState& ds, const SkIRect& tileBounds){
         TileDraw(ds, tileBounds).drawVertices(verts->mode(), verts->vertexCount(),
                                               verts->positions(), verts->texCoords(),
-                                              verts->colors(), bmode, verts->indices(),
-                                              verts->indexCount(), paint);
+                                              verts->colors(), verts->boneIndices(),
+                                              verts->boneWeights(), bmode, verts->indices(),
+                                              verts->indexCount(), paint, bones, boneCount);
     });
 }
 
diff --git a/src/core/SkThreadedBMPDevice.h b/src/core/SkThreadedBMPDevice.h
index 13d8374..0cfb91d 100644
--- a/src/core/SkThreadedBMPDevice.h
+++ b/src/core/SkThreadedBMPDevice.h
@@ -35,7 +35,8 @@
     void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) override;
     void drawPosText(const void* text, size_t len, const SkScalar pos[],
                      int scalarsPerPos, const SkPoint& offset, const SkPaint& paint) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint&) override;
 
     void drawBitmap(const SkBitmap&, const SkMatrix&, const SkRect* dstOrNull,
                     const SkPaint&) override;
diff --git a/src/core/SkVertices.cpp b/src/core/SkVertices.cpp
index d4c6ed5..04db0d3 100644
--- a/src/core/SkVertices.cpp
+++ b/src/core/SkVertices.cpp
@@ -27,12 +27,14 @@
 
 struct SkVertices::Sizes {
     Sizes(SkVertices::VertexMode mode, int vertexCount, int indexCount, bool hasTexs,
-          bool hasColors) {
+          bool hasColors, bool hasBones) {
         SkSafeMath safe;
 
         fVSize = safe.mul(vertexCount, sizeof(SkPoint));
         fTSize = hasTexs ? safe.mul(vertexCount, sizeof(SkPoint)) : 0;
         fCSize = hasColors ? safe.mul(vertexCount, sizeof(SkColor)) : 0;
+        fBISize = hasBones ? safe.mul(vertexCount, sizeof(BoneIndices)) : 0;
+        fBWSize = hasBones ? safe.mul(vertexCount, sizeof(BoneWeights)) : 0;
 
         fBuilderTriFanISize = 0;
         fISize = safe.mul(indexCount, sizeof(uint16_t));
@@ -61,7 +63,9 @@
                  safe.add(fVSize,
                  safe.add(fTSize,
                  safe.add(fCSize,
-                          fISize))));
+                 safe.add(fBISize,
+                 safe.add(fBWSize,
+                          fISize))))));
 
         if (safe.ok()) {
             fArrays = fTotal - sizeof(SkVertices);  // just the sum of the arrays
@@ -73,10 +77,12 @@
     bool isValid() const { return fTotal != 0; }
 
     size_t fTotal;  // size of entire SkVertices allocation (obj + arrays)
-    size_t fArrays; // size of all the arrays (V + T + C + I)
+    size_t fArrays; // size of all the arrays (V + T + C + BI + BW + I)
     size_t fVSize;
     size_t fTSize;
     size_t fCSize;
+    size_t fBISize;
+    size_t fBWSize;
     size_t fISize;
 
     // For indexed tri-fans this is the number of amount of space fo indices needed in the builder
@@ -88,8 +94,9 @@
                              uint32_t builderFlags) {
     bool hasTexs = SkToBool(builderFlags & SkVertices::kHasTexCoords_BuilderFlag);
     bool hasColors = SkToBool(builderFlags & SkVertices::kHasColors_BuilderFlag);
+    bool hasBones = SkToBool(builderFlags & SkVertices::kHasBones_BuilderFlag);
     this->init(mode, vertexCount, indexCount,
-               SkVertices::Sizes(mode, vertexCount, indexCount, hasTexs, hasColors));
+               SkVertices::Sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones));
 }
 
 SkVertices::Builder::Builder(VertexMode mode, int vertexCount, int indexCount,
@@ -113,13 +120,16 @@
     // need to point past the object to store the arrays
     char* ptr = (char*)storage + sizeof(SkVertices);
 
-    fVertices->fPositions = (SkPoint*)ptr;                          ptr += sizes.fVSize;
-    fVertices->fTexs = sizes.fTSize ? (SkPoint*)ptr : nullptr;      ptr += sizes.fTSize;
-    fVertices->fColors = sizes.fCSize ? (SkColor*)ptr : nullptr;    ptr += sizes.fCSize;
+    fVertices->fPositions = (SkPoint*)ptr;                                  ptr += sizes.fVSize;
+    fVertices->fTexs = sizes.fTSize ? (SkPoint*)ptr : nullptr;              ptr += sizes.fTSize;
+    fVertices->fColors = sizes.fCSize ? (SkColor*)ptr : nullptr;            ptr += sizes.fCSize;
+    fVertices->fBoneIndices = sizes.fBISize ? (BoneIndices*) ptr : nullptr; ptr += sizes.fBISize;
+    fVertices->fBoneWeights = sizes.fBWSize ? (BoneWeights*) ptr : nullptr; ptr += sizes.fBWSize;
     fVertices->fIndices = sizes.fISize ? (uint16_t*)ptr : nullptr;
     fVertices->fVertexCnt = vertexCount;
     fVertices->fIndexCnt = indexCount;
     fVertices->fMode = mode;
+
     // We defer assigning fBounds and fUniqueID until detach() is called
 }
 
@@ -173,6 +183,14 @@
     return fVertices ? const_cast<SkColor*>(fVertices->colors()) : nullptr;
 }
 
+SkVertices::BoneIndices* SkVertices::Builder::boneIndices() {
+    return fVertices ? const_cast<BoneIndices*>(fVertices->boneIndices()) : nullptr;
+}
+
+SkVertices::BoneWeights* SkVertices::Builder::boneWeights() {
+    return fVertices ? const_cast<BoneWeights*>(fVertices->boneWeights()) : nullptr;
+}
+
 uint16_t* SkVertices::Builder::indices() {
     if (!fVertices) {
         return nullptr;
@@ -187,9 +205,17 @@
 
 sk_sp<SkVertices> SkVertices::MakeCopy(VertexMode mode, int vertexCount,
                                        const SkPoint pos[], const SkPoint texs[],
-                                       const SkColor colors[], int indexCount,
-                                       const uint16_t indices[]) {
-    Sizes sizes(mode, vertexCount, indexCount, texs != nullptr, colors != nullptr);
+                                       const SkColor colors[],
+                                       const BoneIndices boneIndices[],
+                                       const BoneWeights boneWeights[],
+                                       int indexCount, const uint16_t indices[]) {
+    SkASSERT((!boneIndices && !boneWeights) || (boneIndices && boneWeights));
+    Sizes sizes(mode,
+                vertexCount,
+                indexCount,
+                texs != nullptr,
+                colors != nullptr,
+                boneIndices != nullptr);
     if (!sizes.isValid()) {
         return nullptr;
     }
@@ -200,6 +226,8 @@
     sk_careful_memcpy(builder.positions(), pos, sizes.fVSize);
     sk_careful_memcpy(builder.texCoords(), texs, sizes.fTSize);
     sk_careful_memcpy(builder.colors(), colors, sizes.fCSize);
+    sk_careful_memcpy(builder.boneIndices(), boneIndices, sizes.fBISize);
+    sk_careful_memcpy(builder.boneWeights(), boneWeights, sizes.fBWSize);
     size_t isize = (mode == kTriangleFan_VertexMode) ? sizes.fBuilderTriFanISize : sizes.fISize;
     sk_careful_memcpy(builder.indices(), indices, isize);
 
@@ -207,19 +235,26 @@
 }
 
 size_t SkVertices::approximateSize() const {
-    Sizes sizes(fMode, fVertexCnt, fIndexCnt, this->hasTexCoords(), this->hasColors());
+    Sizes sizes(fMode,
+                fVertexCnt,
+                fIndexCnt,
+                this->hasTexCoords(),
+                this->hasColors(),
+                this->hasBones());
     SkASSERT(sizes.isValid());
     return sizeof(SkVertices) + sizes.fArrays;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-// storage = packed | vertex_count | index_count | pos[] | texs[] | colors[] | indices[]
+// storage = packed | vertex_count | index_count | pos[] | texs[] | colors[] | boneIndices[] |
+//           boneWeights[] | indices[]
 //         = header + arrays
 
 #define kMode_Mask          0x0FF
 #define kHasTexs_Mask       0x100
 #define kHasColors_Mask     0x200
+#define kHasBones_Mask      0x400
 #define kHeaderSize         (3 * sizeof(uint32_t))
 
 sk_sp<SkData> SkVertices::encode() const {
@@ -232,8 +267,16 @@
     if (this->hasColors()) {
         packed |= kHasColors_Mask;
     }
+    if (this->hasBones()) {
+        packed |= kHasBones_Mask;
+    }
 
-    Sizes sizes(fMode, fVertexCnt, fIndexCnt, this->hasTexCoords(), this->hasColors());
+    Sizes sizes(fMode,
+                fVertexCnt,
+                fIndexCnt,
+                this->hasTexCoords(),
+                this->hasColors(),
+                this->hasBones());
     SkASSERT(sizes.isValid());
     SkASSERT(!sizes.fBuilderTriFanISize);
     // need to force alignment to 4 for SkWriter32 -- will pad w/ 0s as needed
@@ -248,6 +291,8 @@
     writer.write(fPositions, sizes.fVSize);
     writer.write(fTexs, sizes.fTSize);
     writer.write(fColors, sizes.fCSize);
+    writer.write(fBoneIndices, sizes.fBISize);
+    writer.write(fBoneWeights, sizes.fBWSize);
     // if index-count is odd, we won't be 4-bytes aligned, so we call the pad version
     writer.writePad(fIndices, sizes.fISize);
 
@@ -272,7 +317,8 @@
     }
     const bool hasTexs = SkToBool(packed & kHasTexs_Mask);
     const bool hasColors = SkToBool(packed & kHasColors_Mask);
-    Sizes sizes(mode, vertexCount, indexCount, hasTexs, hasColors);
+    const bool hasBones = SkToBool(packed & kHasBones_Mask);
+    Sizes sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones);
     if (!sizes.isValid()) {
         return nullptr;
     }
@@ -286,6 +332,8 @@
     reader.read(builder.positions(), sizes.fVSize);
     reader.read(builder.texCoords(), sizes.fTSize);
     reader.read(builder.colors(), sizes.fCSize);
+    reader.read(builder.boneIndices(), sizes.fBISize);
+    reader.read(builder.boneWeights(), sizes.fBWSize);
     size_t isize = (mode == kTriangleFan_VertexMode) ? sizes.fBuilderTriFanISize : sizes.fISize;
     reader.read(builder.indices(), isize);
     if (indexCount > 0) {
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 819acd2..a665517 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1541,7 +1541,9 @@
                                        &primitiveType);
 }
 
-void SkGpuDevice::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
+void SkGpuDevice::drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+                               SkBlendMode mode, const SkPaint& paint) {
+    // TODO: GPU ANIMATION
     ASSERT_SINGLE_OWNER
     GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawVertices", fContext.get());
 
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 7c7030e..48ba8ac 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -91,7 +91,8 @@
                      int scalarsPerPos, const SkPoint& offset, const SkPaint&) override;
     void drawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y,
                       const SkPaint& paint, SkDrawFilter* drawFilter) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint&) override;
     void drawShadow(const SkPath&, const SkDrawShadowRec&) override;
     void drawAtlas(const SkImage* atlas, const SkRSXform[], const SkRect[],
                    const SkColor[], int count, SkBlendMode, const SkPaint&) override;
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 45a9b95..2d2e27b 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1473,7 +1473,8 @@
     }
 }
 
-void SkPDFDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
+void SkPDFDevice::drawVertices(const SkVertices*, const SkMatrix*, int, SkBlendMode,
+                               const SkPaint&) {
     if (this->hasEmptyClip()) {
         return;
     }
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 33d0e70..4c9c9c8 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -100,7 +100,8 @@
                      const SkPoint& offset, const SkPaint&) override;
     void drawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y,
                       const SkPaint &, SkDrawFilter*) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint&) override;
     void drawDevice(SkBaseDevice*, int x, int y,
                     const SkPaint&) override;
 
diff --git a/src/pipe/SkPipeCanvas.cpp b/src/pipe/SkPipeCanvas.cpp
index d66953c..aabff5b 100644
--- a/src/pipe/SkPipeCanvas.cpp
+++ b/src/pipe/SkPipeCanvas.cpp
@@ -728,14 +728,16 @@
     write_paint(writer, paint, kGeometry_PaintUsage);
 }
 
-void SkPipeCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
-                                        const SkPaint& paint) {
+void SkPipeCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                        int boneCount, SkBlendMode bmode, const SkPaint& paint) {
     unsigned extra = static_cast<unsigned>(bmode);
 
     SkPipeWriter writer(this);
     writer.write32(pack_verb(SkPipeVerb::kDrawVertices, extra));
     // TODO: dedup vertices?
     writer.writeDataAsByteArray(vertices->encode().get());
+    writer.write32(boneCount);
+    writer.write(bones, sizeof(SkMatrix) * boneCount);
     write_paint(writer, paint, kVertices_PaintUsage);
 }
 
diff --git a/src/pipe/SkPipeCanvas.h b/src/pipe/SkPipeCanvas.h
index 65bca4d..b39f809 100644
--- a/src/pipe/SkPipeCanvas.h
+++ b/src/pipe/SkPipeCanvas.h
@@ -135,7 +135,8 @@
                          const SkPaint*) override;
     void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst,
                             const SkPaint*) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
 
     void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
     void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
diff --git a/src/pipe/SkPipeReader.cpp b/src/pipe/SkPipeReader.cpp
index ada4a21..312e3da 100644
--- a/src/pipe/SkPipeReader.cpp
+++ b/src/pipe/SkPipeReader.cpp
@@ -562,9 +562,14 @@
 static void drawVertices_handler(SkPipeReader& reader, uint32_t packedVerb, SkCanvas* canvas) {
     SkASSERT(SkPipeVerb::kDrawVertices == unpack_verb(packedVerb));
     SkBlendMode bmode = (SkBlendMode)unpack_verb_extra(packedVerb);
+    sk_sp<SkVertices> vertices = nullptr;
     if (sk_sp<SkData> data = reader.readByteArrayAsData()) {
-        canvas->drawVertices(SkVertices::Decode(data->data(), data->size()), bmode,
-                             read_paint(reader));
+        vertices = SkVertices::Decode(data->data(), data->size());
+    }
+    int boneCount = reader.read32();
+    const SkMatrix* bones = boneCount ? reader.skipT<SkMatrix>(boneCount) : nullptr;
+    if (vertices) {
+        canvas->drawVertices(vertices, bones, boneCount, bmode, read_paint(reader));
     }
 }
 
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index d552803..c2a402f 100644
--- a/src/svg/SkSVGDevice.cpp
+++ b/src/svg/SkSVGDevice.cpp
@@ -988,7 +988,8 @@
     }
 }
 
-void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) {
+void SkSVGDevice::drawVertices(const SkVertices*, const SkMatrix*, int, SkBlendMode,
+                               const SkPaint&) {
     // todo
 }
 
diff --git a/src/svg/SkSVGDevice.h b/src/svg/SkSVGDevice.h
index 4784c7e..6b8ee4f 100644
--- a/src/svg/SkSVGDevice.h
+++ b/src/svg/SkSVGDevice.h
@@ -43,7 +43,8 @@
     void drawTextOnPath(const void* text, size_t len,
                         const SkPath& path, const SkMatrix* matrix,
                         const SkPaint& paint) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint& paint) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint& paint) override;
 
     void drawDevice(SkBaseDevice*, int x, int y,
                     const SkPaint&) override;
diff --git a/src/utils/SkLuaCanvas.cpp b/src/utils/SkLuaCanvas.cpp
index c7e3064..441857f 100644
--- a/src/utils/SkLuaCanvas.cpp
+++ b/src/utils/SkLuaCanvas.cpp
@@ -314,7 +314,8 @@
     this->INHERITED::onDrawDrawable(drawable, matrix);
 }
 
-void SkLuaCanvas::onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint& paint) {
+void SkLuaCanvas::onDrawVerticesObject(const SkVertices*, const SkMatrix*, int, SkBlendMode,
+                                       const SkPaint& paint) {
     AUTO_LUA("drawVertices");
     lua.pushPaint(paint, "paint");
 }
diff --git a/src/utils/SkNWayCanvas.cpp b/src/utils/SkNWayCanvas.cpp
index c9f9768..017ec05 100644
--- a/src/utils/SkNWayCanvas.cpp
+++ b/src/utils/SkNWayCanvas.cpp
@@ -320,11 +320,11 @@
     }
 }
 
-void SkNWayCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
-                                        const SkPaint& paint) {
+void SkNWayCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                        int boneCount, SkBlendMode bmode, const SkPaint& paint) {
     Iter iter(fList);
     while (iter.next()) {
-        iter->drawVertices(vertices, bmode, paint);
+        iter->drawVertices(vertices, bones, boneCount, bmode, paint);
     }
 }
 
diff --git a/src/utils/SkPaintFilterCanvas.cpp b/src/utils/SkPaintFilterCanvas.cpp
index c17990d..929488c 100644
--- a/src/utils/SkPaintFilterCanvas.cpp
+++ b/src/utils/SkPaintFilterCanvas.cpp
@@ -173,11 +173,12 @@
     }
 }
 
-void SkPaintFilterCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
+void SkPaintFilterCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                               int boneCount, SkBlendMode bmode,
                                                const SkPaint& paint) {
     AutoPaintFilter apf(this, kVertices_Type, paint);
     if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawVerticesObject(vertices, bmode, *apf.paint());
+        this->SkNWayCanvas::onDrawVerticesObject(vertices, bones, boneCount, bmode, *apf.paint());
     }
 }
 
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 6d5b7e1..c6b92ad 100644
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -545,7 +545,7 @@
         if (vertices->vertexCount()) {
             SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(),
                                                               SkMatrix::MakeTrans(tx, ty)));
-            this->drawVertices(vertices, mode, paint);
+            this->drawVertices(vertices, nullptr, 0, mode, paint);
         }
     };
 
@@ -580,7 +580,7 @@
                     SkColorFilter::MakeModeFilter(rec.fAmbientColor,
                                                   SkBlendMode::kModulate)->makeComposed(
                                                                    SkGaussianColorFilter::Make()));
-                this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint);
+                this->drawVertices(vertices.get(), nullptr, 0, SkBlendMode::kModulate, paint);
                 success = true;
             }
         }
@@ -661,7 +661,7 @@
                     SkColorFilter::MakeModeFilter(rec.fSpotColor,
                                                   SkBlendMode::kModulate)->makeComposed(
                                                       SkGaussianColorFilter::Make()));
-                this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint);
+                this->drawVertices(vertices.get(), nullptr, 0, SkBlendMode::kModulate, paint);
                 success = true;
             }
         }
diff --git a/src/xps/SkXPSDevice.cpp b/src/xps/SkXPSDevice.cpp
index 36d1bd1..797ff38 100644
--- a/src/xps/SkXPSDevice.cpp
+++ b/src/xps/SkXPSDevice.cpp
@@ -1156,9 +1156,11 @@
     draw(this, &SkDraw::drawPoints, mode, count, points, paint, this);
 }
 
-void SkXPSDevice::drawVertices(const SkVertices* v, SkBlendMode blendMode, const SkPaint& paint) {
+void SkXPSDevice::drawVertices(const SkVertices* v, const SkMatrix* bones, int boneCount,
+                               SkBlendMode blendMode, const SkPaint& paint) {
     draw(this, &SkDraw::drawVertices, v->mode(), v->vertexCount(), v->positions(), v->texCoords(),
-         v->colors(), blendMode, v->indices(), v->indexCount(), paint);
+         v->colors(), v->boneIndices(), v->boneWeights(), blendMode, v->indices(), v->indexCount(),
+         paint, bones, boneCount);
 }
 
 void SkXPSDevice::drawPaint(const SkPaint& origPaint) {
diff --git a/src/xps/SkXPSDevice.h b/src/xps/SkXPSDevice.h
index 81e881e..03de5f1 100644
--- a/src/xps/SkXPSDevice.h
+++ b/src/xps/SkXPSDevice.h
@@ -100,7 +100,8 @@
     void drawPosText(const void* text, size_t len,
                      const SkScalar pos[], int scalarsPerPos,
                      const SkPoint& offset, const SkPaint& paint) override;
-    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void drawVertices(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                      const SkPaint&) override;
     void drawDevice(SkBaseDevice*, int x, int y,
                     const SkPaint&) override;
 
diff --git a/tools/debugger/SkDebugCanvas.cpp b/tools/debugger/SkDebugCanvas.cpp
index 8026eed..537bcb1 100644
--- a/tools/debugger/SkDebugCanvas.cpp
+++ b/tools/debugger/SkDebugCanvas.cpp
@@ -445,8 +445,9 @@
     this->addDrawCommand(new SkDrawPatchCommand(cubics, colors, texCoords, bmode, paint));
 }
 
-void SkDebugCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
-                                         const SkPaint& paint) {
+void SkDebugCanvas::onDrawVerticesObject(const SkVertices* vertices, const SkMatrix* bones,
+                                         int boneCount, SkBlendMode bmode, const SkPaint& paint) {
+    // TODO: ANIMATION NOT LOGGED
     this->addDrawCommand(new SkDrawVerticesCommand(sk_ref_sp(const_cast<SkVertices*>(vertices)),
                                                    bmode, paint));
 }
diff --git a/tools/debugger/SkDebugCanvas.h b/tools/debugger/SkDebugCanvas.h
index c4a61e7..b956149 100644
--- a/tools/debugger/SkDebugCanvas.h
+++ b/tools/debugger/SkDebugCanvas.h
@@ -146,7 +146,8 @@
     void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
     void onDrawRRect(const SkRRect&, const SkPaint&) override;
     void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override;
-    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, const SkMatrix* bones, int boneCount, SkBlendMode,
+                              const SkPaint&) override;
     void onDrawPath(const SkPath&, const SkPaint&) override;
     void onDrawRegion(const SkRegion&, const SkPaint&) override;
     void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override;
diff --git a/tools/flags/SkCommonFlags.cpp b/tools/flags/SkCommonFlags.cpp
index 73b0eb4..ed4d169 100644
--- a/tools/flags/SkCommonFlags.cpp
+++ b/tools/flags/SkCommonFlags.cpp
@@ -57,10 +57,12 @@
 DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from.");
 DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from.");
 DEFINE_string(jsons, "/data/local/tmp/jsons", "Directory to read (Bodymovin) jsons from.");
+DEFINE_string(nimas, "/data/local/tmp/nimas", "Directory to read NIMA animations from.");
 #else
 DEFINE_string(skps, "skps", "Directory to read skps from.");
 DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
 DEFINE_string(jsons, "jsons", "Directory to read (Bodymovin) jsons from.");
+DEFINE_string(nimas, "nimas", "Directory to read NIMA animations from.");
 #endif
 
 DEFINE_int32(skpViewportSize, 1000, "Width & height of the viewport used to crop skp rendering.");
diff --git a/tools/flags/SkCommonFlags.h b/tools/flags/SkCommonFlags.h
index 3dca16f..10f90c0 100644
--- a/tools/flags/SkCommonFlags.h
+++ b/tools/flags/SkCommonFlags.h
@@ -28,6 +28,7 @@
 DECLARE_string(jpgs);
 DECLARE_string(jsons);
 DECLARE_string(svgs);
+DECLARE_string(nimas);
 DECLARE_bool(nativeFonts);
 DECLARE_int32(threads);
 DECLARE_string(resourcePath);
diff --git a/tools/viewer/NIMASlide.cpp b/tools/viewer/NIMASlide.cpp
new file mode 100644
index 0000000..c13ff2f
--- /dev/null
+++ b/tools/viewer/NIMASlide.cpp
@@ -0,0 +1,516 @@
+/*
+* Copyright 2018 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "NIMASlide.h"
+
+#include "SkAnimTimer.h"
+#include "SkOSPath.h"
+#include "Resources.h"
+#include "imgui.h"
+
+#include <algorithm>
+#include <cmath>
+
+using namespace sk_app;
+using namespace nima;
+
+// NIMA stores its matrices as 6 floats to represent translation and scale. This function takes
+// that format and converts it into a 3x3 matrix representation.
+static void nima_to_skmatrix(const float* nimaData, SkMatrix& matrix) {
+    matrix[0] = nimaData[0];
+    matrix[1] = nimaData[2];
+    matrix[2] = nimaData[4];
+    matrix[3] = nimaData[1];
+    matrix[4] = nimaData[3];
+    matrix[5] = nimaData[5];
+    matrix[6] = 0.0f;
+    matrix[7] = 0.0f;
+    matrix[8] = 1.0f;
+}
+
+// ImGui expects an array of const char* when displaying a ListBox. This function is for an
+// overload of ImGui::ListBox that takes a getter so that ListBox works with
+// std::vector<std::string>.
+static bool vector_getter(void* v, int index, const char** out) {
+    auto vector = reinterpret_cast<std::vector<std::string>*>(v);
+    *out = vector->at(index).c_str();
+    return true;
+}
+
+// A wrapper class that handles rendering of ActorImages (renderable components NIMA Actors).
+class NIMAActorImage {
+public:
+    NIMAActorImage(ActorImage* actorImage, SkImage* texture, SkPaint* paint)
+            : fActorImage(actorImage)
+            , fTexture(texture)
+            , fPaint(paint)
+            , fSkinned(false)
+            , fPositions()
+            , fTexs()
+            , fBoneIdx()
+            , fBoneWgt()
+            , fIndices()
+            , fBones()
+            , fVertices(nullptr)
+            , fRenderMode(kBackend_RenderMode) {
+        // Update the vertices and bones.
+        this->updateVertices();
+        this->updateBones();
+
+        // Update the vertices object.
+        this->updateVerticesObject(false);
+    }
+
+    void renderBackend(SkCanvas* canvas) {
+        // Reset vertices if the render mode has changed.
+        if (fRenderMode != kBackend_RenderMode) {
+            fRenderMode = kBackend_RenderMode;
+            this->updateVertices();
+            this->updateVerticesObject(false);
+        }
+
+        canvas->save();
+
+        // Update the vertex data.
+        if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
+            this->updateVertices();
+            this->updateVerticesObject(false);
+            fActorImage->isVertexDeformDirty(false);
+        }
+
+        // Update the bones.
+        this->updateBones();
+
+        // Draw the vertices object.
+        this->drawVerticesObject(canvas, true);
+
+        canvas->restore();
+    }
+
+    void renderImmediate(SkCanvas* canvas) {
+        // Reset vertices if the render mode has changed.
+        if (fRenderMode != kImmediate_RenderMode) {
+            fRenderMode = kImmediate_RenderMode;
+            this->updateVertices();
+            this->updateVerticesObject(true);
+        }
+
+        // Update the vertex data.
+        if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
+            this->updateVertices();
+            fActorImage->isVertexDeformDirty(false);
+        }
+
+        // Update the vertices object.
+        this->updateVerticesObject(true);
+
+        // Draw the vertices object.
+        this->drawVerticesObject(canvas, false);
+    }
+
+    int drawOrder() const { return fActorImage->drawOrder(); }
+
+private:
+    void updateVertices() {
+        // Update whether the image is skinned.
+        fSkinned = fActorImage->connectedBoneCount() > 0;
+
+        // Retrieve data from the image.
+        uint32_t  vertexCount  = fActorImage->vertexCount();
+        uint32_t  vertexStride = fActorImage->vertexStride();
+        float*    vertexData   = fActorImage->vertices();
+        uint32_t  indexCount   = fActorImage->triangleCount() * 3;
+        uint16_t* indexData    = fActorImage->triangles();
+
+        // Don't render if not visible.
+        if (!vertexCount || fActorImage->textureIndex() < 0) {
+            fPositions.clear();
+            fTexs.clear();
+            fBoneIdx.clear();
+            fBoneWgt.clear();
+            fIndices.clear();
+            return;
+        }
+
+        // Split the vertex data.
+        fPositions.resize(vertexCount);
+        fTexs.resize(vertexCount);
+        fIndices.resize(indexCount);
+        if (fSkinned) {
+            fBoneIdx.resize(vertexCount * 4);
+            fBoneWgt.resize(vertexCount * 4);
+        }
+        for (uint32_t i = 0; i < vertexCount; i ++) {
+            uint32_t j = i * vertexStride;
+
+            // Get the attributes.
+            float* attrPosition = vertexData + j;
+            float* attrTex      = vertexData + j + 2;
+            float* attrBoneIdx  = vertexData + j + 4;
+            float* attrBoneWgt  = vertexData + j + 8;
+
+            // Get deformed positions if necessary.
+            if (fActorImage->doesAnimationVertexDeform()) {
+                attrPosition = fActorImage->animationDeformedVertices() + i * 2;
+            }
+
+            // Set the data.
+            fPositions[i].set(attrPosition[0], attrPosition[1]);
+            fTexs[i].set(attrTex[0] * fTexture->width(), attrTex[1] * fTexture->height());
+            if (fSkinned) {
+                for (uint32_t k = 0; k < 4; k ++) {
+                    fBoneIdx[i].indices[k] = static_cast<uint32_t>(attrBoneIdx[k]);
+                    fBoneWgt[i].weights[k] = attrBoneWgt[k];
+                }
+            }
+        }
+        memcpy(fIndices.data(), indexData, indexCount * sizeof(uint16_t));
+    }
+
+    void updateBones() {
+        // NIMA matrices are a collection of 6 floats.
+        constexpr int kNIMAMatrixSize = 6;
+
+        // Set up the matrices for the first time.
+        if (fBones.size() == 0) {
+            int numMatrices = 1;
+            if (fSkinned) {
+                numMatrices = fActorImage->boneInfluenceMatricesLength() / kNIMAMatrixSize;
+            }
+            fBones.assign(numMatrices, SkMatrix());
+        }
+
+        if (fSkinned) {
+            // Update the matrices.
+            float* matrixData = fActorImage->boneInfluenceMatrices();
+            for (uint32_t i = 1; i < fBones.size(); i ++) {
+                SkMatrix& matrix = fBones[i];
+                float* data = matrixData + i * kNIMAMatrixSize;
+                nima_to_skmatrix(data, matrix);
+            }
+        }
+
+        // Set the zero matrix to be the world transform.
+        nima_to_skmatrix(fActorImage->worldTransform().values(), fBones[0]);
+    }
+
+    void updateVerticesObject(bool applyDeforms) {
+        std::vector<SkPoint>* positions = &fPositions;
+
+        // Apply deforms if requested.
+        uint32_t vertexCount = fPositions.size();
+        std::vector<SkPoint> deformedPositions;
+        if (applyDeforms) {
+            positions = &deformedPositions;
+            deformedPositions.reserve(vertexCount);
+            for (uint32_t i = 0; i < vertexCount; i ++) {
+                Vec2D nimaPoint(fPositions[i].x(), fPositions[i].y());
+                uint32_t* boneIdx = nullptr;
+                float* boneWgt = nullptr;
+                if (fSkinned) {
+                    boneIdx = fBoneIdx[i].indices;
+                    boneWgt = fBoneWgt[i].weights;
+                }
+                nimaPoint = this->deform(nimaPoint, boneIdx, boneWgt);
+                deformedPositions.push_back(SkPoint::Make(nimaPoint[0], nimaPoint[1]));
+            }
+        }
+
+        // Update the vertices object.
+        fVertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
+                                         vertexCount,
+                                         positions->data(),
+                                         fTexs.data(),
+                                         nullptr,
+                                         fBoneIdx.data(),
+                                         fBoneWgt.data(),
+                                         fIndices.size(),
+                                         fIndices.data());
+    }
+
+    void drawVerticesObject(SkCanvas* canvas, bool useBones) const {
+        // Determine the blend mode.
+        SkBlendMode blendMode;
+        switch (fActorImage->blendMode()) {
+            case BlendMode::Off: {
+                blendMode = SkBlendMode::kSrc;
+                break;
+            }
+            case BlendMode::Normal: {
+                blendMode = SkBlendMode::kSrcOver;
+                break;
+            }
+            case BlendMode::Additive: {
+                blendMode = SkBlendMode::kPlus;
+                break;
+            }
+            case BlendMode::Multiply: {
+                blendMode = SkBlendMode::kMultiply;
+                break;
+            }
+            case BlendMode::Screen: {
+                blendMode = SkBlendMode::kScreen;
+                break;
+            }
+        }
+
+        // Set the opacity.
+        fPaint->setAlpha(static_cast<U8CPU>(fActorImage->renderOpacity() * 255));
+
+        // Draw the vertices.
+        if (useBones) {
+            canvas->drawVertices(fVertices, fBones.data(), fBones.size(), blendMode, *fPaint);
+        } else {
+            canvas->drawVertices(fVertices, blendMode, *fPaint);
+        }
+
+        // Reset the opacity.
+        fPaint->setAlpha(255);
+    }
+
+    Vec2D deform(const Vec2D& position, uint32_t* boneIdx, float* boneWgt) const {
+        float px = position[0], py = position[1];
+        float px2 = px, py2 = py;
+        float influence[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+        // Apply the world transform.
+        Mat2D worldTransform = fActorImage->worldTransform();
+        px2 = worldTransform[0] * px + worldTransform[2] * py + worldTransform[4];
+        py2 = worldTransform[1] * px + worldTransform[3] * py + worldTransform[5];
+
+        // Apply deformations based on bone offsets.
+        if (boneIdx && boneWgt) {
+            float* matrices = fActorImage->boneInfluenceMatrices();
+
+            for (uint32_t i = 0; i < 4; i ++) {
+                uint32_t index = boneIdx[i];
+                float weight = boneWgt[i];
+                for (int j = 0; j < 6; j ++) {
+                    influence[j] += matrices[index * 6 + j] * weight;
+                }
+            }
+
+            px = influence[0] * px2 + influence[2] * py2 + influence[4];
+            py = influence[1] * px2 + influence[3] * py2 + influence[5];
+        } else {
+            px = px2;
+            py = py2;
+        }
+
+        // Return the transformed position.
+        return Vec2D(px, py);
+    }
+
+private:
+    ActorImage* fActorImage;
+    SkImage*    fTexture;
+    SkPaint*    fPaint;
+
+    bool                                 fSkinned;
+    std::vector<SkPoint>                 fPositions;
+    std::vector<SkPoint>                 fTexs;
+    std::vector<SkVertices::BoneIndices> fBoneIdx;
+    std::vector<SkVertices::BoneWeights> fBoneWgt;
+    std::vector<uint16_t>                fIndices;
+
+    std::vector<SkMatrix> fBones;
+    sk_sp<SkVertices>     fVertices;
+
+    RenderMode fRenderMode;
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Represents an Actor, or an animated character, in NIMA.
+class NIMAActor : public Actor {
+public:
+    NIMAActor(const std::string& basePath)
+        : fTexture(nullptr)
+        , fActorImages()
+        , fPaint()
+        , fAnimations() {
+        // Load the NIMA data.
+        std::string nimaPath((basePath + ".nima").c_str());
+        INHERITED::load(nimaPath);
+
+        // Load the image asset.
+        sk_sp<SkData> imageData = SkData::MakeFromFileName((basePath + ".png").c_str());
+        fTexture = SkImage::MakeFromEncoded(imageData);
+
+        // Create the paint.
+        fPaint.setShader(fTexture->makeShader(nullptr));
+        fPaint.setFilterQuality(SkFilterQuality::kLow_SkFilterQuality);
+
+        // Load the image nodes.
+        fActorImages.reserve(m_ImageNodeCount);
+        for (uint32_t i = 0; i < m_ImageNodeCount; i ++) {
+            fActorImages.emplace_back(m_ImageNodes[i], fTexture.get(), &fPaint);
+        }
+
+        // Sort the image nodes.
+        std::sort(fActorImages.begin(), fActorImages.end(), [](auto a, auto b) {
+            return a.drawOrder() < b.drawOrder();
+        });
+
+        // Get the list of animations.
+        fAnimations.reserve(m_AnimationsCount);
+        for (uint32_t i = 0; i < m_AnimationsCount; i ++) {
+            fAnimations.push_back(m_Animations[i].name());
+        }
+    }
+
+    void render(SkCanvas* canvas, RenderMode renderMode) {
+        // Render the image nodes.
+        for (auto& image : fActorImages) {
+            switch (renderMode) {
+            case kBackend_RenderMode: {
+                // Render with Skia backend.
+                image.renderBackend(canvas);
+                break;
+            }
+            case kImmediate_RenderMode: {
+                // Render with immediate backend.
+                image.renderImmediate(canvas);
+                break;
+            }
+            }
+        }
+    }
+
+    const std::vector<std::string>& getAnimations() const {
+        return fAnimations;
+    }
+
+private:
+    sk_sp<SkImage>              fTexture;
+    std::vector<NIMAActorImage> fActorImages;
+    SkPaint                     fPaint;
+    std::vector<std::string>    fAnimations;
+
+    typedef Actor INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+NIMASlide::NIMASlide(const SkString& name, const SkString& path)
+        : fBasePath()
+        , fActor(nullptr)
+        , fPlaying(true)
+        , fTime(0.0f)
+        , fRenderMode(kBackend_RenderMode)
+        , fAnimation(nullptr)
+        , fAnimationIndex(0) {
+    fName = name;
+
+    // Get the path components.
+    SkString baseName = SkOSPath::Basename(path.c_str());
+    baseName.resize(baseName.size() - 5);
+    SkString dirName = SkOSPath::Dirname(path.c_str());
+    SkString basePath = SkOSPath::Join(dirName.c_str(), baseName.c_str());
+
+    // Save the base path.
+    fBasePath = std::string(basePath.c_str());
+}
+
+NIMASlide::~NIMASlide() {}
+
+void NIMASlide::draw(SkCanvas* canvas) {
+    canvas->save();
+
+    canvas->translate(500, 500);
+    canvas->scale(1, -1);
+
+    // Render the actor.
+    fActor->render(canvas, fRenderMode);
+
+    canvas->restore();
+
+    // Render the GUI.
+    this->renderGUI();
+}
+
+void NIMASlide::load(SkScalar winWidth, SkScalar winHeight) {
+    this->resetActor();
+}
+
+void NIMASlide::unload() {
+    // Discard resources.
+    fAnimation = nullptr;
+    fActor.reset(nullptr);
+}
+
+bool NIMASlide::animate(const SkAnimTimer& timer) {
+    // Apply the animation.
+    if (fAnimation) {
+        if (fPlaying) {
+            fTime = std::fmod(timer.secs(), fAnimation->max());
+        }
+        fAnimation->time(fTime);
+        fAnimation->apply(1.0f);
+    }
+    return true;
+}
+
+bool NIMASlide::onChar(SkUnichar c) {
+    return false;
+}
+
+bool NIMASlide::onMouse(SkScalar x, SkScalar y, Window::InputState state, uint32_t modifiers) {
+    return false;
+}
+
+void NIMASlide::resetActor() {
+    // Create the actor.
+    fActor = std::make_unique<NIMAActor>(fBasePath);
+
+    // Get the animation.
+    fAnimation = fActor->animationInstance(fActor->getAnimations()[fAnimationIndex]);
+}
+
+void NIMASlide::renderGUI() {
+    ImGui::SetNextWindowSize(ImVec2(300, 220));
+    ImGui::Begin("NIMA");
+
+    // List of animations.
+    auto animations = const_cast<std::vector<std::string>&>(fActor->getAnimations());
+    ImGui::PushItemWidth(-1);
+    if (ImGui::ListBox("Animations",
+                       &fAnimationIndex,
+                       vector_getter,
+                       reinterpret_cast<void*>(&animations),
+                       animations.size(),
+                       5)) {
+        resetActor();
+    }
+
+    // Playback control.
+    ImGui::Spacing();
+    if (ImGui::Button("Play")) {
+        fPlaying = true;
+    }
+    ImGui::SameLine();
+    if (ImGui::Button("Pause")) {
+        fPlaying = false;
+    }
+
+    // Time slider.
+    ImGui::PushItemWidth(-1);
+    ImGui::SliderFloat("Time", &fTime, 0.0f, fAnimation->max(), "Time: %.3f");
+
+    // Backend control.
+    int renderMode = fRenderMode;
+    ImGui::Spacing();
+    ImGui::RadioButton("Skia Backend", &renderMode, 0);
+    ImGui::RadioButton("Immediate Backend", &renderMode, 1);
+    if (renderMode == 0) {
+        fRenderMode = kBackend_RenderMode;
+    } else {
+        fRenderMode = kImmediate_RenderMode;
+    }
+
+    ImGui::End();
+}
diff --git a/tools/viewer/NIMASlide.h b/tools/viewer/NIMASlide.h
new file mode 100644
index 0000000..d19d101
--- /dev/null
+++ b/tools/viewer/NIMASlide.h
@@ -0,0 +1,59 @@
+/*
+* Copyright 2018 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef NIMASlide_DEFINED
+#define NIMASlide_DEFINED
+
+#include "Slide.h"
+
+#include "SkCanvas.h"
+#include "SkVertices.h"
+#include <nima/Actor.hpp>
+#include <nima/ActorImage.hpp>
+#include <nima/Animation/ActorAnimationInstance.hpp>
+#include <nima/Vec2D.hpp>
+
+class NIMAActor;
+class NIMAActorImage;
+
+enum RenderMode {
+    kBackend_RenderMode   = 0,
+    kImmediate_RenderMode = 1,
+};
+
+class NIMASlide : public Slide {
+public:
+    NIMASlide(const SkString& name, const SkString& path);
+    ~NIMASlide() override;
+
+    void draw(SkCanvas* canvas) override;
+    void load(SkScalar winWidth, SkScalar winHeight) override;
+    void unload() override;
+    bool animate(const SkAnimTimer& timer) override;
+
+    bool onChar(SkUnichar c) override;
+    bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState state,
+                 uint32_t modifiers) override;
+
+private:
+    void resetActor();
+
+    void renderGUI();
+
+private:
+    std::string                fBasePath;
+    std::unique_ptr<NIMAActor> fActor;
+
+    bool fPlaying;
+    float fTime;
+    RenderMode fRenderMode;
+
+    nima::ActorAnimationInstance* fAnimation;
+    int                           fAnimationIndex;
+};
+
+#endif
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 81c1851..27c8819 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -52,6 +52,10 @@
     #include "SkottieSlide.h"
 #endif
 
+#if !(defined(SK_BUILD_FOR_WIN) && defined(__clang__))
+    #include "NIMASlide.h"
+#endif
+
 using namespace sk_app;
 
 static std::map<GpuPathRenderers, std::string> gPathRendererNames;
@@ -565,6 +569,12 @@
             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
                 return sk_make_sp<SvgSlide>(name, path);}
         },
+#if !(defined(SK_BUILD_FOR_WIN) && defined(__clang__))
+        { ".nima", "nima-dir", FLAGS_nimas,
+            [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
+                return sk_make_sp<NIMASlide>(name, path);}
+        },
+#endif
     };
 
     SkTArray<sk_sp<Slide>, true> dirSlides;