Fix stroking of zero length paths with end caps on NVPR
authorkkinnunen <kkinnunen@nvidia.com>
Tue, 1 Dec 2015 12:35:37 +0000 (04:35 -0800)
committerCommit bot <commit-bot@chromium.org>
Tue, 1 Dec 2015 12:35:37 +0000 (04:35 -0800)
Fix stroking of zero length paths with end caps on NVPR.
In case of such paths, stroke them using Skia and just
fill the path with NVPR.

BUG=skia:4427

Review URL: https://codereview.chromium.org/1471763002

src/gpu/gl/GrGLPath.cpp
src/gpu/gl/GrGLPath.h
src/gpu/gl/GrGLPathRange.cpp

index 1dfeaee..d1fc39d 100644 (file)
@@ -86,128 +86,218 @@ inline void points_to_coords(const SkPoint points[], size_t first_point, size_t
         coords[i * 2 + 1] = SkScalarToFloat(points[first_point + i].fY);
     }
 }
+
+template<bool checkForDegenerates>
+inline bool init_path_object_for_general_path(GrGLGpu* gpu, GrGLuint pathID,
+                                              const SkPath& skPath) {
+    SkDEBUGCODE(int numCoords = 0);
+    int verbCnt = skPath.countVerbs();
+    int pointCnt = skPath.countPoints();
+    int minCoordCnt = pointCnt * 2;
+
+    SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt);
+    SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt);
+    bool lastVerbWasMove = true; // A path with just "close;" means "moveto(0,0); close;"
+    SkPoint points[4];
+    SkPath::RawIter iter(skPath);
+    SkPath::Verb verb;
+    while ((verb = iter.next(points)) != SkPath::kDone_Verb) {
+        pathCommands.push_back(verb_to_gl_path_cmd(verb));
+        GrGLfloat coords[6];
+        int coordsForVerb;
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                if (checkForDegenerates) {
+                    lastVerbWasMove = true;
+                }
+                points_to_coords(points, 0, 1, coords);
+                coordsForVerb = 2;
+                break;
+            case SkPath::kLine_Verb:
+                if (checkForDegenerates) {
+                    if (SkPath::IsLineDegenerate(points[0], points[1], true)) {
+                        return false;
+                    }
+                    lastVerbWasMove = false;
+                }
+
+                points_to_coords(points, 1, 1, coords);
+                coordsForVerb = 2;
+                break;
+            case SkPath::kConic_Verb:
+                if (checkForDegenerates) {
+                    if (SkPath::IsQuadDegenerate(points[0], points[1], points[2], true)) {
+                        return false;
+                    }
+                    lastVerbWasMove = false;
+                }
+                points_to_coords(points, 1, 2, coords);
+                coords[4] = SkScalarToFloat(iter.conicWeight());
+                coordsForVerb = 5;
+                break;
+            case SkPath::kQuad_Verb:
+                if (checkForDegenerates) {
+                    if (SkPath::IsQuadDegenerate(points[0], points[1], points[2], true)) {
+                        return false;
+                    }
+                    lastVerbWasMove = false;
+                }
+                points_to_coords(points, 1, 2, coords);
+                coordsForVerb = 4;
+                break;
+            case SkPath::kCubic_Verb:
+                if (checkForDegenerates) {
+                    if (SkPath::IsCubicDegenerate(points[0], points[1], points[2], points[3],
+                                                  true)) {
+                        return false;
+                    }
+                    lastVerbWasMove = false;
+                }
+                points_to_coords(points, 1, 3, coords);
+                coordsForVerb = 6;
+                break;
+            case SkPath::kClose_Verb:
+                if (checkForDegenerates) {
+                    if (lastVerbWasMove) {
+                        // Interpret "move(x,y);close;" as "move(x,y);lineto(x,y);close;".
+                        // which produces a degenerate segment.
+                        return false;
+                    }
+                }
+                continue;
+            default:
+                SkASSERT(false);  // Not reached.
+                continue;
+        }
+        SkDEBUGCODE(numCoords += num_coords(verb));
+        pathCoords.push_back_n(coordsForVerb, coords);
+    }
+    SkASSERT(verbCnt == pathCommands.count());
+    SkASSERT(numCoords == pathCoords.count());
+
+    GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0],
+                                                pathCoords.count(), GR_GL_FLOAT, &pathCoords[0]));
+    return true;
 }
+} // namespace
 
-void GrGLPath::InitPathObject(GrGLGpu* gpu,
-                              GrGLuint pathID,
-                              const SkPath& skPath,
-                              const GrStrokeInfo& stroke) {
-    SkASSERT(!stroke.isDashed());
-    if (!skPath.isEmpty()) {
+bool GrGLPath::InitPathObjectPathDataCheckingDegenerates(GrGLGpu* gpu, GrGLuint pathID,
+                                                         const SkPath& skPath) {
+    return init_path_object_for_general_path<true>(gpu, pathID, skPath);
+}
+
+void GrGLPath::InitPathObjectPathData(GrGLGpu* gpu,
+                                      GrGLuint pathID,
+                                      const SkPath& skPath) {
+    SkASSERT(!skPath.isEmpty());
+
+#ifdef SK_SCALAR_IS_FLOAT
+    // This branch does type punning, converting SkPoint* to GrGLfloat*.
+    if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) {
         int verbCnt = skPath.countVerbs();
         int pointCnt = skPath.countPoints();
-        int minCoordCnt = pointCnt * 2;
-
+        int coordCnt = pointCnt * 2;
         SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt);
-        SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt);
+        SkSTArray<16, GrGLfloat, true> pathCoords(coordCnt);
 
-        SkDEBUGCODE(int numCoords = 0);
+        static_assert(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, "sk_point_not_two_floats");
 
-        if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) {
-            // This branch does type punning, converting SkPoint* to GrGLfloat*.
-            static_assert(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, "sk_point_not_two_floats");
-            // This branch does not convert with SkScalarToFloat.
-#ifndef SK_SCALAR_IS_FLOAT
-#error Need SK_SCALAR_IS_FLOAT.
-#endif
-            pathCommands.resize_back(verbCnt);
-            pathCoords.resize_back(minCoordCnt);
-            skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt);
-            skPath.getVerbs(&pathCommands[0], verbCnt);
-            for (int i = 0; i < verbCnt; ++i) {
-                SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
-                pathCommands[i] = verb_to_gl_path_cmd(v);
-                SkDEBUGCODE(numCoords += num_coords(v));
-            }
-        } else {
-            SkPoint points[4];
-            SkPath::RawIter iter(skPath);
-            SkPath::Verb verb;
-            while ((verb = iter.next(points)) != SkPath::kDone_Verb) {
-                pathCommands.push_back(verb_to_gl_path_cmd(verb));
-                GrGLfloat coords[6];
-                int coordsForVerb;
-                switch (verb) {
-                    case SkPath::kMove_Verb:
-                        points_to_coords(points, 0, 1, coords);
-                        coordsForVerb = 2;
-                        break;
-                    case SkPath::kLine_Verb:
-                        points_to_coords(points, 1, 1, coords);
-                        coordsForVerb = 2;
-                        break;
-                    case SkPath::kConic_Verb:
-                        points_to_coords(points, 1, 2, coords);
-                        coords[4] = SkScalarToFloat(iter.conicWeight());
-                        coordsForVerb = 5;
-                        break;
-                    case SkPath::kQuad_Verb:
-                        points_to_coords(points, 1, 2, coords);
-                        coordsForVerb = 4;
-                        break;
-                    case SkPath::kCubic_Verb:
-                        points_to_coords(points, 1, 3, coords);
-                        coordsForVerb = 6;
-                        break;
-                    case SkPath::kClose_Verb:
-                        continue;
-                    default:
-                        SkASSERT(false);  // Not reached.
-                        continue;
-                }
-                SkDEBUGCODE(numCoords += num_coords(verb));
-                pathCoords.push_back_n(coordsForVerb, coords);
-            }
-        }
+        pathCommands.resize_back(verbCnt);
+        pathCoords.resize_back(coordCnt);
+        skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt);
+        skPath.getVerbs(&pathCommands[0], verbCnt);
 
+        SkDEBUGCODE(int verbCoordCnt = 0);
+        for (int i = 0; i < verbCnt; ++i) {
+            SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]);
+            pathCommands[i] = verb_to_gl_path_cmd(v);
+            SkDEBUGCODE(verbCoordCnt += num_coords(v));
+        }
         SkASSERT(verbCnt == pathCommands.count());
-        SkASSERT(numCoords == pathCoords.count());
-
+        SkASSERT(verbCoordCnt == pathCoords.count());
         GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0],
-                   pathCoords.count(), GR_GL_FLOAT, &pathCoords[0]));
-    } else {
-        GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, nullptr, 0, GR_GL_FLOAT, nullptr));
+                                                    pathCoords.count(), GR_GL_FLOAT,
+                                                    &pathCoords[0]));
+        return;
     }
+#endif
+    SkAssertResult(init_path_object_for_general_path<false>(gpu, pathID, skPath));
+}
 
-    if (stroke.needToApply()) {
-        SkASSERT(!stroke.isHairlineStyle());
-        GR_GL_CALL(gpu->glInterface(),
-            PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
-        GR_GL_CALL(gpu->glInterface(),
-            PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
-        GrGLenum join = join_to_gl_join(stroke.getJoin());
-        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join));
-        GrGLenum cap = cap_to_gl_cap(stroke.getCap());
-        GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap));
-        GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f));
-    }
+void GrGLPath::InitPathObjectStroke(GrGLGpu* gpu, GrGLuint pathID, const GrStrokeInfo& stroke) {
+    SkASSERT(stroke.needToApply());
+    SkASSERT(!stroke.isDashed());
+    SkASSERT(!stroke.isHairlineStyle());
+    GR_GL_CALL(gpu->glInterface(),
+               PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth())));
+    GR_GL_CALL(gpu->glInterface(),
+               PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter())));
+    GrGLenum join = join_to_gl_join(stroke.getJoin());
+    GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join));
+    GrGLenum cap = cap_to_gl_cap(stroke.getCap());
+    GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap));
+    GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f));
+}
+
+void GrGLPath::InitPathObjectEmptyPath(GrGLGpu* gpu, GrGLuint pathID) {
+    GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, nullptr, 0, GR_GL_FLOAT, nullptr));
 }
 
 GrGLPath::GrGLPath(GrGLGpu* gpu, const SkPath& origSkPath, const GrStrokeInfo& origStroke)
     : INHERITED(gpu, origSkPath, origStroke),
       fPathID(gpu->glPathRendering()->genPaths(1)) {
-    // Convert a dashing to either a stroke or a fill.
-    const SkPath* skPath = &origSkPath;
-    SkTLazy<SkPath> tmpPath;
-    const GrStrokeInfo* stroke = &origStroke;
-    GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle);
-
-    if (stroke->isDashed()) {
-        if (stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) {
-            skPath = tmpPath.get();
-            stroke = &tmpStroke;
+
+    if (origSkPath.isEmpty()) {
+        InitPathObjectEmptyPath(gpu, fPathID);
+        fShouldStroke = false;
+        fShouldFill = false;
+    } else {
+        const SkPath* skPath = &origSkPath;
+        SkTLazy<SkPath> tmpPath;
+        const GrStrokeInfo* stroke = &origStroke;
+        GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle);
+
+        if (stroke->isDashed()) {
+            // Skia stroking and NVPR stroking differ with respect to dashing
+            // pattern.
+            // Convert a dashing to either a stroke or a fill.
+            if (stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) {
+                skPath = tmpPath.get();
+                stroke = &tmpStroke;
+            }
+        }
+
+        bool didInit = false;
+        if (stroke->needToApply() && stroke->getCap() != SkPaint::kButt_Cap) {
+            // Skia stroking and NVPR stroking differ with respect to stroking
+            // end caps of empty subpaths.
+            // Convert stroke to fill if path contains empty subpaths.
+            didInit = InitPathObjectPathDataCheckingDegenerates(gpu, fPathID, *skPath);
+            if (!didInit) {
+                if (!tmpPath.isValid()) {
+                    tmpPath.init();
+                }
+                SkAssertResult(stroke->applyToPath(tmpPath.get(), *skPath));
+                skPath = tmpPath.get();
+                tmpStroke.setFillStyle();
+                stroke = &tmpStroke;
+            }
         }
-    }
 
-    InitPathObject(gpu, fPathID, *skPath, *stroke);
+        if (!didInit) {
+            InitPathObjectPathData(gpu, fPathID, *skPath);
+        }
 
-    fShouldStroke = stroke->needToApply();
-    fShouldFill = stroke->isFillStyle() ||
-            stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style;
+        fShouldStroke = stroke->needToApply();
+        fShouldFill = stroke->isFillStyle() ||
+                stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style;
 
-    if (fShouldStroke) {
-        // FIXME: try to account for stroking, without rasterizing the stroke.
-        fBounds.outset(stroke->getWidth(), stroke->getWidth());
+        if (fShouldStroke) {
+            InitPathObjectStroke(gpu, fPathID, *stroke);
+
+            // FIXME: try to account for stroking, without rasterizing the stroke.
+            fBounds.outset(stroke->getWidth(), stroke->getWidth());
+        }
     }
 
     this->registerWithCache();
index b5346fd..e8642f3 100644 (file)
@@ -22,10 +22,16 @@ class GrGLGpu;
 
 class GrGLPath : public GrPath {
 public:
-    static void InitPathObject(GrGLGpu*,
-                               GrGLuint pathID,
-                               const SkPath&,
-                               const GrStrokeInfo&);
+    static bool InitPathObjectPathDataCheckingDegenerates(GrGLGpu*,
+                                                          GrGLuint pathID,
+                                                          const SkPath&);
+    static void InitPathObjectPathData(GrGLGpu*,
+                                       GrGLuint pathID,
+                                       const SkPath&);
+    static void InitPathObjectStroke(GrGLGpu* gpu, GrGLuint pathID, const GrStrokeInfo& stroke);
+
+    static void InitPathObjectEmptyPath(GrGLGpu*, GrGLuint pathID);
+
 
     GrGLPath(GrGLGpu* gpu, const SkPath& path, const GrStrokeInfo& stroke);
     GrGLuint pathID() const { return fPathID; }
index bd213d4..4714b92 100644 (file)
@@ -34,7 +34,13 @@ GrGLPathRange::GrGLPathRange(GrGLGpu* gpu,
 }
 
 void GrGLPathRange::init() {
-    if (fStroke.isDashed()) {
+    // Must force fill:
+    // * dashing: NVPR stroke dashing is different to Skia.
+    // * end caps: NVPR stroking degenerate contours with end caps is different to Skia.
+    bool forceFill = fStroke.isDashed() ||
+            (fStroke.needToApply() && fStroke.getCap() != SkPaint::kButt_Cap);
+
+    if (forceFill) {
         fShouldStroke = false;
         fShouldFill = true;
     } else {
@@ -56,32 +62,39 @@ void GrGLPathRange::onInitPath(int index, const SkPath& origSkPath) const {
         GR_GL_CALL_RET(gpu->glInterface(), isPath, IsPath(fBasePathID + index)));
     SkASSERT(GR_GL_FALSE == isPath);
 
-    const SkPath* skPath = &origSkPath;
-    SkTLazy<SkPath> tmpPath;
-    const GrStrokeInfo* stroke = &fStroke;
-    GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle);
-
-    // Dashing must be applied to the path. However, if dashing is present,
-    // we must convert all the paths to fills. The GrStrokeInfo::applyDash leaves
-    // simple paths as strokes but converts other paths to fills.
-    // Thus we must stroke the strokes here, so that all paths in the
-    // path range are using the same style.
-    if (fStroke.isDashed()) {
-        if (!stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) {
-            return;
+    if (origSkPath.isEmpty()) {
+        GrGLPath::InitPathObjectEmptyPath(gpu, fBasePathID + index);
+    } else if (fShouldStroke) {
+        GrGLPath::InitPathObjectPathData(gpu, fBasePathID + index, origSkPath);
+        GrGLPath::InitPathObjectStroke(gpu, fBasePathID + index, fStroke);
+    } else {
+        const SkPath* skPath = &origSkPath;
+        SkTLazy<SkPath> tmpPath;
+        const GrStrokeInfo* stroke = &fStroke;
+        GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle);
+
+        // Dashing must be applied to the path. However, if dashing is present,
+        // we must convert all the paths to fills. The GrStrokeInfo::applyDash leaves
+        // simple paths as strokes but converts other paths to fills.
+        // Thus we must stroke the strokes here, so that all paths in the
+        // path range are using the same style.
+        if (fStroke.isDashed()) {
+            if (!stroke->applyDashToPath(tmpPath.init(), &tmpStroke, *skPath)) {
+                return;
+            }
+            skPath = tmpPath.get();
+            stroke = &tmpStroke;
         }
-        skPath = tmpPath.get();
-        stroke = &tmpStroke;
-        if (tmpStroke.needToApply()) {
-            if (!tmpStroke.applyToPath(tmpPath.get(), *tmpPath.get())) {
+        if (stroke->needToApply()) {
+            if (!tmpPath.isValid()) {
+                tmpPath.init();
+            }
+            if (!stroke->applyToPath(tmpPath.get(), *tmpPath.get())) {
                 return;
             }
-            tmpStroke.setFillStyle();
         }
+        GrGLPath::InitPathObjectPathData(gpu, fBasePathID + index, *skPath);
     }
-
-    GrGLPath::InitPathObject(gpu, fBasePathID + index, *skPath, *stroke);
-
     // TODO: Use a better approximation for the individual path sizes.
     fGpuMemorySize += 100;
 }