added skeletal animation support to GPU backend

added caching of SkVertices

Docs-Preview: https://skia.org/?cl=138596
Bug: skia:
Change-Id: Ia750f55f5f6d0de250d9e9c5619f4d1ac856f9f5
Reviewed-on: https://skia-review.googlesource.com/138596
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Ruiqi Mao <ruiqimao@google.com>
diff --git a/docs/SkCanvas_Reference.bmh b/docs/SkCanvas_Reference.bmh
index cfeef63..bfd3e9d 100644
--- a/docs/SkCanvas_Reference.bmh
+++ b/docs/SkCanvas_Reference.bmh
@@ -5772,6 +5772,7 @@
 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.
+boneCount must be at most 100, and thus the size of bones should be at most 100.
 
 #Param  vertices  triangle mesh to draw ##
 #Param  bones     bone matrix data ##
@@ -5821,6 +5822,7 @@
 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.
+boneCount must be at most 100, and thus the size of bones should be at most 100.
 
 #Param  vertices  triangle mesh to draw ##
 #Param  bones     bone matrix data ##
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 90e51e9..abac335 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -6,7 +6,7 @@
  */
 
 /* Generated by tools/bookmaker from include/core/SkCanvas.h and docs/SkCanvas_Reference.bmh
-   on 2018-07-02 10:27:08. Additional documentation and examples can be found at:
+   on 2018-07-03 11:34:22. Additional documentation and examples can be found at:
    https://skia.org/user/api/SkCanvas_Reference
 
    You may edit either file directly. Structural changes to public interfaces require
@@ -2156,6 +2156,7 @@
         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.
+        boneCount must be at most 100, and thus the size of bones should be at most 100.
 
         @param vertices   triangle mesh to draw
         @param bones      bone matrix data
@@ -2173,6 +2174,7 @@
         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.
+        boneCount must be at most 100, and thus the size of bones should be at most 100.
 
         @param vertices   triangle mesh to draw
         @param bones      bone matrix data
diff --git a/include/core/SkVertices.h b/include/core/SkVertices.h
index 6c4fb39..5208bfb 100644
--- a/include/core/SkVertices.h
+++ b/include/core/SkVertices.h
@@ -55,14 +55,16 @@
                                       const BoneIndices boneIndices[],
                                       const BoneWeights boneWeights[],
                                       int indexCount,
-                                      const uint16_t indices[]);
+                                      const uint16_t indices[],
+                                      bool isVolatile = true);
 
     static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
                                       const SkPoint positions[],
                                       const SkPoint texs[],
                                       const SkColor colors[],
                                       const BoneIndices boneIndices[],
-                                      const BoneWeights boneWeights[]) {
+                                      const BoneWeights boneWeights[],
+                                      bool isVolatile = true) {
         return MakeCopy(mode,
                         vertexCount,
                         positions,
@@ -71,7 +73,8 @@
                         boneIndices,
                         boneWeights,
                         0,
-                        nullptr);
+                        nullptr,
+                        isVolatile);
     }
 
     static sk_sp<SkVertices> MakeCopy(VertexMode mode, int vertexCount,
@@ -79,7 +82,8 @@
                                       const SkPoint texs[],
                                       const SkColor colors[],
                                       int indexCount,
-                                      const uint16_t indices[]) {
+                                      const uint16_t indices[],
+                                      bool isVolatile = true) {
         return MakeCopy(mode,
                         vertexCount,
                         positions,
@@ -88,14 +92,16 @@
                         nullptr,
                         nullptr,
                         indexCount,
-                        indices);
+                        indices,
+                        isVolatile);
     }
 
     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, nullptr, nullptr);
+                                      const SkColor colors[],
+                                      bool isVolatile = true) {
+        return MakeCopy(mode, vertexCount, positions, texs, colors, nullptr, nullptr, isVolatile);
     }
 
     struct Sizes;
@@ -104,6 +110,7 @@
         kHasTexCoords_BuilderFlag   = 1 << 0,
         kHasColors_BuilderFlag      = 1 << 1,
         kHasBones_BuilderFlag       = 1 << 2,
+        kIsVolatile_BuilderFlag     = 1 << 3,
     };
     class Builder {
     public:
@@ -114,6 +121,7 @@
         // if the builder is invalid, these will return 0
         int vertexCount() const;
         int indexCount() const;
+        bool isVolatile() const;
         SkPoint* positions();
         SkPoint* texCoords();       // returns null if there are no texCoords
         SkColor* colors();          // returns null if there are no colors
@@ -125,9 +133,9 @@
         sk_sp<SkVertices> detach();
 
     private:
-        Builder(VertexMode mode, int vertexCount, int indexCount, const Sizes&);
+        Builder(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, const Sizes&);
 
-        void init(VertexMode mode, int vertexCount, int indexCount, const Sizes&);
+        void init(VertexMode mode, int vertexCount, int indexCount, bool isVolatile, const Sizes&);
 
         // holds a partially complete object. only completed in detach()
         sk_sp<SkVertices> fVertices;
@@ -158,6 +166,8 @@
     int indexCount() const { return fIndexCnt; }
     const uint16_t* indices() const { return fIndices; }
 
+    bool isVolatile() const { return fIsVolatile; }
+
     // returns approximate byte size of the vertices object
     size_t approximateSize() const;
 
@@ -199,6 +209,8 @@
     int     fVertexCnt;
     int     fIndexCnt;
 
+    bool fIsVolatile;
+
     VertexMode fMode;
     // below here is where the actual array data is stored.
 };
diff --git a/site/user/api/SkCanvas_Reference.md b/site/user/api/SkCanvas_Reference.md
index 1656128..7d87145 100644
--- a/site/user/api/SkCanvas_Reference.md
+++ b/site/user/api/SkCanvas_Reference.md
@@ -2684,7 +2684,7 @@
 
 ### Example
 
-<div><fiddle-embed name="53c212c4f2449df0b0eedbc6227b6ab7"><div><a href='#SkCanvas_scale'>scale</a> followed by <a href='#SkCanvas_translate'>translate</a> produces different results from <a href='#SkCanvas_translate'>translate</a> followed
+<div><fiddle-embed name="eb93d5fa66a5f7a10f4f9210494d7222"><div><a href='#SkCanvas_scale'>scale</a> followed by <a href='#SkCanvas_translate'>translate</a> produces different results from <a href='#SkCanvas_translate'>translate</a> followed
 by <a href='#SkCanvas_scale'>scale</a>.
 
 The blue stroke follows translate of (50, 50); a black
@@ -4662,7 +4662,7 @@
 
 ### Example
 
-<div><fiddle-embed name="185746dc0faa6f1df30c4afe098646ff"></fiddle-embed></div>
+<div><fiddle-embed name="ac93f30dff13f8a8bb31398de370863b"></fiddle-embed></div>
 
 ### See Also
 
@@ -6412,7 +6412,7 @@
 
 ### Example
 
-<div><fiddle-embed name="9adda80b2dd1b08ec5ccf66da7c8bd91"></fiddle-embed></div>
+<div><fiddle-embed name="759e4e5bac680838added8f70884dcdc"></fiddle-embed></div>
 
 ### See Also
 
@@ -6537,6 +6537,7 @@
 The first element of <a href='#SkCanvas_drawVertices_3_bones'>bones</a> 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.
+<a href='#SkCanvas_drawVertices_3_boneCount'>boneCount</a> must be at most 100, and thus the size of <a href='#SkCanvas_drawVertices_3_bones'>bones</a> should be at most 100.
 
 ### Parameters
 
@@ -6581,6 +6582,7 @@
 The first element of <a href='#SkCanvas_drawVertices_4_bones'>bones</a> 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.
+<a href='#SkCanvas_drawVertices_4_boneCount'>boneCount</a> must be at most 100, and thus the size of <a href='#SkCanvas_drawVertices_4_bones'>bones</a> should be at most 100.
 
 ### Parameters
 
@@ -6773,7 +6775,7 @@
 
 ### Example
 
-<div><fiddle-embed name="fe79a9c1ec350264eb9c7b2509dd3638"></fiddle-embed></div>
+<div><fiddle-embed name="1df575f9b8132306ce0552a2554ed132"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkIRect_Reference.md b/site/user/api/SkIRect_Reference.md
index 829b1bf..06e581f 100644
--- a/site/user/api/SkIRect_Reference.md
+++ b/site/user/api/SkIRect_Reference.md
@@ -1504,7 +1504,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a7958a4e0668f5cf805a8e78eb57f51d">
+<div><fiddle-embed name="1db94b2c76e0a7a71856532335fa56b6">
 
 #### Example Output
 
@@ -2277,7 +2277,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a98993a66616ae406d8bdc54adfb1411">
+<div><fiddle-embed name="d35fbc9fdea71df8b8a12fd3da50d11c">
 
 #### Example Output
 
diff --git a/site/user/api/SkImageInfo_Reference.md b/site/user/api/SkImageInfo_Reference.md
index 0edfe5b..07702da 100644
--- a/site/user/api/SkImageInfo_Reference.md
+++ b/site/user/api/SkImageInfo_Reference.md
@@ -2035,7 +2035,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a818be8945cd0c18f99ffe53e90afa48"></fiddle-embed></div>
+<div><fiddle-embed name="3ce3db36235d80dbac4d39504cf756da"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkImage_Reference.md b/site/user/api/SkImage_Reference.md
index 3c88396..eec6966 100644
--- a/site/user/api/SkImage_Reference.md
+++ b/site/user/api/SkImage_Reference.md
@@ -2300,7 +2300,7 @@
 
 ### Example
 
-<div><fiddle-embed name="4eb2d95c9e9a66f05296e345bb68bd51"></fiddle-embed></div>
+<div><fiddle-embed name="93669037c9eb9d142e7776b9f936fa96"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkMatrix_Reference.md b/site/user/api/SkMatrix_Reference.md
index dad2ae9..4c891ad 100644
--- a/site/user/api/SkMatrix_Reference.md
+++ b/site/user/api/SkMatrix_Reference.md
@@ -3203,7 +3203,7 @@
 
 ### Example
 
-<div><fiddle-embed name="27a0ab44659201f1aa2ac7fea73368c2"></fiddle-embed></div>
+<div><fiddle-embed name="3edbdea8e43d06086abf33ec4a9b415b"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkPaint_Reference.md b/site/user/api/SkPaint_Reference.md
index 3a5a932..06b6eb4 100644
--- a/site/user/api/SkPaint_Reference.md
+++ b/site/user/api/SkPaint_Reference.md
@@ -2609,7 +2609,7 @@
 
 ### Example
 
-<div><fiddle-embed name="9adda80b2dd1b08ec5ccf66da7c8bd91">
+<div><fiddle-embed name="6e70f18300bd676a3c056ceb6b62f8df">
 
 #### Example Output
 
@@ -4477,7 +4477,7 @@
 
 ### Example
 
-<div><fiddle-embed name="29b98ebf58aa9fd1edfaabf9f4490b3a"></fiddle-embed></div>
+<div><fiddle-embed name="983e2a71ba72d4ba8c945420040b8f1c"></fiddle-embed></div>
 
 ---
 
diff --git a/site/user/api/SkPath_Reference.md b/site/user/api/SkPath_Reference.md
index db49eed..51210da 100644
--- a/site/user/api/SkPath_Reference.md
+++ b/site/user/api/SkPath_Reference.md
@@ -5570,7 +5570,7 @@
 
 ### Example
 
-<div><fiddle-embed name="882e8e0103048009a25cfc20400492f7">
+<div><fiddle-embed name="2c6aff73608cd198659db6d1eeaaae4f">
 
 #### Example Output
 
@@ -5753,7 +5753,7 @@
 
 ### Example
 
-<div><fiddle-embed name="882e8e0103048009a25cfc20400492f7"><div>Ignoring the actual <a href='#Verb'>Verbs</a> and replacing them with <a href='#Quad'>Quads</a> rounds the
+<div><fiddle-embed name="2f53df9201769ab7e7c0e164a1334309"><div>Ignoring the actual <a href='#Verb'>Verbs</a> and replacing them with <a href='#Quad'>Quads</a> rounds the
 path of the glyph.
 </div></fiddle-embed></div>
 
diff --git a/site/user/api/SkPixmap_Reference.md b/site/user/api/SkPixmap_Reference.md
index e9472a9..4a5ba1f 100644
--- a/site/user/api/SkPixmap_Reference.md
+++ b/site/user/api/SkPixmap_Reference.md
@@ -492,7 +492,7 @@
 
 ### Example
 
-<div><fiddle-embed name="ab9b3aef7896aee80b780789848fbba4">
+<div><fiddle-embed name="6e0f558bf7fabc655041116288559134">
 
 #### Example Output
 
@@ -1109,7 +1109,7 @@
 
 ### Example
 
-<div><fiddle-embed name="2bffb6384cc20077e632e7d01da045ca">
+<div><fiddle-embed name="9adda80b2dd1b08ec5ccf66da7c8bd91">
 
 #### Example Output
 
diff --git a/site/user/api/SkPoint_Reference.md b/site/user/api/SkPoint_Reference.md
index 8c3b36a..e358018 100644
--- a/site/user/api/SkPoint_Reference.md
+++ b/site/user/api/SkPoint_Reference.md
@@ -1207,7 +1207,7 @@
 
 ### Example
 
-<div><fiddle-embed name="2bffb6384cc20077e632e7d01da045ca"></fiddle-embed></div>
+<div><fiddle-embed name="35b3bc675779de043706ae4817ee950c"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkRRect_Reference.md b/site/user/api/SkRRect_Reference.md
index 3c4decf..c2f16d3 100644
--- a/site/user/api/SkRRect_Reference.md
+++ b/site/user/api/SkRRect_Reference.md
@@ -626,7 +626,7 @@
 
 ### Example
 
-<div><fiddle-embed name="6ac569e40fb68c758319e85428b9ae95"><div>The first radii are scaled down proportionately until both x-axis and y-axis fit
+<div><fiddle-embed name="ab9b3aef7896aee80b780789848fbba4"><div>The first radii are scaled down proportionately until both x-axis and y-axis fit
 within the bounds. After scaling, x-axis radius is smaller than half the width;
 left round rect is not an oval. The second radii are equal to half the
 dimensions; right round rect is an oval.
@@ -1042,7 +1042,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a7958a4e0668f5cf805a8e78eb57f51d"></fiddle-embed></div>
+<div><fiddle-embed name="6ac569e40fb68c758319e85428b9ae95"></fiddle-embed></div>
 
 ### See Also
 
@@ -1292,7 +1292,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a7958a4e0668f5cf805a8e78eb57f51d"></fiddle-embed></div>
+<div><fiddle-embed name="4577e2dcb086b241bb43d8b89ee0b0dd"></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkRect_Reference.md b/site/user/api/SkRect_Reference.md
index e9e55d5..e0402bf 100644
--- a/site/user/api/SkRect_Reference.md
+++ b/site/user/api/SkRect_Reference.md
@@ -1171,7 +1171,7 @@
 
 ### Example
 
-<div><fiddle-embed name="795061764b10c9e05efb466c9cb60644">
+<div><fiddle-embed name="a98993a66616ae406d8bdc54adfb1411">
 
 #### Example Output
 
diff --git a/site/user/api/SkSurface_Reference.md b/site/user/api/SkSurface_Reference.md
index 2b66e0e..387c6cb 100644
--- a/site/user/api/SkSurface_Reference.md
+++ b/site/user/api/SkSurface_Reference.md
@@ -1014,7 +1014,7 @@
 
 ### Example
 
-<div><fiddle-embed name="8b8a4cd8a29d22bb9c5e63b70357bd65">
+<div><fiddle-embed name="99a54b814ccab7d2b1143c88581649ff">
 
 #### Example Output
 
diff --git a/site/user/api/catalog.htm b/site/user/api/catalog.htm
index e1fe83e..52b2f6a 100644
--- a/site/user/api/catalog.htm
+++ b/site/user/api/catalog.htm
@@ -4765,7 +4765,7 @@
     "code": "void draw(SkCanvas* canvas) {\n   // sk_sp<SkImage> image;\n   SkImage* imagePtr = image.get();\n   canvas->drawImage(imagePtr, 0, 0);\n   SkPaint paint;\n   canvas->drawImage(imagePtr, 80, 0, &paint);\n   paint.setAlpha(0x80);\n   canvas->drawImage(imagePtr, 160, 0, &paint);\n}\n",
     "width": 256,
     "height": 64,
-    "hash": "185746dc0faa6f1df30c4afe098646ff",
+    "hash": "ac93f30dff13f8a8bb31398de370863b",
     "file": "SkCanvas_Reference",
     "name": "SkCanvas::drawImage"
 },
@@ -5469,7 +5469,7 @@
     "code": "void draw(SkCanvas* canvas) {\n    canvas->scale(.5f, .5f);\n    SkImageInfo imageInfo = source.info();\n    SkIRect bounds = imageInfo.bounds();\n    for (int x : { 0, bounds.width() } ) {\n        for (int y : { 0, bounds.height() } ) {\n            canvas->drawBitmap(source, x, y);\n        }\n    }\n}",
     "width": 256,
     "height": 64,
-    "hash": "a818be8945cd0c18f99ffe53e90afa48",
+    "hash": "3ce3db36235d80dbac4d39504cf756da",
     "file": "SkImageInfo_Reference",
     "name": "SkImageInfo::bounds()"
 },
diff --git a/src/core/SkBlurMF.cpp b/src/core/SkBlurMF.cpp
index 23cca83..f3d7123 100644
--- a/src/core/SkBlurMF.cpp
+++ b/src/core/SkBlurMF.cpp
@@ -874,7 +874,8 @@
         }
 
         paint.addCoverageFragmentProcessor(std::move(fp));
-        renderTargetContext->drawVertices(clip, std::move(paint), viewMatrix, std::move(vertices));
+        renderTargetContext->drawVertices(clip, std::move(paint), viewMatrix, std::move(vertices),
+                                          nullptr, 0);
     } else {
         SkMatrix inverse;
         if (!viewMatrix.invert(&inverse)) {
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 8314f80..45bbc64 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1715,6 +1715,7 @@
                             SkBlendMode mode, const SkPaint& paint) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     RETURN_ON_NULL(vertices);
+    SkASSERT(boneCount <= 100);
     this->onDrawVerticesObject(vertices.get(), bones, boneCount, mode, paint);
 }
 
@@ -1722,6 +1723,7 @@
                             SkBlendMode mode, const SkPaint& paint) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     RETURN_ON_NULL(vertices);
+    SkASSERT(boneCount <= 100);
     this->onDrawVerticesObject(vertices, bones, boneCount, mode, paint);
 }
 
diff --git a/src/core/SkVertices.cpp b/src/core/SkVertices.cpp
index 04db0d3..7cbf209 100644
--- a/src/core/SkVertices.cpp
+++ b/src/core/SkVertices.cpp
@@ -95,16 +95,17 @@
     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,
+    bool isVolatile = SkToBool(builderFlags & SkVertices::kIsVolatile_BuilderFlag);
+    this->init(mode, vertexCount, indexCount, isVolatile,
                SkVertices::Sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones));
 }
 
-SkVertices::Builder::Builder(VertexMode mode, int vertexCount, int indexCount,
+SkVertices::Builder::Builder(VertexMode mode, int vertexCount, int indexCount, bool isVolatile,
                              const SkVertices::Sizes& sizes) {
-    this->init(mode, vertexCount, indexCount, sizes);
+    this->init(mode, vertexCount, indexCount, isVolatile, sizes);
 }
 
-void SkVertices::Builder::init(VertexMode mode, int vertexCount, int indexCount,
+void SkVertices::Builder::init(VertexMode mode, int vertexCount, int indexCount, bool isVolatile,
                                const SkVertices::Sizes& sizes) {
     if (!sizes.isValid()) {
         return; // fVertices will already be null
@@ -128,6 +129,7 @@
     fVertices->fIndices = sizes.fISize ? (uint16_t*)ptr : nullptr;
     fVertices->fVertexCnt = vertexCount;
     fVertices->fIndexCnt = indexCount;
+    fVertices->fIsVolatile = isVolatile;
     fVertices->fMode = mode;
 
     // We defer assigning fBounds and fUniqueID until detach() is called
@@ -171,6 +173,10 @@
     return fVertices ? fVertices->indexCount() : 0;
 }
 
+bool SkVertices::Builder::isVolatile() const {
+    return fVertices ? fVertices->isVolatile() : false;
+}
+
 SkPoint* SkVertices::Builder::positions() {
     return fVertices ? const_cast<SkPoint*>(fVertices->positions()) : nullptr;
 }
@@ -208,7 +214,8 @@
                                        const SkColor colors[],
                                        const BoneIndices boneIndices[],
                                        const BoneWeights boneWeights[],
-                                       int indexCount, const uint16_t indices[]) {
+                                       int indexCount, const uint16_t indices[],
+                                       bool isVolatile) {
     SkASSERT((!boneIndices && !boneWeights) || (boneIndices && boneWeights));
     Sizes sizes(mode,
                 vertexCount,
@@ -220,7 +227,7 @@
         return nullptr;
     }
 
-    Builder builder(mode, vertexCount, indexCount, sizes);
+    Builder builder(mode, vertexCount, indexCount, isVolatile, sizes);
     SkASSERT(builder.isValid());
 
     sk_careful_memcpy(builder.positions(), pos, sizes.fVSize);
@@ -247,15 +254,15 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-// storage = packed | vertex_count | index_count | pos[] | texs[] | colors[] | boneIndices[] |
-//           boneWeights[] | indices[]
+// storage = packed | vertex_count | index_count | is_volatile | 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))
+#define kHeaderSize         (4 * sizeof(uint32_t))
 
 sk_sp<SkData> SkVertices::encode() const {
     // packed has room for addtional flags in the future (e.g. versioning)
@@ -288,6 +295,7 @@
     writer.write32(packed);
     writer.write32(fVertexCnt);
     writer.write32(fIndexCnt);
+    writer.writeBool(fIsVolatile);
     writer.write(fPositions, sizes.fVSize);
     writer.write(fTexs, sizes.fTSize);
     writer.write(fColors, sizes.fCSize);
@@ -310,6 +318,7 @@
     const uint32_t packed = reader.readInt();
     const int vertexCount = safe.checkGE(reader.readInt(), 0);
     const int indexCount = safe.checkGE(reader.readInt(), 0);
+    const bool isVolatile = reader.readBool();
     const VertexMode mode = safe.checkLE<VertexMode>(packed & kMode_Mask,
                                                      SkVertices::kLast_VertexMode);
     if (!safe) {
@@ -327,7 +336,7 @@
         return nullptr;
     }
 
-    Builder builder(mode, vertexCount, indexCount, sizes);
+    Builder builder(mode, vertexCount, indexCount, isVolatile, sizes);
 
     reader.read(builder.positions(), sizes.fVSize);
     reader.read(builder.texCoords(), sizes.fTSize);
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index e2bf87b..c9108e3 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -27,8 +27,13 @@
     kColorAttributeIsSkColor_GPFlag = 0x2,
     kLocalCoordAttribute_GPFlag     = 0x4,
     kCoverageAttribute_GPFlag       = 0x8,
+    kBonesAttribute_GPFlag          = 0x10,
 };
 
+static constexpr int kNumFloatsPerSkMatrix = 9;
+static constexpr int kMaxBones = 100; // Due to GPU memory limitations, only up to 100 bone
+                                      // matrices are accepted.
+
 class DefaultGeoProc : public GrGeometryProcessor {
 public:
     static sk_sp<GrGeometryProcessor> Make(uint32_t gpTypeFlags,
@@ -37,10 +42,12 @@
                                            const SkMatrix& viewMatrix,
                                            const SkMatrix& localMatrix,
                                            bool localCoordsWillBeRead,
-                                           uint8_t coverage) {
+                                           uint8_t coverage,
+                                           const SkMatrix bones[],
+                                           int boneCount) {
         return sk_sp<GrGeometryProcessor>(new DefaultGeoProc(
                 gpTypeFlags, color, std::move(colorSpaceXform), viewMatrix, localMatrix, coverage,
-                localCoordsWillBeRead));
+                localCoordsWillBeRead, bones, boneCount));
     }
 
     const char* name() const override { return "DefaultGeometryProcessor"; }
@@ -52,6 +59,9 @@
     bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
     uint8_t coverage() const { return fCoverage; }
     bool hasVertexCoverage() const { return fInCoverage.isInitialized(); }
+    const float* bones() const { return fBones.data(); }
+    int boneCount() const { return fBones.size() / kNumFloatsPerSkMatrix; }
+    bool hasBones() const { return fBones.size() > 0; }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
     public:
@@ -99,11 +109,37 @@
                                         &fColorUniform);
             }
 
+            // Setup bone transforms
+            const char* transformedPositionName = gp.fInPosition.name();
+            if (gp.hasBones()) {
+                const char* vertBonesUniformName;
+                fBonesUniform = uniformHandler->addUniformArray(kVertex_GrShaderFlag,
+                                                                kFloat3x3_GrSLType,
+                                                                "Bones",
+                                                                kMaxBones,
+                                                                &vertBonesUniformName);
+                vertBuilder->codeAppendf(
+                        "float2 transformedPosition = (%s[0] * float3(%s, 1)).xy;"
+                        "float3x3 influence = float3x3(0);"
+                        "for (int i = 0; i < 4; i++) {"
+                        "   int index = %s[i];"
+                        "   float weight = %s[i];"
+                        "   influence += %s[index] * weight;"
+                        "}"
+                        "transformedPosition = (influence * float3(transformedPosition, 1)).xy;",
+                        vertBonesUniformName,
+                        gp.fInPosition.name(),
+                        gp.fInBoneIndices.name(),
+                        gp.fInBoneWeights.name(),
+                        vertBonesUniformName);
+                transformedPositionName = "transformedPosition";
+            }
+
             // Setup position
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      gp.fInPosition.name(),
+                                      transformedPositionName,
                                       gp.viewMatrix(),
                                       &fViewMatrixUniform);
 
@@ -147,8 +183,8 @@
                                   GrProcessorKeyBuilder* b) {
             const DefaultGeoProc& def = gp.cast<DefaultGeoProc>();
             uint32_t key = def.fFlags;
-            key |= (def.coverage() == 0xff) ? 0x10 : 0;
-            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x20 : 0x0;
+            key |= (def.coverage() == 0xff) ? 0x20 : 0;
+            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x40 : 0x0;
             key |= ComputePosKey(def.viewMatrix()) << 20;
             b->add32(key);
             b->add32(GrColorSpaceXform::XformKey(def.fColorSpaceXform.get()));
@@ -180,6 +216,10 @@
             this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter);
 
             fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get());
+
+            if (dgp.hasBones()) {
+                pdman.setMatrix3fv(fBonesUniform, dgp.boneCount(), dgp.bones());
+            }
         }
 
     private:
@@ -189,6 +229,7 @@
         UniformHandle fViewMatrixUniform;
         UniformHandle fColorUniform;
         UniformHandle fCoverageUniform;
+        UniformHandle fBonesUniform;
         GrGLSLColorSpaceXformHelper fColorSpaceHelper;
 
         typedef GrGLSLGeometryProcessor INHERITED;
@@ -209,7 +250,9 @@
                    const SkMatrix& viewMatrix,
                    const SkMatrix& localMatrix,
                    uint8_t coverage,
-                   bool localCoordsWillBeRead)
+                   bool localCoordsWillBeRead,
+                   const SkMatrix* bones,
+                   int boneCount)
             : INHERITED(kDefaultGeoProc_ClassID)
             , fColor(color)
             , fViewMatrix(viewMatrix)
@@ -217,7 +260,8 @@
             , fCoverage(coverage)
             , fFlags(gpTypeFlags)
             , fLocalCoordsWillBeRead(localCoordsWillBeRead)
-            , fColorSpaceXform(std::move(colorSpaceXform)) {
+            , fColorSpaceXform(std::move(colorSpaceXform))
+            , fBones() {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType};
         int cnt = 1;
         if (fFlags & kColorAttribute_GPFlag) {
@@ -232,17 +276,49 @@
             fInCoverage = {"inCoverage", kHalf_GrVertexAttribType};
             ++cnt;
         }
+        if (fFlags & kBonesAttribute_GPFlag) {
+            SkASSERT(bones && (boneCount > 0));
+            fInBoneIndices = {"inBoneIndices", kInt4_GrVertexAttribType};
+            ++cnt;
+            fInBoneWeights = {"inBoneWeights", kFloat4_GrVertexAttribType};
+            ++cnt;
+        }
         this->setVertexAttributeCnt(cnt);
+
+        // Set bone data
+        if (bones) {
+            fBones.reserve(boneCount * kNumFloatsPerSkMatrix);
+            for (int i = 0; i < boneCount; i ++) {
+                const SkMatrix& matrix = bones[i];
+                fBones.push_back(matrix.get(SkMatrix::kMScaleX));
+                fBones.push_back(matrix.get(SkMatrix::kMSkewY));
+                fBones.push_back(matrix.get(SkMatrix::kMPersp0));
+                fBones.push_back(matrix.get(SkMatrix::kMSkewX));
+                fBones.push_back(matrix.get(SkMatrix::kMScaleY));
+                fBones.push_back(matrix.get(SkMatrix::kMPersp1));
+                fBones.push_back(matrix.get(SkMatrix::kMTransX));
+                fBones.push_back(matrix.get(SkMatrix::kMTransY));
+                fBones.push_back(matrix.get(SkMatrix::kMPersp2));
+            }
+        }
     }
 
     const Attribute& onVertexAttribute(int i) const override {
-        return IthInitializedAttribute(i, fInPosition, fInColor, fInLocalCoords, fInCoverage);
+        return IthInitializedAttribute(i,
+                                       fInPosition,
+                                       fInColor,
+                                       fInLocalCoords,
+                                       fInCoverage,
+                                       fInBoneIndices,
+                                       fInBoneWeights);
     }
 
     Attribute fInPosition;
     Attribute fInColor;
     Attribute fInLocalCoords;
     Attribute fInCoverage;
+    Attribute fInBoneIndices;
+    Attribute fInBoneWeights;
     GrColor fColor;
     SkMatrix fViewMatrix;
     SkMatrix fLocalMatrix;
@@ -250,6 +326,7 @@
     uint32_t fFlags;
     bool fLocalCoordsWillBeRead;
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
+    std::vector<float> fBones;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -273,6 +350,17 @@
     if (d->fRandom->nextBool()) {
         flags |= kLocalCoordAttribute_GPFlag;
     }
+    if (d->fRandom->nextBool()) {
+        flags |= kBonesAttribute_GPFlag;
+    }
+
+    int numBones = d->fRandom->nextRangeU(0, kMaxBones);
+    std::vector<SkMatrix> bones(numBones);
+    for (int i = 0; i < numBones; i++) {
+        for (int j = 0; j < kNumFloatsPerSkMatrix; j++) {
+            bones[i][j] = d->fRandom->nextF();
+        }
+    }
 
     return DefaultGeoProc::Make(flags,
                                 GrRandomColor(d->fRandom),
@@ -280,7 +368,9 @@
                                 GrTest::TestMatrix(d->fRandom),
                                 GrTest::TestMatrix(d->fRandom),
                                 d->fRandom->nextBool(),
-                                GrRandomCoverage(d->fRandom));
+                                GrRandomCoverage(d->fRandom),
+                                bones.data(),
+                                numBones);
 }
 #endif
 
@@ -307,7 +397,9 @@
                                 viewMatrix,
                                 localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
                                 localCoordsWillBeRead,
-                                inCoverage);
+                                inCoverage,
+                                nullptr,
+                                0);
 }
 
 sk_sp<GrGeometryProcessor> GrDefaultGeoProcFactory::MakeForDeviceSpace(
@@ -330,3 +422,33 @@
     LocalCoords inverted(LocalCoords::kUsePosition_Type, &invert);
     return Make(color, coverage, inverted, SkMatrix::I());
 }
+
+sk_sp<GrGeometryProcessor> GrDefaultGeoProcFactory::MakeWithBones(const Color& color,
+                                                                  const Coverage& coverage,
+                                                                  const LocalCoords& localCoords,
+                                                                  const Bones& bones,
+                                                                  const SkMatrix& viewMatrix) {
+    uint32_t flags = 0;
+    if (Color::kPremulGrColorAttribute_Type == color.fType) {
+        flags |= kColorAttribute_GPFlag;
+    } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
+        flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
+    }
+    flags |= coverage.fType == Coverage::kAttribute_Type ? kCoverageAttribute_GPFlag : 0;
+    flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
+    flags |= kBonesAttribute_GPFlag;
+
+    uint8_t inCoverage = coverage.fCoverage;
+    bool localCoordsWillBeRead = localCoords.fType != LocalCoords::kUnused_Type;
+
+    GrColor inColor = color.fColor;
+    return DefaultGeoProc::Make(flags,
+                                inColor,
+                                color.fColorSpaceXform,
+                                viewMatrix,
+                                localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
+                                localCoordsWillBeRead,
+                                inCoverage,
+                                bones.fBones,
+                                bones.fBoneCount);
+}
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 314c1bb..c22cefb 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -54,7 +54,7 @@
         SkPoint fLocalCoord;
     };
 
-    struct PositionColorLocalCoordCoverage {
+    struct PositionColorLocalCoordCoverageAttr {
         SkPoint fPosition;
         GrColor fColor;
         SkPoint fLocalCoord;
@@ -118,6 +118,15 @@
         const SkMatrix* fMatrix;
     };
 
+    struct Bones {
+        Bones(const SkMatrix bones[], int boneCount)
+            : fBones(bones)
+            , fBoneCount(boneCount) {}
+
+        const SkMatrix* fBones;
+        int fBoneCount;
+    };
+
     sk_sp<GrGeometryProcessor> Make(const Color&,
                                     const Coverage&,
                                     const LocalCoords&,
@@ -132,6 +141,17 @@
                                                   const Coverage&,
                                                   const LocalCoords&,
                                                   const SkMatrix& viewMatrix);
+
+    /*
+     * Use this factory to create a GrGeometryProcessor that supports skeletal animation through
+     * deformation of vertices using matrices that are passed in. This should only be called from
+     * GrDrawVerticesOp.
+     */
+    sk_sp<GrGeometryProcessor> MakeWithBones(const Color&,
+                                             const Coverage&,
+                                             const LocalCoords&,
+                                             const Bones&,
+                                             const SkMatrix& viewMatrix);
 };
 
 #endif
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 05b3c24..4030366 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -844,6 +844,8 @@
                                          GrPaint&& paint,
                                          const SkMatrix& viewMatrix,
                                          sk_sp<SkVertices> vertices,
+                                         const SkMatrix bones[],
+                                         int boneCount,
                                          GrPrimitiveType* overridePrimType) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
@@ -855,7 +857,7 @@
     SkASSERT(vertices);
     GrAAType aaType = this->chooseAAType(GrAA::kNo, GrAllowMixedSamples::kNo);
     std::unique_ptr<GrDrawOp> op = GrDrawVerticesOp::Make(
-            fContext, std::move(paint), std::move(vertices), viewMatrix, aaType,
+            fContext, std::move(paint), std::move(vertices), bones, boneCount, viewMatrix, aaType,
             this->colorSpaceInfo().refColorSpaceXformFromSRGB(), overridePrimType);
     this->addDrawOp(clip, std::move(op));
 }
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 783b351..ee0d183 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -218,12 +218,16 @@
      * @param   paint            describes how to color pixels.
      * @param   viewMatrix       transformation matrix
      * @param   vertices         specifies the mesh to draw.
+     * @param   bones            bone deformation matrices.
+     * @param   boneCount        number of bone matrices.
      * @param   overridePrimType primitive type to draw. If NULL, derive prim type from vertices.
      */
     void drawVertices(const GrClip&,
                       GrPaint&& paint,
                       const SkMatrix& viewMatrix,
                       sk_sp<SkVertices> vertices,
+                      const SkMatrix bones[],
+                      int boneCount,
                       GrPrimitiveType* overridePrimType = nullptr);
 
     /**
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index a665517..f1e5270 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -365,7 +365,7 @@
                                                       nullptr);
 
     fRenderTargetContext->drawVertices(this->clip(), std::move(grPaint), *viewMatrix,
-                                       std::move(vertices), &primitiveType);
+                                       std::move(vertices), nullptr, 0, &primitiveType);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1480,7 +1480,9 @@
 }
 
 void SkGpuDevice::wireframeVertices(SkVertices::VertexMode vmode, int vertexCount,
-                                    const SkPoint vertices[], SkBlendMode bmode,
+                                    const SkPoint vertices[],
+                                    const SkMatrix bones[], int boneCount,
+                                    SkBlendMode bmode,
                                     const uint16_t indices[], int indexCount,
                                     const SkPaint& paint) {
     ASSERT_SINGLE_OWNER
@@ -1538,12 +1540,13 @@
                                        std::move(grPaint),
                                        this->ctm(),
                                        builder.detach(),
+                                       bones,
+                                       boneCount,
                                        &primitiveType);
 }
 
-void SkGpuDevice::drawVertices(const SkVertices* vertices, const SkMatrix* bones, int boneCount,
+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());
 
@@ -1554,7 +1557,8 @@
     if ((!hasTexs || !paint.getShader()) && !hasColors) {
         // The dreaded wireframe mode. Fallback to drawVertices and go so slooooooow.
         this->wireframeVertices(vertices->mode(), vertices->vertexCount(), vertices->positions(),
-                                mode, vertices->indices(), vertices->indexCount(), paint);
+                                bones, boneCount, mode, vertices->indices(), vertices->indexCount(),
+                                paint);
         return;
     }
     if (!init_vertices_paint(fContext.get(), fRenderTargetContext->colorSpaceInfo(), paint,
@@ -1562,7 +1566,8 @@
         return;
     }
     fRenderTargetContext->drawVertices(this->clip(), std::move(grPaint), this->ctm(),
-                                       sk_ref_sp(const_cast<SkVertices*>(vertices)));
+                                       sk_ref_sp(const_cast<SkVertices*>(vertices)),
+                                       bones, boneCount);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 48ba8ac..c31e624 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -91,7 +91,7 @@
                      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*, const SkMatrix* bones, int boneCount, SkBlendMode,
+    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[],
@@ -248,7 +248,8 @@
     void drawStrokedLine(const SkPoint pts[2], const SkPaint&);
 
     void wireframeVertices(SkVertices::VertexMode, int vertexCount, const SkPoint verts[],
-                           SkBlendMode, const uint16_t indices[], int indexCount, const SkPaint&);
+                           const SkMatrix bones[], int boneCount, SkBlendMode,
+                           const uint16_t indices[], int indexCount, const SkPaint&);
 
     static sk_sp<GrRenderTargetContext> MakeRenderTargetContext(GrContext*,
                                                                 SkBudgeted,
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index ad5aba2..6abca9d 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -10,10 +10,13 @@
 #include "GrDefaultGeoProcFactory.h"
 #include "GrOpFlushState.h"
 #include "SkGr.h"
+#include "SkRectPriv.h"
 
 std::unique_ptr<GrDrawOp> GrDrawVerticesOp::Make(GrContext* context,
                                                  GrPaint&& paint,
                                                  sk_sp<SkVertices> vertices,
+                                                 const SkMatrix bones[],
+                                                 int boneCount,
                                                  const SkMatrix& viewMatrix,
                                                  GrAAType aaType,
                                                  sk_sp<GrColorSpaceXform> colorSpaceXform,
@@ -22,13 +25,14 @@
     GrPrimitiveType primType = overridePrimType ? *overridePrimType
                                                 : SkVertexModeToGrPrimitiveType(vertices->mode());
     return Helper::FactoryHelper<GrDrawVerticesOp>(context, std::move(paint), std::move(vertices),
-                                                   primType, aaType, std::move(colorSpaceXform),
-                                                   viewMatrix);
+                                                   bones, boneCount, primType, aaType,
+                                                   std::move(colorSpaceXform), viewMatrix);
 }
 
 GrDrawVerticesOp::GrDrawVerticesOp(const Helper::MakeArgs& helperArgs, GrColor color,
-                                   sk_sp<SkVertices> vertices, GrPrimitiveType primitiveType,
-                                   GrAAType aaType, sk_sp<GrColorSpaceXform> colorSpaceXform,
+                                   sk_sp<SkVertices> vertices, const SkMatrix bones[],
+                                   int boneCount, GrPrimitiveType primitiveType, GrAAType aaType,
+                                   sk_sp<GrColorSpaceXform> colorSpaceXform,
                                    const SkMatrix& viewMatrix)
         : INHERITED(ClassID())
         , fHelper(helperArgs, aaType)
@@ -45,15 +49,28 @@
     mesh.fColor = color;
     mesh.fViewMatrix = viewMatrix;
     mesh.fVertices = std::move(vertices);
+    if (bones) {
+        mesh.fBones.assign(bones, bones + boneCount);
+    }
     mesh.fIgnoreTexCoords = false;
     mesh.fIgnoreColors = false;
+    mesh.fIgnoreBones = false;
 
     fFlags = 0;
     if (mesh.hasPerVertexColors()) {
         fFlags |= kRequiresPerVertexColors_Flag;
     }
     if (mesh.hasExplicitLocalCoords()) {
-        fFlags |= kAnyMeshHasExplicitLocalCoords;
+        fFlags |= kAnyMeshHasExplicitLocalCoords_Flag;
+    }
+    if (mesh.hasBones()) {
+        fFlags |= kHasBones_Flag;
+    }
+
+    // Special case for meshes with a world transform but no bone weights.
+    // These will be considered normal vertices draws without bones.
+    if (!mesh.fVertices->hasBones() && boneCount == 1) {
+        mesh.fViewMatrix.preConcat(bones[0]);
     }
 
     IsZeroArea zeroArea;
@@ -62,7 +79,27 @@
     } else {
         zeroArea = IsZeroArea::kNo;
     }
-    this->setTransformedBounds(mesh.fVertices->bounds(), viewMatrix, HasAABloat::kNo, zeroArea);
+
+    if (this->hasBones()) {
+        // We don't know the bounds if there are deformations involved, so attempt to calculate
+        // the maximum possible.
+        SkRect bounds;
+        const SkRect& originalBounds = bones[0].mapRect(mesh.fVertices->bounds());
+        for (int i = 1; i < boneCount; i++) {
+            const SkMatrix& matrix = bones[i];
+            bounds.join(matrix.mapRect(originalBounds));
+        }
+
+        this->setTransformedBounds(bounds,
+                                   mesh.fViewMatrix,
+                                   HasAABloat::kNo,
+                                   zeroArea);
+    } else {
+        this->setTransformedBounds(mesh.fVertices->bounds(),
+                                   mesh.fViewMatrix,
+                                   HasAABloat::kNo,
+                                   zeroArea);
+    }
 }
 
 SkString GrDrawVerticesOp::dumpInfo() const {
@@ -96,13 +133,14 @@
     }
     if (!fHelper.usesLocalCoords()) {
         fMeshes[0].fIgnoreTexCoords = true;
-        fFlags &= ~kAnyMeshHasExplicitLocalCoords;
+        fFlags &= ~kAnyMeshHasExplicitLocalCoords_Flag;
     }
     return result;
 }
 
 sk_sp<GrGeometryProcessor> GrDrawVerticesOp::makeGP(bool* hasColorAttribute,
-                                                    bool* hasLocalCoordAttribute) const {
+                                                    bool* hasLocalCoordAttribute,
+                                                    bool* hasBoneAttribute) const {
     using namespace GrDefaultGeoProcFactory;
     LocalCoords::Type localCoordsType;
     if (fHelper.usesLocalCoords()) {
@@ -132,26 +170,53 @@
     } else {
         *hasColorAttribute = false;
     };
+
     const SkMatrix& vm = this->hasMultipleViewMatrices() ? SkMatrix::I() : fMeshes[0].fViewMatrix;
-    return GrDefaultGeoProcFactory::Make(color, Coverage::kSolid_Type, localCoordsType, vm);
+
+    Bones bones(fMeshes[0].fBones.data(), fMeshes[0].fBones.size());
+    *hasBoneAttribute = this->hasBones();
+
+    if (this->hasBones()) {
+        return GrDefaultGeoProcFactory::MakeWithBones(color,
+                                                      Coverage::kSolid_Type,
+                                                      localCoordsType,
+                                                      bones,
+                                                      vm);
+    } else {
+        return GrDefaultGeoProcFactory::Make(color,
+                                             Coverage::kSolid_Type,
+                                             localCoordsType,
+                                             vm);
+    }
 }
 
 void GrDrawVerticesOp::onPrepareDraws(Target* target) {
+    if (fMeshes[0].fVertices->isVolatile()) {
+        this->drawVolatile(target);
+    } else {
+        this->drawNonVolatile(target);
+    }
+}
+
+void GrDrawVerticesOp::drawVolatile(Target* target) {
     bool hasColorAttribute;
     bool hasLocalCoordsAttribute;
-    sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute, &hasLocalCoordsAttribute);
+    bool hasBoneAttribute;
+    sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute,
+                                                 &hasLocalCoordsAttribute,
+                                                 &hasBoneAttribute);
 
-    size_t vertexStride = sizeof(SkPoint) + (hasColorAttribute ? sizeof(uint32_t) : 0) +
-                          (hasLocalCoordsAttribute ? sizeof(SkPoint) : 0);
+    // Calculate the stride.
+    size_t vertexStride = sizeof(SkPoint) +
+                          (hasColorAttribute ? sizeof(uint32_t) : 0) +
+                          (hasLocalCoordsAttribute ? sizeof(SkPoint) : 0) +
+                          (hasBoneAttribute ? 4 * (sizeof(uint32_t) + sizeof(float)) : 0);
     SkASSERT(vertexStride == gp->debugOnly_vertexStride());
 
-    int instanceCount = fMeshes.count();
-
-    const GrBuffer* vertexBuffer;
-    int firstVertex;
-
+    // Allocate buffers.
+    const GrBuffer* vertexBuffer = nullptr;
+    int firstVertex = 0;
     void* verts = target->makeVertexSpace(vertexStride, fVertexCount, &vertexBuffer, &firstVertex);
-
     if (!verts) {
         SkDebugf("Could not allocate vertices\n");
         return;
@@ -159,37 +224,157 @@
 
     const GrBuffer* indexBuffer = nullptr;
     int firstIndex = 0;
-
     uint16_t* indices = nullptr;
     if (this->isIndexed()) {
         indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
-
         if (!indices) {
             SkDebugf("Could not allocate indices\n");
             return;
         }
     }
 
+    // Fill the buffers.
+    this->fillBuffers(hasColorAttribute,
+                      hasLocalCoordsAttribute,
+                      hasBoneAttribute,
+                      vertexStride,
+                      verts,
+                      indices);
+
+    // Draw the vertices.
+    this->drawVertices(target, gp.get(), vertexBuffer, firstVertex, indexBuffer, firstIndex);
+}
+
+void GrDrawVerticesOp::drawNonVolatile(Target* target) {
+    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+
+    bool hasColorAttribute;
+    bool hasLocalCoordsAttribute;
+    bool hasBoneAttribute;
+    sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute,
+                                                 &hasLocalCoordsAttribute,
+                                                 &hasBoneAttribute);
+
+    int instanceCount = fMeshes.count();
+    SkASSERT(instanceCount == 1); // Non-volatile meshes should never combine.
+
+    // Get the resource provider.
+    GrResourceProvider* rp = target->resourceProvider();
+
+    // Generate keys for the buffers.
+    GrUniqueKey vertexKey, indexKey;
+    GrUniqueKey::Builder vertexKeyBuilder(&vertexKey, kDomain, instanceCount + 1);
+    GrUniqueKey::Builder indexKeyBuilder(&indexKey, kDomain, instanceCount + 1);
+    for (int i = 0; i < instanceCount; i ++) {
+        vertexKeyBuilder[i] = indexKeyBuilder[i] = fMeshes[i].fVertices->uniqueID();
+    }
+    vertexKeyBuilder[instanceCount] = 0;
+    indexKeyBuilder[instanceCount] = 1;
+    vertexKeyBuilder.finish();
+    indexKeyBuilder.finish();
+
+    // Try to grab data from the cache.
+    sk_sp<GrBuffer> vertexBuffer = rp->findByUniqueKey<GrBuffer>(vertexKey);
+    sk_sp<GrBuffer> indexBuffer = rp->findByUniqueKey<GrBuffer>(indexKey);
+
+    // Draw using the cached buffers if possible.
+    if (vertexBuffer) {
+        this->drawVertices(target, gp.get(), vertexBuffer.get(), 0, indexBuffer.get(), 0);
+        return;
+    }
+
+    // Calculate the stride.
+    size_t vertexStride = sizeof(SkPoint) +
+                          (hasColorAttribute ? sizeof(uint32_t) : 0) +
+                          (hasLocalCoordsAttribute ? sizeof(SkPoint) : 0) +
+                          (hasBoneAttribute ? 4 * (sizeof(uint32_t) + sizeof(float)) : 0);
+    SkASSERT(vertexStride == gp->debugOnly_vertexStride());
+
+    // Allocate vertex buffer.
+    vertexBuffer.reset(rp->createBuffer(fVertexCount * vertexStride,
+                                        kVertex_GrBufferType,
+                                        kStatic_GrAccessPattern,
+                                        0));
+    void* verts = vertexBuffer ? vertexBuffer->map() : nullptr;
+    if (!verts) {
+        SkDebugf("Could not allocate vertices\n");
+        return;
+    }
+
+    // Allocate index buffer.
+    uint16_t* indices = nullptr;
+    if (this->isIndexed()) {
+        indexBuffer.reset(rp->createBuffer(fIndexCount * sizeof(uint16_t),
+                                           kIndex_GrBufferType,
+                                           kStatic_GrAccessPattern,
+                                           0));
+        indices = indexBuffer ? static_cast<uint16_t*>(indexBuffer->map()) : nullptr;
+        if (!indices) {
+            SkDebugf("Could not allocate indices\n");
+            return;
+        }
+    }
+
+    // Fill the buffers.
+    this->fillBuffers(hasColorAttribute,
+                      hasLocalCoordsAttribute,
+                      hasBoneAttribute,
+                      vertexStride,
+                      verts,
+                      indices);
+
+    // Unmap the buffers.
+    vertexBuffer->unmap();
+    if (indexBuffer) {
+        indexBuffer->unmap();
+    }
+
+    // Cache the buffers.
+    rp->assignUniqueKeyToResource(vertexKey, vertexBuffer.get());
+    rp->assignUniqueKeyToResource(indexKey, indexBuffer.get());
+
+    // Draw the vertices.
+    this->drawVertices(target, gp.get(), vertexBuffer.get(), 0, indexBuffer.get(), 0);
+}
+
+void GrDrawVerticesOp::fillBuffers(bool hasColorAttribute,
+                                   bool hasLocalCoordsAttribute,
+                                   bool hasBoneAttribute,
+                                   size_t vertexStride,
+                                   void* verts,
+                                   uint16_t* indices) const {
+    int instanceCount = fMeshes.count();
+
+    // Copy data into the buffers.
     int vertexOffset = 0;
     // We have a fast case below for uploading the vertex data when the matrix is translate
-    // only and there are colors but not local coords.
-    bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute;
+    // only and there are colors but not local coords. Fast case does not apply when there are bone
+    // transformations.
+    bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute && !hasBoneAttribute;
     for (int i = 0; i < instanceCount; i++) {
+        // Get each mesh.
         const Mesh& mesh = fMeshes[i];
+
+        // Copy data into the index buffer.
         if (indices) {
             int indexCount = mesh.fVertices->indexCount();
             for (int j = 0; j < indexCount; ++j) {
                 *indices++ = mesh.fVertices->indices()[j] + vertexOffset;
             }
         }
+
+        // Copy data into the vertex buffer.
         int vertexCount = mesh.fVertices->vertexCount();
         const SkPoint* positions = mesh.fVertices->positions();
         const SkColor* colors = mesh.fVertices->colors();
         const SkPoint* localCoords = mesh.fVertices->texCoords();
+        const SkVertices::BoneIndices* boneIndices = mesh.fVertices->boneIndices();
+        const SkVertices::BoneWeights* boneWeights = mesh.fVertices->boneWeights();
         bool fastMesh = (!this->hasMultipleViewMatrices() ||
                          mesh.fViewMatrix.getType() <= SkMatrix::kTranslate_Mask) &&
                         mesh.hasPerVertexColors();
         if (fastAttrs && fastMesh) {
+            // Fast case.
             struct V {
                 SkPoint fPos;
                 uint32_t fColor;
@@ -207,9 +392,19 @@
             }
             verts = v + vertexCount;
         } else {
+            // Normal case.
             static constexpr size_t kColorOffset = sizeof(SkPoint);
-            size_t localCoordOffset =
-                    hasColorAttribute ? kColorOffset + sizeof(uint32_t) : kColorOffset;
+            size_t offset = kColorOffset;
+            if (hasColorAttribute) {
+                offset += sizeof(uint32_t);
+            }
+            size_t localCoordOffset = offset;
+            if (hasLocalCoordsAttribute) {
+                offset += sizeof(SkPoint);
+            }
+            size_t boneIndexOffset = offset;
+            offset += 4 * sizeof(uint32_t);
+            size_t boneWeightOffset = offset;
 
             for (int j = 0; j < vertexCount; ++j) {
                 if (this->hasMultipleViewMatrices()) {
@@ -231,22 +426,40 @@
                         *(SkPoint*)((intptr_t)verts + localCoordOffset) = positions[j];
                     }
                 }
+                if (hasBoneAttribute) {
+                    const SkVertices::BoneIndices& indices = boneIndices[j];
+                    const SkVertices::BoneWeights& weights = boneWeights[j];
+                    for (int k = 0; k < 4; k++) {
+                        size_t indexOffset = boneIndexOffset + sizeof(uint32_t) * k;
+                        size_t weightOffset = boneWeightOffset + sizeof(float) * k;
+                        *(uint32_t*)((intptr_t)verts + indexOffset) = indices.indices[k];
+                        *(float*)((intptr_t)verts + weightOffset) = weights.weights[k];
+                    }
+                }
                 verts = (void*)((intptr_t)verts + vertexStride);
             }
         }
         vertexOffset += vertexCount;
     }
+}
 
+void GrDrawVerticesOp::drawVertices(Target* target,
+                                    GrGeometryProcessor* gp,
+                                    const GrBuffer* vertexBuffer,
+                                    int firstVertex,
+                                    const GrBuffer* indexBuffer,
+                                    int firstIndex) {
     GrMesh mesh(this->primitiveType());
-    if (!indices) {
-        mesh.setNonIndexedNonInstanced(fVertexCount);
-    } else {
-        mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertexCount - 1,
+    if (this->isIndexed()) {
+        mesh.setIndexed(indexBuffer, fIndexCount,
+                        firstIndex, 0, fVertexCount - 1,
                         GrPrimitiveRestart::kNo);
+    } else {
+        mesh.setNonIndexedNonInstanced(fVertexCount);
     }
     mesh.setVertexData(vertexBuffer, firstVertex);
     auto pipe = fHelper.makePipeline(target);
-    target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+    target->draw(gp, pipe.fPipeline, pipe.fFixedDynamicState, mesh);
 }
 
 bool GrDrawVerticesOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
@@ -256,6 +469,20 @@
         return false;
     }
 
+    // Meshes with bones cannot be combined because different meshes use different bones, so to
+    // combine them, the matrices would have to be combined, and the bone indices on each vertex
+    // would change, thus making the vertices uncacheable.
+    if (this->hasBones() || that->hasBones()) {
+        return false;
+    }
+
+    // Non-volatile meshes cannot batch, because if a non-volatile mesh batches with another mesh,
+    // then on the next frame, if that non-volatile mesh is drawn, it will draw the other mesh
+    // that was saved in its vertex buffer, which is not necessarily there anymore.
+    if (!this->fMeshes[0].fVertices->isVolatile() || !that->fMeshes[0].fVertices->isVolatile()) {
+        return false;
+    }
+
     if (!this->combinablePrimitive() || this->primitiveType() != that->primitiveType()) {
         return false;
     }
@@ -410,8 +637,8 @@
     if (GrFSAAType::kUnifiedMSAA == fsaaType && random->nextBool()) {
         aaType = GrAAType::kMSAA;
     }
-    return GrDrawVerticesOp::Make(context, std::move(paint), std::move(vertices), viewMatrix,
-                                  aaType, std::move(colorSpaceXform), &type);
+    return GrDrawVerticesOp::Make(context, std::move(paint), std::move(vertices), nullptr, 0,
+                                  viewMatrix, aaType, std::move(colorSpaceXform), &type);
 }
 
 #endif
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index 6d35c0d..2e10fef 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -38,13 +38,15 @@
     static std::unique_ptr<GrDrawOp> Make(GrContext* context,
                                           GrPaint&&,
                                           sk_sp<SkVertices>,
+                                          const SkMatrix bones[],
+                                          int boneCount,
                                           const SkMatrix& viewMatrix,
                                           GrAAType,
                                           sk_sp<GrColorSpaceXform>,
                                           GrPrimitiveType* overridePrimType = nullptr);
 
-    GrDrawVerticesOp(const Helper::MakeArgs& helperArgs, GrColor, sk_sp<SkVertices>,
-                     GrPrimitiveType, GrAAType, sk_sp<GrColorSpaceXform>,
+    GrDrawVerticesOp(const Helper::MakeArgs&, GrColor, sk_sp<SkVertices>, const SkMatrix bones[],
+                     int boneCount, GrPrimitiveType, GrAAType, sk_sp<GrColorSpaceXform>,
                      const SkMatrix& viewMatrix);
 
     const char* name() const override { return "DrawVerticesOp"; }
@@ -68,7 +70,26 @@
 
     void onPrepareDraws(Target*) override;
 
-    sk_sp<GrGeometryProcessor> makeGP(bool* hasColorAttribute, bool* hasLocalCoordAttribute) const;
+    void drawVolatile(Target*);
+    void drawNonVolatile(Target*);
+
+    void fillBuffers(bool hasColorAttribute,
+                     bool hasLocalCoordsAttribute,
+                     bool hasBoneAttribute,
+                     size_t vertexStride,
+                     void* verts,
+                     uint16_t* indices) const;
+
+    void drawVertices(Target*,
+                      GrGeometryProcessor*,
+                      const GrBuffer* vertexBuffer,
+                      int firstVertex,
+                      const GrBuffer* indexBuffer,
+                      int firstIndex);
+
+    sk_sp<GrGeometryProcessor> makeGP(bool* hasColorAttribute,
+                                      bool* hasLocalCoordAttribute,
+                                      bool* hasBoneAttribute) const;
 
     GrPrimitiveType primitiveType() const { return fPrimitiveType; }
     bool combinablePrimitive() const {
@@ -82,9 +103,11 @@
     struct Mesh {
         GrColor fColor;  // Used if this->hasPerVertexColors() is false.
         sk_sp<SkVertices> fVertices;
+        std::vector<SkMatrix> fBones;
         SkMatrix fViewMatrix;
         bool fIgnoreTexCoords;
         bool fIgnoreColors;
+        bool fIgnoreBones;
 
         bool hasExplicitLocalCoords() const {
             return fVertices->hasTexCoords() && !fIgnoreTexCoords;
@@ -93,6 +116,10 @@
         bool hasPerVertexColors() const {
             return fVertices->hasColors() && !fIgnoreColors;
         }
+
+        bool hasBones() const {
+            return fVertices->hasBones() && fBones.size() && !fIgnoreBones;
+        }
     };
 
     bool isIndexed() const {
@@ -105,18 +132,22 @@
     }
 
     bool anyMeshHasExplicitLocalCoords() const {
-        return SkToBool(kAnyMeshHasExplicitLocalCoords & fFlags);
+        return SkToBool(kAnyMeshHasExplicitLocalCoords_Flag & fFlags);
     }
 
     bool hasMultipleViewMatrices() const {
         return SkToBool(kHasMultipleViewMatrices_Flag & fFlags);
     }
 
-    enum Flags {
-        kRequiresPerVertexColors_Flag = 0x1,
-        kAnyMeshHasExplicitLocalCoords = 0x2,
-        kHasMultipleViewMatrices_Flag = 0x4
+    bool hasBones() const {
+        return SkToBool(kHasBones_Flag & fFlags);
+    }
 
+    enum Flags {
+        kRequiresPerVertexColors_Flag       = 0x1,
+        kAnyMeshHasExplicitLocalCoords_Flag = 0x2,
+        kHasMultipleViewMatrices_Flag       = 0x4,
+        kHasBones_Flag                      = 0x8,
     };
 
     Helper fHelper;
diff --git a/tools/viewer/NIMASlide.cpp b/tools/viewer/NIMASlide.cpp
index c13ff2f..25d023f 100644
--- a/tools/viewer/NIMASlide.cpp
+++ b/tools/viewer/NIMASlide.cpp
@@ -62,7 +62,7 @@
         this->updateBones();
 
         // Update the vertices object.
-        this->updateVerticesObject(false);
+        this->updateVerticesObject(false, false);
     }
 
     void renderBackend(SkCanvas* canvas) {
@@ -70,16 +70,13 @@
         if (fRenderMode != kBackend_RenderMode) {
             fRenderMode = kBackend_RenderMode;
             this->updateVertices();
-            this->updateVerticesObject(false);
+            this->updateVerticesObject(false, false);
         }
 
-        canvas->save();
-
         // Update the vertex data.
-        if (fActorImage->doesAnimationVertexDeform() && fActorImage->isVertexDeformDirty()) {
+        if (fActorImage->doesAnimationVertexDeform()) {
             this->updateVertices();
-            this->updateVerticesObject(false);
-            fActorImage->isVertexDeformDirty(false);
+            this->updateVerticesObject(false, true);
         }
 
         // Update the bones.
@@ -87,8 +84,6 @@
 
         // Draw the vertices object.
         this->drawVerticesObject(canvas, true);
-
-        canvas->restore();
     }
 
     void renderImmediate(SkCanvas* canvas) {
@@ -96,7 +91,7 @@
         if (fRenderMode != kImmediate_RenderMode) {
             fRenderMode = kImmediate_RenderMode;
             this->updateVertices();
-            this->updateVerticesObject(true);
+            this->updateVerticesObject(true, true);
         }
 
         // Update the vertex data.
@@ -106,7 +101,7 @@
         }
 
         // Update the vertices object.
-        this->updateVerticesObject(true);
+        this->updateVerticesObject(true, true);
 
         // Draw the vertices object.
         this->drawVerticesObject(canvas, false);
@@ -198,7 +193,7 @@
         nima_to_skmatrix(fActorImage->worldTransform().values(), fBones[0]);
     }
 
-    void updateVerticesObject(bool applyDeforms) {
+    void updateVerticesObject(bool applyDeforms, bool isVolatile) {
         std::vector<SkPoint>* positions = &fPositions;
 
         // Apply deforms if requested.
@@ -229,7 +224,8 @@
                                          fBoneIdx.data(),
                                          fBoneWgt.data(),
                                          fIndices.size(),
-                                         fIndices.data());
+                                         fIndices.data(),
+                                         isVolatile);
     }
 
     void drawVerticesObject(SkCanvas* canvas, bool useBones) const {
@@ -421,11 +417,19 @@
 void NIMASlide::draw(SkCanvas* canvas) {
     canvas->save();
 
-    canvas->translate(500, 500);
-    canvas->scale(1, -1);
+    for (int i = 0; i < 10; i ++) {
+        for (int j = 0; j < 10; j ++) {
+            canvas->save();
 
-    // Render the actor.
-    fActor->render(canvas, fRenderMode);
+            canvas->translate(1250 - 250 * i, 1250 - 250 * j);
+            canvas->scale(0.5, -0.5);
+
+            // Render the actor.
+            fActor->render(canvas, fRenderMode);
+
+            canvas->restore();
+        }
+    }
 
     canvas->restore();