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();
}
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 {
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;
}