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);
|
GrAAType aaType = this->chooseAAType(aa);
|
||||||
|
|
||||||
std::unique_ptr<GrDrawOp> op;
|
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);
|
assert_alive(paint);
|
||||||
op = GrFillRRectOp::Make(
|
op = GrFillRRectOp::Make(
|
||||||
fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
|
fContext, aaType, viewMatrix, rrect, *this->caps(), std::move(paint));
|
||||||
@ -1135,7 +1144,6 @@ void GrRenderTargetContext::drawRRect(const GrClip& origClip,
|
|||||||
assert_alive(paint);
|
assert_alive(paint);
|
||||||
op = GrOvalOpFactory::MakeRRectOp(
|
op = GrOvalOpFactory::MakeRRectOp(
|
||||||
fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
|
fContext, std::move(paint), viewMatrix, rrect, stroke, this->caps()->shaderCaps());
|
||||||
|
|
||||||
}
|
}
|
||||||
if (op) {
|
if (op) {
|
||||||
this->addDrawOp(*clip, std::move(op));
|
this->addDrawOp(*clip, std::move(op));
|
||||||
@ -1534,20 +1542,23 @@ void GrRenderTargetContext::drawOval(const GrClip& clip,
|
|||||||
GrAAType aaType = this->chooseAAType(aa);
|
GrAAType aaType = this->chooseAAType(aa);
|
||||||
|
|
||||||
std::unique_ptr<GrDrawOp> op;
|
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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// ovals the exact same way we do round rects.
|
||||||
//
|
assert_alive(paint);
|
||||||
// However, we still don't draw true circles as round rects in coverage mode, because it can
|
op = GrFillRRectOp::Make(fContext, aaType, viewMatrix, SkRRect::MakeOval(oval),
|
||||||
// cause perf regressions on some platforms as compared to the dedicated circle Op.
|
*this->caps(), std::move(paint));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!op && GrAAType::kCoverage == aaType) {
|
if (!op && GrAAType::kCoverage == aaType) {
|
||||||
assert_alive(paint);
|
assert_alive(paint);
|
||||||
|
@ -2907,6 +2907,58 @@ private:
|
|||||||
typedef GrMeshDrawOp INHERITED;
|
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,
|
static std::unique_ptr<GrDrawOp> make_rrect_op(GrRecordingContext* context,
|
||||||
GrPaint&& paint,
|
GrPaint&& paint,
|
||||||
const SkMatrix& viewMatrix,
|
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;
|
SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style;
|
||||||
bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
|
bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
|
||||||
|
|
||||||
bool isCircular = (xRadius == yRadius);
|
|
||||||
if (hasStroke) {
|
if (hasStroke) {
|
||||||
if (SkStrokeRec::kHairline_Style == style) {
|
if (SkStrokeRec::kHairline_Style == style) {
|
||||||
scaledStroke.set(1, 1);
|
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]));
|
strokeWidth * (viewMatrix[SkMatrix::kMSkewX] + viewMatrix[SkMatrix::kMScaleY]));
|
||||||
}
|
}
|
||||||
|
|
||||||
isCircular = isCircular && scaledStroke.fX == scaledStroke.fY;
|
// if half of strokewidth is greater than radius, we don't handle that right now
|
||||||
// for non-circular rrects, if half of strokewidth is greater than radius,
|
if ((SK_ScalarHalf * scaledStroke.fX > xRadius ||
|
||||||
// we don't handle that right now
|
SK_ScalarHalf * scaledStroke.fY > yRadius)) {
|
||||||
if (!isCircular && (SK_ScalarHalf * scaledStroke.fX > xRadius ||
|
|
||||||
SK_ScalarHalf * scaledStroke.fY > yRadius)) {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The matrix may have a rotation by an odd multiple of 90 degrees.
|
// 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(xRadius, yRadius);
|
||||||
std::swap(scaledStroke.fX, scaledStroke.fY);
|
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 the corners are circles, use the circle renderer
|
||||||
if (isCircular) {
|
return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
|
||||||
return CircularRRectOp::Make(context, std::move(paint), viewMatrix, bounds, xRadius,
|
xRadius, yRadius, scaledStroke, isStrokeOnly);
|
||||||
scaledStroke.fX, isStrokeOnly);
|
|
||||||
// otherwise we use the ellipse renderer
|
|
||||||
} else {
|
|
||||||
return EllipticalRRectOp::Make(context, std::move(paint), viewMatrix, bounds,
|
|
||||||
xRadius, yRadius, scaledStroke, isStrokeOnly);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrRecordingContext* context,
|
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,
|
std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* context,
|
||||||
GrPaint&& paint,
|
GrPaint&& paint,
|
||||||
const SkMatrix& viewMatrix,
|
const SkMatrix& viewMatrix,
|
||||||
@ -3014,36 +3099,7 @@ std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrRecordingContext* contex
|
|||||||
SkScalar width = oval.width();
|
SkScalar width = oval.width();
|
||||||
if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
|
if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
|
||||||
circle_stays_circle(viewMatrix)) {
|
circle_stays_circle(viewMatrix)) {
|
||||||
auto r = width / 2.f;
|
return MakeCircleOp(context, std::move(paint), viewMatrix, oval, style, shaderCaps);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.pathEffect()) {
|
if (style.pathEffect()) {
|
||||||
@ -3174,6 +3230,35 @@ GR_DRAW_OP_TEST_DEFINE(DIEllipseOp) {
|
|||||||
GrTest::TestStrokeRec(random));
|
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) {
|
GR_DRAW_OP_TEST_DEFINE(RRectOp) {
|
||||||
SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
|
SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
|
||||||
const SkRRect& rrect = GrTest::TestRRectSimple(random);
|
const SkRRect& rrect = GrTest::TestRRectSimple(random);
|
||||||
|
@ -26,6 +26,13 @@ class SkStrokeRec;
|
|||||||
*/
|
*/
|
||||||
class GrOvalOpFactory {
|
class GrOvalOpFactory {
|
||||||
public:
|
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*,
|
static std::unique_ptr<GrDrawOp> MakeOvalOp(GrRecordingContext*,
|
||||||
GrPaint&&,
|
GrPaint&&,
|
||||||
const SkMatrix&,
|
const SkMatrix&,
|
||||||
@ -33,6 +40,13 @@ public:
|
|||||||
const GrStyle& style,
|
const GrStyle& style,
|
||||||
const GrShaderCaps*);
|
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*,
|
static std::unique_ptr<GrDrawOp> MakeRRectOp(GrRecordingContext*,
|
||||||
GrPaint&&,
|
GrPaint&&,
|
||||||
const SkMatrix&,
|
const SkMatrix&,
|
||||||
|
Loading…
Reference in New Issue
Block a user