From 8c170971f182d47bc9af71fc88a607740d03dfd5 Mon Sep 17 00:00:00 2001 From: robertphillips Date: Thu, 22 Sep 2016 12:42:30 -0700 Subject: Make AALinearizingConvexPathRenderer able to handle stroke and fill This is intended to catch stroke-and-fill convex paths with potentially small stroke widths (e.g., .1). It does have the disconcerting side effect of changing bevel-joined stroke-and-filled degenerate-single-line-rects into plain rects (w/o triangular end-caps). GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2301353004 Committed: https://skia.googlesource.com/skia/+/522bcd99fa65a8abd130880f59b500cf367d0845 Review-Url: https://codereview.chromium.org/2301353004 --- src/gpu/batches/GrAAConvexTessellator.cpp | 79 ++++++++++++++++++---- src/gpu/batches/GrAAConvexTessellator.h | 15 +++- .../batches/GrAALinearizingConvexPathRenderer.cpp | 46 ++++++++++--- 3 files changed, 116 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/gpu/batches/GrAAConvexTessellator.cpp b/src/gpu/batches/GrAAConvexTessellator.cpp index af3ce89257..2fc33a8357 100644 --- a/src/gpu/batches/GrAAConvexTessellator.cpp +++ b/src/gpu/batches/GrAAConvexTessellator.cpp @@ -32,11 +32,17 @@ static const SkScalar kRoundCapThreshold = 0.8f; // dot product above which we consider two adjacent curves to be part of the "same" curve static const SkScalar kCurveConnectionThreshold = 0.8f; -static SkScalar intersect(const SkPoint& p0, const SkPoint& n0, - const SkPoint& p1, const SkPoint& n1) { +static bool intersect(const SkPoint& p0, const SkPoint& n0, + const SkPoint& p1, const SkPoint& n1, + SkScalar* t) { const SkPoint v = p1 - p0; SkScalar perpDot = n0.fX * n1.fY - n0.fY * n1.fX; - return (v.fX * n1.fY - v.fY * n1.fX) / perpDot; + if (SkScalarNearlyZero(perpDot)) { + return false; + } + *t = (v.fX * n1.fY - v.fY * n1.fX) / perpDot; + SkASSERT(SkScalarIsFinite(*t)); + return true; } // This is a special case version of intersect where we have the vector @@ -218,7 +224,45 @@ bool GrAAConvexTessellator::tessellate(const SkMatrix& m, const SkPath& path) { SkScalar coverage = 1.0f; SkScalar scaleFactor = 0.0f; - if (fStrokeWidth >= 0.0f) { + + if (SkStrokeRec::kStrokeAndFill_Style == fStyle) { + SkASSERT(m.isSimilarity()); + scaleFactor = m.getMaxScale(); // x and y scale are the same + SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; + Ring outerStrokeAndAARing; + this->createOuterRing(fInitialRing, + effectiveStrokeWidth / 2 + kAntialiasingRadius, 0.0, + &outerStrokeAndAARing); + + // discard all the triangles added between the originating ring and the new outer ring + fIndices.rewind(); + + outerStrokeAndAARing.init(*this); + + outerStrokeAndAARing.makeOriginalRing(); + + // Add the outer stroke ring's normals to the originating ring's normals + // so it can also act as an originating ring + fNorms.setCount(fNorms.count() + outerStrokeAndAARing.numPts()); + for (int i = 0; i < outerStrokeAndAARing.numPts(); ++i) { + SkASSERT(outerStrokeAndAARing.index(i) < fNorms.count()); + fNorms[outerStrokeAndAARing.index(i)] = outerStrokeAndAARing.norm(i); + } + + // the bisectors are only needed for the computation of the outer ring + fBisectors.rewind(); + + Ring* insetAARing; + this->createInsetRings(outerStrokeAndAARing, + 0.0f, 0.0f, 2*kAntialiasingRadius, 1.0f, + &insetAARing); + + SkDEBUGCODE(this->validate();) + return true; + } + + if (SkStrokeRec::kStroke_Style == fStyle) { + SkASSERT(fStrokeWidth >= 0.0f); SkASSERT(m.isSimilarity()); scaleFactor = m.getMaxScale(); // x and y scale are the same SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; @@ -235,15 +279,16 @@ bool GrAAConvexTessellator::tessellate(const SkMatrix& m, const SkPath& path) { // the bisectors are only needed for the computation of the outer ring fBisectors.rewind(); - if (fStrokeWidth >= 0.0f && fInitialRing.numPts() > 2) { + if (SkStrokeRec::kStroke_Style == fStyle && fInitialRing.numPts() > 2) { + SkASSERT(fStrokeWidth >= 0.0f); SkScalar effectiveStrokeWidth = scaleFactor * fStrokeWidth; Ring* insetStrokeRing; SkScalar strokeDepth = effectiveStrokeWidth / 2 - kAntialiasingRadius; if (this->createInsetRings(fInitialRing, 0.0f, coverage, strokeDepth, coverage, - &insetStrokeRing)) { + &insetStrokeRing)) { Ring* insetAARing; this->createInsetRings(*insetStrokeRing, strokeDepth, coverage, strokeDepth + - kAntialiasingRadius * 2, 0.0f, &insetAARing); + kAntialiasingRadius * 2, 0.0f, &insetAARing); } } else { Ring* insetAARing; @@ -390,7 +435,7 @@ bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& pat this->computeBisectors(); } else if (this->numPts() == 2) { // We've got two points, so we're degenerate. - if (fStrokeWidth < 0.0f) { + if (fStyle == SkStrokeRec::kFill_Style) { // it's a fill, so we don't need to worry about degenerate paths return false; } @@ -586,7 +631,7 @@ void GrAAConvexTessellator::createOuterRing(const Ring& previousRing, SkScalar o // Something went wrong in the creation of the next ring. If we're filling the shape, just go ahead // and fan it. void GrAAConvexTessellator::terminate(const Ring& ring) { - if (fStrokeWidth < 0.0f) { + if (fStyle != SkStrokeRec::kStroke_Style) { this->fanRing(ring); } } @@ -616,8 +661,14 @@ bool GrAAConvexTessellator::createInsetRing(const Ring& lastRing, Ring* nextRing for (int cur = 0; cur < lastRing.numPts(); ++cur) { int next = (cur + 1) % lastRing.numPts(); - SkScalar t = intersect(this->point(lastRing.index(cur)), lastRing.bisector(cur), - this->point(lastRing.index(next)), lastRing.bisector(next)); + + SkScalar t; + bool result = intersect(this->point(lastRing.index(cur)), lastRing.bisector(cur), + this->point(lastRing.index(next)), lastRing.bisector(next), + &t); + if (!result) { + continue; + } SkScalar dist = -t * lastRing.norm(cur).dot(lastRing.bisector(cur)); if (minDist > dist) { @@ -745,8 +796,8 @@ bool GrAAConvexTessellator::createInsetRing(const Ring& lastRing, Ring* nextRing this->addTri(lastRing.index(i), dst[next], dst[i]); } - if (done && fStrokeWidth < 0.0f) { - // fill + if (done && fStyle != SkStrokeRec::kStroke_Style) { + // fill or stroke-and-fill this->fanRing(*nextRing); } @@ -860,7 +911,7 @@ void GrAAConvexTessellator::lineTo(SkPoint p, CurveState curve) { return; } } - SkScalar initialRingCoverage = fStrokeWidth < 0.0f ? 0.5f : 1.0f; + SkScalar initialRingCoverage = (SkStrokeRec::kFill_Style == fStyle) ? 0.5f : 1.0f; this->addPt(p, 0.0f, initialRingCoverage, false, curve); if (this->numPts() > 1) { *fNorms.push() = fPts.top() - fPts[fPts.count()-2]; diff --git a/src/gpu/batches/GrAAConvexTessellator.h b/src/gpu/batches/GrAAConvexTessellator.h index 2683147912..4ba3a9c78b 100644 --- a/src/gpu/batches/GrAAConvexTessellator.h +++ b/src/gpu/batches/GrAAConvexTessellator.h @@ -12,6 +12,7 @@ #include "SkPaint.h" #include "SkPoint.h" #include "SkScalar.h" +#include "SkStrokeRec.h" #include "SkTDArray.h" class SkCanvas; @@ -31,11 +32,13 @@ class GrAAConvexTessellator; // computeDepthFromEdge requests. class GrAAConvexTessellator { public: - GrAAConvexTessellator(SkScalar strokeWidth = -1.0f, + GrAAConvexTessellator(SkStrokeRec::Style style = SkStrokeRec::kFill_Style, + SkScalar strokeWidth = -1.0f, SkPaint::Join join = SkPaint::Join::kBevel_Join, SkScalar miterLimit = 0.0f) : fSide(SkPoint::kOn_Side) , fStrokeWidth(strokeWidth) + , fStyle(style) , fJoin(join) , fMiterLimit(miterLimit) { } @@ -136,6 +139,13 @@ private: pt->fOrigEdgeId = origEdgeId; } + // Upgrade this ring so that it can behave like an originating ring + void makeOriginalRing() { + for (int i = 0; i < fPts.count(); ++i) { + fPts[i].fOrigEdgeId = fPts[i].fIndex; + } + } + // init should be called after all the indices have been added (via addIdx) void init(const GrAAConvexTessellator& tess); void init(const SkTDArray& norms, const SkTDArray& bisectors); @@ -267,8 +277,9 @@ private: #endif CandidateVerts fCandidateVerts; - // < 0 means filling rather than stroking + // the stroke width is only used for stroke or stroke-and-fill styles SkScalar fStrokeWidth; + SkStrokeRec::Style fStyle; SkPaint::Join fJoin; diff --git a/src/gpu/batches/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/batches/GrAALinearizingConvexPathRenderer.cpp index 20d93d8e90..c2873b6a40 100644 --- a/src/gpu/batches/GrAALinearizingConvexPathRenderer.cpp +++ b/src/gpu/batches/GrAALinearizingConvexPathRenderer.cpp @@ -50,12 +50,17 @@ bool GrAALinearizingConvexPathRenderer::onCanDrawPath(const CanDrawPathArgs& arg return false; } const SkStrokeRec& stroke = args.fShape->style().strokeRec(); - if (stroke.getStyle() == SkStrokeRec::kStroke_Style) { + + if (stroke.getStyle() == SkStrokeRec::kStroke_Style || + stroke.getStyle() == SkStrokeRec::kStrokeAndFill_Style) { if (!args.fViewMatrix->isSimilarity()) { return false; } SkScalar strokeWidth = args.fViewMatrix->getMaxScale() * stroke.getWidth(); - return strokeWidth >= 1.0f && strokeWidth <= kMaxStrokeWidth && + if (strokeWidth < 1.0f && stroke.getStyle() == SkStrokeRec::kStroke_Style) { + return false; + } + return strokeWidth <= kMaxStrokeWidth && args.fShape->knownToBeClosed() && stroke.getJoin() != SkPaint::Join::kRound_Join; } @@ -126,9 +131,11 @@ public: const SkMatrix& viewMatrix, const SkPath& path, SkScalar strokeWidth, + SkStrokeRec::Style style, SkPaint::Join join, SkScalar miterLimit) : INHERITED(ClassID()) { - fGeoData.emplace_back(Geometry{color, viewMatrix, path, strokeWidth, join, miterLimit}); + fGeoData.emplace_back(Geometry{ color, viewMatrix, path, + strokeWidth, style, join, miterLimit }); // compute bounds SkRect bounds = path.getBounds(); @@ -229,7 +236,8 @@ private: uint16_t* indices = (uint16_t*) sk_malloc_throw(maxIndices * sizeof(uint16_t)); for (int i = 0; i < instanceCount; i++) { const Geometry& args = fGeoData[i]; - GrAAConvexTessellator tess(args.fStrokeWidth, args.fJoin, args.fMiterLimit); + GrAAConvexTessellator tess(args.fStyle, args.fStrokeWidth, + args.fJoin, args.fMiterLimit); if (!tess.tessellate(args.fViewMatrix, args.fPath)) { continue; @@ -309,6 +317,7 @@ private: SkMatrix fViewMatrix; SkPath fPath; SkScalar fStrokeWidth; + SkStrokeRec::Style fStyle; SkPaint::Join fJoin; SkScalar fMiterLimit; }; @@ -324,6 +333,7 @@ bool GrAALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { "GrAALinearizingConvexPathRenderer::onDrawPath"); SkASSERT(!args.fDrawContext->isUnifiedMultisampled()); SkASSERT(!args.fShape->isEmpty()); + SkASSERT(!args.fShape->style().pathEffect()); SkPath path; args.fShape->asPath(&path); @@ -335,8 +345,9 @@ bool GrAALinearizingConvexPathRenderer::onDrawPath(const DrawPathArgs& args) { SkAutoTUnref batch(new AAFlatteningConvexPathBatch(args.fPaint->getColor(), *args.fViewMatrix, - path, strokeWidth, join, - miterLimit)); + path, strokeWidth, + stroke.getStyle(), + join, miterLimit)); GrPipelineBuilder pipelineBuilder(*args.fPaint); pipelineBuilder.setUserStencil(args.fUserStencilSettings); @@ -354,10 +365,29 @@ DRAW_BATCH_TEST_DEFINE(AAFlatteningConvexPathBatch) { GrColor color = GrRandomColor(random); SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); SkPath path = GrTest::TestPathConvex(random); - SkScalar strokeWidth = random->nextBool() ? -1.f : 2.f; + + SkStrokeRec::Style styles[3] = { SkStrokeRec::kFill_Style, + SkStrokeRec::kStroke_Style, + SkStrokeRec::kStrokeAndFill_Style }; + + SkStrokeRec::Style style = styles[random->nextU() % 3]; + + SkScalar strokeWidth = -1.f; SkPaint::Join join = SkPaint::kMiter_Join; SkScalar miterLimit = 0.5f; - return new AAFlatteningConvexPathBatch(color, viewMatrix, path, strokeWidth, join, miterLimit); + + if (SkStrokeRec::kFill_Style != style) { + strokeWidth = random->nextRangeF(1.0f, 10.0f); + if (random->nextBool()) { + join = SkPaint::kMiter_Join; + } else { + join = SkPaint::kBevel_Join; + } + miterLimit = random->nextRangeF(0.5f, 2.0f); + } + + return new AAFlatteningConvexPathBatch(color, viewMatrix, path, strokeWidth, + style, join, miterLimit); } #endif -- cgit v1.2.3