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:
parent
1530283c48
commit
1e2913e7cb
@ -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();
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user