Reland "ccpr: Implement stroking with fine triangle strips"

This is a reland of 2f2757fa6ba8134330e05694d08907f6e37abb41

Original change's description:
> ccpr: Implement stroking with fine triangle strips
>
> Implements strokes by linearizing the curve into fine triangle strips
> and interpolating a coverage ramp for edge AA. Each triangle in the
> strip emits either positive or negative coverage, depending on its
> winding direction. Joins and caps are drawn with the existing CCPR
> shaders for triangles and conics.
>
> Conic strokes and non-rigid-body transforms are not yet supported.
>
> Bug: skia:
> Change-Id: I45a819abd64e91c2b62e992587eb85c703e09e77
> Reviewed-on: https://skia-review.googlesource.com/148243
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Allan MacKinnon <allanmac@google.com>

TBR=robertphillips@google.com

Bug: skia:
Change-Id: I3f0065e80975ee8334300bc5e934231b66b49178
Reviewed-on: https://skia-review.googlesource.com/151188
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index aed0672..2ec8379 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -28,25 +28,82 @@
     return sk_64_mul(r.height(), r.width());
 }
 
-std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(GrContext* context,
-                                                       const SkIRect& clipIBounds,
-                                                       const SkMatrix& m,
-                                                       const GrShape& shape,
-                                                       const SkRect& devBounds,
-                                                       GrPaint&& paint) {
-    SkIRect shapeDevIBounds;
-    devBounds.roundOut(&shapeDevIBounds);  // GrCCPathParser might find slightly tighter bounds.
+std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
+        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
+        GrPaint&& paint) {
+    static constexpr int kPathCropThreshold = GrCoverageCountingPathRenderer::kPathCropThreshold;
+
+    SkRect conservativeDevBounds;
+    m.mapRect(&conservativeDevBounds, shape.bounds());
+
+    const SkStrokeRec& stroke = shape.style().strokeRec();
+    float strokeDevWidth = 0;
+    float conservativeInflationRadius = 0;
+    if (!stroke.isFillStyle()) {
+        if (stroke.isHairlineStyle()) {
+            strokeDevWidth = 1;
+        } else {
+            SkASSERT(m.isSimilarity());  // Otherwise matrixScaleFactor = m.getMaxScale().
+            float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY());
+            strokeDevWidth = stroke.getWidth() * matrixScaleFactor;
+        }
+        // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px
+        // wide, we may inflate it to 1px and instead reduce the opacity.
+        conservativeInflationRadius = SkStrokeRec::GetInflationRadius(
+                stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f));
+        conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
+    }
+
+    std::unique_ptr<GrCCDrawPathsOp> op;
+    float conservativeSize = SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width());
+    if (conservativeSize > kPathCropThreshold) {
+        // The path is too large. Crop it or analytic AA can run out of fp32 precision.
+        SkPath croppedDevPath;
+        shape.asPath(&croppedDevPath);
+        croppedDevPath.transform(m, &croppedDevPath);
+
+        SkIRect cropBox = clipIBounds;
+        GrShape croppedDevShape;
+        if (stroke.isFillStyle()) {
+            GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
+            croppedDevShape = GrShape(croppedDevPath);
+            conservativeDevBounds = croppedDevShape.bounds();
+        } else {
+            int r = SkScalarCeilToInt(conservativeInflationRadius);
+            cropBox.outset(r, r);
+            GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
+            SkStrokeRec devStroke = stroke;
+            devStroke.setStrokeStyle(strokeDevWidth);
+            croppedDevShape = GrShape(croppedDevPath, GrStyle(devStroke, nullptr));
+            conservativeDevBounds = croppedDevPath.getBounds();
+            conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
+        }
+
+        // FIXME: This breaks local coords: http://skbug.com/8003
+        return InternalMake(context, clipIBounds, SkMatrix::I(), croppedDevShape, strokeDevWidth,
+                            conservativeDevBounds, std::move(paint));
+    }
+
+    return InternalMake(context, clipIBounds, m, shape, strokeDevWidth, conservativeDevBounds,
+                        std::move(paint));
+}
+
+std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::InternalMake(
+        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
+        float strokeDevWidth, const SkRect& conservativeDevBounds, GrPaint&& paint) {
+    SkIRect shapeConservativeIBounds;
+    conservativeDevBounds.roundOut(&shapeConservativeIBounds);
 
     SkIRect maskDevIBounds;
     Visibility maskVisibility;
-    if (clipIBounds.contains(shapeDevIBounds)) {
-        maskDevIBounds = shapeDevIBounds;
+    if (clipIBounds.contains(shapeConservativeIBounds)) {
+        maskDevIBounds = shapeConservativeIBounds;
         maskVisibility = Visibility::kComplete;
     } else {
-        if (!maskDevIBounds.intersect(clipIBounds, shapeDevIBounds)) {
+        if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) {
             return nullptr;
         }
-        int64_t unclippedArea = area(shapeDevIBounds);
+        int64_t unclippedArea = area(shapeConservativeIBounds);
         int64_t clippedArea = area(maskDevIBounds);
         maskVisibility = (clippedArea >= unclippedArea/2 || unclippedArea < 100*100)
                 ? Visibility::kMostlyComplete  // i.e., visible enough to justify rendering the
@@ -56,22 +113,24 @@
 
     GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
-    return pool->allocate<GrCCDrawPathsOp>(m, shape, shapeDevIBounds, maskDevIBounds,
-                                           maskVisibility, devBounds, std::move(paint));
+    return pool->allocate<GrCCDrawPathsOp>(m, shape, strokeDevWidth, shapeConservativeIBounds,
+                                           maskDevIBounds, maskVisibility, conservativeDevBounds,
+                                           std::move(paint));
 }
 
-GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape,
-                                 const SkIRect& shapeDevIBounds, const SkIRect& maskDevIBounds,
-                                 Visibility maskVisibility, const SkRect& devBounds,
-                                 GrPaint&& paint)
+GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
+                                 const SkIRect& shapeConservativeIBounds,
+                                 const SkIRect& maskDevIBounds, Visibility maskVisibility,
+                                 const SkRect& conservativeDevBounds, GrPaint&& paint)
         : GrDrawOp(ClassID())
         , fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
-        , fDraws(m, shape, shapeDevIBounds, maskDevIBounds, maskVisibility, paint.getColor())
+        , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds, maskVisibility,
+                 paint.getColor())
         , fProcessors(std::move(paint)) {  // Paint must be moved after fetching its color above.
     SkDEBUGCODE(fBaseInstance = -1);
     // FIXME: intersect with clip bounds to (hopefully) improve batching.
     // (This is nontrivial due to assumptions in generating the octagon cover geometry.)
-    this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+    this->setBounds(conservativeDevBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
 }
 
 GrCCDrawPathsOp::~GrCCDrawPathsOp() {
@@ -82,12 +141,14 @@
 }
 
 GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape,
-                                        const SkIRect& shapeDevIBounds,
+                                        float strokeDevWidth,
+                                        const SkIRect& shapeConservativeIBounds,
                                         const SkIRect& maskDevIBounds, Visibility maskVisibility,
                                         GrColor color)
         : fMatrix(m)
         , fShape(shape)
-        , fShapeDevIBounds(shapeDevIBounds)
+        , fStrokeDevWidth(strokeDevWidth)
+        , fShapeConservativeIBounds(shapeConservativeIBounds)
         , fMaskDevIBounds(maskDevIBounds)
         , fMaskVisibility(maskVisibility)
         , fColor(color) {
@@ -111,9 +172,39 @@
 GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::finalize(const GrCaps& caps,
                                                        const GrAppliedClip* clip) {
     SkASSERT(1 == fNumDraws);  // There should only be one single path draw in this Op right now.
-    GrProcessorSet::Analysis analysis =
-            fProcessors.finalize(fDraws.head().fColor, GrProcessorAnalysisCoverage::kSingleChannel,
-                                 clip, false, caps, &fDraws.head().fColor);
+    SingleDraw* draw = &fDraws.head();
+
+    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
+            draw->fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
+            &draw->fColor);
+
+    // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better
+    // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead
+    // reduce the opacity to create the illusion of thin-ness. This strategy also helps reduce
+    // artifacts from coverage dilation when there are self intersections.
+    if (analysis.isCompatibleWithCoverageAsAlpha() &&
+            !draw->fShape.style().strokeRec().isFillStyle() && draw->fStrokeDevWidth < 1) {
+        // Modifying the shape affects its cache key. The draw can't have a cache entry yet or else
+        // our next step would invalidate it.
+        SkASSERT(!draw->fCacheEntry);
+        SkASSERT(SkStrokeRec::kStroke_Style == draw->fShape.style().strokeRec().getStyle());
+
+        SkPath path;
+        draw->fShape.asPath(&path);
+
+        // Create a hairline version of our stroke.
+        SkStrokeRec hairlineStroke = draw->fShape.style().strokeRec();
+        hairlineStroke.setStrokeStyle(0);
+
+        // How transparent does a 1px stroke have to be in order to appear as thin as the real one?
+        GrColor coverageAsAlpha = GrColorPackA4(SkScalarFloorToInt(draw->fStrokeDevWidth * 255));
+
+        draw->fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
+        draw->fStrokeDevWidth = 1;
+        // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke.
+        draw->fColor = GrColorMul(draw->fColor, coverageAsAlpha);
+    }
+
     return RequiresDstTexture(analysis.requiresDstTexture());
 }
 
@@ -180,8 +271,11 @@
                 // can copy it into a new 8-bit atlas and keep it in the resource cache.
                 if (stashedAtlasKey.isValid() && stashedAtlasKey == cacheEntry->atlasKey()) {
                     SkASSERT(!cacheEntry->hasCachedAtlas());
-                    ++specs->fNumCopiedPaths;
-                    specs->fCopyPathStats.statPath(path);
+                    int idx = (draw.fShape.style().strokeRec().isFillStyle())
+                            ? GrCCPerFlushResourceSpecs::kFillIdx
+                            : GrCCPerFlushResourceSpecs::kStrokeIdx;
+                    ++specs->fNumCopiedPaths[idx];
+                    specs->fCopyPathStats[idx].statPath(path);
                     specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
                                                            cacheEntry->height());
                     continue;
@@ -191,18 +285,23 @@
                 cacheEntry->resetAtlasKeyAndInfo();
             }
 
-            if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1 &&
-                SkTMax(draw.fShapeDevIBounds.height(),
-                       draw.fShapeDevIBounds.width()) <= onFlushRP->caps()->maxRenderTargetSize()) {
-                // We've seen this path before with a compatible matrix, and it's mostly visible.
-                // Just render the whole mask so we can try to cache it.
-                draw.fMaskDevIBounds = draw.fShapeDevIBounds;
-                draw.fMaskVisibility = Visibility::kComplete;
+            if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1) {
+                int shapeSize = SkTMax(draw.fShapeConservativeIBounds.height(),
+                                       draw.fShapeConservativeIBounds.width());
+                if (shapeSize <= onFlushRP->caps()->maxRenderTargetSize()) {
+                    // We've seen this path before with a compatible matrix, and it's mostly
+                    // visible. Just render the whole mask so we can try to cache it.
+                    draw.fMaskDevIBounds = draw.fShapeConservativeIBounds;
+                    draw.fMaskVisibility = Visibility::kComplete;
+                }
             }
         }
 
-        ++specs->fNumRenderedPaths;
-        specs->fRenderedPathStats.statPath(path);
+        int idx = (draw.fShape.style().strokeRec().isFillStyle())
+                ? GrCCPerFlushResourceSpecs::kFillIdx
+                : GrCCPerFlushResourceSpecs::kStrokeIdx;
+        ++specs->fNumRenderedPaths[idx];
+        specs->fRenderedPathStats[idx].statPath(path);
         specs->fRenderedAtlasSpecs.accountForSpace(draw.fMaskDevIBounds.width(),
                                                    draw.fMaskDevIBounds.height());
     }
@@ -219,7 +318,8 @@
         SkPath path;
         draw.fShape.asPath(&path);
 
-        auto doEvenOddFill = DoEvenOddFill(SkPath::kEvenOdd_FillType == path.getFillType());
+        auto doEvenOddFill = DoEvenOddFill(draw.fShape.style().strokeRec().isFillStyle() &&
+                                           SkPath::kEvenOdd_FillType == path.getFillType());
         SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
                  SkPath::kWinding_FillType == path.getFillType());
 
@@ -270,9 +370,9 @@
         SkRect devBounds, devBounds45;
         SkIRect devIBounds;
         SkIVector devToAtlasOffset;
-        if (auto atlas = resources->renderPathInAtlas(draw.fMaskDevIBounds, draw.fMatrix, path,
-                                                      &devBounds, &devBounds45, &devIBounds,
-                                                      &devToAtlasOffset)) {
+        if (auto atlas = resources->renderShapeInAtlas(
+                    draw.fMaskDevIBounds, draw.fMatrix, draw.fShape, draw.fStrokeDevWidth,
+                    &devBounds, &devBounds45, &devIBounds, &devToAtlasOffset)) {
             this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
             resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
                                                     draw.fColor, doEvenOddFill);