Make lines a special case in GrShape
authorbsalomon <bsalomon@google.com>
Tue, 28 Jun 2016 18:56:42 +0000 (11:56 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 28 Jun 2016 18:56:42 +0000 (11:56 -0700)
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2108523002

Committed: https://skia.googlesource.com/skia/+/c62318c748a1907649bd75382c4f4fd10533f2b3
Review-Url: https://codereview.chromium.org/2108523002

src/gpu/GrDrawContext.cpp
src/gpu/GrShape.cpp
src/gpu/GrShape.h
src/gpu/GrSoftwarePathRenderer.cpp
src/gpu/batches/GrAADistanceFieldPathRenderer.cpp
src/gpu/batches/GrDashLinePathRenderer.cpp
tests/GrShapeTest.cpp

index 7ee4e620e58ce040d9cddd1b1f7d51a0cf779a06..eec1389a81f8d6626aa9cbea3059f148097ca626 100644 (file)
@@ -976,6 +976,9 @@ void GrDrawContext::internalDrawPath(const GrClip& clip,
                       : GrPathRendererChain::kColor_DrawType;
 
     GrShape shape(path, style);
+    if (shape.isEmpty()) {
+        return;
+    }
     GrPathRenderer::CanDrawPathArgs canDrawArgs;
     canDrawArgs.fShaderCaps = fDrawingManager->getContext()->caps()->shaderCaps();
     canDrawArgs.fViewMatrix = &viewMatrix;
index 45ddb777789214c95714a2e75cd7fc20d329b519..5ffd32d46de2f36b61f8093d614e8e408624f49d 100644 (file)
@@ -14,10 +14,10 @@ GrShape& GrShape::operator=(const GrShape& that) {
         case Type::kEmpty:
             break;
         case Type::kRRect:
-            fRRectData.fRRect = that.fRRectData.fRRect;
-            fRRectData.fDir = that.fRRectData.fDir;
-            fRRectData.fStart = that.fRRectData.fStart;
-            fRRectData.fInverted = that.fRRectData.fInverted;
+            fRRectData = that.fRRectData;
+            break;
+        case Type::kLine:
+            fLineData = that.fLineData;
             break;
         case Type::kPath:
             fPathData.fGenID = that.fPathData.fGenID;
@@ -29,11 +29,29 @@ GrShape& GrShape::operator=(const GrShape& that) {
     return *this;
 }
 
-const SkRect& GrShape::bounds() const {
+SkRect GrShape::bounds() const {
     static constexpr SkRect kEmpty = SkRect::MakeEmpty();
     switch (fType) {
         case Type::kEmpty:
             return kEmpty;
+        case Type::kLine: {
+            SkRect bounds;
+            if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) {
+                bounds.fLeft = fLineData.fPts[0].fX;
+                bounds.fRight = fLineData.fPts[1].fX;
+            } else {
+                bounds.fLeft = fLineData.fPts[1].fX;
+                bounds.fRight = fLineData.fPts[0].fX;
+            }
+            if (fLineData.fPts[0].fY < fLineData.fPts[1].fY) {
+                bounds.fTop = fLineData.fPts[0].fY;
+                bounds.fBottom = fLineData.fPts[1].fY;
+            } else {
+                bounds.fTop = fLineData.fPts[1].fY;
+                bounds.fBottom = fLineData.fPts[0].fY;
+            }
+            return bounds;
+        }
         case Type::kRRect:
             return fRRectData.fRRect.getBounds();
         case Type::kPath:
@@ -43,12 +61,13 @@ const SkRect& GrShape::bounds() const {
     return kEmpty;
 }
 
-void GrShape::styledBounds(SkRect* bounds) const {
+SkRect GrShape::styledBounds() const {
     if (Type::kEmpty == fType && !fStyle.hasNonDashPathEffect()) {
-        *bounds = SkRect::MakeEmpty();
-    } else {
-        fStyle.adjustBounds(bounds, this->bounds());
+        return SkRect::MakeEmpty();
     }
+    SkRect bounds;
+    fStyle.adjustBounds(&bounds, this->bounds());
+    return bounds;
 }
 
 int GrShape::unstyledKeySize() const {
@@ -63,6 +82,10 @@ int GrShape::unstyledKeySize() const {
             SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
             // + 1 for the direction, start index, and inverseness.
             return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
+        case Type::kLine:
+            GR_STATIC_ASSERT(2 * sizeof(uint32_t) == sizeof(SkPoint));
+            // 4 for the end points and 1 for the inverseness
+            return 5;
         case Type::kPath:
             if (0 == fPathData.fGenID) {
                 return -1;
@@ -94,6 +117,11 @@ void GrShape::writeUnstyledKey(uint32_t* key) const {
                 *key++ |= fRRectData.fStart;
                 SkASSERT(fRRectData.fStart < 8);
                 break;
+            case Type::kLine:
+                memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint));
+                key += 4;
+                *key++ = fLineData.fInverted ? 1 : 0;
+                break;
             case Type::kPath:
                 SkASSERT(fPathData.fGenID);
                 *key++ = fPathData.fGenID;
@@ -159,10 +187,10 @@ GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) {
         case Type::kEmpty:
             break;
         case Type::kRRect:
-            fRRectData.fRRect = that.fRRectData.fRRect;
-            fRRectData.fDir = that.fRRectData.fDir;
-            fRRectData.fStart = that.fRRectData.fStart;
-            fRRectData.fInverted = that.fRRectData.fInverted;
+            fRRectData = that.fRRectData;
+            break;
+        case Type::kLine:
+            fLineData = that.fLineData;
             break;
         case Type::kPath:
             fPathData.fGenID = that.fPathData.fGenID;
@@ -266,8 +294,14 @@ void GrShape::attemptToSimplifyPath() {
     SkPath::Direction rrectDir;
     unsigned rrectStart;
     bool inverted = this->path().isInverseFillType();
+    SkPoint pts[2];
     if (this->path().isEmpty()) {
         this->changeType(Type::kEmpty);
+    } else if (this->path().isLine(pts)) {
+        this->changeType(Type::kLine);
+        fLineData.fPts[0] = pts[0];
+        fLineData.fPts[1] = pts[1];
+        fLineData.fInverted = inverted;
     } else if (this->path().isRRect(&rrect, &rrectDir, &rrectStart)) {
         this->changeType(Type::kRRect);
         fRRectData.fRRect = rrect;
@@ -313,6 +347,8 @@ void GrShape::attemptToSimplifyPath() {
         fInheritedKey.reset(0);
         if (Type::kRRect == fType) {
             this->attemptToSimplifyRRect();
+        } else if (Type::kLine == fType) {
+            this->attemptToSimplifyLine();
         }
     } else {
         if (fInheritedKey.count() || this->path().isVolatile()) {
@@ -321,13 +357,8 @@ void GrShape::attemptToSimplifyPath() {
             fPathData.fGenID = this->path().getGenerationID();
         }
         if (this->style().isSimpleFill()) {
-            // Filled paths are treated as though all their contours were closed.
-            // Since SkPath doesn't track individual contours, this will only close the last. :(
-            // There is no point in closing lines, though, since they loose their line-ness.
-            if (!this->path().isLine(nullptr)) {
-                this->path().close();
-                this->path().setIsVolatile(true);
-            }
+            this->path().close();
+            this->path().setIsVolatile(true);
         }
         if (!this->style().hasNonDashPathEffect()) {
             if (this->style().strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
@@ -368,3 +399,21 @@ void GrShape::attemptToSimplifyRRect() {
         fRRectData.fInverted = false;
     }
 }
+
+void GrShape::attemptToSimplifyLine() {
+    if (fStyle.isSimpleFill() && !fLineData.fInverted) {
+        this->changeType(Type::kEmpty);
+    } else {
+        // Only path effects could care about the order of the points. Otherwise canonicalize
+        // the point order
+        if (!fStyle.hasPathEffect()) {
+            SkPoint* pts = fLineData.fPts;
+            if (pts[1].fY < pts[0].fY || (pts[1].fY == pts[0].fY && pts[1].fX < pts[0].fX)) {
+                SkTSwap(pts[0], pts[1]);
+            }
+        } else if (fStyle.isDashed()) {
+            // Dashing ignores inverseness.
+            fLineData.fInverted = false;
+        }
+    }
+}
index 87ee26e17333a50b7df08cb0bde4b19d51835ced..8f3be58702820eb0c4d7161b290a9477a5d640ac 100644 (file)
@@ -152,12 +152,19 @@ public:
      * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
      * An inverse filled line path is still considered a line.
      */
-     bool asLine(SkPoint pts[2]) const {
-         if (fType != Type::kPath) {
-             return false;
-         }
-         return this->path().isLine(pts);
-     }
+    bool asLine(SkPoint pts[2], bool* inverted) const {
+        if (fType != Type::kLine) {
+            return false;
+        }
+        if (pts) {
+            pts[0] = fLineData.fPts[0];
+            pts[1] = fLineData.fPts[1];
+        }
+        if (inverted) {
+            *inverted = fLineData.fInverted;
+        }
+        return true;
+    }
 
     /** Returns the unstyled geometry as a path. */
     void asPath(SkPath* out) const {
@@ -175,6 +182,16 @@ public:
                     out->setFillType(kDefaultPathFillType);
                 }
                 break;
+            case Type::kLine:
+                out->reset();
+                out->moveTo(fLineData.fPts[0]);
+                out->lineTo(fLineData.fPts[1]);
+                if (fLineData.fInverted) {
+                    out->setFillType(kDefaultPathInverseFillType);
+                } else {
+                    out->setFillType(kDefaultPathFillType);
+                }
+                break;
             case Type::kPath:
                 *out = this->path();
                 break;
@@ -191,13 +208,13 @@ public:
      * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
      * the inverse fill nature of the geometry.
      */
-    const SkRect& bounds() const;
+    SkRect bounds() const;
 
     /**
      * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
      * status).
      */
-    void styledBounds(SkRect* bounds) const;
+    SkRect styledBounds() const;
 
     /**
      * Is this shape known to be convex, before styling is applied. An unclosed but otherwise
@@ -210,6 +227,8 @@ public:
                 return true;
             case Type::kRRect:
                 return true;
+            case Type::kLine:
+                return true;
             case Type::kPath:
                 // SkPath.isConvex() really means "is this path convex were it to be closed" and
                 // thus doesn't give the correct answer for stroked paths, hence we also check
@@ -231,6 +250,9 @@ public:
             case Type::kRRect:
                 ret = fRRectData.fInverted;
                 break;
+            case Type::kLine:
+                ret = fLineData.fInverted;
+                break;
             case Type::kPath:
                 ret = this->path().isInverseFillType();
                 break;
@@ -264,6 +286,8 @@ public:
                 return true;
             case Type::kRRect:
                 return true;
+            case Type::kLine:
+                return false;
             case Type::kPath:
                 // SkPath doesn't keep track of the closed status of each contour.
                 return SkPathPriv::IsClosedSingleContour(this->path());
@@ -282,6 +306,8 @@ public:
                     return SkPath::kLine_SegmentMask;
                 }
                 return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
+            case Type::kLine:
+                return SkPath::kLine_SegmentMask;
             case Type::kPath:
                 return this->path().getSegmentMasks();
         }
@@ -307,6 +333,7 @@ private:
     enum class Type {
         kEmpty,
         kRRect,
+        kLine,
         kPath,
     };
 
@@ -356,6 +383,7 @@ private:
 
     void attemptToSimplifyPath();
     void attemptToSimplifyRRect();
+    void attemptToSimplifyLine();
 
     // Defaults to use when there is no distinction between even/odd and winding fills.
     static constexpr SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
@@ -420,6 +448,10 @@ private:
             // Gen ID of the original path (fPath may be modified)
             int32_t                     fGenID;
         } fPathData;
+        struct {
+            SkPoint                     fPts[2];
+            bool                        fInverted;
+        } fLineData;
     };
     GrStyle                     fStyle;
     SkAutoSTArray<8, uint32_t>  fInheritedKey;
index 983bd20cbe8a87743fcbc631cc5349b8b658fddf..45a4b78b18614dff6e2ebc6bde24a61c7ad4843c 100644 (file)
@@ -38,8 +38,7 @@ bool get_shape_and_clip_bounds(int width, int height,
         *devShapeBounds = SkIRect::MakeWH(width, height);
         return false;
     }
-    SkRect shapeBounds;
-    shape.styledBounds(&shapeBounds);
+    SkRect shapeBounds = shape.styledBounds();
     if (!shapeBounds.isEmpty()) {
         SkRect shapeSBounds;
         matrix.mapRect(&shapeSBounds, shapeBounds);
index b178a68107965ba1e0fbca8e5fd18d14cc33ea6b..eb6a67dc20c9f32cc73c1c98494854ef6a1b7223 100644 (file)
@@ -109,8 +109,7 @@ bool GrAADistanceFieldPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) c
     // scaled to have bounds within 2.0f*kLargeMIP by 2.0f*kLargeMIP
     // the goal is to accelerate rendering of lots of small paths that may be scaling
     SkScalar maxScale = args.fViewMatrix->getMaxScale();
-    SkRect bounds;
-    args.fShape->styledBounds(&bounds);
+    SkRect bounds = args.fShape->styledBounds();
     SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
 
     return maxDim <= kMediumMIP && maxDim * maxScale <= 2.0f*kLargeMIP;
index 8dba7b94da757b35df3981fcfa43645a6837a8ed..e7ef2402a9cc7f182d8e528022dacc8ea49290ac 100644 (file)
 
 bool GrDashLinePathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
     SkPoint pts[2];
-    if (args.fShape->style().isDashed() && args.fShape->asLine(pts)) {
+    bool inverted;
+    if (args.fShape->style().isDashed() && args.fShape->asLine(pts, &inverted)) {
+        // We should never have an inverse dashed case.
+        SkASSERT(!inverted);
         return GrDashingEffect::CanDrawDashLine(pts, args.fShape->style(), *args.fViewMatrix);
     }
     return false;
@@ -34,7 +37,7 @@ bool GrDashLinePathRenderer::onDrawPath(const DrawPathArgs& args) {
         aaMode = GrDashingEffect::AAMode::kNone;
     }
     SkPoint pts[2];
-    SkAssertResult(args.fShape->asLine(pts));
+    SkAssertResult(args.fShape->asLine(pts, nullptr));
     SkAutoTUnref<GrDrawBatch> batch(GrDashingEffect::CreateDashLineBatch(args.fColor,
                                                                          *args.fViewMatrix,
                                                                          pts,
index 1ee9bbc018270dbc9e7817d38d6ce79fc30f1d41..ca907a1f87b4289cc891480dd0ade81e4a939a22 100644 (file)
@@ -177,10 +177,9 @@ private:
         CheckBounds(r, fAppliedPE, fAppliedPE.bounds());
         CheckBounds(r, fAppliedPEThenStroke, fAppliedPEThenStroke.bounds());
         CheckBounds(r, fAppliedFull, fAppliedFull.bounds());
-        SkRect styledBounds;
-        fBase.styledBounds(&styledBounds);
+        SkRect styledBounds = fBase.styledBounds();
         CheckBounds(r, fAppliedFull, styledBounds);
-        fAppliedPE.styledBounds(&styledBounds);
+        styledBounds = fAppliedPE.styledBounds();
         CheckBounds(r, fAppliedFull, styledBounds);
 
         // Check that the same path is produced when style is applied by GrShape and GrStyle.
@@ -372,8 +371,10 @@ static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrS
     }
     REPORTER_ASSERT(r, a.bounds() == b.bounds());
     REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
-    SkPoint pts[4];
-    REPORTER_ASSERT(r, a.asLine(pts) == b.asLine(pts + 2));
+    // Init these to suppress warnings.
+    SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
+    bool invertedLine[2] {true, true};
+    REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
     // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
     // doesn't (since the PE can set any fill type on its output path).
     // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
@@ -383,8 +384,11 @@ static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrS
         REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
                            b.mayBeInverseFilledAfterStyling());
     }
-    if (a.asLine(pts)) {
+    if (a.asLine(nullptr, nullptr)) {
         REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
+        REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
+        REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
+        REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
     }
     REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
 }
@@ -1349,6 +1353,114 @@ void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) {
     }
 }
 
+void test_lines(skiatest::Reporter* r) {
+    static constexpr SkPoint kA { 1,  1};
+    static constexpr SkPoint kB { 5, -9};
+    static constexpr SkPoint kC {-3, 17};
+
+    SkPath lineAB;
+    lineAB.moveTo(kA);
+    lineAB.lineTo(kB);
+
+    SkPath lineBA;
+    lineBA.moveTo(kB);
+    lineBA.lineTo(kA);
+
+    SkPath lineAC;
+    lineAC.moveTo(kB);
+    lineAC.lineTo(kC);
+
+    SkPath invLineAB = lineAB;
+    invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
+
+    SkPaint fill;
+    SkPaint stroke;
+    stroke.setStyle(SkPaint::kStroke_Style);
+    stroke.setStrokeWidth(2.f);
+    SkPaint hairline;
+    hairline.setStyle(SkPaint::kStroke_Style);
+    hairline.setStrokeWidth(0.f);
+    SkPaint dash = stroke;
+    dash.setPathEffect(make_dash());
+
+    TestCase fillAB(lineAB, fill, r);
+    TestCase fillEmpty(SkPath(), fill, r);
+    fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
+    REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
+
+    TestCase strokeAB(lineAB, stroke, r);
+    TestCase strokeBA(lineBA, stroke, r);
+    TestCase strokeAC(lineAC, stroke, r);
+
+    TestCase hairlineAB(lineAB, hairline, r);
+    TestCase hairlineBA(lineBA, hairline, r);
+    TestCase hairlineAC(lineAC, hairline, r);
+
+    TestCase dashAB(lineAB, dash, r);
+    TestCase dashBA(lineBA, dash, r);
+    TestCase dashAC(lineAC, dash, r);
+
+    strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
+
+    strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
+    strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
+
+    hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
+    hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
+
+    dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
+    dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
+
+    strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
+
+    // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
+    // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
+    bool canonicalizeAsAB;
+    SkPoint canonicalPts[2] {kA, kB};
+    // Init these to suppress warnings.
+    bool inverted = true;
+    SkPoint pts[2] {{0, 0}, {0, 0}};
+    REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
+    if (pts[0] == kA && pts[1] == kB) {
+        canonicalizeAsAB = true;
+    } else if (pts[1] == kA && pts[0] == kB) {
+        canonicalizeAsAB = false;
+        SkTSwap(canonicalPts[0], canonicalPts[1]);
+    } else {
+        ERRORF(r, "Should return pts (a,b) or (b, a)");
+        return;
+    };
+
+    strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
+                     TestCase::kSameUpToPE_ComparisonExpecation);
+    REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
+                       pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
+    REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
+                       pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
+    REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
+                       pts[0] == kA && pts[1] == kB);
+    REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
+                       pts[0] == kB && pts[1] == kA);
+
+
+    TestCase strokeInvAB(invLineAB, stroke, r);
+    TestCase hairlineInvAB(invLineAB, hairline, r);
+    TestCase dashInvAB(invLineAB, dash, r);
+    strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation);
+    hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation);
+    // Dashing ignores inverse.
+    dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
+
+    REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
+                       pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
+    REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
+                       pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
+    // Dashing ignores inverse.
+    REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
+                       pts[0] == kA && pts[1] == kB);
+
+}
+
 DEF_TEST(GrShape, reporter) {
     for (auto r : { SkRect::MakeWH(10, 20),
                     SkRect::MakeWH(-10, -20),
@@ -1375,7 +1487,7 @@ DEF_TEST(GrShape, reporter) {
         test_path_effect_fails(reporter, r);
         test_make_hairline_path_effect(reporter, r, true);
         GrShape shape(r);
-        REPORTER_ASSERT(reporter, !shape.asLine(nullptr));
+        REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr));
     }
 
     for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
@@ -1403,7 +1515,7 @@ DEF_TEST(GrShape, reporter) {
         test_path_effect_fails(reporter, rr);
         test_make_hairline_path_effect(reporter, rr, true);
         GrShape shape(rr);
-        REPORTER_ASSERT(reporter, !shape.asLine(nullptr));
+        REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr));
     }
 
     struct TestPath {
@@ -1459,15 +1571,16 @@ DEF_TEST(GrShape, reporter) {
             // These tests all assume that the original GrShape for fill and stroke will be the
             // same.
             // However, that is not the case in special cases (e.g. an unclosed rect becomes a RRect
-            // GrShape with a fill style but becomes a Path GrShape when stroked).
-            if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) {
+            // GrShape with a fill style but becomes a Path GrShape when stroked). Similarly, a path
+            // that is a line becomes empty when filled but is special-cased as a line when stroked.
+            if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke && !testPath.fIsLine) {
                 test_basic(reporter, path);
                 test_null_dash(reporter, path);
                 test_path_effect_makes_rrect(reporter, path);
             }
             test_scale(reporter, path);
             // This test uses a stroking paint, hence use of fIsRRectForStroke
-            test_volatile_path(reporter, path, testPath.fIsRRectForStroke);
+            test_volatile_path(reporter, path, testPath.fIsRRectForStroke || testPath.fIsLine);
             test_dash_fill(reporter, path);
             // Test modifying various stroke params.
             test_stroke_param<SkPath, SkScalar>(
@@ -1483,7 +1596,8 @@ DEF_TEST(GrShape, reporter) {
             test_unknown_path_effect(reporter, path);
             test_path_effect_makes_empty_shape(reporter, path);
             test_path_effect_fails(reporter, path);
-            test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke);
+            test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke ||
+                                                           testPath.fIsLine);
         }
     }
 
@@ -1516,14 +1630,14 @@ DEF_TEST(GrShape, reporter) {
             strokePathCase.compare(reporter, strokeRRectCase,
                                    TestCase::kAllSame_ComparisonExpecation);
         }
-        REPORTER_ASSERT(reporter, testPath.fIsLine == fillPathCase.baseShape().asLine(nullptr));
-        REPORTER_ASSERT(reporter, testPath.fIsLine == strokePathCase.baseShape().asLine(nullptr));
     }
 
     // Test a volatile empty path.
     test_volatile_path(reporter, SkPath(), true);
 
     test_empty_shape(reporter);
+
+    test_lines(reporter);
 }
 
 #endif