Make TessellationHelper resettable

Change-Id: I94d22edf13b8392e1d7b84367fd65701f99a0e54
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/255137
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/geometry/GrQuadUtils.cpp b/src/gpu/geometry/GrQuadUtils.cpp
index 4619d44..412867f 100644
--- a/src/gpu/geometry/GrQuadUtils.cpp
+++ b/src/gpu/geometry/GrQuadUtils.cpp
@@ -385,9 +385,16 @@
 // TessellationHelper implementation
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-TessellationHelper::TessellationHelper(const GrQuad& deviceQuad, const GrQuad* localQuad)
-        : fDeviceType(deviceQuad.quadType())
-        , fLocalType(localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned) {
+void TessellationHelper::reset(const GrQuad& deviceQuad, const GrQuad* localQuad) {
+    // Record basic state that isn't recorded on the Vertices struct itself
+    fDeviceType = deviceQuad.quadType();
+    fLocalType = localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned;
+
+    // Reset metadata validity
+    fOutsetRequestValid = false;
+    fEdgeEquationsValid = false;
+
+    // Set vertices to match the device and local quad
     fOriginal.fX = deviceQuad.x4f();
     fOriginal.fY = deviceQuad.y4f();
     fOriginal.fW = deviceQuad.w4f();
@@ -431,10 +438,12 @@
         // on thefInvSinTheta since it will approach infinity.
         fEdgeVectors.fInvSinTheta = rsqrt(1.f - fEdgeVectors.fCosTheta * fEdgeVectors.fCosTheta);
     }
+
+    fVerticesValid = true;
 }
 
 const TessellationHelper::EdgeEquations& TessellationHelper::getEdgeEquations() {
-    if (!fEdgeEquations.fValid) {
+    if (!fEdgeEquationsValid) {
         V4f dx = fEdgeVectors.fDX;
         V4f dy = fEdgeVectors.fDY;
         // Correct for bad edges by copying adjacent edge information into the bad component
@@ -453,7 +462,7 @@
             fEdgeEquations.fC = c;
         }
 
-        fEdgeEquations.fValid = true;
+        fEdgeEquationsValid = true;
     }
     return fEdgeEquations;
 }
@@ -465,7 +474,7 @@
     SkASSERT(all(edgeDistances >= 0.f));
 
     // Rebuild outset request if invalid or if the edge distances have changed.
-    if (!fOutsetRequest.fValid || any(edgeDistances != fOutsetRequest.fEdgeDistances)) {
+    if (!fOutsetRequestValid || any(edgeDistances != fOutsetRequest.fEdgeDistances)) {
         // Based on the edge distances, determine if it's acceptable to use fInvSinTheta to
         // calculate the inset or outset geometry.
         if (fDeviceType <= GrQuad::Type::kRectilinear) {
@@ -513,7 +522,7 @@
         }
 
         fOutsetRequest.fEdgeDistances = edgeDistances;
-        fOutsetRequest.fValid = true;
+        fOutsetRequestValid = true;
     }
     return fOutsetRequest;
 }
@@ -654,6 +663,28 @@
     }
 }
 
+void TessellationHelper::Vertices::asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType,
+                                             GrQuad* localOut, GrQuad::Type localType) const {
+    SkASSERT(deviceOut);
+    SkASSERT(fUVRCount == 0 || localOut);
+
+    fX.store(deviceOut->xs());
+    fY.store(deviceOut->ys());
+    if (deviceType == GrQuad::Type::kPerspective) {
+        fW.store(deviceOut->ws());
+    }
+    deviceOut->setQuadType(deviceType); // This sets ws == 1 when device type != perspective
+
+    if (fUVRCount > 0) {
+        fU.store(localOut->xs());
+        fV.store(localOut->ys());
+        if (fUVRCount == 3) {
+            fR.store(localOut->ws());
+        }
+        localOut->setQuadType(localType);
+    }
+}
+
 V4f TessellationHelper::EdgeEquations::estimateCoverage(const V4f& x2d, const V4f& y2d) const {
     // Calculate distance of the 4 inset points (px, py) to the 4 edges
     V4f d0 = mad(fA[0], x2d, mad(fB[0], y2d, fC[0]));
@@ -799,9 +830,11 @@
 
 V4f TessellationHelper::inset(const skvx::Vec<4, float>& edgeDistances,
                               GrQuad* deviceInset, GrQuad* localInset) {
+    SkASSERT(fVerticesValid);
+
     Vertices inset = fOriginal;
     int vertexCount = this->adjustVertices(edgeDistances, true, &inset);
-    this->setQuads(inset, deviceInset, localInset);
+    inset.asGrQuads(deviceInset, fDeviceType, localInset, fLocalType);
 
     if (vertexCount < 3) {
         // The interior has less than a full pixel's area so estimate reduced coverage using
@@ -815,31 +848,11 @@
 
 void TessellationHelper::outset(const skvx::Vec<4, float>& edgeDistances,
                                 GrQuad* deviceOutset, GrQuad* localOutset) {
+    SkASSERT(fVerticesValid);
+
     Vertices outset = fOriginal;
     this->adjustVertices(edgeDistances, false, &outset);
-    this->setQuads(outset, deviceOutset, localOutset);
-}
-
-void TessellationHelper::setQuads(const Vertices& vertices,
-                                  GrQuad* deviceOut, GrQuad* localOut) const {
-    SkASSERT(deviceOut);
-    SkASSERT(vertices.fUVRCount == 0 || localOut);
-
-    vertices.fX.store(deviceOut->xs());
-    vertices.fY.store(deviceOut->ys());
-    if (fDeviceType == GrQuad::Type::kPerspective) {
-        vertices.fW.store(deviceOut->ws());
-    }
-    deviceOut->setQuadType(fDeviceType); // This sets ws == 1 when device type != perspective
-
-    if (vertices.fUVRCount > 0) {
-        vertices.fU.store(localOut->xs());
-        vertices.fV.store(localOut->ys());
-        if (vertices.fUVRCount == 3) {
-            vertices.fR.store(localOut->ws());
-        }
-        localOut->setQuadType(fLocalType);
-    }
+    outset.asGrQuads(deviceOutset, fDeviceType, localOutset, fLocalType);
 }
 
 }; // namespace GrQuadUtils
diff --git a/src/gpu/geometry/GrQuadUtils.h b/src/gpu/geometry/GrQuadUtils.h
index d18e9d2..58f6237 100644
--- a/src/gpu/geometry/GrQuadUtils.h
+++ b/src/gpu/geometry/GrQuadUtils.h
@@ -42,8 +42,9 @@
 
     class TessellationHelper {
     public:
-        // Provide nullptr if there are no local coordinates to track
-        TessellationHelper(const GrQuad& deviceQuad, const GrQuad* localQuad);
+        // Set the original device and (optional) local coordinates that are inset or outset
+        // by the requested edge distances. Use nullptr if there are no local coordinates to update.
+        void reset(const GrQuad& deviceQuad, const GrQuad* localQuad);
 
         // Calculates a new quadrilateral with edges parallel to the original except that they
         // have been moved inwards by edgeDistances (which should be positive). Distances are
@@ -94,6 +95,9 @@
             void moveTo(const skvx::Vec<4, float>& x2d,
                         const skvx::Vec<4, float>& y2d,
                         const skvx::Vec<4, int32_t>& mask);
+
+            void asGrQuads(GrQuad* deviceOut, GrQuad::Type deviceType,
+                           GrQuad* localOut, GrQuad::Type localType) const;
         };
 
         // NOTE: This struct is named 'EdgeVectors' because it holds a lot of cached calculations
@@ -117,8 +121,6 @@
         struct EdgeEquations {
             // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
             skvx::Vec<4, float> fA, fB, fC;
-            // True if the field is up to date with the state of fOriginal+fAAFlags
-            bool fValid = false;
 
             skvx::Vec<4, float> estimateCoverage(const skvx::Vec<4, float>& x2d,
                                                  const skvx::Vec<4, float>& y2d) const;
@@ -134,11 +136,8 @@
             // be because of the requested edge distances (collapse of inset, etc.)
             bool fInsetDegenerate;
             bool fOutsetDegenerate;
-            // True if the field is up to date with the state of fOriginal+fAAFlags
-            bool fValid = false;
         };
 
-        // Always valid
         Vertices            fOriginal;
         EdgeVectors         fEdgeVectors;
         GrQuad::Type        fDeviceType;
@@ -148,7 +147,13 @@
         OutsetRequest       fOutsetRequest;
         EdgeEquations       fEdgeEquations;
 
-        void setQuads(const Vertices& vertices, GrQuad* deviceOut, GrQuad* localOut) const;
+        // Validity of Vertices/EdgeVectors (always true after first call to set()).
+        bool fVerticesValid = false;
+        // Validity of outset request (true after calling getOutsetRequest() until next set() call
+        // or next inset/outset() with different edge distances).
+        bool fOutsetRequestValid = false;
+        // Validity of edge equations (true after calling getEdgeEquations() until next set() call).
+        bool fEdgeEquationsValid = false;
 
         // The requested edge distances must be positive so that they can be reused between inset
         // and outset calls.
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 687d3a5..6833107 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -107,6 +107,7 @@
             geomDomain.outset(0.5f, 0.5f); // account for AA expansion
         }
 
+        GrQuadUtils::TessellationHelper helper;
         if (aaFlags == GrQuadAAFlags::kNone) {
             // Have to write the coverage AA vertex structure, but there's no math to be done for a
             // non-aa quad batched into a coverage AA op.
@@ -118,8 +119,7 @@
             // TODO(michaelludwig) - Update TessellateHelper to select processing functions based on
             // the vertexspec once per op, and then burn through all quads with the selected
             // function ptr.
-            GrQuadUtils::TessellationHelper helper(deviceQuad,
-                                                   spec.hasLocalCoords() ? &localQuad : nullptr);
+            helper.reset(deviceQuad, spec.hasLocalCoords() ? &localQuad : nullptr);
 
             // Edge inset/outset distance ordered LBTR, set to 0.5 for a half pixel if the AA flag
             // is turned on, or 0.0 if the edge is not anti-aliased.