Add conic fill support to the tessellator

This started out as a sandbox to experiment with a Wang's formula
analog for rational quadratics, but it quickly became apparent that
running Wang's formula on the down-projected points was an upper bound
on what the rational version would have been (for both w<1 and w>1).

This CL therefore adds conic support by upgrading the tessellation
shaders to use ratoinal cubics, converting every path verb to a
rational cubic, and then running Wang's formula on the down-projected
points. In the future we can always drop in a better formula if we
work one out.

Bug: skia:10419
Change-Id: I97021ea56afea54fdbe76745bacd3251e350fd97
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/337156
Reviewed-by: Tyler Denniston <tdenniston@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/samplecode/SampleTessellatedWedge.cpp b/samplecode/SampleTessellatedWedge.cpp
index 929489c..6465d75 100644
--- a/samplecode/SampleTessellatedWedge.cpp
+++ b/samplecode/SampleTessellatedWedge.cpp
@@ -20,6 +20,8 @@
 #include "src/gpu/GrRenderTargetContextPriv.h"
 #include "src/gpu/tessellate/GrPathTessellateOp.h"
 
+static float kConicWeight = .5;
+
 // This sample enables wireframe and visualizes the triangulation generated by
 // GrTessellateWedgeShader.
 class TessellatedWedge : public Sample {
@@ -35,9 +37,9 @@
         fPath.transform(SkMatrix::Scale(200, 200));
         fPath.transform(SkMatrix::Translate(300, 300));
 #else
-        fPath.moveTo(100, 200);
-        fPath.cubicTo(100, 100, 400, 100, 400, 200);
-        fPath.lineTo(250, 500);
+        fPath.moveTo(100, 300);
+        fPath.conicTo(300, 100, 500, 300, kConicWeight);
+        fPath.cubicTo(433, 366, 366, 433, 300, 500);
 #endif
     }
 
@@ -107,6 +109,14 @@
     }
 
     fLastViewMatrix = canvas->getTotalMatrix();
+
+
+    SkString caption;
+    caption.printf("w=%f  (=/- and +/_ to change)", kConicWeight);
+    SkFont font(nullptr, 20);
+    SkPaint captionPaint;
+    captionPaint.setColor(SK_ColorWHITE);
+    canvas->drawString(caption, 10, 30, font, captionPaint);
 }
 
 class TessellatedWedge::Click : public Sample::Click {
@@ -145,6 +155,32 @@
     return true;
 }
 
+static SkPath update_weight(const SkPath& path) {
+    SkPath path_;
+    for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
+        switch (verb) {
+            case SkPathVerb::kMove:
+                path_.moveTo(pts[0]);
+                break;
+            case SkPathVerb::kLine:
+                path_.lineTo(pts[1]);
+                break;
+            case SkPathVerb::kQuad:
+                path_.quadTo(pts[1], pts[2]);
+                break;
+            case SkPathVerb::kCubic:
+                path_.cubicTo(pts[1], pts[2], pts[3]);
+                break;
+            case SkPathVerb::kConic:
+                path_.conicTo(pts[1], pts[2], (kConicWeight != 1) ? kConicWeight : .99f);
+                break;
+            default:
+                SkUNREACHABLE;
+        }
+    }
+    return path_;
+}
+
 bool TessellatedWedge::onChar(SkUnichar unichar) {
     switch (unichar) {
         case 'w':
@@ -155,6 +191,22 @@
             fPath.dump();
             return true;
         }
+        case '+':
+            kConicWeight *= 2;
+            fPath = update_weight(fPath);
+            return true;
+        case '=':
+            kConicWeight *= 5/4.f;
+            fPath = update_weight(fPath);
+            return true;
+        case '_':
+            kConicWeight *= .5f;
+            fPath = update_weight(fPath);
+            return true;
+        case '-':
+            kConicWeight *= 4/5.f;
+            fPath = update_weight(fPath);
+            return true;
     }
     return false;
 }
diff --git a/src/gpu/tessellate/GrFillPathShader.cpp b/src/gpu/tessellate/GrFillPathShader.cpp
index 3d6005c..6ae6c98 100644
--- a/src/gpu/tessellate/GrFillPathShader.cpp
+++ b/src/gpu/tessellate/GrFillPathShader.cpp
@@ -63,49 +63,60 @@
 void GrFillTriangleShader::emitVertexCode(Impl*, GrGLSLVertexBuilder* v, const char* viewMatrix,
                                           GrGLSLUniformHandler* uniformHandler) const {
     v->codeAppendf(R"(
-            localcoord = input_point;
-            vertexpos = (%s * float3(localcoord, 1)).xy;)", viewMatrix);
+    localcoord = input_point;
+    vertexpos = (%s * float3(localcoord, 1)).xy;)", viewMatrix);
 }
 
 void GrFillCubicHullShader::emitVertexCode(Impl*, GrGLSLVertexBuilder* v, const char* viewMatrix,
                                            GrGLSLUniformHandler* uniformHandler) const {
     v->codeAppend(R"(
-            float4x2 P = float4x2(input_points_0_1, input_points_2_3);
+    float4x2 P = float4x2(input_points_0_1, input_points_2_3);
+    if (isnan(P[3].y)) {
+        // This curve is actually a conic. Convert the control points to a trapeziodal hull
+        // that circumcscribes the conic.
+        float w = P[3].x;
+        float2 p1w = P[1] * w;
+        float T = .51;  // Bias outward a bit to ensure we cover the outermost samples.
+        float2 c1 = mix(P[0], p1w, T);
+        float2 c2 = mix(P[2], p1w, T);
+        float iw = 1 / mix(1, w, T);
+        P = float4x2(P[0], c1 * iw, c2 * iw, P[2]);
+    }
 
-            // Translate the points to v0..3 where v0=0.
-            float2 v1 = P[1] - P[0], v2 = P[2] - P[0], v3 = P[3] - P[0];
+    // Translate the points to v0..3 where v0=0.
+    float2 v1 = P[1] - P[0], v2 = P[2] - P[0], v3 = P[3] - P[0];
 
-            // Reorder the points so v2 bisects v1 and v3.
-            if (sign(determinant(float2x2(v2,v1))) == sign(determinant(float2x2(v2,v3)))) {
-                float2 tmp = P[2];
-                if (sign(determinant(float2x2(v1,v2))) != sign(determinant(float2x2(v1,v3)))) {
-                    P[2] = P[1];  // swap(P2, P1)
-                    P[1] = tmp;
-                } else {
-                    P[2] = P[3];  // swap(P2, P3)
-                    P[3] = tmp;
-                }
-            }
+    // Reorder the points so v2 bisects v1 and v3.
+    if (sign(determinant(float2x2(v2,v1))) == sign(determinant(float2x2(v2,v3)))) {
+        float2 tmp = P[2];
+        if (sign(determinant(float2x2(v1,v2))) != sign(determinant(float2x2(v1,v3)))) {
+            P[2] = P[1];  // swap(P2, P1)
+            P[1] = tmp;
+        } else {
+            P[2] = P[3];  // swap(P2, P3)
+            P[3] = tmp;
+        }
+    }
 
-            // Find the "turn direction" of each corner and net turn direction.
-            float4 dir;
-            float netdir = 0.0;
-            for (int i = 0; i < 4; ++i) {
-                float2 prev = P[i] - P[(i + 3) & 3], next = P[(i + 1) & 3] - P[i];
-                dir[i] = sign(determinant(float2x2(prev, next)));
-                netdir += dir[i];
-            }
+    // Find the "turn direction" of each corner and net turn direction.
+    float4 dir;
+    float netdir = 0.0;
+    for (int i = 0; i < 4; ++i) {
+        float2 prev = P[i] - P[(i + 3) & 3], next = P[(i + 1) & 3] - P[i];
+        dir[i] = sign(determinant(float2x2(prev, next)));
+        netdir += dir[i];
+    }
 
-            // sk_VertexID comes in fan order. Convert to strip order.
-            int vertexidx = sk_VertexID;
-            vertexidx ^= vertexidx >> 1;
+    // sk_VertexID comes in fan order. Convert to strip order.
+    int vertexidx = sk_VertexID;
+    vertexidx ^= vertexidx >> 1;
 
-            // Remove the non-convex vertex, if any.
-            if (dir[vertexidx] != sign(netdir)) {
-                vertexidx = (vertexidx + 1) & 3;
-            }
+    // Remove the non-convex vertex, if any.
+    if (dir[vertexidx] != sign(netdir)) {
+        vertexidx = (vertexidx + 1) & 3;
+    }
 
-            localcoord = P[vertexidx];)");
+    localcoord = P[vertexidx];)");
 
     v->codeAppendf("vertexpos = (%s * float3(localcoord, 1)).xy;", viewMatrix);
 }
@@ -118,14 +129,14 @@
             nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "path_bounds", &pathBounds);
 
     v->codeAppendf(R"(
-            // Use sk_VertexID and uniforms (instead of vertex data) to find vertex positions.
-            float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
-            localcoord = mix(%s.xy, %s.zw, T);
-            vertexpos = (%s * float3(localcoord, 1)).xy;
+    // Use sk_VertexID and uniforms (instead of vertex data) to find vertex positions.
+    float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
+    localcoord = mix(%s.xy, %s.zw, T);
+    vertexpos = (%s * float3(localcoord, 1)).xy;
 
-            // Outset to avoid possible T-junctions with extreme edges of the path.
-            float2x2 M2 = float2x2(%s);
-            float2 devoutset = .25 * sign(M2 * (T - .5));
-            localcoord += inverse(M2) * devoutset;
-            vertexpos += devoutset;)", pathBounds, pathBounds, viewMatrix, viewMatrix);
+    // Outset to avoid possible T-junctions with extreme edges of the path.
+    float2x2 M2 = float2x2(%s);
+    float2 devoutset = .25 * sign(M2 * (T - .5));
+    localcoord += inverse(M2) * devoutset;
+    vertexpos += devoutset;)", pathBounds, pathBounds, viewMatrix, viewMatrix);
 }
diff --git a/src/gpu/tessellate/GrMidpointContourParser.h b/src/gpu/tessellate/GrMidpointContourParser.h
index 1d5d7d9..2b8c87a 100644
--- a/src/gpu/tessellate/GrMidpointContourParser.h
+++ b/src/gpu/tessellate/GrMidpointContourParser.h
@@ -26,7 +26,8 @@
             : fPath(path)
             , fVerbs(SkPathPriv::VerbData(fPath))
             , fNumRemainingVerbs(fPath.countVerbs())
-            , fPoints(SkPathPriv::PointData(fPath)) {}
+            , fPoints(SkPathPriv::PointData(fPath))
+            , fWeights(SkPathPriv::ConicWeightData(fPath)) {}
     // Advances the internal state to the next contour in the path. Returns false if there are no
     // more contours.
     bool parseNextContour() {
@@ -47,8 +48,10 @@
                 case SkPath::kLine_Verb:
                     ++fPtsIdx;
                     break;
-                case SkPath::kQuad_Verb:
                 case SkPath::kConic_Verb:
+                    ++fWtsIdx;
+                    [[fallthrough]];
+                case SkPath::kQuad_Verb:
                     fPtsIdx += 2;
                     break;
                 case SkPath::kCubic_Verb:
@@ -64,7 +67,7 @@
 
     // Allows for iterating the current contour using a range-for loop.
     SkPathPriv::Iterate currentContour() {
-        return SkPathPriv::Iterate(fVerbs, fVerbs + fVerbsIdx, fPoints, nullptr);
+        return SkPathPriv::Iterate(fVerbs, fVerbs + fVerbsIdx, fPoints, fWeights);
     }
 
     SkPoint currentMidpoint() { return fMidpoint * (1.f / fMidpointWeight); }
@@ -76,6 +79,8 @@
         fVerbsIdx = 0;
         fPoints += fPtsIdx;
         fPtsIdx = 0;
+        fWeights += fWtsIdx;
+        fWtsIdx = 0;
     }
 
     const SkPath& fPath;
@@ -87,6 +92,9 @@
     const SkPoint* fPoints;
     int fPtsIdx = 0;
 
+    const float* fWeights;
+    int fWtsIdx = 0;
+
     SkPoint fMidpoint;
     int fMidpointWeight;
 };
diff --git a/src/gpu/tessellate/GrPathShader.h b/src/gpu/tessellate/GrPathShader.h
index c5e1536..c302684 100644
--- a/src/gpu/tessellate/GrPathShader.h
+++ b/src/gpu/tessellate/GrPathShader.h
@@ -14,6 +14,7 @@
 #include "src/gpu/GrOpsRenderPass.h"
 #include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
+#include <limits>
 
  // This is a common base class for shaders in the GPU tessellator.
 class GrPathShader : public GrGeometryProcessor {
@@ -66,6 +67,14 @@
                                           renderPassXferBarriers, colorLoadOp);
     }
 
+    // Fills in a 4-point patch in such a way that the shader will recognize it as a conic.
+    static void WriteConicPatch(const SkPoint pts[3], float w, SkPoint patch[4]) {
+        // Write out the 3 conic points to patch[0..2], the weight to patch[3].x, and then set
+        // patch[3].y as NaN to flag this patch as a conic.
+        memcpy(patch, pts, sizeof(SkPoint) * 3);
+        patch[3].set(w, std::numeric_limits<float>::quiet_NaN());
+    }
+
 private:
     const SkMatrix fViewMatrix;
     const GrPrimitiveType fPrimitiveType;
diff --git a/src/gpu/tessellate/GrPathTessellateOp.cpp b/src/gpu/tessellate/GrPathTessellateOp.cpp
index 07dc51a..0e38c17 100644
--- a/src/gpu/tessellate/GrPathTessellateOp.cpp
+++ b/src/gpu/tessellate/GrPathTessellateOp.cpp
@@ -20,6 +20,7 @@
 #include "src/gpu/tessellate/GrResolveLevelCounter.h"
 #include "src/gpu/tessellate/GrStencilPathShader.h"
 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
+#include "src/gpu/tessellate/GrWangsFormula.h"
 
 constexpr static float kLinearizationIntolerance =
         GrTessellationPathRenderer::kLinearizationIntolerance;
@@ -499,11 +500,14 @@
             case SkPathVerb::kLine:
                 middleOut.pushVertex(pts[1]);
                 break;
+            case SkPathVerb::kConic:
+                // We use the same quadratic formula for conics, ignoring w. This appears to be an
+                // upper bound on what the actual number of subdivisions would have been.
+                [[fallthrough]];
             case SkPathVerb::kQuad:
                 middleOut.pushVertex(pts[2]);
                 if (resolveLevelCounter) {
-                    // Quadratics get converted to cubics before rendering.
-                    resolveLevelCounter->countCubic(GrWangsFormula::quadratic_log2(
+                    resolveLevelCounter->countInstance(GrWangsFormula::quadratic_log2(
                             kLinearizationIntolerance, pts, xform));
                     break;
                 }
@@ -512,7 +516,7 @@
             case SkPathVerb::kCubic:
                 middleOut.pushVertex(pts[3]);
                 if (resolveLevelCounter) {
-                    resolveLevelCounter->countCubic(GrWangsFormula::cubic_log2(
+                    resolveLevelCounter->countInstance(GrWangsFormula::cubic_log2(
                             kLinearizationIntolerance, pts, xform));
                     break;
                 }
@@ -521,8 +525,6 @@
             case SkPathVerb::kClose:
                 middleOut.close();
                 break;
-            case SkPathVerb::kConic:
-                SkUNREACHABLE;
         }
     }
     int triangleCount = middleOut.close();
@@ -530,7 +532,7 @@
 
     if (!fStencilTrianglesProgram) {
         SkASSERT(resolveLevelCounter);
-        int totalInstanceCount = triangleCount + resolveLevelCounter->totalCubicInstanceCount();
+        int totalInstanceCount = triangleCount + resolveLevelCounter->totalInstanceCount();
         SkASSERT(vertexAdvancePerTriangle == 4);
         target->putBackVertices(maxInnerTriangles + maxCubics - totalInstanceCount,
                                 sizeof(SkPoint) * 4);
@@ -552,15 +554,15 @@
 
 void GrPathTessellateOp::prepareIndirectOuterCubics(
         GrMeshDrawOp::Target* target, const GrResolveLevelCounter& resolveLevelCounter) {
-    SkASSERT(resolveLevelCounter.totalCubicInstanceCount() >= 0);
-    if (resolveLevelCounter.totalCubicInstanceCount() == 0) {
+    SkASSERT(resolveLevelCounter.totalInstanceCount() >= 0);
+    if (resolveLevelCounter.totalInstanceCount() == 0) {
         return;
     }
     // Allocate a buffer to store the cubic data.
     SkPoint* cubicData;
     int baseInstance;
     cubicData = static_cast<SkPoint*>(target->makeVertexSpace(
-            sizeof(SkPoint) * 4, resolveLevelCounter.totalCubicInstanceCount(), &fCubicBuffer,
+            sizeof(SkPoint) * 4, resolveLevelCounter.totalInstanceCount(), &fCubicBuffer,
             &baseInstance));
     if (!cubicData) {
         return;
@@ -574,7 +576,7 @@
         GrMeshDrawOp::Target* target, const GrResolveLevelCounter& resolveLevelCounter,
         SkPoint* cubicData, int numTrianglesAtBeginningOfData) {
     SkASSERT(target->caps().drawInstancedSupport());
-    SkASSERT(numTrianglesAtBeginningOfData + resolveLevelCounter.totalCubicInstanceCount() > 0);
+    SkASSERT(numTrianglesAtBeginningOfData + resolveLevelCounter.totalInstanceCount() > 0);
     SkASSERT(fStencilCubicsProgram);
     SkASSERT(cubicData);
     SkASSERT(fCubicVertexCount == 0);
@@ -591,7 +593,7 @@
     int baseInstance = fBaseCubicVertex >> 2;
 
     // Start preparing the indirect draw buffer.
-    fIndirectDrawCount = resolveLevelCounter.totalCubicIndirectDrawCount();
+    fIndirectDrawCount = resolveLevelCounter.totalIndirectDrawCount();
     if (numTrianglesAtBeginningOfData) {
         ++fIndirectDrawCount;  // Add an indirect draw for the triangles at the beginning.
     }
@@ -633,7 +635,7 @@
 #ifdef SK_DEBUG
     SkASSERT(indirectIdx == fIndirectDrawCount);
     SkASSERT(runningInstanceCount == numTrianglesAtBeginningOfData +
-                                     resolveLevelCounter.totalCubicInstanceCount());
+                                     resolveLevelCounter.totalInstanceCount());
     SkASSERT(fIndirectDrawCount > 0);
 
     SkPoint* endLocations[kMaxResolveLevel + 1];
@@ -647,35 +649,46 @@
         lastResolveLevel = resolveLevel;
     }
     int totalInstanceCount = numTrianglesAtBeginningOfData +
-                             resolveLevelCounter.totalCubicInstanceCount();
+                             resolveLevelCounter.totalInstanceCount();
     endLocations[lastResolveLevel] = cubicData + totalInstanceCount * 4;
 #endif
 
     fCubicVertexCount = numTrianglesAtBeginningOfData * 4;
 
-    if (resolveLevelCounter.totalCubicInstanceCount()) {
+    if (resolveLevelCounter.totalInstanceCount()) {
         GrVectorXform xform(fViewMatrix);
         for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
             int level;
             switch (verb) {
                 default:
                     continue;
+                case SkPathVerb::kConic:
+                    // We use the same quadratic formula for conics, ignoring w. This appears to be
+                    // an upper bound on what the actual number of subdivisions would have been.
+                    [[fallthrough]];
                 case SkPathVerb::kQuad:
                     level = GrWangsFormula::quadratic_log2(kLinearizationIntolerance, pts, xform);
-                    if (level == 0) {
-                        continue;
-                    }
-                    level = std::min(level, kMaxResolveLevel);
-                    GrPathUtils::convertQuadToCubic(pts, instanceLocations[level]);
                     break;
                 case SkPathVerb::kCubic:
                     level = GrWangsFormula::cubic_log2(kLinearizationIntolerance, pts, xform);
-                    if (level == 0) {
-                        continue;
-                    }
-                    level = std::min(level, kMaxResolveLevel);
+                    break;
+            }
+            if (level == 0) {
+                continue;
+            }
+            level = std::min(level, kMaxResolveLevel);
+            switch (verb) {
+                case SkPathVerb::kQuad:
+                    GrPathUtils::convertQuadToCubic(pts, instanceLocations[level]);
+                    break;
+                case SkPathVerb::kCubic:
                     memcpy(instanceLocations[level], pts, sizeof(SkPoint) * 4);
                     break;
+                case SkPathVerb::kConic:
+                    GrPathShader::WriteConicPatch(pts, *w, instanceLocations[level]);
+                    break;
+                default:
+                    SkUNREACHABLE;
             }
             instanceLocations[level] += 4;
             fCubicVertexCount += 4;
@@ -687,7 +700,7 @@
         SkASSERT(instanceLocations[i] == endLocations[i]);
     }
     SkASSERT(fCubicVertexCount == (numTrianglesAtBeginningOfData +
-                                   resolveLevelCounter.totalCubicInstanceCount()) * 4);
+                                   resolveLevelCounter.totalInstanceCount()) * 4);
 #endif
 }
 
@@ -721,6 +734,10 @@
                 SkASSERT(fCubicVertexCount < numCountedCurves * 4);
                 memcpy(vertexData + fCubicVertexCount, pts, sizeof(SkPoint) * 4);
                 break;
+            case SkPathVerb::kConic:
+                SkASSERT(fCubicVertexCount < numCountedCurves * 4);
+                GrPathShader::WriteConicPatch(pts, *w, vertexData + fCubicVertexCount);
+                break;
         }
         fCubicVertexCount += 4;
     }
@@ -768,7 +785,9 @@
                     lastPoint = pts[3];
                     break;
                 case SkPathVerb::kConic:
-                    SkUNREACHABLE;
+                    GrPathShader::WriteConicPatch(pts, *w, vertexData + fCubicVertexCount);
+                    lastPoint = pts[2];
+                    break;
             }
             vertexData[fCubicVertexCount + 4] = midpoint;
             fCubicVertexCount += 5;
diff --git a/src/gpu/tessellate/GrResolveLevelCounter.h b/src/gpu/tessellate/GrResolveLevelCounter.h
index d37ee7a..ebce30c 100644
--- a/src/gpu/tessellate/GrResolveLevelCounter.h
+++ b/src/gpu/tessellate/GrResolveLevelCounter.h
@@ -12,9 +12,9 @@
 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 #include "src/gpu/tessellate/GrWangsFormula.h"
 
-// This class helps bin cubics by log2 "resolveLevel" when we don't use hardware tessellation. It is
-// composed of simple counters that track how many cubics we intend to draw at each resolveLevel,
-// and how many resolveLevels there are that have at least one cubic.
+// This class helps bin instances by log2 "resolveLevel" when we don't use hardware tessellation. It
+// is composed of simple counters that track how many instances we intend to draw at each
+// resolveLevel, and how many resolveLevels there are that have at least one instances.
 class GrResolveLevelCounter {
 public:
     void reset() {
@@ -27,12 +27,15 @@
         GrVectorXform xform(viewMatrix);
         for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
             switch (verb) {
+                case SkPathVerb::kConic:
+                    // We use the same quadratic formula for conics, ignoring w. This appears to be
+                    // an upper bound on what the actual number of subdivisions would have been.
+                    [[fallthrough]];
                 case SkPathVerb::kQuad:
-                    // Quadratics get converted to cubics before rendering.
-                    this->countCubic(GrWangsFormula::quadratic_log2(intolerance, pts, xform));
+                    this->countInstance(GrWangsFormula::quadratic_log2(intolerance, pts, xform));
                     break;
                 case SkPathVerb::kCubic:
-                    this->countCubic(GrWangsFormula::cubic_log2(intolerance, pts, xform));
+                    this->countInstance(GrWangsFormula::cubic_log2(intolerance, pts, xform));
                     break;
                 default:
                     break;
@@ -40,34 +43,34 @@
         }
     }
 
-    void countCubic(int resolveLevel) {
+    void countInstance(int resolveLevel) {
         SkASSERT(fHasCalledReset);
         SkASSERT(resolveLevel >= 0);
         if (resolveLevel == 0) {
-            // Cubics with 2^0=1 segments are empty (zero area). We ignore them completely.
+            // Instances with 2^0=1 segments are empty (zero area). We ignore them completely.
             return;
         }
         resolveLevel = std::min(resolveLevel, GrTessellationPathRenderer::kMaxResolveLevel);
         if (!fInstanceCounts[resolveLevel]++) {
-            ++fTotalCubicIndirectDrawCount;
+            ++fTotalIndirectDrawCount;
         }
-        ++fTotalCubicInstanceCount;
+        ++fTotalInstanceCount;
     }
 
     int operator[](int resolveLevel) const {
         SkASSERT(fHasCalledReset);
-        SkASSERT(resolveLevel > 0);  // Empty cubics with 2^0=1 segments do not need to be drawn.
+        SkASSERT(resolveLevel > 0);  // Empty instances with 2^0=1 segments do not need to be drawn.
         SkASSERT(resolveLevel <= GrTessellationPathRenderer::kMaxResolveLevel);
         return fInstanceCounts[resolveLevel];
     }
-    int totalCubicInstanceCount() const { return fTotalCubicInstanceCount; }
-    int totalCubicIndirectDrawCount() const { return fTotalCubicIndirectDrawCount; }
+    int totalInstanceCount() const { return fTotalInstanceCount; }
+    int totalIndirectDrawCount() const { return fTotalIndirectDrawCount; }
 
 private:
     SkDEBUGCODE(bool fHasCalledReset = false;)
     int fInstanceCounts[GrTessellationPathRenderer::kMaxResolveLevel + 1];
-    int fTotalCubicInstanceCount = 0;
-    int fTotalCubicIndirectDrawCount = 0;
+    int fTotalInstanceCount = 0;
+    int fTotalIndirectDrawCount = 0;
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrStencilPathShader.cpp b/src/gpu/tessellate/GrStencilPathShader.cpp
index f204488..7dc6916 100644
--- a/src/gpu/tessellate/GrStencilPathShader.cpp
+++ b/src/gpu/tessellate/GrStencilPathShader.cpp
@@ -15,50 +15,79 @@
 // parametric sense) line segments that are guaranteed to be within a distance of
 // "MAX_LINEARIZATION_ERROR" from the actual curve.
 constexpr static char kWangsFormulaCubicFn[] = R"(
-        #define MAX_LINEARIZATION_ERROR 0.25  // 1/4 pixel
-        float length_pow2(vec2 v) {
-            return dot(v, v);
-        }
-        float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
-            float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR);
-            float m = max(length_pow2(-2.0*p1 + p2 + p0),
-                          length_pow2(-2.0*p2 + p3 + p1));
-            return max(1.0, ceil(sqrt(k * sqrt(m))));
-        })";
+#define MAX_LINEARIZATION_ERROR 0.25  // 1/4 pixel
+float length_pow2(vec2 v) {
+    return dot(v, v);
+}
+float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
+    float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR);
+    float m = max(length_pow2(-2.0*p1 + p2 + p0),
+                  length_pow2(-2.0*p2 + p3 + p1));
+    return max(1.0, ceil(sqrt(k * sqrt(m))));
+})";
 
-// Evaluate our point of interest using numerically stable mix() operations.
-constexpr static char kEvalCubicFn[] = R"(
-        vec2 eval_cubic(mat4x2 P, float T) {
-            vec2 ab = mix(P[0], P[1], T);
-            vec2 bc = mix(P[1], P[2], T);
-            vec2 cd = mix(P[2], P[3], T);
-            vec2 abc = mix(ab, bc, T);
-            vec2 bcd = mix(bc, cd, T);
-            return mix(abc, bcd, T);
-        })";
+constexpr static char kSkSLTypeDefs[] = R"(
+#define float4x3 mat4x3
+#define float3 vec3
+#define float2 vec2
+)";
+
+// Converts a 4-point input patch into the rational cubic it intended to represent.
+constexpr static char kUnpackRationalCubicFn[] = R"(
+float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) {
+    float4x3 P = float4x3(p0,1, p1,1, p2,1, p3,1);
+    if (isnan(P[3].y)) {
+        // This patch is actually a conic. Convert to a rational cubic.
+        float w = P[3].x;
+        float3 c = P[1] * (2/3.0 * w);
+        P = float4x3(P[0], fma(P[0], float3(1/3.0), c), fma(P[2], float3(1/3.0), c), P[2]);
+    }
+    return P;
+})";
+
+// Evaluate our point of interest using numerically stable linear interpolations. We add our own
+// "safe_mix" method to guarantee we get exactly "b" when T=1. The builtin mix() function seems
+// spec'd to behave this way, but empirical results results have shown it does not always.
+constexpr static char kEvalRationalCubicFn[] = R"(
+float3 safe_mix(float3 a, float3 b, float T, float one_minus_T) {
+    return a*one_minus_T + b*T;
+}
+float2 eval_rational_cubic(float4x3 P, float T) {
+    float one_minus_T = 1 - T;
+    float3 ab = safe_mix(P[0], P[1], T, one_minus_T);
+    float3 bc = safe_mix(P[1], P[2], T, one_minus_T);
+    float3 cd = safe_mix(P[2], P[3], T, one_minus_T);
+    float3 abc = safe_mix(ab, bc, T, one_minus_T);
+    float3 bcd = safe_mix(bc, cd, T, one_minus_T);
+    float3 abcd = safe_mix(abc, bcd, T, one_minus_T);
+    return abcd.xy / abcd.z;
+})";
 
 class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor {
 protected:
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
         const auto& shader = args.fGP.cast<GrStencilPathShader>();
         args.fVaryingHandler->emitAttributes(shader);
+        auto v = args.fVertBuilder;
 
         GrShaderVar vertexPos = (*shader.vertexAttributes().begin()).asShaderVar();
         if (!shader.viewMatrix().isIdentity()) {
             const char* viewMatrix;
             fViewMatrixUniform = args.fUniformHandler->addUniform(
                     nullptr, kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
-            args.fVertBuilder->codeAppendf(
-                    "float2 vertexpos = (%s * float3(inputPoint, 1)).xy;", viewMatrix);
+            v->codeAppendf("float2 vertexpos = (%s * float3(inputPoint, 1)).xy;", viewMatrix);
+            if (shader.willUseTessellationShaders()) {
+                // If y is NaN then x is a conic weight. Don't transform.
+                v->codeAppendf("vertexpos = (isnan(vertexpos.y)) ? inputPoint : vertexpos;");
+            }
             vertexPos.set(kFloat2_GrSLType, "vertexpos");
         }
 
-        if (!shader.willUseTessellationShaders()) {
+        if (!shader.willUseTessellationShaders()) {  // This is the case for the triangle shader.
             gpArgs->fPositionVar = vertexPos;
         } else {
-            args.fVertBuilder->declareGlobal(GrShaderVar(
-                    "P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
-            args.fVertBuilder->codeAppendf("P = %s;", vertexPos.c_str());
+            v->declareGlobal(GrShaderVar("vsPt", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
+            v->codeAppendf("vsPt = %s;", vertexPos.c_str());
         }
 
         // No fragment shader.
@@ -85,38 +114,52 @@
                                                            const GrShaderCaps&) const {
     SkString code(versionAndExtensionDecls);
     code.append(kWangsFormulaCubicFn);
+    code.append(kSkSLTypeDefs);
+    code.append(kUnpackRationalCubicFn);
     code.append(R"(
-            layout(vertices = 1) out;
+    layout(vertices = 1) out;
 
-            in vec2 P[];
-            out vec4 X[];
-            out vec4 Y[];
+    in vec2 vsPt[];
+    out vec4 X[];
+    out vec4 Y[];
+    out float w[];
 
-            void main() {
-                // Chop the curve at T=1/2.
-                vec2 ab = mix(P[0], P[1], .5);
-                vec2 bc = mix(P[1], P[2], .5);
-                vec2 cd = mix(P[2], P[3], .5);
-                vec2 abc = mix(ab, bc, .5);
-                vec2 bcd = mix(bc, cd, .5);
-                vec2 abcd = mix(abc, bcd, .5);
+    void main() {
+        mat4x3 P = unpack_rational_cubic(vsPt[0], vsPt[1], vsPt[2], vsPt[3]);
 
-                // Calculate how many triangles we need to linearize each half of the curve.
-                float l0 = wangs_formula_cubic(P[0], ab, abc, abcd);
-                float l1 = wangs_formula_cubic(abcd, bcd, cd, P[3]);
+        // Chop the curve at T=1/2. Here we take advantage of the fact that a uniform scalar has no
+        // effect on homogeneous coordinates in order to eval quickly at .5:
+        //
+        //    mix(p0, p1, .5) / mix(w0, w1, .5)
+        //    == ((p0 + p1) * .5) / ((w0 + w1) * .5)
+        //    == (p0 + p1) / (w0 + w1)
+        //
+        vec3 ab = P[0] + P[1];
+        vec3 bc = P[1] + P[2];
+        vec3 cd = P[2] + P[3];
+        vec3 abc = ab + bc;
+        vec3 bcd = bc + cd;
+        vec3 abcd = abc + bcd;
 
-                gl_TessLevelOuter[0] = l1;
-                gl_TessLevelOuter[1] = 1.0;
-                gl_TessLevelOuter[2] = l0;
+        // Calculate how many triangles we need to linearize each half of the curve. We simply call
+        // Wang's formula for integral cubics with the down-projected points. This appears to be an
+        // upper bound on what the actual number of subdivisions would have been.
+        float w0 = wangs_formula_cubic(P[0].xy, ab.xy/ab.z, abc.xy/abc.z, abcd.xy/abcd.z);
+        float w1 = wangs_formula_cubic(abcd.xy/abcd.z, bcd.xy/bcd.z, cd.xy/cd.z, P[3].xy);
 
-                // Changing the inner level to 1 when l0 == l1 == 1 collapses the entire patch to a
-                // single triangle. Otherwise, we need an inner level of 2 so our curve triangles
-                // have an interior point to originate from.
-                gl_TessLevelInner[0] = min(max(l0, l1), 2.0);
+        gl_TessLevelOuter[0] = w1;
+        gl_TessLevelOuter[1] = 1.0;
+        gl_TessLevelOuter[2] = w0;
 
-                X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
-                Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
-            })");
+        // Changing the inner level to 1 when w0 == w1 == 1 collapses the entire patch to a single
+        // triangle. Otherwise, we need an inner level of 2 so our curve triangles have an interior
+        // point to originate from.
+        gl_TessLevelInner[0] = min(max(w0, w1), 2.0);
+
+        X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
+        Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
+        w[gl_InvocationID /*== 0*/] = P[1].z;
+    })");
 
     return code;
 }
@@ -125,31 +168,33 @@
         const GrGLSLPrimitiveProcessor*, const char* versionAndExtensionDecls,
         const GrGLSLUniformHandler&, const GrShaderCaps&) const {
     SkString code(versionAndExtensionDecls);
-    code.append(kEvalCubicFn);
+    code.append(kSkSLTypeDefs);
+    code.append(kEvalRationalCubicFn);
     code.append(R"(
-            layout(triangles, equal_spacing, ccw) in;
+    layout(triangles, equal_spacing, ccw) in;
 
-            uniform vec4 sk_RTAdjust;
+    uniform vec4 sk_RTAdjust;
 
-            in vec4 X[];
-            in vec4 Y[];
+    in vec4 X[];
+    in vec4 Y[];
+    in float w[];
 
-            void main() {
-                // Locate our parametric point of interest. T ramps from [0..1/2] on the left edge
-                // of the triangle, and [1/2..1] on the right. If we are the patch's interior
-                // vertex, then we want T=1/2. Since the barycentric coords are (1/3, 1/3, 1/3) at
-                // the interior vertex, the below fma() works in all 3 scenarios.
-                float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
+    void main() {
+        // Locate our parametric point of interest. T ramps from [0..1/2] on the left edge of the
+        // triangle, and [1/2..1] on the right. If we are the patch's interior vertex, then we want
+        // T=1/2. Since the barycentric coords are (1/3, 1/3, 1/3) at the interior vertex, the below
+        // fma() works in all 3 scenarios.
+        float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
 
-                mat4x2 P = transpose(mat2x4(X[0], Y[0]));
-                vec2 vertexpos = eval_cubic(P, T);
-                if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
-                    // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
-                    vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
-                }
+        mat4x3 P = transpose(mat3x4(X[0], Y[0], 1,w[0],w[0],1));
+        vec2 vertexpos = eval_rational_cubic(P, T);
+        if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
+            // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
+            vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
+        }
 
-                gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
-            })");
+        gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
+    })");
 
     return code;
 }
@@ -160,34 +205,42 @@
                                                            const GrShaderCaps&) const {
     SkString code(versionAndExtensionDecls);
     code.append(kWangsFormulaCubicFn);
+    code.append(kSkSLTypeDefs);
+    code.append(kUnpackRationalCubicFn);
     code.append(R"(
-            layout(vertices = 1) out;
+    layout(vertices = 1) out;
 
-            in vec2 P[];
-            out vec4 X[];
-            out vec4 Y[];
-            out vec2 fanpoint[];
+    in vec2 vsPt[];
+    out vec4 X[];
+    out vec4 Y[];
+    out float w[];
+    out vec2 fanpoint[];
 
-            void main() {
-                // Calculate how many triangles we need to linearize the curve.
-                float num_segments = wangs_formula_cubic(P[0], P[1], P[2], P[3]);
+    void main() {
+        mat4x3 P = unpack_rational_cubic(vsPt[0], vsPt[1], vsPt[2], vsPt[3]);
 
-                // Tessellate the first side of the patch into num_segments triangles.
-                gl_TessLevelOuter[0] = num_segments;
+        // Figure out how many segments to divide the curve into. To do this we simply call Wang's
+        // formula for integral cubics with the down-projected points. This appears to be an upper
+        // bound on what the actual number of subdivisions would have been.
+        float num_segments = wangs_formula_cubic(P[0].xy, P[1].xy/P[1].z, P[2].xy/P[2].z, P[3].xy);
 
-                // Leave the other two sides of the patch as single segments.
-                gl_TessLevelOuter[1] = 1.0;
-                gl_TessLevelOuter[2] = 1.0;
+        // Tessellate the first side of the patch into num_segments triangles.
+        gl_TessLevelOuter[0] = num_segments;
 
-                // Changing the inner level to 1 when num_segments == 1 collapses the entire
-                // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
-                // triangles have an interior point to originate from.
-                gl_TessLevelInner[0] = min(num_segments, 2.0);
+        // Leave the other two sides of the patch as single segments.
+        gl_TessLevelOuter[1] = 1.0;
+        gl_TessLevelOuter[2] = 1.0;
 
-                X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
-                Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
-                fanpoint[gl_InvocationID /*== 0*/] = P[4];
-            })");
+        // Changing the inner level to 1 when num_segments == 1 collapses the entire
+        // patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
+        // triangles have an interior point to originate from.
+        gl_TessLevelInner[0] = min(num_segments, 2.0);
+
+        X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x);
+        Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y);
+        w[gl_InvocationID /*== 0*/] = P[1].z;
+        fanpoint[gl_InvocationID /*== 0*/] = vsPt[4];
+    })");
 
     return code;
 }
@@ -196,35 +249,38 @@
         const GrGLSLPrimitiveProcessor*, const char* versionAndExtensionDecls,
         const GrGLSLUniformHandler&, const GrShaderCaps&) const {
     SkString code(versionAndExtensionDecls);
-    code.append(kEvalCubicFn);
+    code.append(kSkSLTypeDefs);
+    code.append(kEvalRationalCubicFn);
     code.append(R"(
-            layout(triangles, equal_spacing, ccw) in;
+    layout(triangles, equal_spacing, ccw) in;
 
-            uniform vec4 sk_RTAdjust;
+    uniform vec4 sk_RTAdjust;
 
-            in vec4 X[];
-            in vec4 Y[];
-            in vec2 fanpoint[];
+    in vec4 X[];
+    in vec4 Y[];
+    in float w[];
+    in vec2 fanpoint[];
 
-            void main() {
-                // Locate our parametric point of interest. It is equal to the barycentric
-                // y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
-                // 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
-                // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
-                float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
+    void main() {
+        // Locate our parametric point of interest. It is equal to the barycentric y-coordinate if
+        // we are a vertex on the tessellated edge of the triangle patch, 0.5 if we are the patch's
+        // interior vertex, or N/A if we are the fan point.
+        // NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
+        float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
 
-                mat4x2 P = transpose(mat2x4(X[0], Y[0]));
-                vec2 vertexpos = eval_cubic(P, T);
-                if (gl_TessCoord.x == 1.0) {
-                    // We are the anchor point that fans from the center of the curve's contour.
-                    vertexpos = fanpoint[0];
-                } else if (gl_TessCoord.x != 0.0) {
-                    // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
-                    vertexpos = (P[0] + vertexpos + P[3]) / 3.0;
-                }
+        mat4x3 P = transpose(mat3x4(X[0], Y[0], 1,w[0],w[0],1));
+        vec2 vertexpos = eval_rational_cubic(P, T);
 
-                gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
-            })");
+        if (gl_TessCoord.x == 1.0) {
+            // We are the anchor point that fans from the center of the curve's contour.
+            vertexpos = fanpoint[0];
+        } else if (gl_TessCoord.x != 0.0) {
+            // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
+            vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
+        }
+
+        gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
+    })");
 
     return code;
 }
@@ -297,30 +353,29 @@
         args.fVertBuilder->defineConstantf("int", "kMaxVertexID", "%i", 1 << kMaxResolveLevel);
         args.fVertBuilder->defineConstantf("float", "kInverseMaxVertexID", "exp2(-%i.0)",
                                            kMaxResolveLevel);
+        args.fVertBuilder->insertFunction(kUnpackRationalCubicFn);
+        args.fVertBuilder->insertFunction(kEvalRationalCubicFn);
         args.fVertBuilder->codeAppend(R"(
-                float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
-                float2 point;
-                if (sk_VertexID > kMaxVertexID) {
-                    // This is a special index value that wants us to emit a specific point.
-                    point = P[sk_VertexID & 3];
-                } else {
-                    // Evaluate the cubic at T = (sk_VertexID / 2^kMaxResolveLevel).
-                    float T = sk_VertexID * kInverseMaxVertexID;
-                    float2 ab = mix(P[0], P[1], T);
-                    float2 bc = mix(P[1], P[2], T);
-                    float2 cd = mix(P[2], P[3], T);
-                    float2 abc = mix(ab, bc, T);
-                    float2 bcd = mix(bc, cd, T);
-                    point = mix(abc, bcd, T);
-                })");
+        float2 pos;
+        if (sk_VertexID > kMaxVertexID) {
+            // This is a special index value that instructs us to emit a specific point.
+            pos = ((sk_VertexID & 3) == 0) ? inputPoints_0_1.xy :
+                  ((sk_VertexID & 2) == 0) ? inputPoints_0_1.zw : inputPoints_2_3.xy;
+        } else {
+            // Evaluate the cubic at T = (sk_VertexID / 2^kMaxResolveLevel).
+            float T = sk_VertexID * kInverseMaxVertexID;
+            float4x3 P = unpack_rational_cubic(inputPoints_0_1.xy, inputPoints_0_1.zw,
+                                               inputPoints_2_3.xy, inputPoints_2_3.zw);
+            pos = eval_rational_cubic(P, T);
+        })");
 
-        GrShaderVar vertexPos("point", kFloat2_GrSLType);
+        GrShaderVar vertexPos("pos", kFloat2_GrSLType);
         if (!shader.viewMatrix().isIdentity()) {
             const char* viewMatrix;
             fViewMatrixUniform = args.fUniformHandler->addUniform(
                     nullptr, kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix);
             args.fVertBuilder->codeAppendf(R"(
-                    float2 transformedPoint = (%s * float3(point, 1)).xy;)", viewMatrix);
+            float2 transformedPoint = (%s * float3(pos, 1)).xy;)", viewMatrix);
             vertexPos.set(kFloat2_GrSLType, "transformedPoint");
         }
         gpArgs->fPositionVar = vertexPos;
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index a1d75f8..b8ecd53 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -139,11 +139,11 @@
 
     SkPath path;
     shape.asPath(&path);
-    if (SkPathPriv::ConicWeightCnt(path)) {
-        return CanDrawPath::kNo;
-    }
 
     if (!shape.style().isSimpleFill()) {
+        if (SkPathPriv::ConicWeightCnt(path)) {
+            return CanDrawPath::kNo;
+        }
         SkPMColor4f constantColor;
         // These are only temporary restrictions while we bootstrap tessellated stroking. Every one
         // of them will eventually go away.