Add a robust "isinf" workaround for tessellation

When isinf is not supported, we simply add another instanced attrib that
tells the shader exactly what type of curve it's dealing with.

Bug: chromium:1220246
Change-Id: I3496de674ce8c7df205e3c40559ae89dc29488e1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/429676
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index e075995..9e8f56b 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -245,6 +245,20 @@
         fDrawInstancedSupport = version >= GR_GL_VER(2, 0);
     }
 
+#ifdef GR_DISABLE_TESSELLATION_ON_ES2
+    if (GR_IS_GR_GL_ES(standard) && version < GR_GL_VER(3, 0)) {
+        // Temporarily disable the tessellation path renderer on Chrome ES2 while we roll the
+        // necessary Skia changes.
+        fDisableTessellationPathRenderer = true;
+    }
+#else
+    if (GR_IS_GR_GL_ES(standard) && ctxInfo.isOverCommandBuffer() && version < GR_GL_VER(3, 0)) {
+        // Temporarily disable the tessellation path renderer over the ES2 command buffer. This is
+        // an attempt to lower impact while we roll out tessellation in Chrome.
+        fDisableTessellationPathRenderer = true;
+    }
+#endif
+
     if (GR_IS_GR_GL(standard)) {
         if (version >= GR_GL_VER(3, 0)) {
             fBindFragDataLocationSupport = true;
diff --git a/src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h b/src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h
index 79b80b3..a2df17b 100644
--- a/src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h
+++ b/src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h
@@ -43,15 +43,14 @@
 // recursion, we manipulate an O(log N) stack to determine the correct middle-out triangulation.
 class GrMiddleOutPolygonTriangulator {
 public:
-    enum class OutputType : bool {
-        kTriangles,  // Output 3-vertex triangles.
-        kConicsWithInfiniteWeight  // Output 4-vertex conics with w=Inf.
-    };
-
-    GrMiddleOutPolygonTriangulator(GrVertexWriter* vertexWriter, OutputType outputType,
-                                   int maxPushVertexCalls)
-            : fVertexWriter(vertexWriter)
-            , fOutputType(outputType) {
+    // Writes out 3 SkPoints per triangle to "vertexWriter". Additionally writes out "pad32Count"
+    // repetitions of "pad32Value" after each triangle. Set pad32Count to 0 if the triangles are
+    // to be tightly packed.
+    GrMiddleOutPolygonTriangulator(GrVertexWriter* vertexWriter, int pad32Count,
+                                   uint32_t pad32Value, int maxPushVertexCalls)
+            : fPad32Count(pad32Count)
+            , fPad32Value(pad32Value)
+            , fVertexWriter(vertexWriter) {
         // Determine the deepest our stack can ever go.
         int maxStackDepth = SkNextLog2(maxPushVertexCalls) + 1;
         if (maxStackDepth > kStackPreallocCount) {
@@ -123,9 +122,10 @@
         SkASSERT(fTop->fVertexIdxDelta == 0);  // Ensure we are in the initial stack state.
     }
 
-    static int WritePathInnerFan(GrVertexWriter* vertexWriter, OutputType outputType,
+    static int WritePathInnerFan(GrVertexWriter* vertexWriter, int pad32Count, uint32_t pad32Value,
                                  const SkPath& path) {
-        GrMiddleOutPolygonTriangulator middleOut(vertexWriter, outputType, path.countVerbs());
+        GrMiddleOutPolygonTriangulator middleOut(vertexWriter, pad32Count, pad32Value,
+                                                 path.countVerbs());
         for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
             switch (verb) {
                 case SkPathVerb::kMove:
@@ -169,19 +169,20 @@
         SkASSERT(fTop > fVertexStack);  // We should never pop the starting point.
         --fTop;
         fVertexWriter->write(fTop[0].fPoint, fTop[1].fPoint, lastPt);
-        if (fOutputType == OutputType::kConicsWithInfiniteWeight) {
+        if (fPad32Count) {
             // Output a 4-point conic with w=Inf.
-            fVertexWriter->fill(GrVertexWriter::kIEEE_32_infinity, 2);
+            fVertexWriter->fill(fPad32Value, fPad32Count);
         }
     }
 
     constexpr static int kStackPreallocCount = 32;
+    const int fPad32Count;
+    const uint32_t fPad32Value;
     SkAutoSTMalloc<kStackPreallocCount, StackVertex> fVertexStack;
     SkDEBUGCODE(int fStackAllocCount;)
     StackVertex* fTop;
     GrVertexWriter* fVertexWriter;
     int fTotalClosedTriangleCount = 0;
-    OutputType fOutputType;
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrPathCurveTessellator.cpp b/src/gpu/tessellate/GrPathCurveTessellator.cpp
index bc796ec..f0b5423 100644
--- a/src/gpu/tessellate/GrPathCurveTessellator.cpp
+++ b/src/gpu/tessellate/GrPathCurveTessellator.cpp
@@ -7,6 +7,7 @@
 
 #include "src/gpu/tessellate/GrPathCurveTessellator.h"
 
+#include "src/core/SkUtils.h"
 #include "src/gpu/GrResourceProvider.h"
 #include "src/gpu/geometry/GrPathUtils.h"
 #include "src/gpu/geometry/GrWangsFormula.h"
@@ -29,44 +30,53 @@
             , fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) {
     }
 
-    SK_ALWAYS_INLINE void writeQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
+    SK_ALWAYS_INLINE void writeQuadratic(const GrShaderCaps& shaderCaps,
+                                         GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
         float numSegments_pow4 = GrWangsFormula::quadratic_pow4(kPrecision, p, fVectorXform);
         if (numSegments_pow4 > fMaxSegments_pow4) {
-            this->chopAndWriteQuadratic(chunker, p);
+            this->chopAndWriteQuadratic(shaderCaps, chunker, p);
             return;
         }
         if (numSegments_pow4 > 1) {
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 GrPathUtils::writeQuadAsCubic(p, &vertexWriter);
+                vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                      GrTessellationShader::kCubicCurveType));
             }
             fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
         }
     }
 
-    SK_ALWAYS_INLINE void writeConic(GrVertexChunkBuilder* chunker, const SkPoint p[3], float w) {
+    SK_ALWAYS_INLINE void writeConic(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                                     const SkPoint p[3], float w) {
         float numSegments_pow2 = GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform);
         if (numSegments_pow2 > fMaxSegments_pow2) {
-            this->chopAndWriteConic(chunker, {p, w});
+            this->chopAndWriteConic(shaderCaps, chunker, {p, w});
             return;
         }
         if (numSegments_pow2 > 1) {
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
+                vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                      GrTessellationShader::kConicCurveType));
             }
             fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
                                               fNumFixedSegments_pow4);
         }
     }
 
-    SK_ALWAYS_INLINE void writeCubic(GrVertexChunkBuilder* chunker, const SkPoint p[4]) {
+    SK_ALWAYS_INLINE void writeCubic(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                                     const SkPoint p[4]) {
         float numSegments_pow4 = GrWangsFormula::cubic_pow4(kPrecision, p, fVectorXform);
         if (numSegments_pow4 > fMaxSegments_pow4) {
-            this->chopAndWriteCubic(chunker, p);
+            this->chopAndWriteCubic(shaderCaps, chunker, p);
             return;
         }
         if (numSegments_pow4 > 1) {
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 vertexWriter.writeArray(p, 4);
+                vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                      GrTessellationShader::kCubicCurveType));
             }
             fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
         }
@@ -75,51 +85,57 @@
     int numFixedSegments_pow4() const { return fNumFixedSegments_pow4; }
 
 private:
-    void chopAndWriteQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
+    void chopAndWriteQuadratic(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                               const SkPoint p[3]) {
         SkPoint chops[5];
         SkChopQuadAtHalf(p, chops);
         for (int i = 0; i < 2; ++i) {
             const SkPoint* q = chops + i*2;
             if (fCullTest.areVisible3(q)) {
-                this->writeQuadratic(chunker, q);
+                this->writeQuadratic(shaderCaps, chunker, q);
             }
         }
         // Connect the two halves.
-        this->writeTriangle(chunker, chops[0], chops[2], chops[4]);
+        this->writeTriangle(shaderCaps, chunker, chops[0], chops[2], chops[4]);
     }
 
-    void chopAndWriteConic(GrVertexChunkBuilder* chunker, const SkConic& conic) {
+    void chopAndWriteConic(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                           const SkConic& conic) {
         SkConic chops[2];
         if (!conic.chopAt(.5, chops)) {
             return;
         }
         for (int i = 0; i < 2; ++i) {
             if (fCullTest.areVisible3(chops[i].fPts)) {
-                this->writeConic(chunker, chops[i].fPts, chops[i].fW);
+                this->writeConic(shaderCaps, chunker, chops[i].fPts, chops[i].fW);
             }
         }
         // Connect the two halves.
-        this->writeTriangle(chunker, conic.fPts[0], chops[0].fPts[2], chops[1].fPts[2]);
+        this->writeTriangle(shaderCaps, chunker, conic.fPts[0], chops[0].fPts[2], chops[1].fPts[2]);
     }
 
-    void chopAndWriteCubic(GrVertexChunkBuilder* chunker, const SkPoint p[4]) {
+    void chopAndWriteCubic(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                           const SkPoint p[4]) {
         SkPoint chops[7];
         SkChopCubicAtHalf(p, chops);
         for (int i = 0; i < 2; ++i) {
             const SkPoint* c = chops + i*3;
             if (fCullTest.areVisible4(c)) {
-                this->writeCubic(chunker, c);
+                this->writeCubic(shaderCaps, chunker, c);
             }
         }
         // Connect the two halves.
-        this->writeTriangle(chunker, chops[0], chops[3], chops[6]);
+        this->writeTriangle(shaderCaps, chunker, chops[0], chops[3], chops[6]);
     }
 
-    void writeTriangle(GrVertexChunkBuilder* chunker, SkPoint p0, SkPoint p1, SkPoint p2) {
+    void writeTriangle(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker, SkPoint p0,
+                       SkPoint p1, SkPoint p2) {
         if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
             vertexWriter.write(p0, p1, p2);
             // Mark this instance as a triangle by setting it to a conic with w=Inf.
             vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
+            vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                  GrTessellationShader::kTriangularConicCurveType));
         }
     }
 
@@ -145,12 +161,14 @@
     using PatchType = GrPathTessellationShader::PatchType;
     GrPathTessellationShader* shader;
     if (caps.shaderCaps()->tessellationSupport() &&
+        caps.shaderCaps()->infinitySupport() &&  // The hw tessellation shaders use infinity.
         !pipeline.usesVaryingCoords() &&  // Our tessellation back door doesn't handle varyings.
         numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
         shader = GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color,
                                                                           PatchType::kCurves);
     } else {
-        shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(arena, viewMatrix, color,
+        shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena,
+                                                                         viewMatrix, color,
                                                                          PatchType::kCurves);
     }
     return arena->make([=](void* objStart) {
@@ -166,6 +184,8 @@
                                      const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(fVertexChunkArray.empty());
 
+    const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
+
     // Determine how many triangles to allocate.
     int maxTriangles = 0;
     if (fDrawInnerFan) {
@@ -183,7 +203,9 @@
     if (!patchAllocCount) {
         return;
     }
-    GrVertexChunkBuilder chunker(target, &fVertexChunkArray, sizeof(SkPoint) * 4, patchAllocCount);
+    size_t patchStride = fShader->willUseTessellationShaders() ? fShader->vertexStride() * 4
+                                                               : fShader->instanceStride();
+    GrVertexChunkBuilder chunker(target, &fVertexChunkArray, patchStride, patchAllocCount);
 
     // Write out the triangles.
     if (maxTriangles) {
@@ -193,9 +215,16 @@
         }
         int numRemainingTriangles = maxTriangles;
         if (fDrawInnerFan) {
-            int numWritten = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
-                    &vertexWriter,
-                    GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight, path);
+            // Pad the triangles with 2 infinities. This produces conic patches with w=Infinity. In
+            // the case where infinity is not supported, we also write out a 3rd float that
+            // explicitly tells the shader to interpret these patches as triangles.
+            int pad32Count = shaderCaps.infinitySupport() ? 2 : 3;
+            uint32_t pad32Value = shaderCaps.infinitySupport()
+                    ? GrVertexWriter::kIEEE_32_infinity
+                    : sk_bit_cast<uint32_t>(GrTessellationShader::kTriangularConicCurveType);
+            int numWritten = GrMiddleOutPolygonTriangulator::WritePathInnerFan(&vertexWriter,
+                                                                               pad32Count,
+                                                                               pad32Value, path);
             numRemainingTriangles -= numWritten;
         }
         if (breadcrumbTriangleList) {
@@ -217,6 +246,9 @@
                 vertexWriter.writeArray(tri->fPts, 3);
                 // Mark this instance as a triangle by setting it to a conic with w=Inf.
                 vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
+                vertexWriter.write(
+                        GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                           GrTessellationShader::kTriangularConicCurveType));
                 ++numWritten;
             }
             SkASSERT(count == breadcrumbTriangleList->count());
@@ -230,7 +262,7 @@
         // The curve shader tessellates T=0..(1/2) on the first side of the canonical triangle and
         // T=(1/2)..1 on the second side. This means we get double the max tessellation segments
         // for the range T=0..1.
-        maxSegments = target->caps().shaderCaps()->maxTessellationSegments() * 2;
+        maxSegments = shaderCaps.maxTessellationSegments() * 2;
     } else {
         maxSegments = GrPathTessellationShader::kMaxFixedCountSegments;
     }
@@ -239,13 +271,13 @@
     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
         switch (verb) {
             case SkPathVerb::kQuad:
-                curveWriter.writeQuadratic(&chunker, pts);
+                curveWriter.writeQuadratic(shaderCaps, &chunker, pts);
                 break;
             case SkPathVerb::kConic:
-                curveWriter.writeConic(&chunker, pts, *w);
+                curveWriter.writeConic(shaderCaps, &chunker, pts, *w);
                 break;
             case SkPathVerb::kCubic:
-                curveWriter.writeCubic(&chunker, pts);
+                curveWriter.writeCubic(shaderCaps, &chunker, pts);
                 break;
             default:
                 break;
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index 0a69e0a..be9875a 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -29,10 +29,17 @@
     HullShader(const SkMatrix& viewMatrix, SkPMColor4f color, const GrShaderCaps& shaderCaps)
             : GrPathTessellationShader(kTessellate_HullShader_ClassID,
                                        GrPrimitiveType::kTriangleStrip, 0, viewMatrix, color) {
-        constexpr static Attribute kPtsAttribs[] = {
-                {"p01", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
-                {"p23", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
-        this->setInstanceAttributes(kPtsAttribs, SK_ARRAY_COUNT(kPtsAttribs));
+        fInstanceAttribs.emplace_back("p01", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        fInstanceAttribs.emplace_back("p23", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+        if (!shaderCaps.infinitySupport()) {
+            // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
+            // infinity can't detect this. On these platforms we also write out an extra float with
+            // each patch that explicitly tells the shader what type of curve it is.
+            fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
+        }
+        this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
+        SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
+
         if (!shaderCaps.vertexIDSupport()) {
             constexpr static Attribute kVertexIdxAttrib("vertexidx", kFloat_GrVertexAttribType,
                                                         kFloat_GrSLType);
@@ -44,20 +51,39 @@
     const char* name() const final { return "tessellate_HullShader"; }
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
     GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+    constexpr static int kMaxInstanceAttribCount = 3;
+    SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
 };
 
 GrGLSLGeometryProcessor* HullShader::createGLSLInstance(const GrShaderCaps&) const {
     class Impl : public GrPathTessellationShader::Impl {
         void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
                             GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
+            if (shaderCaps.infinitySupport()) {
+                v->insertFunction(R"(
+                bool is_conic_curve() { return isinf(p23.w); }
+                bool is_non_triangular_conic_curve() {
+                    // We consider a conic non-triangular as long as its weight isn't infinity.
+                    // NOTE: "isinf == false" works on Mac Radeon GLSL; "!isinf" can get the wrong
+                    // answer.
+                    return isinf(p23.z) == false;
+                })");
+            } else {
+                v->insertFunction(SkStringPrintf(R"(
+                bool is_conic_curve() { return curveType != %g; })", kCubicCurveType).c_str());
+                v->insertFunction(SkStringPrintf(R"(
+                bool is_non_triangular_conic_curve() {
+                    return curveType == %g;
+                })", kConicCurveType).c_str());
+            }
             v->codeAppend(R"(
             float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
-            if (isinf(p3.y)) {  // Is the curve a conic?
+            if (is_conic_curve()) {
+                // Conics are 3 points, with the weight in p3.
                 float w = p3.x;
-                p3 = p2;
-                // A conic with w=Inf is an exact triangle.
-                // NOTE: "isinf == false" works on Mac Radeon GLSL. "!isinf" gets the wrong answer.
-                if (isinf(w) == false) {
+                p3 = p2;  // Duplicate the endpoint for shared code that also runs on cubics.
+                if (is_non_triangular_conic_curve()) {
                     // Convert the points to a trapeziodal hull that circumcscribes the conic.
                     float2 p1w = p1 * w;
                     float T = .51;  // Bias outward a bit to ensure we cover the outermost samples.
diff --git a/src/gpu/tessellate/GrPathStencilCoverOp.cpp b/src/gpu/tessellate/GrPathStencilCoverOp.cpp
index 1dfc2de..7bc5dea 100644
--- a/src/gpu/tessellate/GrPathStencilCoverOp.cpp
+++ b/src/gpu/tessellate/GrPathStencilCoverOp.cpp
@@ -184,9 +184,8 @@
         GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fFanBuffer, &fFanBaseVertex);
         int maxFanTriangles = fPath.countVerbs() - 2;  // n - 2 triangles make an n-gon.
         GrVertexWriter triangleVertexWriter = vertexAlloc.lock<SkPoint>(maxFanTriangles * 3);
-        fFanVertexCount = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
-                &triangleVertexWriter, GrMiddleOutPolygonTriangulator::OutputType::kTriangles,
-                fPath) * 3;
+        fFanVertexCount = 3 * GrMiddleOutPolygonTriangulator::WritePathInnerFan(
+                &triangleVertexWriter, 0, 0, fPath);
         SkASSERT(fFanVertexCount <= maxFanTriangles * 3);
         vertexAlloc.unlock(fFanVertexCount);
     }
diff --git a/src/gpu/tessellate/GrPathWedgeTessellator.cpp b/src/gpu/tessellate/GrPathWedgeTessellator.cpp
index f311b2a..5949795 100644
--- a/src/gpu/tessellate/GrPathWedgeTessellator.cpp
+++ b/src/gpu/tessellate/GrPathWedgeTessellator.cpp
@@ -117,53 +117,65 @@
             , fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) {
     }
 
-    SK_ALWAYS_INLINE void writeFlatWedge(GrVertexChunkBuilder* chunker, SkPoint p0, SkPoint p1,
+    SK_ALWAYS_INLINE void writeFlatWedge(const GrShaderCaps& shaderCaps,
+                                         GrVertexChunkBuilder* chunker, SkPoint p0, SkPoint p1,
                                          SkPoint midpoint) {
         if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
             GrPathUtils::writeLineAsCubic(p0, p1, &vertexWriter);
             vertexWriter.write(midpoint);
+            vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                  GrTessellationShader::kCubicCurveType));
         }
     }
 
-    SK_ALWAYS_INLINE void writeQuadraticWedge(GrVertexChunkBuilder* chunker, const SkPoint p[3],
+    SK_ALWAYS_INLINE void writeQuadraticWedge(const GrShaderCaps& shaderCaps,
+                                              GrVertexChunkBuilder* chunker, const SkPoint p[3],
                                               SkPoint midpoint) {
         float numSegments_pow4 = GrWangsFormula::quadratic_pow4(kPrecision, p, fVectorXform);
         if (numSegments_pow4 > fMaxSegments_pow4) {
-            this->chopAndWriteQuadraticWedges(chunker, p, midpoint);
+            this->chopAndWriteQuadraticWedges(shaderCaps, chunker, p, midpoint);
             return;
         }
         if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
             GrPathUtils::writeQuadAsCubic(p, &vertexWriter);
             vertexWriter.write(midpoint);
+            vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                  GrTessellationShader::kCubicCurveType));
         }
         fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
     }
 
-    SK_ALWAYS_INLINE void writeConicWedge(GrVertexChunkBuilder* chunker, const SkPoint p[3],
+    SK_ALWAYS_INLINE void writeConicWedge(const GrShaderCaps& shaderCaps,
+                                          GrVertexChunkBuilder* chunker, const SkPoint p[3],
                                           float w, SkPoint midpoint) {
         float numSegments_pow2 = GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform);
         if (GrWangsFormula::conic_pow2(kPrecision, p, w, fVectorXform) > fMaxSegments_pow2) {
-            this->chopAndWriteConicWedges(chunker, {p, w}, midpoint);
+            this->chopAndWriteConicWedges(shaderCaps, chunker, {p, w}, midpoint);
             return;
         }
         if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
             GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
             vertexWriter.write(midpoint);
+            vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                  GrTessellationShader::kConicCurveType));
         }
         fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
                                           fNumFixedSegments_pow4);
     }
 
-    SK_ALWAYS_INLINE void writeCubicWedge(GrVertexChunkBuilder* chunker, const SkPoint p[4],
+    SK_ALWAYS_INLINE void writeCubicWedge(const GrShaderCaps& shaderCaps,
+                                          GrVertexChunkBuilder* chunker, const SkPoint p[4],
                                           SkPoint midpoint) {
         float numSegments_pow4 = GrWangsFormula::cubic_pow4(kPrecision, p, fVectorXform);
         if (numSegments_pow4 > fMaxSegments_pow4) {
-            this->chopAndWriteCubicWedges(chunker, p, midpoint);
+            this->chopAndWriteCubicWedges(shaderCaps, chunker, p, midpoint);
             return;
         }
         if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
             vertexWriter.writeArray(p, 4);
             vertexWriter.write(midpoint);
+            vertexWriter.write(GrVertexWriter::If(!shaderCaps.infinitySupport(),
+                                                  GrTessellationShader::kCubicCurveType));
         }
         fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
     }
@@ -171,45 +183,46 @@
     int numFixedSegments_pow4() const { return fNumFixedSegments_pow4; }
 
 private:
-    void chopAndWriteQuadraticWedges(GrVertexChunkBuilder* chunker, const SkPoint p[3],
-                                     SkPoint midpoint) {
+    void chopAndWriteQuadraticWedges(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                                     const SkPoint p[3], SkPoint midpoint) {
         SkPoint chops[5];
         SkChopQuadAtHalf(p, chops);
         for (int i = 0; i < 2; ++i) {
             const SkPoint* q = chops + i*2;
             if (fCullTest.areVisible3(q)) {
-                this->writeQuadraticWedge(chunker, q, midpoint);
+                this->writeQuadraticWedge(shaderCaps, chunker, q, midpoint);
             } else {
-                this->writeFlatWedge(chunker, q[0], q[2], midpoint);
+                this->writeFlatWedge(shaderCaps, chunker, q[0], q[2], midpoint);
             }
         }
     }
 
-    void chopAndWriteConicWedges(GrVertexChunkBuilder* chunker, const SkConic& conic,
-                                 SkPoint midpoint) {
+    void chopAndWriteConicWedges(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                                 const SkConic& conic, SkPoint midpoint) {
         SkConic chops[2];
         if (!conic.chopAt(.5, chops)) {
             return;
         }
         for (int i = 0; i < 2; ++i) {
             if (fCullTest.areVisible3(chops[i].fPts)) {
-                this->writeConicWedge(chunker, chops[i].fPts, chops[i].fW, midpoint);
+                this->writeConicWedge(shaderCaps, chunker, chops[i].fPts, chops[i].fW, midpoint);
             } else {
-                this->writeFlatWedge(chunker, chops[i].fPts[0], chops[i].fPts[2], midpoint);
+                this->writeFlatWedge(shaderCaps, chunker, chops[i].fPts[0], chops[i].fPts[2],
+                                     midpoint);
             }
         }
     }
 
-    void chopAndWriteCubicWedges(GrVertexChunkBuilder* chunker, const SkPoint p[4],
-                                 SkPoint midpoint) {
+    void chopAndWriteCubicWedges(const GrShaderCaps& shaderCaps, GrVertexChunkBuilder* chunker,
+                                 const SkPoint p[4], SkPoint midpoint) {
         SkPoint chops[7];
         SkChopCubicAtHalf(p, chops);
         for (int i = 0; i < 2; ++i) {
             const SkPoint* c = chops + i*3;
             if (fCullTest.areVisible4(c)) {
-                this->writeCubicWedge(chunker, c, midpoint);
+                this->writeCubicWedge(shaderCaps, chunker, c, midpoint);
             } else {
-                this->writeFlatWedge(chunker, c[0], c[3], midpoint);
+                this->writeFlatWedge(shaderCaps, chunker, c[0], c[3], midpoint);
             }
         }
     }
@@ -232,12 +245,14 @@
     using PatchType = GrPathTessellationShader::PatchType;
     GrPathTessellationShader* shader;
     if (caps.shaderCaps()->tessellationSupport() &&
+        caps.shaderCaps()->infinitySupport() &&  // The hw tessellation shaders use infinity.
         !pipeline.usesVaryingCoords() &&  // Our tessellation back door doesn't handle varyings.
         numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
         shader = GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color,
                                                                           PatchType::kWedges);
     } else {
-        shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(arena, viewMatrix, color,
+        shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena,
+                                                                         viewMatrix, color,
                                                                          PatchType::kWedges);
     }
     return arena->make([=](void* objStart) {
@@ -254,17 +269,21 @@
     SkASSERT(!breadcrumbTriangleList);
     SkASSERT(fVertexChunkArray.empty());
 
+    const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
+
     // Over-allocate enough wedges for 1 in 4 to chop.
     int maxWedges = GrPathTessellator::MaxSegmentsInPath(path);
     int wedgeAllocCount = (maxWedges * 5 + 3) / 4;  // i.e., ceil(maxWedges * 5/4)
     if (!wedgeAllocCount) {
         return;
     }
-    GrVertexChunkBuilder chunker(target, &fVertexChunkArray, sizeof(SkPoint) * 5, wedgeAllocCount);
+    size_t patchStride = fShader->willUseTessellationShaders() ? fShader->vertexStride() * 5
+                                                               : fShader->instanceStride();
+    GrVertexChunkBuilder chunker(target, &fVertexChunkArray, patchStride, wedgeAllocCount);
 
     int maxSegments;
     if (fShader->willUseTessellationShaders()) {
-        maxSegments = target->caps().shaderCaps()->maxTessellationSegments();
+        maxSegments = shaderCaps.maxTessellationSegments();
     } else {
         maxSegments = GrPathTessellationShader::kMaxFixedCountSegments;
     }
@@ -283,25 +302,25 @@
                 case SkPathVerb::kClose:
                     break;  // Ignore. We can assume an implicit close at the end.
                 case SkPathVerb::kLine:
-                    wedgeWriter.writeFlatWedge(&chunker, pts[0], pts[1], midpoint);
+                    wedgeWriter.writeFlatWedge(shaderCaps, &chunker, pts[0], pts[1], midpoint);
                     lastPoint = pts[1];
                     break;
                 case SkPathVerb::kQuad:
-                    wedgeWriter.writeQuadraticWedge(&chunker, pts, midpoint);
+                    wedgeWriter.writeQuadraticWedge(shaderCaps, &chunker, pts, midpoint);
                     lastPoint = pts[2];
                     break;
                 case SkPathVerb::kConic:
-                    wedgeWriter.writeConicWedge(&chunker, pts, *w, midpoint);
+                    wedgeWriter.writeConicWedge(shaderCaps, &chunker, pts, *w, midpoint);
                     lastPoint = pts[2];
                     break;
                 case SkPathVerb::kCubic:
-                    wedgeWriter.writeCubicWedge(&chunker, pts, midpoint);
+                    wedgeWriter.writeCubicWedge(shaderCaps, &chunker, pts, midpoint);
                     lastPoint = pts[3];
                     break;
             }
         }
         if (lastPoint != startPoint) {
-            wedgeWriter.writeFlatWedge(&chunker, lastPoint, startPoint, midpoint);
+            wedgeWriter.writeFlatWedge(shaderCaps, &chunker, lastPoint, startPoint, midpoint);
         }
     }
 
diff --git a/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp b/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp
index 3c181a6..4e66de6 100644
--- a/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp
+++ b/src/gpu/tessellate/GrStrokeFixedCountTessellator.cpp
@@ -26,10 +26,12 @@
 public:
     using ShaderFlags = GrStrokeTessellator::ShaderFlags;
 
-    InstanceWriter(ShaderFlags shaderFlags, GrMeshDrawTarget* target, float matrixMaxScale,
-                   const SkRect& strokeCullBounds, const SkMatrix& viewMatrix,
-                   GrVertexChunkArray* patchChunks, size_t instanceStride, int minInstancesPerChunk)
-            : fShaderFlags(shaderFlags)
+    InstanceWriter(const GrShaderCaps* shaderCaps, ShaderFlags shaderFlags,
+                   GrMeshDrawTarget* target, float matrixMaxScale, const SkRect& strokeCullBounds,
+                   const SkMatrix& viewMatrix, GrVertexChunkArray* patchChunks,
+                   size_t instanceStride, int minInstancesPerChunk)
+            : fShaderCaps(shaderCaps)
+            , fShaderFlags(shaderFlags)
             , fCullTest(strokeCullBounds, viewMatrix)
             , fChunkBuilder(target, patchChunks, instanceStride, minInstancesPerChunk)
             , fParametricPrecision(GrStrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
@@ -60,7 +62,7 @@
     SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
         SkPoint cubic[] = {start, start, end, end};
         SkPoint endControlPoint = start;
-        this->writeStroke(cubic, endControlPoint);
+        this->writeStroke(cubic, endControlPoint, GrTessellationShader::kCubicCurveType);
     }
 
     SK_ALWAYS_INLINE void quadraticTo(const SkPoint p[3]) {
@@ -72,7 +74,7 @@
         SkPoint cubic[4];
         GrPathUtils::convertQuadToCubic(p, cubic);
         SkPoint endControlPoint = cubic[2];
-        this->writeStroke(cubic, endControlPoint);
+        this->writeStroke(cubic, endControlPoint, GrTessellationShader::kCubicCurveType);
         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
                                                fMaxParametricSegments_pow4);
     }
@@ -87,7 +89,7 @@
         SkPoint conic[4];
         GrTessellationShader::WriteConicPatch(p, w, conic);
         SkPoint endControlPoint = conic[1];
-        this->writeStroke(conic, endControlPoint);
+        this->writeStroke(conic, endControlPoint, GrTessellationShader::kConicCurveType);
         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
                                                fMaxParametricSegments_pow4);
     }
@@ -99,7 +101,7 @@
             return;
         }
         SkPoint endControlPoint = (p[3] != p[2]) ? p[2] : (p[2] != p[1]) ? p[1] : p[0];
-        this->writeStroke(p, endControlPoint);
+        this->writeStroke(p, endControlPoint, GrTessellationShader::kCubicCurveType);
         fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
                                                fMaxParametricSegments_pow4);
     }
@@ -118,6 +120,8 @@
             // The shader interprets an empty stroke + empty join as a special case that denotes a
             // circle, or 180-degree point stroke.
             writer.fill(location, 5);
+            writer.write(GrVertexWriter::If(!fShaderCaps->infinitySupport(),
+                                            GrTessellationShader::kCubicCurveType));
             this->writeDynamicAttribs(&writer);
         }
     }
@@ -127,7 +131,8 @@
             // We deferred the first stroke because we didn't know the previous control point to use
             // for its join. We write it out now.
             SkASSERT(fHasLastControlPoint);
-            this->writeStroke(fDeferredFirstStroke, SkPoint());
+            this->writeStroke(fDeferredFirstStroke, SkPoint(),
+                              fDeferredCurveTypeIfUnsupportedInfinity);
             fHasDeferredFirstStroke = false;
         }
         fHasLastControlPoint = false;
@@ -174,16 +179,20 @@
         }
     }
 
-    SK_ALWAYS_INLINE void writeStroke(const SkPoint p[4], SkPoint endControlPoint) {
+    SK_ALWAYS_INLINE void writeStroke(const SkPoint p[4], SkPoint endControlPoint,
+                                      float curveTypeIfUnsupportedInfinity) {
         if (!fHasLastControlPoint) {
             // We don't know the previous control point yet to use for the join. Defer writing out
             // this stroke until the end.
             memcpy(fDeferredFirstStroke, p, sizeof(fDeferredFirstStroke));
+            fDeferredCurveTypeIfUnsupportedInfinity = curveTypeIfUnsupportedInfinity;
             fHasDeferredFirstStroke = true;
             fHasLastControlPoint = true;
         } else if (GrVertexWriter writer = fChunkBuilder.appendVertex()) {
             writer.writeArray(p, 4);
             writer.write(fLastControlPoint);
+            writer.write(GrVertexWriter::If(!fShaderCaps->infinitySupport(),
+                                            curveTypeIfUnsupportedInfinity));
             this->writeDynamicAttribs(&writer);
         }
         fLastControlPoint = endControlPoint;
@@ -205,6 +214,7 @@
         fHasLastControlPoint = true;
     }
 
+    const GrShaderCaps* fShaderCaps;
     const ShaderFlags fShaderFlags;
     const GrCullTest fCullTest;
     GrVertexChunkBuilder fChunkBuilder;
@@ -213,6 +223,7 @@
 
     // We can't write out the first stroke until we know the previous control point for its join.
     SkPoint fDeferredFirstStroke[4];
+    float fDeferredCurveTypeIfUnsupportedInfinity;
     SkPoint fLastControlPoint;  // Used to configure the joins in the instance data.
     bool fHasDeferredFirstStroke = false;
     bool fHasLastControlPoint = false;
@@ -259,9 +270,9 @@
     int strokePreallocCount = totalCombinedVerbCnt * 2;
     int capPreallocCount = 8;
     int minInstancesPerChunk = strokePreallocCount + capPreallocCount;
-    InstanceWriter instanceWriter(fShader.flags(), target, fMatrixMinMaxScales[1],
-                                  fStrokeCullBounds, fShader.viewMatrix(), &fInstanceChunks,
-                                  fShader.instanceStride(), minInstancesPerChunk);
+    InstanceWriter instanceWriter(target->caps().shaderCaps(), fShader.flags(), target,
+                                  fMatrixMinMaxScales[1], fStrokeCullBounds, fShader.viewMatrix(),
+                                  &fInstanceChunks, fShader.instanceStride(), minInstancesPerChunk);
 
     if (!fShader.hasDynamicStroke()) {
         // Strokes are static. Calculate tolerances once.
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index df87b4c..d83670d 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -147,7 +147,8 @@
         0xffff>());
 
 bool can_use_hardware_tessellation(int numVerbs, const GrPipeline& pipeline, const GrCaps& caps) {
-    if (!caps.shaderCaps()->tessellationSupport()) {
+    if (!caps.shaderCaps()->tessellationSupport() ||
+        !caps.shaderCaps()->infinitySupport() /* The hw tessellation shaders use infinity. */) {
         return false;
     }
     if (pipeline.usesVaryingCoords()) {
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index b80071f..be3a73b 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -39,7 +39,6 @@
 bool GrTessellationPathRenderer::IsSupported(const GrCaps& caps) {
     return !caps.avoidStencilBuffers() &&
            caps.drawInstancedSupport() &&
-           caps.shaderCaps()->infinitySupport() &&
            !caps.disableTessellationPathRenderer();
 }
 
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader.h b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
index 844bd15..a2000b0 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader.h
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
@@ -50,7 +50,8 @@
     // smoothly, and emits empty triangles at any vertices whose sk_VertexIDs are higher than
     // necessary. It is the caller's responsibility to draw enough vertices per instance for the
     // most complex curve in the batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
-    static GrPathTessellationShader* MakeMiddleOutFixedCountShader(SkArenaAlloc*,
+    static GrPathTessellationShader* MakeMiddleOutFixedCountShader(const GrShaderCaps&,
+                                                                   SkArenaAlloc*,
                                                                    const SkMatrix& viewMatrix,
                                                                    const SkPMColor4f&, PatchType);
 
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
index 8e6c7c5..da8a82d 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
@@ -27,7 +27,8 @@
 // batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
 class MiddleOutShader : public GrPathTessellationShader {
 public:
-    MiddleOutShader(const SkMatrix& viewMatrix, const SkPMColor4f& color, PatchType patchType)
+    MiddleOutShader(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
+                    const SkPMColor4f& color, PatchType patchType)
             : GrPathTessellationShader(kTessellate_MiddleOutShader_ClassID,
                                        GrPrimitiveType::kTriangles, 0, viewMatrix, color)
             , fPatchType(patchType) {
@@ -36,6 +37,12 @@
         if (fPatchType == PatchType::kWedges) {
             fInstanceAttribs.emplace_back("fanPoint", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
         }
+        if (!shaderCaps.infinitySupport()) {
+            // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
+            // infinity can't detect this. On these platforms we also write out an extra float with
+            // each patch that explicitly tells the shader what type of curve it is.
+            fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
+        }
         this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
         SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
 
@@ -52,7 +59,7 @@
     GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
     const PatchType fPatchType;
 
-    constexpr static int kMaxInstanceAttribCount = 3;
+    constexpr static int kMaxInstanceAttribCount = 4;
     SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
 };
 
@@ -64,6 +71,18 @@
             v->defineConstant("MAX_FIXED_RESOLVE_LEVEL", (float)kMaxFixedCountResolveLevel);
             v->defineConstant("MAX_FIXED_SEGMENTS", (float)kMaxFixedCountSegments);
             v->insertFunction(GrWangsFormula::as_sksl().c_str());
+            if (shaderCaps.infinitySupport()) {
+                v->insertFunction(R"(
+                bool is_conic_curve() { return isinf(p23.w); }
+                bool is_triangular_conic_curve() { return isinf(p23.z); })");
+            } else {
+                v->insertFunction(SkStringPrintf(R"(
+                bool is_conic_curve() { return curveType != %g; })", kCubicCurveType).c_str());
+                v->insertFunction(SkStringPrintf(R"(
+                bool is_triangular_conic_curve() {
+                    return curveType == %g;
+                })", kTriangularConicCurveType).c_str());
+            }
             if (shaderCaps.bitManipulationSupport()) {
                 v->insertFunction(R"(
                 float ldexp_portable(float x, float p) {
@@ -87,8 +106,8 @@
                 } else)");  // Fall through to next if ().
             }
             v->codeAppend(R"(
-            if (isinf(p23.z)) {
-                // A conic with w=Inf is an exact triangle.
+            if (is_triangular_conic_curve()) {
+                // This patch is an exact triangle.
                 localcoord = (resolveLevel != 0)      ? p01.zw
                            : (idxInResolveLevel != 0) ? p23.xy
                                                       : p01.xy;
@@ -96,14 +115,14 @@
                 float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
                 float w = -1;  // w < 0 tells us to treat the instance as an integral cubic.
                 float maxResolveLevel;
-                if (isinf(p3.y)) {
-                    // The patch is a conic.
+                if (is_conic_curve()) {
+                    // Conics are 3 points, with the weight in p3.
                     w = p3.x;
                     maxResolveLevel = wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * p0,
                                                                           AFFINE_MATRIX * p1,
                                                                           AFFINE_MATRIX * p2, w);
                     p1 *= w;  // Unproject p1.
-                    p3 = p2;  // Duplicate the endpoint.
+                    p3 = p2;  // Duplicate the endpoint for shared code that also runs on cubics.
                 } else {
                     // The patch is an integral cubic.
                     maxResolveLevel = wangs_formula_cubic_log2(PRECISION, p0, p1, p2, p3,
@@ -154,9 +173,9 @@
 }  // namespace
 
 GrPathTessellationShader* GrPathTessellationShader::MakeMiddleOutFixedCountShader(
-        SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color,
-        PatchType patchType) {
-    return arena->make<MiddleOutShader>(viewMatrix, color, patchType);
+        const GrShaderCaps& shaderCaps, SkArenaAlloc* arena, const SkMatrix& viewMatrix,
+        const SkPMColor4f& color, PatchType patchType) {
+    return arena->make<MiddleOutShader>(shaderCaps, viewMatrix, color, patchType);
 }
 
 void GrPathTessellationShader::InitializeVertexBufferForMiddleOutCurves(GrVertexWriter vertexWriter,
diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp
index a524ce7..2e88d9f 100644
--- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp
+++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp
@@ -64,6 +64,12 @@
             // argsAttr contains the lastControlPoint for setting up the join.
             fAttribs.emplace_back("argsAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
         }
+        if (!shaderCaps.infinitySupport()) {
+            // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
+            // infinity can't detect this. On these platforms we write out an extra float with each
+            // patch that explicitly tells the shader what type of curve it is.
+            fAttribs.emplace_back("curveTypeAttr", kFloat_GrVertexAttribType, kFloat_GrSLType);
+        }
     }
     if (fShaderFlags & ShaderFlags::kDynamicStroke) {
         fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType,
diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h
index 3b5e24f..2da360e 100644
--- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h
+++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader.h
@@ -141,7 +141,7 @@
     const SkStrokeRec fStroke;
     const int8_t fMaxParametricSegments_log2;
 
-    constexpr static int kMaxAttribCount = 5;
+    constexpr static int kMaxAttribCount = 6;
     SkSTArray<kMaxAttribCount, Attribute> fAttribs;
 
     // This is a uniform value used when fMode is kFixedCount that tells the shader how many total
diff --git a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp
index a8c3e29..42081f0 100644
--- a/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp
+++ b/src/gpu/tessellate/shaders/GrStrokeTessellationShader_InstancedImpl.cpp
@@ -84,13 +84,22 @@
     args.fVertBuilder->codeAppendf("float2x2 AFFINE_MATRIX = float2x2(%s);\n", affineMatrixName);
     args.fVertBuilder->codeAppendf("float2 TRANSLATE = %s;\n", translateName);
 
+    if (args.fShaderCaps->infinitySupport()) {
+        args.fVertBuilder->insertFunction(R"(
+        bool is_conic_curve() { return isinf(pts23Attr.w); })");
+    } else {
+        args.fVertBuilder->insertFunction(SkStringPrintf(R"(
+        bool is_conic_curve() { return curveTypeAttr != %g; })", kCubicCurveType).c_str());
+    }
+
     // Tessellation code.
     args.fVertBuilder->codeAppend(R"(
     float2 p0=pts01Attr.xy, p1=pts01Attr.zw, p2=pts23Attr.xy, p3=pts23Attr.zw;
     float2 lastControlPoint = argsAttr.xy;
     float w = -1;  // w<0 means the curve is an integral cubic.
-    if (isinf(p3.y)) {
-        w = p3.x;  // The curve is actually a conic.
+    if (is_conic_curve()) {
+        // Conics are 3 points, with the weight in p3.
+        w = p3.x;
         p3 = p2;  // Setting p3 equal to p2 works for the remaining rotational logic.
     })");
     if (shader.stroke().isHairlineStyle()) {
diff --git a/src/gpu/tessellate/shaders/GrTessellationShader.h b/src/gpu/tessellate/shaders/GrTessellationShader.h
index b565dcd..ef02ea1 100644
--- a/src/gpu/tessellate/shaders/GrTessellationShader.h
+++ b/src/gpu/tessellate/shaders/GrTessellationShader.h
@@ -39,6 +39,13 @@
     const SkMatrix& viewMatrix() const { return fViewMatrix; }
     const SkPMColor4f& color() const { return fColor;}
 
+    // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support infinity can't
+    // detect this. On these platforms we also write out an extra float with each patch that
+    // explicitly tells the shader what type of curve it is.
+    constexpr static float kCubicCurveType = 0;
+    constexpr static float kConicCurveType = 1;
+    constexpr static float kTriangularConicCurveType = 2;  // Conic curve with w=Infinity.
+
     // 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, GrVertexWriter* writer) {
         // Write out the 3 conic points to patch[0..2], the weight to patch[3].x, and then set