Add a stencil test for transparent tessellated strokes

Initially we restricted tessellated stroking to opaque solid colors.
This CL adds support for transparency by enabling a stencil test. The
stencil test also allows us to use mixed samples.

Bug: skia:10419
Change-Id: Ie40f3099d2b009f92ed49f7f43e5f269b1a479af
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/340798
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index e6f599e..a0972d7 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -61,10 +61,6 @@
     return GrPrimitiveType::kLines == type || GrPrimitiveType::kLineStrip == type;
 }
 
-static constexpr bool GrIsPrimTypeTris(GrPrimitiveType type) {
-    return GrPrimitiveType::kTriangles == type || GrPrimitiveType::kTriangleStrip == type;
-}
-
 enum class GrPrimitiveRestart : bool {
     kNo = false,
     kYes = true
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index c22ae3f..e48c14c 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -68,10 +68,6 @@
     }
     if (programInfo.pipeline().usesConservativeRaster()) {
         SkASSERT(this->gpu()->caps()->conservativeRasterSupport());
-        // Conservative raster, by default, only supports triangles. Implementations can
-        // optionally indicate that they also support points and lines, but we don't currently
-        // query or track that info.
-        SkASSERT(GrIsPrimTypeTris(programInfo.primitiveType()));
     }
     if (programInfo.pipeline().isWireframe()) {
          SkASSERT(this->gpu()->caps()->wireframeSupport());
diff --git a/src/gpu/GrPaint.h b/src/gpu/GrPaint.h
index 1ac34fb..15788c3 100644
--- a/src/gpu/GrPaint.h
+++ b/src/gpu/GrPaint.h
@@ -94,6 +94,10 @@
     GrFragmentProcessor* getCoverageFragmentProcessor() const {
         return fCoverageFragmentProcessor.get();
     }
+    bool usesVaryingCoords() const {
+        return (fColorFragmentProcessor && fColorFragmentProcessor->usesVaryingCoords()) ||
+               (fCoverageFragmentProcessor && fCoverageFragmentProcessor->usesVaryingCoords());
+    }
 
     /**
      * Returns true if the paint's output color will be constant after blending. If the result is
diff --git a/src/gpu/tessellate/GrStrokeIndirectOp.cpp b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
index 67b7b5d..a33c15e 100644
--- a/src/gpu/tessellate/GrStrokeIndirectOp.cpp
+++ b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
@@ -37,10 +37,15 @@
     auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
             GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
             fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
-    this->prePrepareColorProgram(context->priv().recordTimeAllocator(), strokeTessellateShader,
-                                 writeView, std::move(*clip), dstProxyView, renderPassXferBarriers,
-                                 colorLoadOp, *context->priv().caps());
-    context->priv().recordProgramInfo(fColorProgram);
+    this->prePreparePrograms(context->priv().recordTimeAllocator(), strokeTessellateShader,
+                             writeView, std::move(*clip), dstProxyView, renderPassXferBarriers,
+                             colorLoadOp, *context->priv().caps());
+    if (fFillProgram) {
+        context->priv().recordProgramInfo(fFillProgram);
+    }
+    if (fStencilProgram) {
+        context->priv().recordProgramInfo(fStencilProgram);
+    }
 }
 
 // Helpers for GrStrokeIndirectOp::prePrepareResolveLevels.
@@ -580,10 +585,10 @@
         auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
                 GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
                 fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
-        this->prePrepareColorProgram(arena, strokeTessellateShader, flushState->writeView(),
-                                     flushState->detachAppliedClip(), flushState->dstProxyView(),
-                                     flushState->renderPassBarriers(), flushState->colorLoadOp(),
-                                     flushState->caps());
+        this->prePreparePrograms(arena, strokeTessellateShader, flushState->writeView(),
+                                 flushState->detachAppliedClip(), flushState->dstProxyView(),
+                                 flushState->renderPassBarriers(), flushState->colorLoadOp(),
+                                 flushState->caps());
     }
     SkASSERT(fResolveLevels);
 
@@ -809,11 +814,20 @@
 
     SkASSERT(fDrawIndirectCount);
     SkASSERT(fTotalInstanceCount > 0);
-    SkASSERT(fColorProgram);
     SkASSERT(chainBounds == this->bounds());
 
-    flushState->bindPipelineAndScissorClip(*fColorProgram, this->bounds());
-    flushState->bindTextures(fColorProgram->primProc(), nullptr, fColorProgram->pipeline());
-    flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
-    flushState->drawIndirect(fDrawIndirectBuffer.get(), fDrawIndirectOffset, fDrawIndirectCount);
+    if (fStencilProgram) {
+        flushState->bindPipelineAndScissorClip(*fStencilProgram, this->bounds());
+        flushState->bindTextures(fStencilProgram->primProc(), nullptr, fStencilProgram->pipeline());
+        flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
+        flushState->drawIndirect(fDrawIndirectBuffer.get(), fDrawIndirectOffset,
+                                 fDrawIndirectCount);
+    }
+    if (fFillProgram) {
+        flushState->bindPipelineAndScissorClip(*fFillProgram, this->bounds());
+        flushState->bindTextures(fFillProgram->primProc(), nullptr, fFillProgram->pipeline());
+        flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
+        flushState->drawIndirect(fDrawIndirectBuffer.get(), fDrawIndirectOffset,
+                                 fDrawIndirectCount);
+    }
 }
diff --git a/src/gpu/tessellate/GrStrokeOp.cpp b/src/gpu/tessellate/GrStrokeOp.cpp
index 217338d..4daf21a 100644
--- a/src/gpu/tessellate/GrStrokeOp.cpp
+++ b/src/gpu/tessellate/GrStrokeOp.cpp
@@ -12,14 +12,6 @@
 #include "src/gpu/tessellate/GrStrokeTessellateOp.h"
 #include "src/gpu/tessellate/GrStrokeTessellateShader.h"
 
-static SkPMColor4f get_paint_constant_blended_color(const GrPaint& paint) {
-    SkPMColor4f constantColor;
-    // Patches can overlap, so until a stencil technique is implemented, the provided paints must be
-    // constant blended colors.
-    SkAssertResult(paint.isConstantBlendedColor(&constantColor));
-    return constantColor;
-}
-
 GrStrokeOp::GrStrokeOp(uint32_t classID, GrAAType aaType, const SkMatrix& viewMatrix,
                        const SkStrokeRec& stroke, const SkPath& path, GrPaint&& paint)
         : GrDrawOp(classID)
@@ -30,14 +22,13 @@
                 fViewMatrix.getMaxScale() * GrTessellationPathRenderer::kLinearizationIntolerance)
         , fNumRadialSegmentsPerRadian(
                 .5f / acosf(std::max(1 - 2/(fParametricIntolerance * fStroke.getWidth()), -1.f)))
-        , fColor(get_paint_constant_blended_color(paint))
+        , fColor(paint.getColor4f())
         , fProcessors(std::move(paint))
         , fPathList(path)
         , fTotalCombinedVerbCnt(path.countVerbs()) {
     // 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);
-    SkASSERT(fAAType != GrAAType::kCoverage);  // No mixed samples support yet.
     SkASSERT(fParametricIntolerance >= 0);
     SkRect devBounds = path.getBounds();
     float inflationRadius = fStroke.getInflationRadius();
@@ -47,7 +38,9 @@
 }
 
 GrDrawOp::FixedFunctionFlags GrStrokeOp::fixedFunctionFlags() const {
-    auto flags = FixedFunctionFlags::kNone;
+    // We might not actually end up needing stencil, but won't know for sure until finalize().
+    // Request it just in case we do end up needing it.
+    auto flags = FixedFunctionFlags::kUsesStencil;
     if (GrAAType::kNone != fAAType) {
         flags |= FixedFunctionFlags::kUsesHWAA;
     }
@@ -56,16 +49,22 @@
 
 GrProcessorSet::Analysis GrStrokeOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                               bool hasMixedSampledCoverage, GrClampType clampType) {
-    return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip,
-                                &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps,
-                                clampType, &fColor);
+    // Make sure the finalize happens before combining. We might change fNeedsStencil here.
+    SkASSERT(fPathList.begin().fCurr->fNext == nullptr);
+    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
+            fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
+            hasMixedSampledCoverage, caps, clampType, &fColor);
+    fNeedsStencil = !analysis.unaffectedByDstValue();
+    return analysis;
 }
 
 GrOp::CombineResult GrStrokeOp::onCombineIfPossible(GrOp* grOp, SkArenaAlloc* alloc,
                                                     const GrCaps&) {
     SkASSERT(grOp->classID() == this->classID());
     auto* op = static_cast<GrStrokeOp*>(grOp);
-    if (fColor != op->fColor ||
+    if (fNeedsStencil ||
+        op->fNeedsStencil ||
+        fColor != op->fColor ||
         fViewMatrix != op->fViewMatrix ||
         fAAType != op->fAAType ||
         !fStroke.hasEqualEffect(op->fStroke) ||
@@ -79,23 +78,84 @@
     return CombineResult::kMerged;
 }
 
-void GrStrokeOp::prePrepareColorProgram(SkArenaAlloc* arena,
-                                        GrStrokeTessellateShader* strokeTessellateShader,
-                                        const GrSurfaceProxyView& writeView, GrAppliedClip&& clip,
-                                        const GrXferProcessor::DstProxyView& dstProxyView,
-                                        GrXferBarrierFlags renderPassXferBarriers,
-                                        GrLoadOp colorLoadOp,
-                                        const GrCaps& caps) {
-    SkASSERT(!fColorProgram);
-    auto pipelineFlags = GrPipeline::InputFlags::kNone;
-    if (GrAAType::kNone != fAAType) {
-        pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
-        SkASSERT(writeView.asRenderTargetProxy()->numSamples() > 1);  // No mixed samples yet.
-        SkASSERT(fAAType != GrAAType::kCoverage);  // No mixed samples yet.
+// Marks every stencil value as "1".
+constexpr static GrUserStencilSettings kMarkStencil(
+    GrUserStencilSettings::StaticInit<
+        0x0001,
+        GrUserStencilTest::kLessIfInClip,  // Match kTestAndResetStencil.
+        0x0000,  // Always fail.
+        GrUserStencilOp::kZero,
+        GrUserStencilOp::kReplace,
+        0xffff>());
+
+// Passes if the stencil value is nonzero. Also resets the stencil value to zero on pass. This is
+// formulated to match kMarkStencil everywhere except the ref and compare mask. This will allow us
+// to use the same pipeline for both stencil and fill if dynamic stencil state is supported.
+constexpr static GrUserStencilSettings kTestAndResetStencil(
+    GrUserStencilSettings::StaticInit<
+        0x0000,
+        GrUserStencilTest::kLessIfInClip,  // i.e., "not equal to zero, if in clip".
+        0x0001,
+        GrUserStencilOp::kZero,
+        GrUserStencilOp::kReplace,
+        0xffff>());
+
+void GrStrokeOp::prePreparePrograms(SkArenaAlloc* arena,
+                                    GrStrokeTessellateShader* strokeTessellateShader,
+                                    const GrSurfaceProxyView& writeView, GrAppliedClip&& clip,
+                                    const GrXferProcessor::DstProxyView& dstProxyView,
+                                    GrXferBarrierFlags renderPassXferBarriers,
+                                    GrLoadOp colorLoadOp, const GrCaps& caps) {
+    using InputFlags = GrPipeline::InputFlags;
+    SkASSERT(!fFillProgram);
+    SkASSERT(!fStencilProgram);
+
+    // This will be created iff the stencil pass can't share a pipeline with the fill pass.
+    GrPipeline* standaloneStencilPipeline = nullptr;
+
+    GrPipeline::InitArgs fillArgs;
+    fillArgs.fCaps = &caps;
+    fillArgs.fDstProxyView = dstProxyView;
+    fillArgs.fWriteSwizzle = writeView.swizzle();
+    if (fAAType != GrAAType::kNone) {
+        if (writeView.asRenderTargetProxy()->numSamples() == 1) {
+            // We are mixed sampled. We need to either enable conservative raster (preferred) or
+            // disable MSAA in order to avoid double blend artifacts. (Even if we disable MSAA for
+            // the cover geometry, the stencil test is still multisampled and will still produce
+            // smooth results.)
+            SkASSERT(GrAAType::kCoverage == fAAType);
+            if (caps.conservativeRasterSupport()) {
+                fillArgs.fInputFlags |= InputFlags::kHWAntialias | InputFlags::kConservativeRaster;
+            }
+            // Since we either need conservative raster enabled or MSAA disabled during fill, we
+            // need a separate pipeline for the stencil pass.
+            SkASSERT(fNeedsStencil);  // Mixed samples always needs stencil.
+            GrPipeline::InitArgs stencilArgs;
+            stencilArgs.fCaps = &caps;
+            stencilArgs.fInputFlags = InputFlags::kHWAntialias;
+            stencilArgs.fWriteSwizzle = writeView.swizzle();
+            standaloneStencilPipeline = arena->make<GrPipeline>(
+                    stencilArgs, GrDisableColorXPFactory::MakeXferProcessor(), clip.hardClip());
+        } else {
+            // We are standard MSAA. Leave MSAA enabled for both the fill and stencil passes.
+            fillArgs.fInputFlags |= InputFlags::kHWAntialias;
+        }
     }
-    fColorProgram = GrPathShader::MakeProgramInfo(strokeTessellateShader, arena, writeView,
-                                                  pipelineFlags, std::move(fProcessors),
-                                                  std::move(clip), dstProxyView,
-                                                  renderPassXferBarriers, colorLoadOp,
-                                                  &GrUserStencilSettings::kUnused, caps);
+
+    auto fillPipeline = arena->make<GrPipeline>(fillArgs, std::move(fProcessors), std::move(clip));
+    auto fillStencil = &GrUserStencilSettings::kUnused;
+    auto fillXferFlags = renderPassXferBarriers;
+    if (fNeedsStencil) {
+        auto* stencilPipeline = (standaloneStencilPipeline) ? standaloneStencilPipeline
+                                                            : fillPipeline;
+        fStencilProgram = GrPathShader::MakeProgramInfo(strokeTessellateShader, arena, writeView,
+                                                        stencilPipeline, dstProxyView,
+                                                        renderPassXferBarriers, colorLoadOp,
+                                                        &kMarkStencil, caps);
+        fillStencil = &kTestAndResetStencil;
+        fillXferFlags = GrXferBarrierFlags::kNone;
+    }
+    fFillProgram = GrPathShader::MakeProgramInfo(strokeTessellateShader, arena, writeView,
+                                                 fillPipeline, dstProxyView, fillXferFlags,
+                                                 colorLoadOp, fillStencil, caps);
 }
diff --git a/src/gpu/tessellate/GrStrokeOp.h b/src/gpu/tessellate/GrStrokeOp.h
index ce5d855..7cd4c82 100644
--- a/src/gpu/tessellate/GrStrokeOp.h
+++ b/src/gpu/tessellate/GrStrokeOp.h
@@ -34,10 +34,10 @@
                                       bool hasMixedSampledCoverage, GrClampType) override;
     CombineResult onCombineIfPossible(GrOp*, SkArenaAlloc*, const GrCaps&) override;
 
-    void prePrepareColorProgram(SkArenaAlloc* arena, GrStrokeTessellateShader*,
-                                const GrSurfaceProxyView&, GrAppliedClip&&,
-                                const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
-                                GrLoadOp colorLoadOp, const GrCaps&);
+    void prePreparePrograms(SkArenaAlloc* arena, GrStrokeTessellateShader*,
+                            const GrSurfaceProxyView&, GrAppliedClip&&,
+                            const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
+                            GrLoadOp colorLoadOp, const GrCaps&);
 
     static float NumCombinedSegments(float numParametricSegments, float numRadialSegments) {
         // The first and last edges are shared by both the parametric and radial sets of edges, so
@@ -76,12 +76,14 @@
     // smoothness.
     const float fNumRadialSegmentsPerRadian;
     SkPMColor4f fColor;
+    bool fNeedsStencil = false;
     GrProcessorSet fProcessors;
 
     GrSTArenaList<SkPath> fPathList;
     int fTotalCombinedVerbCnt;
 
-    const GrProgramInfo* fColorProgram = nullptr;
+    const GrProgramInfo* fStencilProgram = nullptr;
+    const GrProgramInfo* fFillProgram = nullptr;
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index 72c3f5f..b1cd656 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -23,23 +23,29 @@
     auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
                 GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
                 fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
-    this->prePrepareColorProgram(arena, strokeTessellateShader, writeView, std::move(*clip),
-                                 dstProxyView, renderPassXferBarriers, colorLoadOp,
-                                 *context->priv().caps());
-    context->priv().recordProgramInfo(fColorProgram);
+    this->prePreparePrograms(arena, strokeTessellateShader, writeView, std::move(*clip),
+                             dstProxyView, renderPassXferBarriers, colorLoadOp,
+                             *context->priv().caps());
+    if (fStencilProgram) {
+        context->priv().recordProgramInfo(fStencilProgram);
+    }
+    if (fFillProgram) {
+        context->priv().recordProgramInfo(fFillProgram);
+    }
 }
 
 void GrStrokeTessellateOp::onPrepare(GrOpFlushState* flushState) {
-    if (!fColorProgram) {
+    if (!fFillProgram && !fStencilProgram) {
         SkArenaAlloc* arena = flushState->allocator();
         auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
                 GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
                 fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
-        this->prePrepareColorProgram(flushState->allocator(), strokeTessellateShader,
-                                     flushState->writeView(), flushState->detachAppliedClip(),
-                                     flushState->dstProxyView(), flushState->renderPassBarriers(),
-                                     flushState->colorLoadOp(), flushState->caps());
+        this->prePreparePrograms(flushState->allocator(), strokeTessellateShader,
+                                 flushState->writeView(), flushState->detachAppliedClip(),
+                                 flushState->dstProxyView(), flushState->renderPassBarriers(),
+                                 flushState->colorLoadOp(), flushState->caps());
     }
+    SkASSERT(fFillProgram || fStencilProgram);
 
     fTarget = flushState;
     this->prepareBuffers();
@@ -585,15 +591,25 @@
 }
 
 void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
-    SkASSERT(fColorProgram);
     SkASSERT(chainBounds == this->bounds());
-
-    flushState->bindPipelineAndScissorClip(*fColorProgram, this->bounds());
-    flushState->bindTextures(fColorProgram->primProc(), nullptr, fColorProgram->pipeline());
-    for (const auto& chunk : fPatchChunks) {
-        if (chunk.fPatchBuffer) {
-            flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fPatchBuffer));
-            flushState->draw(chunk.fPatchCount, chunk.fBasePatch);
+    if (fStencilProgram) {
+        flushState->bindPipelineAndScissorClip(*fStencilProgram, this->bounds());
+        flushState->bindTextures(fStencilProgram->primProc(), nullptr, fStencilProgram->pipeline());
+        for (const auto& chunk : fPatchChunks) {
+            if (chunk.fPatchBuffer) {
+                flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fPatchBuffer));
+                flushState->draw(chunk.fPatchCount, chunk.fBasePatch);
+            }
+        }
+    }
+    if (fFillProgram) {
+        flushState->bindPipelineAndScissorClip(*fFillProgram, this->bounds());
+        flushState->bindTextures(fFillProgram->primProc(), nullptr, fFillProgram->pipeline());
+        for (const auto& chunk : fPatchChunks) {
+            if (chunk.fPatchBuffer) {
+                flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fPatchBuffer));
+                flushState->draw(chunk.fPatchCount, chunk.fBasePatch);
+            }
         }
     }
 }
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.h b/src/gpu/tessellate/GrStrokeTessellateOp.h
index 6610580..fd7c7b9 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.h
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.h
@@ -20,9 +20,6 @@
     DEFINE_OP_CLASS_ID
 
 private:
-    // The provided matrix must be a similarity matrix for the time being. This is so we can
-    // bootstrap this Op on top of GrStrokeGeometry with minimal modifications.
-    //
     // Patches can overlap, so until a stencil technique is implemented, the provided paint must be
     // a constant blended color.
     GrStrokeTessellateOp(GrAAType aaType, const SkMatrix& viewMatrix, const SkStrokeRec& stroke,
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.cpp b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
index 471c166..cfab725 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
@@ -930,26 +930,26 @@
         }
 
         args.fVertBuilder->codeAppend(R"(
-        float2 tangent, position;
+        float2 tangent, localCoord;
         eval_stroke_edge(P, numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
-                         tangent, position);
+                         tangent, localCoord);
 
         if (combinedEdgeID == 0) {
             // Edges at the beginning of their section use P[0] and tan0. This ensures crack-free
             // seaming between instances.
-            position = P[0];
+            localCoord = P[0];
             tangent = tan0;
         }
 
         if (combinedEdgeID == numCombinedSegments) {
             // Edges at the end of their section use P[1] and tan1. This ensures crack-free seaming
             // between instances.
-            position = P[3];
+            localCoord = P[3];
             tangent = tan1;
         }
 
         float2 ortho = normalize(float2(tangent.y, -tangent.x));
-        position += ortho * (uStrokeRadius * outset);)");
+        localCoord += ortho * (uStrokeRadius * outset);)");
 
         // Do the transform after tessellation. Stroke widths and normals are defined in
         // (pre-transform) local path space.
@@ -960,11 +960,13 @@
             fAffineMatrixUniform = args.fUniformHandler->addUniform(
                     nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "affineMatrix",
                     &affineMatrixName);
-            args.fVertBuilder->codeAppendf("position = float2x2(%s) * position + %s;",
+            args.fVertBuilder->codeAppendf("float2 devCoord = float2x2(%s) * localCoord + %s;",
                                            affineMatrixName, translateName);
+            gpArgs->fPositionVar.set(kFloat2_GrSLType, "devCoord");
+        } else {
+            gpArgs->fPositionVar.set(kFloat2_GrSLType, "localCoord");
         }
-        gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
-        gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "position");
+        gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localCoord");
 
         // The fragment shader just outputs a uniform color.
         const char* colorUniformName;
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index ba135ee..292b430 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -141,18 +141,19 @@
     shape.asPath(&path);
 
     if (!shape.style().isSimpleFill()) {
-        if (SkPathPriv::ConicWeightCnt(path)) {
-            return CanDrawPath::kNo;
-        }
-        SkPMColor4f constantColor;
         // These are only temporary restrictions while we bootstrap tessellated stroking. Every one
         // of them will eventually go away.
         if (shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
-            GrAAType::kCoverage == args.fAAType ||
-            !args.fPaint->isConstantBlendedColor(&constantColor) ||
-            args.fPaint->hasCoverageFragmentProcessor()) {
+            SkPathPriv::ConicWeightCnt(path)) {
             return CanDrawPath::kNo;
         }
+        if (shape.style().isSimpleHairline()) {
+            // For the time being we translate hairline paths to device space. We can't do this if
+            // it's possible the paint might use local coordinates.
+            if (args.fPaint->usesVaryingCoords()) {
+                return CanDrawPath::kNo;
+            }
+        }
     }
 
     return CanDrawPath::kYes;
@@ -163,8 +164,10 @@
                                   const SkPath& path, GrPaint&& paint,
                                   const GrShaderCaps& shaderCaps) {
     // Only use hardware tessellation if the path has a somewhat large number of verbs. Otherwise we
-    // seem to be better off using indirect draws.
-    if (shaderCaps.tessellationSupport() && path.countVerbs() > 50) {
+    // 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()) {
         return GrOp::Make<GrStrokeTessellateOp>(context, aaType, viewMatrix, stroke, path,
                                                 std::move(paint));
     } else {
@@ -261,13 +264,10 @@
     }
 
     if (args.fShape->style().isSimpleHairline()) {
-        // Pre-transform the path into device space and use a stroke width of 1.
-#ifdef SK_DEBUG
         // Since we will be transforming the path, just double check that we are still in a position
         // where the paint will not use local coordinates.
-        SkPMColor4f constantColor;
-        SkASSERT(args.fPaint.isConstantBlendedColor(&constantColor));
-#endif
+        SkASSERT(!args.fPaint.usesVaryingCoords());
+        // Pre-transform the path into device space and use a stroke width of 1.
         SkPath devPath;
         path.transform(*args.fViewMatrix, &devPath);
         SkStrokeRec devStroke = args.fShape->style().strokeRec();