ccpr: Skip extremely wide strokes
Instead let them be converted to fill paths and drawn by the CCPR filler.
Bug: skia:8360
Change-Id: Ide3e1cc65e2cf103f2eee9854a1a444fe69ba7fd
Reviewed-on: https://skia-review.googlesource.com/153726
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 2ec8379..1ec9493 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -31,8 +31,6 @@
std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
GrPaint&& paint) {
- static constexpr int kPathCropThreshold = GrCoverageCountingPathRenderer::kPathCropThreshold;
-
SkRect conservativeDevBounds;
m.mapRect(&conservativeDevBounds, shape.bounds());
@@ -40,23 +38,14 @@
float strokeDevWidth = 0;
float conservativeInflationRadius = 0;
if (!stroke.isFillStyle()) {
- if (stroke.isHairlineStyle()) {
- strokeDevWidth = 1;
- } else {
- SkASSERT(m.isSimilarity()); // Otherwise matrixScaleFactor = m.getMaxScale().
- float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY());
- strokeDevWidth = stroke.getWidth() * matrixScaleFactor;
- }
- // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px
- // wide, we may inflate it to 1px and instead reduce the opacity.
- conservativeInflationRadius = SkStrokeRec::GetInflationRadius(
- stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f));
+ strokeDevWidth = GrCoverageCountingPathRenderer::GetStrokeDevWidth(
+ m, stroke, &conservativeInflationRadius);
conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
}
std::unique_ptr<GrCCDrawPathsOp> op;
float conservativeSize = SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width());
- if (conservativeSize > kPathCropThreshold) {
+ if (conservativeSize > GrCoverageCountingPathRenderer::kPathCropThreshold) {
// The path is too large. Crop it or analytic AA can run out of fp32 precision.
SkPath croppedDevPath;
shape.asPath(&croppedDevPath);
@@ -91,6 +80,12 @@
std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::InternalMake(
GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
float strokeDevWidth, const SkRect& conservativeDevBounds, GrPaint&& paint) {
+ // The path itself should have been cropped if larger than kPathCropThreshold. If it had a
+ // stroke, that would have further inflated its draw bounds.
+ SkASSERT(SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width()) <
+ GrCoverageCountingPathRenderer::kPathCropThreshold +
+ GrCoverageCountingPathRenderer::kMaxBoundsInflationFromStroke*2 + 1);
+
SkIRect shapeConservativeIBounds;
conservativeDevBounds.roundOut(&shapeConservativeIBounds);
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index 0899690..9d30bb4 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -78,7 +78,8 @@
SkPath path;
shape.asPath(&path);
- switch (shape.style().strokeRec().getStyle()) {
+ const SkStrokeRec& stroke = shape.style().strokeRec();
+ switch (stroke.getStyle()) {
case SkStrokeRec::kFill_Style: {
SkRect devBounds;
args.fViewMatrix->mapRect(&devBounds, path.getBounds());
@@ -122,9 +123,21 @@
return CanDrawPath::kNo;
}
// fallthru
- case SkStrokeRec::kHairline_Style:
- // The stroker does not support conics yet.
- return !SkPathPriv::ConicWeightCnt(path) ? CanDrawPath::kYes : CanDrawPath::kNo;
+ case SkStrokeRec::kHairline_Style: {
+ float inflationRadius;
+ GetStrokeDevWidth(*args.fViewMatrix, stroke, &inflationRadius);
+ if (!(inflationRadius <= kMaxBoundsInflationFromStroke)) {
+ // Let extremely wide strokes be converted to fill paths and drawn by the CCPR
+ // filler instead. (Cast the logic negatively in order to also catch r=NaN.)
+ return CanDrawPath::kNo;
+ }
+ SkASSERT(!SkScalarIsNaN(inflationRadius));
+ if (SkPathPriv::ConicWeightCnt(path)) {
+ // The stroker does not support conics yet.
+ return CanDrawPath::kNo;
+ }
+ return CanDrawPath::kYes;
+ }
case SkStrokeRec::kStrokeAndFill_Style:
return CanDrawPath::kNo;
@@ -323,3 +336,24 @@
}
out->setIsVolatile(true);
}
+
+float GrCoverageCountingPathRenderer::GetStrokeDevWidth(const SkMatrix& m,
+ const SkStrokeRec& stroke,
+ float* inflationRadius) {
+ float strokeDevWidth;
+ if (stroke.isHairlineStyle()) {
+ strokeDevWidth = 1;
+ } else {
+ SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle());
+ SkASSERT(m.isSimilarity()); // Otherwise matrixScaleFactor = m.getMaxScale().
+ float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY());
+ strokeDevWidth = stroke.getWidth() * matrixScaleFactor;
+ }
+ if (inflationRadius) {
+ // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px
+ // wide, we may inflate it to 1px and instead reduce the opacity.
+ *inflationRadius = SkStrokeRec::GetInflationRadius(
+ stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f));
+ }
+ return strokeDevWidth;
+}
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index bc336a5..1d3bfd8 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -76,6 +76,13 @@
static void CropPath(const SkPath&, const SkIRect& cropbox, SkPath* out);
+ // Maximum inflation of path bounds due to stroking (from width, miter, caps). Strokes wider
+ // than this will be converted to fill paths and drawn by the CCPR filler instead.
+ static constexpr float kMaxBoundsInflationFromStroke = 4096;
+
+ static float GetStrokeDevWidth(const SkMatrix&, const SkStrokeRec&,
+ float* inflationRadius = nullptr);
+
private:
GrCoverageCountingPathRenderer(AllowCaching);