Reland "Prefer using GrOvalOpFactory over GrFillRRect for circles and axis-aligned circular roundrects."
This is a reland of 731454a08543be3793bb7b28391a7ff69db09884
Original change's description:
> Prefer using GrOvalOpFactory over GrFillRRect for circles and
> axis-aligned circular roundrects.
>
> Bug: chromium:971936
> Change-Id: I4cd0cd9047b9b06d657826820ba5a937547f87c3
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/221000
> Commit-Queue: Jim Van Verth <jvanverth@google.com>
> Reviewed-by: Khushal Sagar <khushalsagar@chromium.org>
Bug: chromium:971936
Change-Id: I8a61cff3e065177a5b2320072b45c1a619970ff6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222794
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 2b37973..43a7086 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -1126,7 +1126,16 @@
GrAAType aaType = this->chooseAAType(aa);
std::unique_ptr<GrDrawOp> op;
- if (style.isSimpleFill()) {
+ if (GrAAType::kCoverage == aaType && rrect.isSimple() &&
+ rrect.getSimpleRadii().fX == rrect.getSimpleRadii().fY &&
+ viewMatrix.rectStaysRect() && viewMatrix.isSimilarity()) {
+ // In coverage mode, we draw axis-aligned circular roundrects with the GrOvalOpFactory
+ // to avoid perf regressions on some platforms.
+ assert_alive(paint);
+ op = GrOvalOpFactory::MakeCircularRRectOp(
+ fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
+ }
+ if (!op && style.isSimpleFill()) {
assert_alive(paint);
op = GrFillRRectOp::Make(
fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
@@ -1135,7 +1144,6 @@
assert_alive(paint);
op = GrOvalOpFactory::MakeRRectOp(
fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
-
}
if (op) {
this->addDrawOp(*clip, std::move(op));
@@ -1534,20 +1542,23 @@
GrAAType aaType = this->chooseAAType(aa);
std::unique_ptr<GrDrawOp> op;
- if (style.isSimpleFill()) {
+ if (GrAAType::kCoverage == aaType && oval.width() == oval.height() &&
+ viewMatrix.isSimilarity()) {
+ // We don't draw true circles as round rects in coverage mode, because it can
+ // cause perf regressions on some platforms as compared to the dedicated circle Op.
+ assert_alive(paint);
+ op = GrOvalOpFactory::MakeCircleOp(fContext, std::move(paint), viewMatrix, oval, style,
+ this->caps()->shaderCaps());
+ }
+ if (!op && style.isSimpleFill()) {
// GrFillRRectOp has special geometry and a fragment-shader branch to conditionally evaluate
// the arc equation. This same special geometry and fragment branch also turn out to be a
// substantial optimization for drawing ovals (namely, by not evaluating the arc equation
// inside the oval's inner diamond). Given these optimizations, it's a clear win to draw
// ovals the exact same way we do round rects.
- //
- // However, we still don't draw true circles as round rects in coverage mode, because it can
- // cause perf regressions on some platforms as compared to the dedicated circle Op.
- if (GrAAType::kCoverage != aaType || oval.height() != oval.width()) {
- assert_alive(paint);
- op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
- *this->caps(), std::move(paint));
- }
+ assert_alive(paint);
+ op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
+ *this->caps(), std::move(paint));
}
if (!op && GrAAType::kCoverage == aaType) {
assert_alive(paint);
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index f6dfc8e..f9d004f 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -2907,6 +2907,58 @@
typedef GrMeshDrawOp INHERITED;
};
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircularRRectOp(GrRecordingContext* context,
+ GrPaint&& paint,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps) {
+ SkASSERT(viewMatrix.rectStaysRect());
+ SkASSERT(viewMatrix.isSimilarity());
+ SkASSERT(rrect.isSimple());
+ SkASSERT(!rrect.isOval());
+ SkASSERT(SkRRectPriv::GetSimpleRadii(rrect).fX == SkRRectPriv::GetSimpleRadii(rrect).fY);
+
+ // RRect ops only handle simple, but not too simple, rrects.
+ // Do any matrix crunching before we reset the draw state for device coords.
+ const SkRect& rrectBounds = rrect.getBounds();
+ SkRect bounds;
+ viewMatrix.mapRect(&bounds, rrectBounds);
+
+ SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
+ SkScalar scaledRadius = SkScalarAbs(radius * (viewMatrix[SkMatrix::kMScaleX] +
+ viewMatrix[SkMatrix::kMSkewY]));
+
+ // Do mapping of stroke. Use -1 to indicate fill-only draws.
+ SkScalar scaledStroke = -1;
+ SkScalar strokeWidth = stroke.getWidth();
+ SkStrokeRec::Style style = stroke.getStyle();
+
+ bool isStrokeOnly =
+ SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
+ bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+
+ if (hasStroke) {
+ if (SkStrokeRec::kHairline_Style == style) {
+ scaledStroke = SK_Scalar1;
+ } else {
+ scaledStroke = SkScalarAbs(strokeWidth * (viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewY]));
+ }
+ }
+
+ // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
+ // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
+ // patch will have fractional coverage. This only matters when the interior is actually filled.
+ // We could consider falling back to rect rendering here, since a tiny radius is
+ // indistinguishable from a square corner.
+ if (!isStrokeOnly && SK_ScalarHalf > scaledRadius) {
+ return nullptr;
+ }
+
+ return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, scaledRadius,
+ scaledStroke, isStrokeOnly);
+}
+
static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
GrPaint&& paint,
const SkMatrix& viewMatrix,
@@ -2938,7 +2990,6 @@
SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
- bool isCircular = (xRadius == yRadius);
if (hasStroke) {
if (SkStrokeRec::kHairline_Style == style) {
scaledStroke.set(1, 1);
@@ -2949,17 +3000,15 @@
strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
}
- isCircular = isCircular && scaledStroke.fX == scaledStroke.fY;
- // for non-circular rrects, if half of strokewidth is greater than radius,
- // we don't handle that right now
- if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius ||
- SK_ScalarHalf * scaledStroke.fY > yRadius)) {
+ // if half of strokewidth is greater than radius, we don't handle that right now
+ if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
+ SK_ScalarHalf * scaledStroke.fY > yRadius)) {
return nullptr;
}
}
// The matrix may have a rotation by an odd multiple of 90 degrees.
- if (!isCircular && viewMatrix.getScaleX() == 0) {
+ if (viewMatrix.getScaleX() == 0) {
std::swap(xRadius, yRadius);
std::swap(scaledStroke.fX, scaledStroke.fY);
}
@@ -2974,14 +3023,8 @@
}
// if the corners are circles, use the circle renderer
- if (isCircular) {
- return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
- scaledStroke.fX, isStrokeOnly);
- // otherwise we use the ellipse renderer
- } else {
- return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
- xRadius, yRadius, scaledStroke, isStrokeOnly);
- }
+ return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
+ xRadius, yRadius, scaledStroke, isStrokeOnly);
}
std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
@@ -3004,6 +3047,48 @@
///////////////////////////////////////////////////////////////////////////////
+std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeCircleOp(GrRecordingContext* context,
+ GrPaint&& paint,
+ const SkMatrix& viewMatrix,
+ const SkRect& oval,
+ const GrStyle& style,
+ const GrShaderCaps* shaderCaps) {
+ SkScalar width = oval.width();
+ SkASSERT(width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
+ circle_stays_circle(viewMatrix));
+
+ auto r = width / 2.f;
+ SkPoint center = { oval.centerX(), oval.centerY() };
+ if (style.hasNonDashPathEffect()) {
+ return nullptr;
+ } else if (style.isDashed()) {
+ if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
+ style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
+ return nullptr;
+ }
+ auto onInterval = style.dashIntervals()[0];
+ auto offInterval = style.dashIntervals()[1];
+ if (offInterval == 0) {
+ GrStyle strokeStyle(style.strokeRec(), nullptr);
+ return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
+ strokeStyle, shaderCaps);
+ } else if (onInterval == 0) {
+ // There is nothing to draw but we have no way to indicate that here.
+ return nullptr;
+ }
+ auto angularOnInterval = onInterval / r;
+ auto angularOffInterval = offInterval / r;
+ auto phaseAngle = style.dashPhase() / r;
+ // Currently this function doesn't accept ovals with different start angles, though
+ // it could.
+ static const SkScalar kStartAngle = 0.f;
+ return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
+ style.strokeRec().getWidth(), kStartAngle,
+ angularOnInterval, angularOffInterval, phaseAngle);
+ }
+ return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
+}
+
std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
GrPaint&& paint,
const SkMatrix& viewMatrix,
@@ -3014,36 +3099,7 @@
SkScalar width = oval.width();
if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
circle_stays_circle(viewMatrix)) {
- auto r = width / 2.f;
- SkPoint center = {oval.centerX(), oval.centerY()};
- if (style.hasNonDashPathEffect()) {
- return nullptr;
- } else if (style.isDashed()) {
- if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
- style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
- return nullptr;
- }
- auto onInterval = style.dashIntervals()[0];
- auto offInterval = style.dashIntervals()[1];
- if (offInterval == 0) {
- GrStyle strokeStyle(style.strokeRec(), nullptr);
- return MakeOvalOp(context, std::move(paint), viewMatrix, oval,
- strokeStyle, shaderCaps);
- } else if (onInterval == 0) {
- // There is nothing to draw but we have no way to indicate that here.
- return nullptr;
- }
- auto angularOnInterval = onInterval / r;
- auto angularOffInterval = offInterval / r;
- auto phaseAngle = style.dashPhase() / r;
- // Currently this function doesn't accept ovals with different start angles, though
- // it could.
- static const SkScalar kStartAngle = 0.f;
- return ButtCapDashedCircleOp::Make(context, std::move(paint), viewMatrix, center, r,
- style.strokeRec().getWidth(), kStartAngle,
- angularOnInterval, angularOffInterval, phaseAngle);
- }
- return CircleOp::Make(context, std::move(paint), viewMatrix, center, r, style);
+ return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps);
}
if (style.pathEffect()) {
@@ -3174,6 +3230,35 @@
GrTest::TestStrokeRec(random));
}
+GR_DRAW_OP_TEST_DEFINE(CircularRRectOp) {
+ do {
+ SkScalar rotate = random->nextSScalar1() * 360.f;
+ SkScalar translateX = random->nextSScalar1() * 1000.f;
+ SkScalar translateY = random->nextSScalar1() * 1000.f;
+ SkScalar scale;
+ do {
+ scale = random->nextSScalar1() * 100.f;
+ } while (scale == 0);
+ SkMatrix viewMatrix;
+ viewMatrix.setRotate(rotate);
+ viewMatrix.postTranslate(translateX, translateY);
+ viewMatrix.postScale(scale, scale);
+ SkRect rect = GrTest::TestRect(random);
+ SkScalar radius = random->nextRangeF(0.1f, 10.f);
+ SkRRect rrect = SkRRect::MakeRectXY(rect, radius, radius);
+ if (rrect.isOval()) {
+ continue;
+ }
+ std::unique_ptr<GrDrawOp> op =
+ GrOvalOpFactory::MakeCircularRRectOp(context, std::move(paint), viewMatrix, rrect,
+ GrTest::TestStrokeRec(random), nullptr);
+ if (op) {
+ return op;
+ }
+ assert_alive(paint);
+ } while (true);
+}
+
GR_DRAW_OP_TEST_DEFINE(RRectOp) {
SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
const SkRRect& rrect = GrTest::TestRRectSimple(random);
diff --git a/src/gpu/ops/GrOvalOpFactory.h b/src/gpu/ops/GrOvalOpFactory.h
index bf86ca3..de25fa1 100644
--- a/src/gpu/ops/GrOvalOpFactory.h
+++ b/src/gpu/ops/GrOvalOpFactory.h
@@ -26,6 +26,13 @@
*/
class GrOvalOpFactory {
public:
+ static std::unique_ptr<GrDrawOp> MakeCircleOp(GrRecordingContext*,
+ GrPaint&&,
+ const SkMatrix&,
+ const SkRect& oval,
+ const GrStyle& style,
+ const GrShaderCaps*);
+
static std::unique_ptr<GrDrawOp> MakeOvalOp(GrRecordingContext*,
GrPaint&&,
const SkMatrix&,
@@ -33,6 +40,13 @@
const GrStyle& style,
const GrShaderCaps*);
+ static std::unique_ptr<GrDrawOp> MakeCircularRRectOp(GrRecordingContext*,
+ GrPaint&&,
+ const SkMatrix&,
+ const SkRRect&,
+ const SkStrokeRec&,
+ const GrShaderCaps*);
+
static std::unique_ptr<GrDrawOp> MakeRRectOp(GrRecordingContext*,
GrPaint&&,
const SkMatrix&,