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
This commit is contained in:
kkinnunen 2015-12-01 04:35:37 -08:00 committed by Commit bot
parent 1530283c48
commit 1e2913e7cb
3 changed files with 235 additions and 126 deletions

View File

@ -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
bool GrGLPath::InitPathObjectPathDataCheckingDegenerates(GrGLGpu* gpu, GrGLuint pathID,
const SkPath& skPath) {
return init_path_object_for_general_path<true>(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<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;
}
}
}
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();

View File

@ -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; }

View File

@ -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);
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;
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;
}