Implement indirect stroking for conics

Just like for fills, we use the normal Wang's formula on the conic's
down-projected control points until we can formalize on a better
formula.

Bug: skia:10419
Change-Id: Ifd735534a2e793f79f4f5d5b7e7acf50db81fe5e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341156
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/gm/trickycubicstrokes.cpp b/gm/trickycubicstrokes.cpp
index 2e905f0..c551cec 100644
--- a/gm/trickycubicstrokes.cpp
+++ b/gm/trickycubicstrokes.cpp
@@ -26,7 +26,7 @@
 static constexpr float kStrokeWidth = 30;
 static constexpr int kCellSize = 200;
 static constexpr int kNumCols = 5;
-static constexpr int kNumRows = 4;
+static constexpr int kNumRows = 5;
 static constexpr int kTestWidth = kNumCols * kCellSize;
 static constexpr int kTestHeight = kNumRows * kCellSize;
 
@@ -64,8 +64,12 @@
      CellFillMode::kStretch},  // Flat line with no turns
     {{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with 2 180s
     {{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch},  // Flat line with a 180
-    {{{1,1}, {2,1}, {1,1}, {std::numeric_limits<float>::quiet_NaN(), 0}}, 3,
-     CellFillMode::kStretch},  // Flat QUAD with a 180
+    {{{1,1}, {2,1}, {1,1}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
+     CellFillMode::kStretch},  // Flat QUAD with a cusp
+    {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
+     CellFillMode::kStretch},  // Flat CONIC with a cusp
+    {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
+     CellFillMode::kStretch},  // Flat CONIC with a cusp
 };
 
 static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
@@ -113,6 +117,7 @@
         for (int j = 0; j < numPts; ++j) {
             p[j] *= scale;
         }
+        float w = originalPts[3].fX;
 
         auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
                                          kCellSize, kCellSize);
@@ -143,9 +148,12 @@
         SkPath path = SkPath().moveTo(p[0]);
         if (numPts == 4) {
             path.cubicTo(p[1], p[2], p[3]);
-        } else {
+        } else if (w == 1) {
             SkASSERT(numPts == 3);
             path.quadTo(p[1], p[2]);
+        } else {
+            SkASSERT(numPts == 3);
+            path.conicTo(p[1], p[2], w);
         }
         canvas->drawPath(path, strokePaint);
     }
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index 89ed0f8..399a453 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -422,10 +422,10 @@
             return true;
         }
         float* valueToScale = nullptr;
-        if (fDoStroke) {
-            valueToScale = &fStrokeWidth;
-        } else if (PrimitiveType::kConics == fPrimitiveType) {
+        if (PrimitiveType::kConics == fPrimitiveType) {
             valueToScale = &fConicWeight;
+        } else if (fDoStroke) {
+            valueToScale = &fStrokeWidth;
         }
         if (valueToScale) {
             if (unichar == '+') {
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index ff56d1c..eab8227 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -588,6 +588,25 @@
     return SkNx_fma(f, Sk4f(m), a);
 }
 
+// Finds the root nearest 0.5. Returns 0.5 if the roots are undefined or outside 0..1.
+static float solve_quadratic_equation_for_midtangent(float a, float b, float c, float discr) {
+    // Quadratic formula from Numerical Recipes in C:
+    float q = -.5f * (b + copysignf(sqrtf(discr), b));
+    // The roots are q/a and c/q. Pick the midtangent closer to T=.5.
+    float _5qa = -.5f*q*a;
+    float T = fabsf(q*q + _5qa) < fabsf(a*c + _5qa) ? sk_ieee_float_divide(q,a)
+                                                    : sk_ieee_float_divide(c,q);
+    if (!(T > 0 && T < 1)) {  // Use "!(positive_logic)" so T=NaN will take this branch.
+        // Either the curve is a flat line with no rotation or FP precision failed us. Chop at .5.
+        T = .5;
+    }
+    return T;
+}
+
+static float solve_quadratic_equation_for_midtangent(float a, float b, float c) {
+    return solve_quadratic_equation_for_midtangent(a, b, c, b*b - 4*a*c);
+}
+
 float SkFindCubicMidTangent(const SkPoint src[4]) {
     // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
     // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent:
@@ -623,13 +642,7 @@
     float a=coeffs[0], b=coeffs[1], c=coeffs[2];
     float discr = b*b - 4*a*c;
     if (discr > 0) {  // This will only be false if the curve is a line.
-        // Quadratic formula from Numerical Recipes in C:
-        float q = -.5f * (b + copysignf(std::sqrt(discr), b));
-        // The roots are q/a and c/q. Pick the midtangent closer to T=.5.
-        float qa_5 = .5f*q*a;
-        if (a != 0 || q != 0) {
-            T = std::abs(q*q - qa_5) < std::abs(a*c - qa_5) ? q/a : c/q;
-        }
+        return solve_quadratic_equation_for_midtangent(a, b, c, discr);
     } else {
         // This is a 0- or 360-degree flat line. It doesn't have single points of midtangent.
         // (tangent == midtangent at every point on the curve except the cusp points.)
@@ -651,13 +664,13 @@
             //     -b / (2*a)
             T = -b / (2*a);
         }
+        if (!(T > 0 && T < 1)) {  // Use "!(positive_logic)" so T=NaN will take this branch.
+            // Either the curve is a flat line with no rotation or FP precision failed us. Chop at
+            // .5.
+            T = .5;
+        }
+        return T;
     }
-    if (!(T > 0 && T < 1)) {  // Use "!(positive_logic)" so T=NaN will take this branch.
-        // Either the curve is a flat line with no rotation or FP precision failed us. Chop at .5.
-        T = .5;
-    }
-
-    return T;
 }
 
 static void flatten_double_cubic_extrema(SkScalar coords[14]) {
@@ -1499,6 +1512,43 @@
     return 1 << pow2;
 }
 
+float SkConic::findMidTangent() const {
+    // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
+    // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent:
+    //
+    //     bisector dot midtangent = 0
+    //
+    SkVector tan0 = fPts[1] - fPts[0];
+    SkVector tan1 = fPts[2] - fPts[1];
+    SkVector bisector = SkFindBisector(tan0, -tan1);
+
+    // Start by finding the tangent function's power basis coefficients. These define a tangent
+    // direction (scaled by some uniform value) as:
+    //                                                |T^2|
+    //     Tangent_Direction(T) = dx,dy = |A  B  C| * |T  |
+    //                                    |.  .  .|   |1  |
+    //
+    // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't necessary
+    // if we are only interested in a vector in the same *direction* as a given tangent line. Since
+    // the denominator scales dx and dy uniformly, we can throw it out completely after evaluating
+    // the derivative with the standard quotient rule. This leaves us with a simpler quadratic
+    // function that we use to find a tangent.
+    SkVector A = (fPts[2] - fPts[0]) * (fW - 1);
+    SkVector B = (fPts[2] - fPts[0]) - (fPts[1] - fPts[0]) * (fW*2);
+    SkVector C = (fPts[1] - fPts[0]) * fW;
+
+    // Now solve for "bisector dot midtangent = 0":
+    //
+    //                            |T^2|
+    //     bisector * |A  B  C| * |T  | = 0
+    //                |.  .  .|   |1  |
+    //
+    float a = bisector.dot(A);
+    float b = bisector.dot(B);
+    float c = bisector.dot(C);
+    return solve_quadratic_equation_for_midtangent(a, b, c);
+}
+
 bool SkConic::findXExtrema(SkScalar* t) const {
     return conic_find_extrema(&fPts[0].fX, fW, t);
 }
diff --git a/src/core/SkGeometry.h b/src/core/SkGeometry.h
index 4c4e72d..85efc7b 100644
--- a/src/core/SkGeometry.h
+++ b/src/core/SkGeometry.h
@@ -352,6 +352,7 @@
      */
     int SK_SPI SK_WARN_UNUSED_RESULT chopIntoQuadsPOW2(SkPoint pts[], int pow2) const;
 
+    float findMidTangent() const;
     bool findXExtrema(SkScalar* t) const;
     bool findYExtrema(SkScalar* t) const;
     bool chopAtXExtrema(SkConic dst[2]) const;
diff --git a/src/gpu/glsl/GrGLSLVertexGeoBuilder.h b/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
index 882f22a..913e120 100644
--- a/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
+++ b/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
@@ -22,6 +22,7 @@
     void insertFunction(const char* functionDefinition) {
         this->functions().append(functionDefinition);
     }
+    using GrGLSLShaderBuilder::functions;
 
 protected:
     GrGLSLVertexGeoBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {}
diff --git a/src/gpu/tessellate/GrStrokeIndirectOp.cpp b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
index a33c15e..b34d6df 100644
--- a/src/gpu/tessellate/GrStrokeIndirectOp.cpp
+++ b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
@@ -35,8 +35,8 @@
         return;
     }
     auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
-            GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
-            fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+            GrStrokeTessellateShader::Mode::kIndirect, fTotalConicWeightCnt, fStroke,
+            fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
     this->prePreparePrograms(context->priv().recordTimeAllocator(), strokeTessellateShader,
                              writeView, std::move(*clip), dstProxyView, renderPassXferBarriers,
                              colorLoadOp, *context->priv().caps());
@@ -472,6 +472,7 @@
                         }
                         [[fallthrough]];
                     case Verb::kQuad:
+                    case Verb::kConic:
                         if (prevPts[1] != prevPts[2]) {
                             lastControlPoint = prevPts[1];
                             break;
@@ -498,11 +499,15 @@
                     }
                     ++fTotalInstanceCount;
                     break;
+                case Verb::kConic:
+                    // We use the same quadratic formula for conics, ignoring w. This is pretty
+                    // close to what the actual number of subdivisions would have been.
+                    [[fallthrough]];
                 case Verb::kQuad: {
-                    // Check for a cusp. A quadratic can only have a cusp if it is a degenerate flat
-                    // line with a 180 degree turnarund. To detect this, the beginning and ending
-                    // tangents must be parallel (a.cross(b) == 0) and pointing in opposite
-                    // directions (a.dot(b) < 0).
+                    // Check for a cusp. A conic of any class can only have a cusp if it is a
+                    // degenerate flat line with a 180 degree turnarund. To detect this, the
+                    // beginning and ending tangents must be parallel (a.cross(b) == 0) and pointing
+                    // in opposite directions (a.dot(b) < 0).
                     SkVector a = pts[1] - pts[0];
                     SkVector b = pts[2] - pts[1];
                     if (a.cross(b) == 0 && a.dot(b) < 0) {
@@ -583,8 +588,8 @@
             return;
         }
         auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
-                GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
-                fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+                GrStrokeTessellateShader::Mode::kIndirect, fTotalConicWeightCnt, fStroke,
+                fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
         this->prePreparePrograms(arena, strokeTessellateShader, flushState->writeView(),
                                  flushState->detachAppliedClip(), flushState->dstProxyView(),
                                  flushState->renderPassBarriers(), flushState->colorLoadOp(),
@@ -710,10 +715,10 @@
                     scratch = scratchBuffer;
                     continue;
                 case Verb::kLine:
+                    resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
                     scratch[0] = scratch[1] = pts[0];
                     scratch[2] = scratch[3] = pts[1];
                     pts_ = scratch;
-                    resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
                     break;
                 case Verb::kQuad:
                     resolveLevel = *nextResolveLevel++;
@@ -734,6 +739,27 @@
                     }
                     pts_ = scratch;
                     break;
+                case Verb::kConic:
+                    resolveLevel = *nextResolveLevel++;
+                    if (resolveLevel < 0) {
+                        // The curve has a cusp. Draw two lines and a cusp instead of a conic.
+                        SkASSERT(resolveLevel == -1);
+                        SkPoint cusp;
+                        SkConic conic(pts, iter.w());
+                        float cuspT = conic.findMidTangent();
+                        conic.evalAt(cuspT, &cusp);
+                        resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
+                        numChops = 1;
+                        scratch[0] = scratch[1] = pts[0];
+                        scratch[2] = scratch[3] = scratch[4] = cusp;
+                        scratch[5] = scratch[6] = pts[2];
+                        nextInstanceLocations[fResolveLevelForCircles]++->setCircle(
+                                cusp, numEdgesPerResolveLevel[fResolveLevelForCircles]);
+                    } else {
+                        GrPathShader::WriteConicPatch(pts, iter.w(), scratch);
+                    }
+                    pts_ = scratch;
+                    break;
                 case Verb::kCubic:
                     resolveLevel = *nextResolveLevel++;
                     if (resolveLevel < 0) {
@@ -769,11 +795,11 @@
                             (i == 0) ? numEdges : -numEdges);
                 }
                 // Determine the last control point.
-                if (pts_[2] != pts_[3]) {
+                if (pts_[2] != pts_[3] && verb != Verb::kConic) {  // Conics use pts_[3] for w.
                     lastControlPoint = pts_[2];
-                } else if (pts_[1] != pts_[3]) {
+                } else if (pts_[1] != pts_[2]) {
                     lastControlPoint = pts_[1];
-                } else if (pts_[0] != pts_[3]) {
+                } else if (pts_[0] != pts_[1]) {
                     lastControlPoint = pts_[0];
                 } else {
                     // This is very unusual, but all chops became degenerate. Don't update the
@@ -786,7 +812,7 @@
                 pts_ += 3;
                 // If a non-cubic got chopped, it means it was chopped into lines and a circle.
                 resolveLevel = (verb == Verb::kCubic) ? *nextResolveLevel++ : 0;
-                SkASSERT(verb == Verb::kQuad || verb == Verb::kCubic);
+                SkASSERT(verb == Verb::kQuad || verb == Verb::kConic || verb == Verb::kCubic);
             }
         }
     }
diff --git a/src/gpu/tessellate/GrStrokeIterator.h b/src/gpu/tessellate/GrStrokeIterator.h
index adf647f..ef75faa 100644
--- a/src/gpu/tessellate/GrStrokeIterator.h
+++ b/src/gpu/tessellate/GrStrokeIterator.h
@@ -22,6 +22,7 @@
 //     while (iter.next()) {  // Call next() first.
 //         iter.verb();
 //         iter.pts();
+//         iter.w();
 //         iter.prevVerb();
 //         iter.prevPts();
 //     }
@@ -39,6 +40,7 @@
         // Verbs that describe stroke geometry.
         kLine = (int)SkPathVerb::kLine,
         kQuad = (int)SkPathVerb::kQuad,
+        kConic = (int)SkPathVerb::kConic,
         kCubic = (int)SkPathVerb::kCubic,
         kCircle,  // A stroke-width circle drawn as a 180-degree point stroke.
 
@@ -75,6 +77,7 @@
                 case SkPathVerb::kCubic:
                     if (pts[3] == pts[2]) {
                         [[fallthrough]];  // i.e., "if (p3 == p2 && p2 == p1 && p1 == p0)"
+                case SkPathVerb::kConic:
                 case SkPathVerb::kQuad:
                     if (pts[2] == pts[1]) {
                         [[fallthrough]];  // i.e., "if (p2 == p1 && p1 == p0)"
@@ -83,11 +86,12 @@
                         fLastDegenerateStrokePt = pts;
                         continue;
                     }}}
-                    this->enqueue((Verb)verb, pts);
+                    this->enqueue((Verb)verb, pts, w);
                     if (fQueueCount == 1) {
                         // Defer the first verb until the end when we know what it's joined to.
                         fFirstVerbInContour = (Verb)verb;
                         fFirstPtsInContour = pts;
+                        fFirstWInContour = w;
                         continue;
                     }
                     break;
@@ -99,15 +103,13 @@
                     if (pts[0] != fFirstPtsInContour[0]) {
                         // Draw a line back to the contour's starting point.
                         fClosePts = {pts[0], fFirstPtsInContour[0]};
-                        this->enqueue(Verb::kLine, fClosePts.data());
+                        this->enqueue(Verb::kLine, fClosePts.data(), nullptr);
                     }
                     // Repeat the first verb, this time as the "current" stroke instead of the prev.
-                    this->enqueue(fFirstVerbInContour, fFirstPtsInContour);
-                    this->enqueue(Verb::kContourFinished, nullptr);
+                    this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
+                    this->enqueue(Verb::kContourFinished, nullptr, nullptr);
                     fLastDegenerateStrokePt = nullptr;
                     break;
-                case SkPathVerb::kConic:
-                    SkUNREACHABLE;
             }
             SkASSERT(fQueueCount >= 2);
             ++fIter;
@@ -121,6 +123,7 @@
 
     Verb verb() const { return this->atVerb(1); }
     const SkPoint* pts() const { return this->atPts(1); }
+    float w() const { return this->atW(1); }
 
     Verb firstVerbInContour() const { SkASSERT(fQueueCount > 0); return fFirstVerbInContour; }
     const SkPoint* firstPtsInContour() const {
@@ -144,11 +147,18 @@
     const SkPoint* backPts() const {
         return this->atPts(fQueueCount - 1);
     }
-    void enqueue(Verb verb, const SkPoint* pts) {
+    float atW(int i) const {
+        SkASSERT(0 <= i && i < fQueueCount);
+        const float* w = fW[(fQueueFrontIdx + i) & (kQueueBufferCount - 1)];
+        SkASSERT(w);
+        return *w;
+    }
+    void enqueue(Verb verb, const SkPoint* pts, const float* w) {
         SkASSERT(fQueueCount < kQueueBufferCount);
         int i = (fQueueFrontIdx + fQueueCount) & (kQueueBufferCount - 1);
         fVerbs[i] = verb;
         fPts[i] = pts;
+        fW[i] = w;
         ++fQueueCount;
     }
     void popFront() {
@@ -163,30 +173,30 @@
     bool finishOpenContour() {
         if (fQueueCount) {
             SkASSERT(this->backVerb() == Verb::kLine || this->backVerb() == Verb::kQuad ||
-                     this->backVerb() == Verb::kCubic);
+                     this->backVerb() == Verb::kConic || this->backVerb() == Verb::kCubic);
             switch (fCapType) {
                 case SkPaint::kButt_Cap:
                     // There are no caps, but inject a "move" so the first stroke doesn't get joined
                     // with the end of the contour when it's processed.
-                    this->enqueue(Verb::kMoveWithinContour, fFirstPtsInContour);
+                    this->enqueue(Verb::kMoveWithinContour, fFirstPtsInContour, fFirstWInContour);
                     break;
                 case SkPaint::kRound_Cap: {
                     // The "kCircle" verb serves as our barrier to prevent the first stroke from
                     // getting joined with the end of the contour. We just need to make sure that
                     // the first point of the contour goes last.
                     int backIdx = SkPathPriv::PtsInIter((unsigned)this->backVerb()) - 1;
-                    this->enqueue(Verb::kCircle, this->backPts() + backIdx);
-                    this->enqueue(Verb::kCircle, fFirstPtsInContour);
+                    this->enqueue(Verb::kCircle, this->backPts() + backIdx, nullptr);
+                    this->enqueue(Verb::kCircle, fFirstPtsInContour, fFirstWInContour);
                     break;
                 }
                 case SkPaint::kSquare_Cap:
                     this->fillSquareCapPoints();  // Fills in fEndingCapPts and fBeginningCapPts.
                     // Append the ending cap onto the current contour.
-                    this->enqueue(Verb::kLine, fEndingCapPts.data());
+                    this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
                     // Move to the beginning cap and append it right before (and joined to) the
                     // first stroke (that we will add below).
-                    this->enqueue(Verb::kMoveWithinContour, fBeginningCapPts.data());
-                    this->enqueue(Verb::kLine, fBeginningCapPts.data());
+                    this->enqueue(Verb::kMoveWithinContour, fBeginningCapPts.data(), nullptr);
+                    this->enqueue(Verb::kLine, fBeginningCapPts.data(), nullptr);
                     break;
             }
         } else if (fLastDegenerateStrokePt) {
@@ -203,22 +213,24 @@
                     // generate.
                     return false;
                 case SkPaint::kRound_Cap:
-                    this->enqueue(Verb::kCircle, fLastDegenerateStrokePt);
+                    this->enqueue(Verb::kCircle, fLastDegenerateStrokePt, nullptr);
                     // Setting the "first" stroke as the circle causes it to be added again below,
                     // this time as the "current" stroke.
                     fFirstVerbInContour = Verb::kCircle;
                     fFirstPtsInContour = fLastDegenerateStrokePt;
+                    fFirstWInContour = nullptr;
                     break;
                 case SkPaint::kSquare_Cap:
                     fEndingCapPts = {*fLastDegenerateStrokePt - SkPoint{fStrokeRadius, 0},
                                      *fLastDegenerateStrokePt + SkPoint{fStrokeRadius, 0}};
                     // Add the square first as the "prev" join.
-                    this->enqueue(Verb::kLine, fEndingCapPts.data());
-                    this->enqueue(Verb::kMoveWithinContour, fEndingCapPts.data());
+                    this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
+                    this->enqueue(Verb::kMoveWithinContour, fEndingCapPts.data(), nullptr);
                     // Setting the "first" stroke as the square causes it to be added again below,
                     // this time as the "current" stroke.
                     fFirstVerbInContour = Verb::kLine;
                     fFirstPtsInContour = fEndingCapPts.data();
+                    fFirstWInContour = nullptr;
                     break;
             }
         } else {
@@ -228,8 +240,8 @@
         }
 
         // Repeat the first verb, this time as the "current" stroke instead of the prev.
-        this->enqueue(fFirstVerbInContour, fFirstPtsInContour);
-        this->enqueue(Verb::kContourFinished, nullptr);
+        this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
+        this->enqueue(Verb::kContourFinished, nullptr, nullptr);
         fLastDegenerateStrokePt = nullptr;
         return true;
     }
@@ -248,6 +260,7 @@
                     break;
                 }
                 [[fallthrough]];
+            case Verb::kConic:
             case Verb::kQuad:
                 lastTangent = lastPts[2] - lastPts[1];
                 if (!lastTangent.isZero()) {
@@ -268,7 +281,8 @@
         // Find the endpoints of the cap at the beginning of the contour.
         SkVector firstTangent = fFirstPtsInContour[1] - fFirstPtsInContour[0];
         if (firstTangent.isZero()) {
-            SkASSERT(fFirstVerbInContour == Verb::kQuad || fFirstVerbInContour == Verb::kCubic);
+            SkASSERT(fFirstVerbInContour == Verb::kQuad || fFirstVerbInContour == Verb::kConic ||
+                     fFirstVerbInContour == Verb::kCubic);
             firstTangent = fFirstPtsInContour[2] - fFirstPtsInContour[0];
             if (firstTangent.isZero()) {
                 SkASSERT(fFirstVerbInContour == Verb::kCubic);
@@ -290,11 +304,13 @@
     // Info for the current contour we are iterating.
     Verb fFirstVerbInContour;
     const SkPoint* fFirstPtsInContour;
+    const float* fFirstWInContour;
     const SkPoint* fLastDegenerateStrokePt = nullptr;
 
     // The queue is implemented as a roll-over array with a floating front index.
     Verb fVerbs[kQueueBufferCount];
     const SkPoint* fPts[kQueueBufferCount];
+    const float* fW[kQueueBufferCount];
     int fQueueFrontIdx = 0;
     int fQueueCount = 0;
 
diff --git a/src/gpu/tessellate/GrStrokeOp.cpp b/src/gpu/tessellate/GrStrokeOp.cpp
index 4daf21a..f5924e3 100644
--- a/src/gpu/tessellate/GrStrokeOp.cpp
+++ b/src/gpu/tessellate/GrStrokeOp.cpp
@@ -25,7 +25,8 @@
         , fColor(paint.getColor4f())
         , fProcessors(std::move(paint))
         , fPathList(path)
-        , fTotalCombinedVerbCnt(path.countVerbs()) {
+        , fTotalCombinedVerbCnt(path.countVerbs())
+        , fTotalConicWeightCnt(SkPathPriv::ConicWeightCnt(path)) {
     // We don't support hairline strokes. For now, the client can transform the path into device
     // space and then use a stroke width of 1.
     SkASSERT(fStroke.getWidth() > 0);
@@ -74,6 +75,7 @@
 
     fPathList.concat(std::move(op->fPathList), alloc);
     fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
+    fTotalConicWeightCnt += op->fTotalConicWeightCnt;
 
     return CombineResult::kMerged;
 }
diff --git a/src/gpu/tessellate/GrStrokeOp.h b/src/gpu/tessellate/GrStrokeOp.h
index 7cd4c82..f9e6762 100644
--- a/src/gpu/tessellate/GrStrokeOp.h
+++ b/src/gpu/tessellate/GrStrokeOp.h
@@ -80,7 +80,8 @@
     GrProcessorSet fProcessors;
 
     GrSTArenaList<SkPath> fPathList;
-    int fTotalCombinedVerbCnt;
+    int fTotalCombinedVerbCnt = 0;
+    int fTotalConicWeightCnt = 0;
 
     const GrProgramInfo* fStencilProgram = nullptr;
     const GrProgramInfo* fFillProgram = nullptr;
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index b1cd656..3dcb127 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -21,8 +21,8 @@
                                         GrLoadOp colorLoadOp) {
     SkArenaAlloc* arena = context->priv().recordTimeAllocator();
     auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
-                GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
-                fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+                GrStrokeTessellateShader::Mode::kTessellation, false/*hasConics*/, fStroke,
+                fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
     this->prePreparePrograms(arena, strokeTessellateShader, writeView, std::move(*clip),
                              dstProxyView, renderPassXferBarriers, colorLoadOp,
                              *context->priv().caps());
@@ -38,8 +38,8 @@
     if (!fFillProgram && !fStencilProgram) {
         SkArenaAlloc* arena = flushState->allocator();
         auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
-                GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
-                fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+                GrStrokeTessellateShader::Mode::kTessellation, false/*hasConics*/, fStroke,
+                fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
         this->prePreparePrograms(flushState->allocator(), strokeTessellateShader,
                                  flushState->writeView(), flushState->detachAppliedClip(),
                                  flushState->dstProxyView(), flushState->renderPassBarriers(),
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.cpp b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
index cfab725..5c671cb 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
@@ -33,6 +33,8 @@
         const auto& shader = args.fGP.cast<GrStrokeTessellateShader>();
         auto* uniHandler = args.fUniformHandler;
 
+        SkASSERT(!shader.fHasConics);
+
         args.fVaryingHandler->emitAttributes(shader);
 
         // uNumSegmentsInJoin, uWangsTermPow2, uNumRadialSegmentsPerRadian, uMiterLimitInvPow2.
@@ -298,13 +300,15 @@
     return atan(v.y, v.x) + bias;
 })";
 
+static const char* kLengthPow2Fn = R"(
+float length_pow2(float2 v) {
+    return dot(v, v);
+})";
+
 // Calculates the number of evenly spaced (in the parametric sense) segments to chop a cubic into.
 // (See GrWangsFormula::cubic() for more documentation on this formula.) The final tessellated strip
 // will be a composition of these parametric segments as well as radial segments.
 static const char* kWangsFormulaCubicFn = R"(
-float length_pow2(float2 v) {
-    return dot(v, v);
-}
 float wangs_formula_cubic(float4x2 P, float wangsTermPow2) {
     float m = max(length_pow2(fma(float2(-2), P[1], P[2]) + P[0]),
                   length_pow2(fma(float2(-2), P[2], P[3]) + P[1])) * wangsTermPow2;
@@ -372,6 +376,7 @@
     })");
 
     code.append(kAtan2Fn);
+    code.append(kLengthPow2Fn);
     code.append(kWangsFormulaCubicFn);
     code.append(kMiterExtentFn);
 
@@ -518,127 +523,172 @@
 // precision than "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points.
 // We override this result anyway when t==1 so it shouldn't be a problem.
 static const char* kUncheckedMixFn = R"(
-float2 unchecked_mix(float2 a, float2 b, float t) {
-    return fma(b - a, float2(t), a);
+float unchecked_mix(float a, float b, float T) {
+    return fma(b - a, T, a);
+}
+float2 unchecked_mix(float2 a, float2 b, float T) {
+    return fma(b - a, float2(T), a);
 })";
 
 // Computes the location and tangent direction of the stroke edge with the integral id
 // "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial edges.
-static const char* kEvalStrokeEdgeFn = R"(
-void eval_stroke_edge(in float4x2 P, in float numParametricSegments, in float combinedEdgeID,
-                      in float2 tan0, in float radsPerSegment, in float angle0,
-                      out float2 tangent, out float2 position) {
-    // Start by finding the cubic's power basis coefficients. These define a tangent direction
-    // (scaled by uniform 1/3) as:
-    //                                                 |T^2|
-    //     Tangent_Direction(T) = dx,dy = |A  2B  C| * |T  |
-    //                                    |.   .  .|   |1  |
-    float2 C = P[1] - P[0];
-    float2 D = P[2] - P[1];
-    float2 E = P[3] - P[0];
-    float2 B = D - C;
-    float2 A = fma(float2(-3), D, E);
+static void append_eval_stroke_edge_fn(SkString* code, bool hasConics) {
+    code->append(R"(
+    void eval_stroke_edge(in float4x2 P, )");
+    if (hasConics) {
+        code->append(R"(
+                          in float w, )");
+    }
+    code->append(R"(
+                          in float numParametricSegments, in float combinedEdgeID, in float2 tan0,
+                          in float radsPerSegment, in float angle0, out float2 tangent,
+                          out float2 position) {
+        // Start by finding the tangent function's power basis coefficients. These define a tangent
+        // direction (scaled by some uniform value) as:
+        //                                                 |T^2|
+        //     Tangent_Direction(T) = dx,dy = |A  2B  C| * |T  |
+        //                                    |.   .  .|   |1  |
+        float2 A, B, C = P[1] - P[0];
+        float2 D = P[3] - P[0];)");
+    if (hasConics) {
+        code->append(R"(
+        if (w >= 0) {
+            // P0..P2 represent a conic and P3==P2. The derivative of a conic has a cumbersome
+            // order-4 denominator. However, this isn't necessary if we are only interested in a
+            // vector in the same *direction* as a given tangent line. Since the denominator scales
+            // dx and dy uniformly, we can throw it out completely after evaluating the derivative
+            // with the standard quotient rule. This leaves us with a simpler quadratic function
+            // that we use to find a tangent.
+            C *= w;
+            B = .5*D - C;
+            A = (w - 1) * D;
+            P[1] *= w;
+        } else {)");
+    } else {
+        code->append(R"(
+        {)");
+    }
+    code->append(R"(
+            float2 E = P[2] - P[1];
+            B = E - C;
+            A = fma(float2(-3), E, D);
+        }
 
-    // Now find the coefficients that give a tangent direction from a parametric edge ID:
-    //
-    //                                                                 |parametricEdgeID^2|
-    //     Tangent_Direction(parametricEdgeID) = dx,dy = |A  B_  C_| * |parametricEdgeID  |
-    //                                                   |.   .   .|   |1                 |
-    //
-    float2 B_ = B * (numParametricSegments * 2);
-    float2 C_ = C * (numParametricSegments * numParametricSegments);
+        // Now find the coefficients that give a tangent direction from a parametric edge ID:
+        //
+        //                                                                 |parametricEdgeID^2|
+        //     Tangent_Direction(parametricEdgeID) = dx,dy = |A  B_  C_| * |parametricEdgeID  |
+        //                                                   |.   .   .|   |1                 |
+        //
+        float2 B_ = B * (numParametricSegments * 2);
+        float2 C_ = C * (numParametricSegments * numParametricSegments);
 
-    // Run a binary search to determine the highest parametric edge that is located on or before the
-    // combinedEdgeID. A combined ID is determined by the sum of complete parametric and radial
-    // segments behind it. i.e., find the highest parametric edge where:
-    //
-    //    parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
-    //
-    float lastParametricEdgeID = 0;
-    float maxParametricEdgeID = min(numParametricSegments - 1, combinedEdgeID);
-    float2 tan0norm = normalize(tan0);
-    float negAbsRadsPerSegment = -abs(radsPerSegment);
-    float maxRotation0 = (1 + combinedEdgeID) * abs(radsPerSegment);
-    for (int exp = MAX_PARAMETRIC_SEGMENTS_LOG2 - 1; exp >= 0; --exp) {
-        // Test the parametric edge at lastParametricEdgeID + 2^exp.
-        float testParametricID = lastParametricEdgeID + (1 << exp);
-        if (testParametricID <= maxParametricEdgeID) {
-            float2 testTan = fma(float2(testParametricID), A, B_);
-            testTan = fma(float2(testParametricID), testTan, C_);
-            float cosRotation = dot(normalize(testTan), tan0norm);
-            float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
-            maxRotation = min(maxRotation, PI);
-            // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
-            // behind testT, + testParametricID <= combinedEdgeID?)
-            if (cosRotation >= cos(maxRotation)) {
-                // testParametricID is on or before the combinedEdgeID. Keep it!
-                lastParametricEdgeID = testParametricID;
+        // Run a binary search to determine the highest parametric edge that is located on or before
+        // the combinedEdgeID. A combined ID is determined by the sum of complete parametric and
+        // radial segments behind it. i.e., find the highest parametric edge where:
+        //
+        //    parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
+        //
+        float lastParametricEdgeID = 0;
+        float maxParametricEdgeID = min(numParametricSegments - 1, combinedEdgeID);
+        float2 tan0norm = normalize(tan0);
+        float negAbsRadsPerSegment = -abs(radsPerSegment);
+        float maxRotation0 = (1 + combinedEdgeID) * abs(radsPerSegment);
+        for (int exp = MAX_PARAMETRIC_SEGMENTS_LOG2 - 1; exp >= 0; --exp) {
+            // Test the parametric edge at lastParametricEdgeID + 2^exp.
+            float testParametricID = lastParametricEdgeID + (1 << exp);
+            if (testParametricID <= maxParametricEdgeID) {
+                float2 testTan = fma(float2(testParametricID), A, B_);
+                testTan = fma(float2(testParametricID), testTan, C_);
+                float cosRotation = dot(normalize(testTan), tan0norm);
+                float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
+                maxRotation = min(maxRotation, PI);
+                // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
+                // behind testT, + testParametricID <= combinedEdgeID?)
+                if (cosRotation >= cos(maxRotation)) {
+                    // testParametricID is on or before the combinedEdgeID. Keep it!
+                    lastParametricEdgeID = testParametricID;
+                }
             }
         }
+
+        // Find the T value of the parametric edge at lastParametricEdgeID.
+        float parametricT = lastParametricEdgeID / numParametricSegments;
+
+        // Now that we've identified the highest parametric edge on or before the combinedEdgeID,
+        // the highest radial edge is easy:
+        float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
+
+        // Find the tangent vector on the edge at lastRadialEdgeID.
+        float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
+        tangent = float2(cos(radialAngle), sin(radialAngle));
+        float2 norm = float2(-tangent.y, tangent.x);
+
+        // Find the T value where the cubic's tangent is orthogonal to norm. This is a quadratic:
+        //
+        //     dot(norm, Tangent_Direction(T)) == 0
+        //
+        //                         |T^2|
+        //     norm * |A  2B  C| * |T  | == 0
+        //            |.   .  .|   |1  |
+        //
+        float3 coeffs = norm * float3x2(A,B,C);
+        float a=coeffs.x, b_over_2=coeffs.y, c=coeffs.z;
+        float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0);
+        float q = sqrt(discr_over_4);
+        if (b_over_2 > 0) {
+            q = -q;
+        }
+        q -= b_over_2;
+
+        // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
+        // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
+        // nearest .5.
+        float _5qa = -.5*q*a;
+        float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
+        float radialT = (root.t != 0) ? root.s / root.t : 0;
+        radialT = clamp(radialT, 0, 1);
+
+        if (lastRadialEdgeID == 0) {
+            // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there
+            // are roots at exatly 0 and 1 both). radialT should always == 0 in this case.
+            radialT = 0;
+        }
+
+        // Now that we've identified the T values of the last parametric and radial edges, our final
+        // T value for combinedEdgeID is whichever is larger.
+        float T = max(parametricT, radialT);
+
+        // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
+        float2 ab = unchecked_mix(P[0], P[1], T);
+        float2 bc = unchecked_mix(P[1], P[2], T);
+        float2 cd = unchecked_mix(P[2], P[3], T);
+        float2 abc = unchecked_mix(ab, bc, T);
+        float2 bcd = unchecked_mix(bc, cd, T);
+        float2 abcd = unchecked_mix(abc, bcd, T);)");
+
+    if (hasConics) {
+        code->append(R"(
+        // Evaluate the conic weights at T.
+        float u = unchecked_mix(1, w, T);
+        float v = unchecked_mix(w, 1, T);
+        float uv = unchecked_mix(u, v, T);)");
     }
 
-    // Find the T value of the parametric edge at lastParametricEdgeID.
-    float parametricT = lastParametricEdgeID / numParametricSegments;
+    code->appendf(R"(
+        position =%s abcd;)", (hasConics) ? " (w >= 0) ? abc/uv :" : "");
 
-    // Now that we've identified the highest parametric edge on or before the combinedEdgeID, the
-    // highest radial edge is easy:
-    float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
-
-    // Find the tangent vector on the edge at lastRadialEdgeID.
-    float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
-    tangent = float2(cos(radialAngle), sin(radialAngle));
-    float2 norm = float2(-tangent.y, tangent.x);
-
-    // Find the T value where the cubic's tangent is orthogonal to norm. This is a quadratic:
-    //
-    //     dot(norm, Tangent_Direction(T)) == 0
-    //
-    //                         |T^2|
-    //     norm * |A  2B  C| * |T  | == 0
-    //            |.   .  .|   |1  |
-    //
-    float3 coeffs = norm * float3x2(A,B,C);
-    float a=coeffs.x, b_over_2=coeffs.y, c=coeffs.z;
-    float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0);
-    float q = sqrt(discr_over_4);
-    if (b_over_2 > 0) {
-        q = -q;
-    }
-    q -= b_over_2;
-
-    // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
-    // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
-    // nearest .5.
-    float _5qa = -.5*q*a;
-    float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
-    float radialT = (root.t != 0) ? root.s / root.t : 0;
-    radialT = clamp(radialT, 0, 1);
-
-    if (lastRadialEdgeID == 0) {
-        // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there
-        // are roots at exatly 0 and 1 both). radialT should always == 0 in this case.
-        radialT = 0;
-    }
-
-    // Now that we've identified the T values of the last parametric and radial edges, our final
-    // T value for combinedEdgeID is whichever is larger.
-    float T = max(parametricT, radialT);
-
-    // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
-    float2 ab = unchecked_mix(P[0], P[1], T);
-    float2 bc = unchecked_mix(P[1], P[2], T);
-    float2 cd = unchecked_mix(P[2], P[3], T);
-    float2 abc = unchecked_mix(ab, bc, T);
-    float2 bcd = unchecked_mix(bc, cd, T);
-    position = unchecked_mix(abc, bcd, T);
-
-    // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
-    // tangent found previously. (In the event that parametricT == radialT, we keep the radial
-    // tangent.)
-    if (T != radialT) {
-        tangent = bcd - abc;
-    }
-})";
+    code->appendf(R"(
+        // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
+        // tangent found previously. (In the event that parametricT == radialT, we keep the radial
+        // tangent.)
+        if (T != radialT) {)");
+    code->appendf(R"(
+            tangent =%s bcd - abc;)", (hasConics) ? " (w >= 0) ? bc*u - ab*v :" : "");
+    code->appendf(R"(
+        }
+    })");
+}
 
 SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
         const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls,
@@ -685,7 +735,7 @@
     uniform vec4 sk_RTAdjust;)");
 
     code.append(kUncheckedMixFn);
-    code.append(kEvalStrokeEdgeFn);
+    append_eval_stroke_edge_fn(&code, false/*hasConics*/);
 
     code.append(R"(
     void main() {
@@ -783,13 +833,17 @@
         args.fVertBuilder->defineConstant("MAX_PARAMETRIC_SEGMENTS_LOG2",
                                           GrTessellationPathRenderer::kMaxResolveLevel);
         args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238");
+        args.fVertBuilder->defineConstant("QUAD_TERM_POW2",
+                                          GrWangsFormula::length_term_pow2<2>(1));
+        args.fVertBuilder->defineConstant("CUBIC_TERM_POW2",
+                                          GrWangsFormula::length_term_pow2<3>(1));
 
         // Helper functions.
         args.fVertBuilder->insertFunction(kAtan2Fn);
-        args.fVertBuilder->insertFunction(kWangsFormulaCubicFn);
+        args.fVertBuilder->insertFunction(kLengthPow2Fn);
         args.fVertBuilder->insertFunction(kMiterExtentFn);
         args.fVertBuilder->insertFunction(kUncheckedMixFn);
-        args.fVertBuilder->insertFunction(kEvalStrokeEdgeFn);
+        append_eval_stroke_edge_fn(&args.fVertBuilder->functions(), shader.fHasConics);
         args.fVertBuilder->insertFunction(R"(
         float cosine_between_vectors(float2 a, float2 b) {
             float ab_cosTheta = dot(a,b);
@@ -801,20 +855,37 @@
         const char* tessArgsName;
         fTessControlArgsUniform = args.fUniformHandler->addUniform(
                 nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "tessControlArgs", &tessArgsName);
-        args.fVertBuilder->codeAppendf("float uWangsTermPow2 = %s.x;\n", tessArgsName);
+        args.fVertBuilder->codeAppendf("float uParametricIntolerance = %s.x;\n", tessArgsName);
         args.fVertBuilder->codeAppendf("float uNumRadialSegmentsPerRadian = %s.y;\n", tessArgsName);
         args.fVertBuilder->codeAppendf("float uMiterLimitInvPow2 = %s.z;\n", tessArgsName);
         args.fVertBuilder->codeAppendf("float uStrokeRadius = %s.w;\n", tessArgsName);
 
         // Tessellation code.
         args.fVertBuilder->codeAppend(R"(
-        float4x2 P = float4x2(pts01, pts23);
+        float4x2 P = float4x2(pts01, pts23);)");
+        if (shader.fHasConics) {
+            args.fVertBuilder->codeAppend(R"(
+            float w = -1;  // w<0 means the curve is an integral cubic.
+            if (isinf(P[3].y)) {
+                w = P[3].x;  // The curve is actually a conic.
+                P[3] = P[2];  // Setting p3 equal to p2 works for the remaining rotational logic.
+            })");
+        }
+        args.fVertBuilder->codeAppend(R"(
         float2 lastControlPoint = args.xy;
         float numTotalEdges = abs(args.z);
 
-        // Find how many parametric segments this stroke requires.
-        float numParametricSegments = min(wangs_formula_cubic(P, uWangsTermPow2),
-                                          1 << MAX_PARAMETRIC_SEGMENTS_LOG2);
+        // Use wang's formula to find how many parametric segments this stroke requires.
+        float l0 = length_pow2(fma(float2(-2), P[1], P[2]) + P[0]);
+        float l1 = length_pow2(fma(float2(-2), P[2], P[3]) + P[1]);)");
+
+        args.fVertBuilder->codeAppendf(R"(
+        float m =%s CUBIC_TERM_POW2 * max(l0, l1);)",
+                (shader.fHasConics) ? " (w >= 0) ? QUAD_TERM_POW2 * l0 :" : "");
+
+        args.fVertBuilder->codeAppend(R"(
+        float numParametricSegments = ceil(sqrt(uParametricIntolerance * sqrt(m)));
+        numParametricSegments = clamp(numParametricSegments, 1, 1 << MAX_PARAMETRIC_SEGMENTS_LOG2);
         if (P[0] == P[1] && P[2] == P[3]) {
             // This is how we describe lines, but Wang's formula does not return 1 in this case.
             numParametricSegments = 1;
@@ -929,11 +1000,18 @@
             })");
         }
 
-        args.fVertBuilder->codeAppend(R"(
+        args.fVertBuilder->codeAppendf(R"(
         float2 tangent, localCoord;
-        eval_stroke_edge(P, numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
-                         tangent, localCoord);
+        eval_stroke_edge(P,)");
+        if (shader.fHasConics) {
+            args.fVertBuilder->codeAppend(R"(
+                         w,)");
+        }
+        args.fVertBuilder->codeAppend(R"(
+                         numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
+                         tangent, localCoord);)");
 
+        args.fVertBuilder->codeAppend(R"(
         if (combinedEdgeID == 0) {
             // Edges at the beginning of their section use P[0] and tan0. This ensures crack-free
             // seaming between instances.
@@ -983,7 +1061,7 @@
         // Set up the tessellation control uniforms.
         float miterLimit = shader.fStroke.getMiter();
         pdman.set4f(fTessControlArgsUniform,
-            GrWangsFormula::length_term_pow2<3>(shader.fParametricIntolerance),  // uWangsTermPow2
+            shader.fParametricIntolerance,  // uParametricIntolerance
             shader.fNumRadialSegmentsPerRadian,  // uNumRadialSegmentsPerRadian
             1 / (miterLimit * miterLimit),  // uMiterLimitInvPow2.
             shader.fStroke.getWidth() * .5);  // uStrokeRadius.
@@ -1012,6 +1090,7 @@
         SkASSERT(fStroke.getJoin() >> 2 == 0);
         key = (key << 2) | fStroke.getJoin();
     }
+    key = (key << 1) | (uint32_t)fHasConics;
     key = (key << 1) | (uint32_t)fMode;  // Must be last.
     b->add32(key);
 }
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.h b/src/gpu/tessellate/GrStrokeTessellateShader.h
index fca1ee8..de84d23 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.h
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.h
@@ -107,14 +107,15 @@
     // smoothness.
     //
     // 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
-    GrStrokeTessellateShader(Mode mode, const SkStrokeRec& stroke, float parametricIntolerance,
-                             float numRadialSegmentsPerRadian, const SkMatrix& viewMatrix,
-                             SkPMColor4f color)
+    GrStrokeTessellateShader(Mode mode, bool hasConics, const SkStrokeRec& stroke,
+                             float parametricIntolerance, float numRadialSegmentsPerRadian,
+                             const SkMatrix& viewMatrix, SkPMColor4f color)
             : GrPathShader(kTessellate_GrStrokeTessellateShader_ClassID, viewMatrix,
                            (mode == Mode::kTessellation) ?
                                    GrPrimitiveType::kPatches : GrPrimitiveType::kTriangleStrip,
                            (mode == Mode::kTessellation) ? 1 : 0)
             , fMode(mode)
+            , fHasConics(hasConics)
             , fStroke(stroke)
             , fParametricIntolerance(parametricIntolerance)
             , fNumRadialSegmentsPerRadian(numRadialSegmentsPerRadian)
@@ -154,6 +155,7 @@
                                          const GrShaderCaps&) const override;
 
     const Mode fMode;
+    const bool fHasConics;
     const SkStrokeRec fStroke;
     const float fParametricIntolerance;
     const float fNumRadialSegmentsPerRadian;
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index d1f9dc7..4ee7e93 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -143,8 +143,7 @@
     if (!shape.style().isSimpleFill()) {
         // These are only temporary restrictions while we bootstrap tessellated stroking. Every one
         // of them will eventually go away.
-        if (shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
-            SkPathPriv::ConicWeightCnt(path)) {
+        if (shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
             return CanDrawPath::kNo;
         }
         if (shape.style().isSimpleHairline()) {
@@ -167,7 +166,8 @@
     // seem to be better off using indirect draws. Our back door for HW tessellation shaders isn't
     // currently capable of passing varyings to the fragment shader either, so if the paint uses
     // varyings we need to use indirect draws.
-    if (shaderCaps.tessellationSupport() && path.countVerbs() > 50 && !paint.usesVaryingCoords()) {
+    if (shaderCaps.tessellationSupport() && path.countVerbs() > 50 && !paint.usesVaryingCoords() &&
+        !SkPathPriv::ConicWeightCnt(path)) {
         return GrOp::Make<GrStrokeTessellateOp>(context, aaType, viewMatrix, stroke, path,
                                                 std::move(paint));
     } else {