| Index: src/gpu/gl/GrGLPath.cpp | 
| diff --git a/src/gpu/gl/GrGLPath.cpp b/src/gpu/gl/GrGLPath.cpp | 
| index 1dfeaee7b3f8e0f7144cae87b7cb49da2cde5c9f..d1fc39dffcc554e22035ced711275323b2baa9fb 100644 | 
| --- a/src/gpu/gl/GrGLPath.cpp | 
| +++ b/src/gpu/gl/GrGLPath.cpp | 
| @@ -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(); | 
|  |