diff --git a/src/gpu/gl/GrGLPath.cpp b/src/gpu/gl/GrGLPath.cpp index 1dfeaee7b3..d1fc39dffc 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 +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 + +bool GrGLPath::InitPathObjectPathDataCheckingDegenerates(GrGLGpu* gpu, GrGLuint pathID, + const SkPath& skPath) { + return init_path_object_for_general_path(gpu, pathID, skPath); } -void GrGLPath::InitPathObject(GrGLGpu* gpu, - GrGLuint pathID, - const SkPath& skPath, - const GrStrokeInfo& stroke) { - SkASSERT(!stroke.isDashed()); - if (!skPath.isEmpty()) { +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(&pathCoords[0]), pointCnt); - skPath.getVerbs(&pathCommands[0], verbCnt); - for (int i = 0; i < verbCnt; ++i) { - SkPath::Verb v = static_cast(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(&pathCoords[0]), pointCnt); + skPath.getVerbs(&pathCommands[0], verbCnt); + + SkDEBUGCODE(int verbCoordCnt = 0); + for (int i = 0; i < verbCnt; ++i) { + SkPath::Verb v = static_cast(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(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 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 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; + } } - } - InitPathObject(gpu, fPathID, *skPath, *stroke); + 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; + } + } - fShouldStroke = stroke->needToApply(); - fShouldFill = stroke->isFillStyle() || - stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style; + if (!didInit) { + InitPathObjectPathData(gpu, fPathID, *skPath); + } - if (fShouldStroke) { - // FIXME: try to account for stroking, without rasterizing the stroke. - fBounds.outset(stroke->getWidth(), stroke->getWidth()); + fShouldStroke = stroke->needToApply(); + fShouldFill = stroke->isFillStyle() || + stroke->getStyle() == SkStrokeRec::kStrokeAndFill_Style; + + if (fShouldStroke) { + InitPathObjectStroke(gpu, fPathID, *stroke); + + // FIXME: try to account for stroking, without rasterizing the stroke. + fBounds.outset(stroke->getWidth(), stroke->getWidth()); + } } this->registerWithCache(); diff --git a/src/gpu/gl/GrGLPath.h b/src/gpu/gl/GrGLPath.h index b5346fd4bb..e8642f3468 100644 --- a/src/gpu/gl/GrGLPath.h +++ b/src/gpu/gl/GrGLPath.h @@ -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; } diff --git a/src/gpu/gl/GrGLPathRange.cpp b/src/gpu/gl/GrGLPathRange.cpp index bd213d4e09..4714b924ab 100644 --- a/src/gpu/gl/GrGLPathRange.cpp +++ b/src/gpu/gl/GrGLPathRange.cpp @@ -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 tmpPath; - const GrStrokeInfo* stroke = &fStroke; - GrStrokeInfo tmpStroke(SkStrokeRec::kFill_InitStyle); + 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 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; - if (tmpStroke.needToApply()) { - if (!tmpStroke.applyToPath(tmpPath.get(), *tmpPath.get())) { + // 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; } - tmpStroke.setFillStyle(); + skPath = tmpPath.get(); + stroke = &tmpStroke; } + if (stroke->needToApply()) { + if (!tmpPath.isValid()) { + tmpPath.init(); + } + if (!stroke->applyToPath(tmpPath.get(), *tmpPath.get())) { + return; + } + } + 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; }