Reland "Prefer using GrOvalOpFactory over GrFillRRect for circles and axis-aligned circular roundrects."

This is a reland of 731454a085

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>
This commit is contained in:
Jim Van Verth 2019-06-17 12:01:46 -04:00 committed by Skia Commit-Bot
parent e14cfbec68
commit 64b858986b
3 changed files with 166 additions and 56 deletions

View File

@ -1126,7 +1126,16 @@ void GrRenderTargetContext::drawRRect(const GrClip& origClip,
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 @@ void GrRenderTargetContext::drawRRect(const GrClip& origClip,
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 @@ void GrRenderTargetContext::drawOval(const GrClip& clip,
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);

View File

@ -2907,6 +2907,58 @@ private:
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 @@ static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
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 @@ static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
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 @@ static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
}
// 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::MakeRRectOp(GrRecordingContext* conte
///////////////////////////////////////////////////////////////////////////////
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 @@ std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* contex
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 @@ GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
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);

View File

@ -26,6 +26,13 @@ class SkStrokeRec;
*/
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 @@ public:
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&,