added caching of SkVertices

SkVertices can now be "volatile", meaning they should not be cached.
SkVertices is volatile by default if the argument is not given.

Pulled from reverted CL: https://skia-review.googlesource.com/c/skia/+/138596

Docs-Preview: https://skia.org/?cl=139545
Bug: skia:
Change-Id: I92cf832efe1c0aaa8f432eedde2678582dd2454e
Reviewed-on: https://skia-review.googlesource.com/139545
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Ruiqi Mao <ruiqimao@google.com>
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..97a136c 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::kIsNonVolatile_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() : true;
+}
+
 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);
@@ -255,6 +262,7 @@
 #define kHasTexs_Mask       0x100
 #define kHasColors_Mask     0x200
 #define kHasBones_Mask      0x400
+#define kIsNonVolatile_Mask 0x800
 #define kHeaderSize         (3 * sizeof(uint32_t))
 
 sk_sp<SkData> SkVertices::encode() const {
@@ -270,6 +278,9 @@
     if (this->hasBones()) {
         packed |= kHasBones_Mask;
     }
+    if (!this->isVolatile()) {
+        packed |= kIsNonVolatile_Mask;
+    }
 
     Sizes sizes(fMode,
                 fVertexCnt,
@@ -318,6 +329,7 @@
     const bool hasTexs = SkToBool(packed & kHasTexs_Mask);
     const bool hasColors = SkToBool(packed & kHasColors_Mask);
     const bool hasBones = SkToBool(packed & kHasBones_Mask);
+    const bool isVolatile = !SkToBool(packed & kIsNonVolatile_Mask);
     Sizes sizes(mode, vertexCount, indexCount, hasTexs, hasColors, hasBones);
     if (!sizes.isValid()) {
         return nullptr;
@@ -327,7 +339,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/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index ad5aba2..7cfb3a2 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -10,6 +10,7 @@
 #include "GrDefaultGeoProcFactory.h"
 #include "GrOpFlushState.h"
 #include "SkGr.h"
+#include "SkRectPriv.h"
 
 std::unique_ptr<GrDrawOp> GrDrawVerticesOp::Make(GrContext* context,
                                                  GrPaint&& paint,
@@ -53,7 +54,7 @@
         fFlags |= kRequiresPerVertexColors_Flag;
     }
     if (mesh.hasExplicitLocalCoords()) {
-        fFlags |= kAnyMeshHasExplicitLocalCoords;
+        fFlags |= kAnyMeshHasExplicitLocalCoords_Flag;
     }
 
     IsZeroArea zeroArea;
@@ -62,7 +63,11 @@
     } else {
         zeroArea = IsZeroArea::kNo;
     }
-    this->setTransformedBounds(mesh.fVertices->bounds(), viewMatrix, HasAABloat::kNo, zeroArea);
+
+    this->setTransformedBounds(mesh.fVertices->bounds(),
+                               mesh.fViewMatrix,
+                               HasAABloat::kNo,
+                               zeroArea);
 }
 
 SkString GrDrawVerticesOp::dumpInfo() const {
@@ -96,7 +101,7 @@
     }
     if (!fHelper.usesLocalCoords()) {
         fMeshes[0].fIgnoreTexCoords = true;
-        fFlags &= ~kAnyMeshHasExplicitLocalCoords;
+        fFlags &= ~kAnyMeshHasExplicitLocalCoords_Flag;
     }
     return result;
 }
@@ -132,26 +137,39 @@
     } else {
         *hasColorAttribute = false;
     };
+
     const SkMatrix& vm = this->hasMultipleViewMatrices() ? SkMatrix::I() : fMeshes[0].fViewMatrix;
-    return GrDefaultGeoProcFactory::Make(color, Coverage::kSolid_Type, localCoordsType, vm);
+
+    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);
+    sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute,
+                                                 &hasLocalCoordsAttribute);
 
-    size_t vertexStride = sizeof(SkPoint) + (hasColorAttribute ? sizeof(uint32_t) : 0) +
+    // Calculate the stride.
+    size_t vertexStride = sizeof(SkPoint) +
+                          (hasColorAttribute ? sizeof(uint32_t) : 0) +
                           (hasLocalCoordsAttribute ? sizeof(SkPoint) : 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,29 +177,139 @@
 
     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,
+                      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;
+    sk_sp<GrGeometryProcessor> gp = this->makeGP(&hasColorAttribute,
+                                                 &hasLocalCoordsAttribute);
+
+    SkASSERT(fMeshes.count() == 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, 2);
+    GrUniqueKey::Builder indexKeyBuilder(&indexKey, kDomain, 2);
+    vertexKeyBuilder[0] = indexKeyBuilder[0] = fMeshes[0].fVertices->uniqueID();
+    vertexKeyBuilder[1] = 0;
+    indexKeyBuilder[1] = 1;
+    vertexKeyBuilder.finish();
+    indexKeyBuilder.finish();
+
+    // Try to grab data from the cache.
+    sk_sp<GrBuffer> vertexBuffer = rp->findByUniqueKey<GrBuffer>(vertexKey);
+    sk_sp<GrBuffer> indexBuffer = this->isIndexed() ?
+            rp->findByUniqueKey<GrBuffer>(indexKey) :
+            nullptr;
+
+    // Draw using the cached buffers if possible.
+    if (vertexBuffer && (!this->isIndexed() || indexBuffer)) {
+        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);
+    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,
+                      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,
+                                   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.
+    // only and there are colors but not local coords. Fast case does not apply when there are bone
+    // transformations.
     bool fastAttrs = hasColorAttribute && !hasLocalCoordsAttribute;
     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();
@@ -190,6 +318,7 @@
                          mesh.fViewMatrix.getType() <= SkMatrix::kTranslate_Mask) &&
                         mesh.hasPerVertexColors();
         if (fastAttrs && fastMesh) {
+            // Fast case.
             struct V {
                 SkPoint fPos;
                 uint32_t fColor;
@@ -207,9 +336,13 @@
             }
             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;
 
             for (int j = 0; j < vertexCount; ++j) {
                 if (this->hasMultipleViewMatrices()) {
@@ -236,17 +369,25 @@
         }
         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 +397,13 @@
         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 +558,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),
+                                  viewMatrix, aaType, std::move(colorSpaceXform), &type);
 }
 
 #endif
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index 6d35c0d..3a6eca0 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -43,9 +43,8 @@
                                           sk_sp<GrColorSpaceXform>,
                                           GrPrimitiveType* overridePrimType = nullptr);
 
-    GrDrawVerticesOp(const Helper::MakeArgs& helperArgs, GrColor, sk_sp<SkVertices>,
-                     GrPrimitiveType, GrAAType, sk_sp<GrColorSpaceXform>,
-                     const SkMatrix& viewMatrix);
+    GrDrawVerticesOp(const Helper::MakeArgs&, GrColor, sk_sp<SkVertices>, GrPrimitiveType, GrAAType,
+                     sk_sp<GrColorSpaceXform>, const SkMatrix& viewMatrix);
 
     const char* name() const override { return "DrawVerticesOp"; }
 
@@ -68,7 +67,24 @@
 
     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,
+                     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) const;
 
     GrPrimitiveType primitiveType() const { return fPrimitiveType; }
     bool combinablePrimitive() const {
@@ -105,7 +121,7 @@
     }
 
     bool anyMeshHasExplicitLocalCoords() const {
-        return SkToBool(kAnyMeshHasExplicitLocalCoords & fFlags);
+        return SkToBool(kAnyMeshHasExplicitLocalCoords_Flag & fFlags);
     }
 
     bool hasMultipleViewMatrices() const {
@@ -113,10 +129,9 @@
     }
 
     enum Flags {
-        kRequiresPerVertexColors_Flag = 0x1,
-        kAnyMeshHasExplicitLocalCoords = 0x2,
-        kHasMultipleViewMatrices_Flag = 0x4
-
+        kRequiresPerVertexColors_Flag       = 0x1,
+        kAnyMeshHasExplicitLocalCoords_Flag = 0x2,
+        kHasMultipleViewMatrices_Flag       = 0x4,
     };
 
     Helper fHelper;