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:
parent
e14cfbec68
commit
64b858986b
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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&,
|
||||
|
Loading…
Reference in New Issue
Block a user