aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar kkinnunen <kkinnunen@nvidia.com>2015-12-01 04:35:37 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2015-12-01 04:35:37 -0800
commit1e2913e7cb8c8122151cabd0aa6c77011253e95b (patch)
treed1c91ed5d3fac1ea1b113394a58bcb32eef4676d
parent1530283c483cb88aa725bce50a6d193dd00ee570 (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.cpp292
-rw-r--r--src/gpu/gl/GrGLPath.h14
-rw-r--r--src/gpu/gl/GrGLPathRange.cpp57
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;
}