Add quad type and persp/aa utilities to GrQuad
This refactor makes some of the quad logic in GrTextureOp available for
use with other quad GrOps.
Bug: skia:
Change-Id: I1c173cfdf61b33c8422ddd8b91406a970a1c8e5d
Reviewed-on: https://skia-review.googlesource.com/c/163253
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/GrQuad.cpp b/src/gpu/GrQuad.cpp
index 93bd761..5adb575 100644
--- a/src/gpu/GrQuad.cpp
+++ b/src/gpu/GrQuad.cpp
@@ -7,6 +7,59 @@
#include "GrQuad.h"
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Functions for identifying the quad type from its coordinates, which are kept debug-only since
+// production code should rely on the matrix to derive the quad type more efficiently. These are
+// useful in asserts that the quad type is as expected.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef SK_DEBUG
+// Allow some tolerance from floating point matrix transformations, but SkScalarNearlyEqual doesn't
+// support comparing infinity, and coords_form_rect should return true for infinite edges
+#define NEARLY_EQUAL(f1, f2) (f1 == f2 || SkScalarNearlyEqual(f1, f2, 1e-5f))
+
+// This is not the most performance critical function; code using GrQuad should rely on the faster
+// quad type from matrix path, so this will only be called as part of SkASSERT.
+static bool coords_form_rect(const float xs[4], const float ys[4]) {
+ return (NEARLY_EQUAL(xs[0], xs[1]) && NEARLY_EQUAL(xs[2], xs[3]) &&
+ NEARLY_EQUAL(ys[0], ys[2]) && NEARLY_EQUAL(ys[1], ys[3])) ||
+ (NEARLY_EQUAL(xs[0], xs[2]) && NEARLY_EQUAL(xs[1], xs[3]) &&
+ NEARLY_EQUAL(ys[0], ys[1]) && NEARLY_EQUAL(ys[2], ys[3]));
+}
+
+GrQuadType GrQuad::quadType() const {
+ // Since GrQuad applies any perspective information at construction time, there's only two
+ // types to choose from.
+ return coords_form_rect(fX, fY) ? GrQuadType::kRect_QuadType : GrQuadType::kStandard_QuadType;
+}
+
+GrQuadType GrPerspQuad::quadType() const {
+ if (this->hasPerspective()) {
+ return GrQuadType::kPerspective_QuadType;
+ } else {
+ // Rect or standard quad, can ignore w since they are all ones
+ return coords_form_rect(fX, fY) ? GrQuadType::kRect_QuadType
+ : GrQuadType::kStandard_QuadType;
+ }
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool aa_affects_rect(float ql, float qt, float qr, float qb) {
+ return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb);
+}
+
+GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix) {
+ if (matrix.rectStaysRect()) {
+ return GrQuadType::kRect_QuadType;
+ } else if (matrix.hasPerspective()) {
+ return GrQuadType::kPerspective_QuadType;
+ } else {
+ return GrQuadType::kStandard_QuadType;
+ }
+}
+
GrQuad::GrQuad(const SkRect& rect, const SkMatrix& m) {
SkMatrix::TypeMask tm = m.getType();
if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
@@ -44,6 +97,11 @@
}
}
+bool GrQuad::aaHasEffectOnRect() const {
+ SkASSERT(this->quadType() == GrQuadType::kRect_QuadType);
+ return aa_affects_rect(fX[0], fY[0], fX[3], fY[3]);
+}
+
GrPerspQuad::GrPerspQuad(const SkRect& rect, const SkMatrix& m) {
SkMatrix::TypeMask tm = m.getType();
if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
@@ -83,3 +141,9 @@
}
}
}
+
+bool GrPerspQuad::aaHasEffectOnRect() const {
+ SkASSERT(this->quadType() == GrQuadType::kRect_QuadType);
+ // If rect, ws must all be 1s so no need to divide
+ return aa_affects_rect(fX[0], fY[0], fX[3], fY[3]);
+}
diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h
index 4a104aa..94fe745 100644
--- a/src/gpu/GrQuad.h
+++ b/src/gpu/GrQuad.h
@@ -13,6 +13,24 @@
#include "SkPoint.h"
#include "SkPoint3.h"
+// Rectangles transformed by matrices (view or local) can be classified in three ways:
+// 1. Stays a rectangle - the matrix rectStaysRect() is true, or x(0) == x(1) && x(2) == x(3)
+// and y(0) == y(2) && y(1) == y(3). Or under mirrors, x(0) == x(2) && x(1) == x(3) and
+// y(0) == y(1) && y(2) == y(3).
+// 2. Is a quadrilateral - the matrix does not have perspective, but may rotate or skew, or
+// ws() == all ones.
+// 3. Is a perspective quad - the matrix has perspective, subsuming all previous quad types.
+enum class GrQuadType {
+ kRect_QuadType,
+ kStandard_QuadType,
+ kPerspective_QuadType
+};
+
+// If an SkRect is transformed by this matrix, what class of quad is required to represent it. Since
+// quadType() is only provided on Gr[Persp]Quad in debug builds, production code should use this
+// to efficiently determine quad types.
+GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix);
+
/**
* GrQuad is a collection of 4 points which can be used to represent an arbitrary quadrilateral. The
* points make a triangle strip with CCW triangles (top-left, bottom-left, top-right, bottom-right).
@@ -49,6 +67,13 @@
Sk4f x4f() const { return Sk4f::Load(fX); }
Sk4f y4f() const { return Sk4f::Load(fY); }
+ // True if anti-aliasing affects this quad. Requires quadType() == kRect_QuadType
+ bool aaHasEffectOnRect() const;
+
+#ifdef SK_DEBUG
+ GrQuadType quadType() const;
+#endif
+
private:
float fX[4];
float fY[4];
@@ -80,6 +105,15 @@
Sk4f w4f() const { return Sk4f::Load(fW); }
Sk4f iw4f() const { return Sk4f::Load(fIW); }
+ bool hasPerspective() const { return (w4f() != Sk4f(1.f)).anyTrue(); }
+
+ // True if anti-aliasing affects this quad. Requires quadType() == kRect_QuadType
+ bool aaHasEffectOnRect() const;
+
+#ifdef SK_DEBUG
+ GrQuadType quadType() const;
+#endif
+
private:
float fX[4];
float fY[4];
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 0c44446..f7a305f 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -351,7 +351,7 @@
GrQuadAAFlags aaFlags, const SkRect& texRect) {
// Should be kNone for non-AA and kAll for MSAA.
SkASSERT(aaFlags == GrQuadAAFlags::kNone || aaFlags == GrQuadAAFlags::kAll);
- SkASSERT((quad.w4f() == Sk4f(1.f)).allTrue());
+ SkASSERT(!quad.hasPerspective());
SkPointPriv::SetRectTriStrip(&vertices[0].fTextureCoords, texRect, sizeof(V));
for (int i = 0; i < 4; ++i) {
vertices[i].fPosition = {quad.x(i), quad.y(i)};
@@ -376,7 +376,7 @@
public:
static void AssignPositionsAndTexCoords(V* vertices, const GrPerspQuad& quad,
GrQuadAAFlags aaFlags, const SkRect& texRect) {
- SkASSERT((quad.w4f() == Sk4f(1.f)).allTrue());
+ SkASSERT(!quad.hasPerspective());
if (aaFlags == GrQuadAAFlags::kNone) {
for (int i = 0; i < 4; ++i) {
vertices[i].fPosition = {quad.x(i), quad.y(i)};
@@ -593,17 +593,8 @@
DomainAssigner<V>::Assign(vertices, domain, filter, srcRect, origin, iw, ih);
}
-static bool aa_has_effect_for_rect_stays_rect(const GrPerspQuad& quad) {
- SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
- float ql = quad.x(0);
- float qt = quad.y(0);
- float qr = quad.x(3);
- float qb = quad.y(3);
- return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb);
-}
-
static bool filter_has_effect_for_rect_stays_rect(const GrPerspQuad& quad, const SkRect& srcRect) {
- SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
+ SkASSERT(quad.quadType() == GrQuadType::kRect_QuadType);
float ql = quad.x(0);
float qt = quad.y(0);
float qr = quad.x(3);
@@ -751,7 +742,7 @@
SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
constraint == SkCanvas::kFast_SrcRectConstraint);
if (viewMatrix.rectStaysRect()) {
- if (this->aaType() == GrAAType::kCoverage && !aa_has_effect_for_rect_stays_rect(quad)) {
+ if (this->aaType() == GrAAType::kCoverage && !quad.aaHasEffectOnRect()) {
fAAType = static_cast<unsigned>(GrAAType::kNone);
aaFlags = GrQuadAAFlags::kNone;
}
@@ -811,8 +802,7 @@
break;
case GrAAType::kCoverage:
if (rectStaysRect) {
- if (aaFlags != GrQuadAAFlags::kNone &&
- !aa_has_effect_for_rect_stays_rect(quad)) {
+ if (aaFlags != GrQuadAAFlags::kNone && !quad.aaHasEffectOnRect()) {
aaFlags = GrQuadAAFlags::kNone;
}
}