diff options
author | kkinnunen <kkinnunen@nvidia.com> | 2015-12-01 04:35:37 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-12-01 04:35:37 -0800 |
commit | 1e2913e7cb8c8122151cabd0aa6c77011253e95b (patch) | |
tree | d1c91ed5d3fac1ea1b113394a58bcb32eef4676d | |
parent | 1530283c483cb88aa725bce50a6d193dd00ee570 (diff) |
Fix stroking of zero length paths with end caps on NVPR
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
-rw-r--r-- | src/gpu/gl/GrGLPath.cpp | 292 | ||||
-rw-r--r-- | src/gpu/gl/GrGLPath.h | 14 | ||||
-rw-r--r-- | src/gpu/gl/GrGLPathRange.cpp | 57 |
3 files changed, 236 insertions, 127 deletions
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<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(); 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<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; } |