Progress towards consolidating aa mask and edge distances

Change-Id: I47bd68532a68b7464ef6ed43733fef3d65792cfb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/251762
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/geometry/GrQuadUtils.cpp b/src/gpu/geometry/GrQuadUtils.cpp
index b525e51..424c7e8 100644
--- a/src/gpu/geometry/GrQuadUtils.cpp
+++ b/src/gpu/geometry/GrQuadUtils.cpp
@@ -425,77 +425,95 @@
 
 TessellationHelper::OutsetRequest TessellationHelper::getOutsetRequest(
         const EdgeVectors& edgeVectors) const {
-    OutsetRequest r;
-    r.fOutsets = 0.5f; // Half a pixel for AA
-    r.fMask = fAAFlags == GrQuadAAFlags::kAll ? V4f(1.f) :
+    V4f mask = fAAFlags == GrQuadAAFlags::kAll ? V4f(1.f) :
             V4f{(GrQuadAAFlags::kLeft & fAAFlags) ? 1.f : 0.f,
                 (GrQuadAAFlags::kBottom & fAAFlags) ? 1.f : 0.f,
                 (GrQuadAAFlags::kTop & fAAFlags) ? 1.f : 0.f,
                 (GrQuadAAFlags::kRight & fAAFlags) ? 1.f : 0.f};
 
+    // Calculate the lengths of the outset/inset edge vectors per corner, before applying the mask
+    bool degenerate;
+    V4f cornerOutsetLen;
     if (fDeviceType <= GrQuad::Type::kRectilinear) {
+        // Since it's rectangular, the corners move the same distance as the edge lines would
+        cornerOutsetLen = 0.5f;
         // While it's still rectangular, must use the degenerate path when the quad is less
         // than a pixel along a side since the coverage must be updated. (len < 1 implies 1/len > 1)
-        r.fDegenerate = any(edgeVectors.fInvLengths > 1.f);
-        return r;
+        degenerate = any(edgeVectors.fInvLengths > 1.f);
     } else if (any(edgeVectors.fInvLengths >= 1.f / kTolerance)) {
         // Have an edge that is effectively length 0, so we're dealing with a triangle. Skip
         // computing corner outsets, since degenerate path won't use them.
-        r.fDegenerate = true;
-        return r;
+        degenerate = true;
+    } else {
+        // Must scale corner distance by 1/2sin(theta), where theta is the angle between the two
+        // edges at that corner. cos(theta) is equal to dot(dXY, next_cw(dXY)),
+        // and sin(theta) = sqrt(1 - cos(theta)^2)
+        V4f cosTheta = mad(edgeVectors.fDX, next_cw(edgeVectors.fDX),
+                           edgeVectors.fDY * next_cw(edgeVectors.fDY));
+
+        // If the angle is too shallow between edges, go through the degenerate path, otherwise
+        // adding and subtracting very large vectors in almost opposite directions leads to float
+        // errors.
+        if (any(abs(cosTheta) >= 0.9f)) {
+            // Skip updating the outsets since degenerate code path doesn't rely on that
+            degenerate = true;
+        } else {
+            cornerOutsetLen = 0.5f * rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta)
+
+            // When outsetting or insetting, the current edge's AA adds to the length:
+            //   cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
+            // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
+            V4f halfTanTheta = -cosTheta * cornerOutsetLen; // cos(pi - theta) = -cos(theta)
+            V4f edgeAdjust = mask * (halfTanTheta + next_ccw(halfTanTheta)) +
+                              next_ccw(mask) * next_ccw(cornerOutsetLen) +
+                              next_cw(mask) * cornerOutsetLen;
+            // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen
+            // negative then it's degenerate
+            V4f threshold = 0.1f - (1.f / edgeVectors.fInvLengths);
+            degenerate = any(edgeAdjust < threshold) || any(edgeAdjust > -threshold);
+        }
     }
 
-    // Must scale corner distance by 1/2sin(theta), where theta is the angle between the two
-    // edges at that corner. cos(theta) is equal to dot(dXY, next_cw(dXY)),
-    // and sin(theta) = sqrt(1 - cos(theta)^2)
-    V4f cosTheta = mad(edgeVectors.fDX, next_cw(edgeVectors.fDX),
-                       edgeVectors.fDY * next_cw(edgeVectors.fDY));
-
-    // If the angle is too shallow between edges, go through the degenerate path, otherwise
-    // adding and subtracting very large vectors in almost opposite directions leads to float
-    // errors (and skip updating the outsets since degenerate code path doesn't rely on that).
-    if (any(abs(cosTheta) >= 0.9f)) {
-        r.fDegenerate = true;
-        return r;
+    OutsetRequest r;
+    r.fEdgeDistances = 0.5f * mask; // Half a pixel for AA on edges that can move
+    r.fDegenerate = degenerate;
+    if (!degenerate) {
+        // When the projected device quad is not degenerate, the vertex corners can move
+        // cornerOutsetLen along their edge and their cw-rotated edge. The vertex's edge points
+        // inwards and the cw-rotated edge points outwards, hence the minus-sign.
+        // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
+        // both its points need to be moved along their other edge vectors.
+        r.fOutsets = -cornerOutsetLen * next_cw(mask); // scales dx, dy
+        r.fOutsetsCW = cornerOutsetLen * mask;         // scales next_cw(dx), next_cw(dy)
     }
 
-    r.fOutsets *= rsqrt(1.f - cosTheta * cosTheta); // 1/2sin(theta)
-
-    // When outsetting or insetting, the current edge's AA adds to the length:
-    //   cos(pi - theta)/2sin(theta) + cos(pi-ccw(theta))/2sin(ccw(theta))
-    // Moving an adjacent edge updates the length by 1/2sin(theta|ccw(theta))
-    V4f halfTanTheta = -cosTheta * r.fOutsets; // cos(pi - theta) = -cos(theta)
-    V4f edgeAdjust = r.fMask * (halfTanTheta + next_ccw(halfTanTheta)) +
-                      next_ccw(r.fMask) * next_ccw(r.fOutsets) +
-                      next_cw(r.fMask) * r.fOutsets;
-    // If either outsetting (plus edgeAdjust) or insetting (minus edgeAdjust) make edgeLen
-    // negative then it's degenerate
-    V4f threshold = 0.1f - (1.f / edgeVectors.fInvLengths);
-    r.fDegenerate = any(edgeAdjust < threshold) || any(edgeAdjust > -threshold);
     return r;
 }
 
 void TessellationHelper::Vertices::moveAlong(const EdgeVectors& edgeVectors,
-                                             const V4f& signedOutsets,
-                                             const V4f& mask) {
-    // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
-    // both its points need to be moved along their other edge vectors.
-    auto maskedOutset = -signedOutsets * next_cw(mask);
-    auto maskedOutsetCW = signedOutsets * mask;
+                                             const OutsetRequest& outsetRequest,
+                                             bool inset) {
+    SkASSERT(!outsetRequest.fDegenerate);
+    V4f signedOutsets = outsetRequest.fOutsets;
+    V4f signedOutsetsCW = outsetRequest.fOutsetsCW;
+    if (inset) {
+        signedOutsets *= -1.f;
+        signedOutsetsCW *= -1.f;
+    }
     // x = x + outset * mask * next_cw(xdiff) - outset * next_cw(mask) * xdiff
-    fX += mad(maskedOutsetCW, next_cw(edgeVectors.fDX), maskedOutset * edgeVectors.fDX);
-    fY += mad(maskedOutsetCW, next_cw(edgeVectors.fDY), maskedOutset * edgeVectors.fDY);
+    fX += mad(signedOutsetsCW, next_cw(edgeVectors.fDX), signedOutsets * edgeVectors.fDX);
+    fY += mad(signedOutsetsCW, next_cw(edgeVectors.fDY), signedOutsets * edgeVectors.fDY);
     if (fUVRCount > 0) {
         // We want to extend the texture coords by the same proportion as the positions.
-        maskedOutset *= edgeVectors.fInvLengths;
-        maskedOutsetCW *= next_cw(edgeVectors.fInvLengths);
+        signedOutsets *= edgeVectors.fInvLengths;
+        signedOutsetsCW *= next_cw(edgeVectors.fInvLengths);
         V4f du = next_ccw(fU) - fU;
         V4f dv = next_ccw(fV) - fV;
-        fU += mad(maskedOutsetCW, next_cw(du), maskedOutset * du);
-        fV += mad(maskedOutsetCW, next_cw(dv), maskedOutset * dv);
+        fU += mad(signedOutsetsCW, next_cw(du), signedOutsets * du);
+        fV += mad(signedOutsetsCW, next_cw(dv), signedOutsets * dv);
         if (fUVRCount == 3) {
             V4f dr = next_ccw(fR) - fR;
-            fR += mad(maskedOutsetCW, next_cw(dr), maskedOutset * dr);
+            fR += mad(signedOutsetsCW, next_cw(dr), signedOutsets * dr);
         }
     }
 }
@@ -625,10 +643,10 @@
     return w * h;
 }
 
-V4f TessellationHelper::computeDegenerateQuad(const V4f& signedEdgeDistances, const V4f& mask,
+V4f TessellationHelper::computeDegenerateQuad(const V4f& signedEdgeDistances,
                                               const EdgeEquations& edges, Vertices* quad) {
-    // Move the edge by the signed edge adjustment, respecting mask.
-    V4f oc = edges.fC + mask * signedEdgeDistances;
+    // Move the edge by the signed edge adjustment.
+    V4f oc = edges.fC + signedEdgeDistances;
 
     // There are 6 points that we care about to determine the final shape of the polygon, which
     // are the intersections between (e0,e2), (e1,e0), (e2,e3), (e3,e1) (corresponding to the
@@ -709,7 +727,7 @@
         coverage = 1.f;
     }
 
-    quad->moveTo(px, py, mask != 0.f);
+    quad->moveTo(px, py, signedEdgeDistances != 0.f);
     return coverage;
 }
 
@@ -721,35 +739,28 @@
     SkASSERT(vertices);
     SkASSERT(vertices->fUVRCount == 0 || vertices->fUVRCount == 2 || vertices->fUVRCount == 3);
 
-    // Get signed outsets from cached outset request (which are positive values)
-    V4f signedOutsets = outsetRequest.fOutsets;
-    if (inset) {
-        signedOutsets *= -1.f;
-    }
-
     if (fDeviceType == GrQuad::Type::kPerspective || outsetRequest.fDegenerate) {
         Vertices projected = { edgeVectors.fX2D, edgeVectors.fY2D, /*w*/ 1.f, 0.f, 0.f, 0.f, 0};
         V4f coverage = 1.f;
         if (outsetRequest.fDegenerate) {
             // Must use the slow path to handle numerical issues and self intersecting geometry
             SkASSERT(edgeEquations);
-            V4f signedEdgeDistances = 0.5f; // Half a pixel for AA
+            V4f signedEdgeDistances = outsetRequest.fEdgeDistances;
             if (inset) {
                 signedEdgeDistances *= -1.f;
             }
-            coverage = computeDegenerateQuad(signedEdgeDistances, outsetRequest.fMask,
-                                             *edgeEquations, &projected);
+            coverage = computeDegenerateQuad(signedEdgeDistances, *edgeEquations, &projected);
         } else {
             // Move the projected quad with the fast path, even though we will reconstruct the
             // perspective corners afterwards.
-            projected.moveAlong(edgeVectors, signedOutsets, outsetRequest.fMask);
+            projected.moveAlong(edgeVectors, outsetRequest, inset);
         }
-        vertices->moveTo(projected.fX, projected.fY, outsetRequest.fMask != 0.f);
+        vertices->moveTo(projected.fX, projected.fY, outsetRequest.fEdgeDistances != 0.f);
         return coverage;
     } else {
         // Quad is 2D and the inset/outset request does not cause the geometry to self intersect, so
         // we can directly move the corners along the already calculated edge vectors.
-        vertices->moveAlong(edgeVectors, signedOutsets, outsetRequest.fMask);
+        vertices->moveAlong(edgeVectors, outsetRequest, inset);
         return 1.f;
     }
 }