Revert "Track quad type on GrQuad directly"

This reverts commit 85766c4cd367596268588f53631a1c43376c929d.

Reason for revert: red - so much red

Original change's description:
> Track quad type on GrQuad directly
> 
> This makes subsequent higher-level drawing APIs more compact, and
> shouldn't come with a performance hit. Everywhere we used to need
> a GrPerspQuad, we'd also take the GrQuadType as a second argument.
> The quad list types already deconstruct the quad so there's no
> extra overhead in the op's memory usage.
> 
> It also improves usability and decreases the likelihood that an
> incorrect quad type is ever associated with the quad.
> 
> Bug: skia:
> Change-Id: Iba908fb133ad664744a5788a7088d90de0d3a1c2
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/214820
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>

TBR=bsalomon@google.com,robertphillips@google.com,michaelludwig@google.com

Change-Id: Ied594673116fb901287b334fea41da3c71494fb1
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215607
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrQuad.cpp b/src/gpu/GrQuad.cpp
index bd01a68..379d1f6 100644
--- a/src/gpu/GrQuad.cpp
+++ b/src/gpu/GrQuad.cpp
@@ -69,39 +69,10 @@
     *ys = V4f{skQuadPts[0].fY, skQuadPts[3].fY, skQuadPts[1].fY, skQuadPts[2].fY};
 }
 
-// If an SkRect is transformed by this matrix, what class of quad is required to represent it.
-static GrQuadType quad_type_for_transformed_rect(const SkMatrix& matrix) {
-    if (matrix.rectStaysRect()) {
-        return GrQuadType::kRect;
-    } else if (matrix.preservesRightAngles()) {
-        return GrQuadType::kRectilinear;
-    } else if (matrix.hasPerspective()) {
-        return GrQuadType::kPerspective;
-    } else {
-        return GrQuadType::kStandard;
-    }
-}
-
-// Perform minimal analysis of 'pts' (which are suitable for MakeFromSkQuad), and determine a
-// quad type that will be as minimally general as possible.
-static GrQuadType quad_type_for_points(const SkPoint pts[4], const SkMatrix& matrix) {
-    if (matrix.hasPerspective()) {
-        return GrQuadType::kPerspective;
-    }
-    // If 'pts' was formed by SkRect::toQuad() and not transformed further, it is safe to use the
-    // quad type derived from 'matrix'. Otherwise don't waste any more time and assume kStandard
-    // (most general 2D quad).
-    if ((pts[0].fX == pts[3].fX && pts[1].fX == pts[2].fX) &&
-        (pts[0].fY == pts[1].fY && pts[2].fY == pts[3].fY)) {
-        return quad_type_for_transformed_rect(matrix);
-    } else {
-        return GrQuadType::kStandard;
-    }
-}
-
 template <typename Q>
 void GrResolveAATypeForQuad(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags,
-                            const Q& quad, GrAAType* outAAType, GrQuadAAFlags* outEdgeFlags) {
+                            const Q& quad, GrQuadType knownType,
+                            GrAAType* outAAType, GrQuadAAFlags* outEdgeFlags) {
     // Most cases will keep the requested types unchanged
     *outAAType = requestedAAType;
     *outEdgeFlags = requestedEdgeFlags;
@@ -115,7 +86,7 @@
             } else {
                 // For coverage AA, if the quad is a rect and it lines up with pixel boundaries
                 // then overall aa and per-edge aa can be completely disabled
-                if (quad.quadType() == GrQuadType::kRect && !quad.aaHasEffectOnRect()) {
+                if (knownType == GrQuadType::kRect && !quad.aaHasEffectOnRect()) {
                     *outAAType = GrAAType::kNone;
                     *outEdgeFlags = GrQuadAAFlags::kNone;
                 }
@@ -136,46 +107,58 @@
 };
 
 // Instantiate GrResolve... for GrQuad and GrPerspQuad
-template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrQuad&,
+template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrQuad&, GrQuadType,
                                      GrAAType*, GrQuadAAFlags*);
-template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrPerspQuad&,
+template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrPerspQuad&, GrQuadType,
                                      GrAAType*, GrQuadAAFlags*);
 
+GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix) {
+    if (matrix.rectStaysRect()) {
+        return GrQuadType::kRect;
+    } else if (matrix.preservesRightAngles()) {
+        return GrQuadType::kRectilinear;
+    } else if (matrix.hasPerspective()) {
+        return GrQuadType::kPerspective;
+    } else {
+        return GrQuadType::kStandard;
+    }
+}
+
+GrQuadType GrQuadTypeForPoints(const SkPoint pts[4], const SkMatrix& matrix) {
+    if (matrix.hasPerspective()) {
+        return GrQuadType::kPerspective;
+    }
+    // If 'pts' was formed by SkRect::toQuad() and not transformed further, it is safe to use the
+    // quad type derived from 'matrix'. Otherwise don't waste any more time and assume kStandard
+    // (most general 2D quad).
+    if ((pts[0].fX == pts[3].fX && pts[1].fX == pts[2].fX) &&
+        (pts[0].fY == pts[1].fY && pts[2].fY == pts[3].fY)) {
+        return GrQuadTypeForTransformedRect(matrix);
+    } else {
+        return GrQuadType::kStandard;
+    }
+}
+
 GrQuad GrQuad::MakeFromRect(const SkRect& rect, const SkMatrix& m) {
     V4f x, y;
     SkMatrix::TypeMask tm = m.getType();
-    GrQuadType type;
     if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
         map_rect_translate_scale(rect, m, &x, &y);
-        type = GrQuadType::kRect;
     } else {
         map_rect_general(rect, m, &x, &y, nullptr);
-        type = quad_type_for_transformed_rect(m);
-        if (type == GrQuadType::kPerspective) {
-            // While the matrix created perspective, the coordinates were projected to a 2D quad
-            // in map_rect_general since no w V4f was provided.
-            type = GrQuadType::kStandard;
-        }
     }
-    return GrQuad(x, y, type);
+    return GrQuad(x, y);
 }
 
 GrQuad GrQuad::MakeFromSkQuad(const SkPoint pts[4], const SkMatrix& matrix) {
     V4f xs, ys;
     rearrange_sk_to_gr_points(pts, &xs, &ys);
-    GrQuadType type = quad_type_for_points(pts, matrix);
-    if (type == GrQuadType::kPerspective) {
-        // While the matrix created perspective, the coordinates were projected to a 2D quad
-        // in map_rect_general since no w V4f was provided.
-        type = GrQuadType::kStandard;
-    }
-
     if (matrix.isIdentity()) {
-        return GrQuad(xs, ys, type);
+        return GrQuad(xs, ys);
     } else {
         V4f mx, my;
         map_quad_general(xs, ys, matrix, &mx, &my, nullptr);
-        return GrQuad(mx, my, type);
+        return GrQuad(mx, my);
     }
 }
 
@@ -184,8 +167,7 @@
 }
 
 // Private constructor used by GrQuadList to quickly fill in a quad's values from the channel arrays
-GrPerspQuad::GrPerspQuad(const float* xs, const float* ys, const float* ws, GrQuadType type)
-        : fType(type) {
+GrPerspQuad::GrPerspQuad(const float* xs, const float* ys, const float* ws) {
     memcpy(fX, xs, 4 * sizeof(float));
     memcpy(fY, ys, 4 * sizeof(float));
     memcpy(fW, ws, 4 * sizeof(float));
@@ -194,28 +176,24 @@
 GrPerspQuad GrPerspQuad::MakeFromRect(const SkRect& rect, const SkMatrix& m) {
     V4f x, y, w;
     SkMatrix::TypeMask tm = m.getType();
-    GrQuadType type;
     if (tm <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) {
         map_rect_translate_scale(rect, m, &x, &y);
         w = 1.f;
-        type = GrQuadType::kRect;
     } else {
         map_rect_general(rect, m, &x, &y, &w);
-        type = quad_type_for_transformed_rect(m);
     }
-    return GrPerspQuad(x, y, w, type);
+    return GrPerspQuad(x, y, w);
 }
 
 GrPerspQuad GrPerspQuad::MakeFromSkQuad(const SkPoint pts[4], const SkMatrix& matrix) {
     V4f xs, ys;
     rearrange_sk_to_gr_points(pts, &xs, &ys);
-    GrQuadType type = quad_type_for_points(pts, matrix);
     if (matrix.isIdentity()) {
-        return GrPerspQuad(xs, ys, 1.f, type);
+        return GrPerspQuad(xs, ys, 1.f);
     } else {
         V4f mx, my, mw;
         map_quad_general(xs, ys, matrix, &mx, &my, &mw);
-        return GrPerspQuad(mx, my, mw, type);
+        return GrPerspQuad(mx, my, mw);
     }
 }
 
diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h
index cb2f15d..73b7310 100644
--- a/src/gpu/GrQuad.h
+++ b/src/gpu/GrQuad.h
@@ -34,12 +34,19 @@
 };
 static const int kGrQuadTypeCount = static_cast<int>(GrQuadType::kLast) + 1;
 
+// If an SkRect is transformed by this matrix, what class of quad is required to represent it.
+GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix);
+// Perform minimal analysis of 'pts' (which are suitable for MakeFromSkQuad), and determine a
+// quad type that will be as minimally general as possible.
+GrQuadType GrQuadTypeForPoints(const SkPoint pts[4], const SkMatrix& matrix);
+
 // Resolve disagreements between the overall requested AA type and the per-edge quad AA flags.
 // knownQuadType must have come from GrQuadTypeForTransformedRect with the matrix that created the
 // provided quad. Both outAAType and outEdgeFlags will be updated.
 template <typename Q>
 void GrResolveAATypeForQuad(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags,
-                            const Q& quad, GrAAType* outAAtype, GrQuadAAFlags* outEdgeFlags);
+                            const Q& quad, GrQuadType knownQuadType,
+                            GrAAType* outAAtype, GrQuadAAFlags* outEdgeFlags);
 
 /**
  * GrQuad is a collection of 4 points which can be used to represent an arbitrary quadrilateral. The
@@ -53,15 +60,17 @@
 
     explicit GrQuad(const SkRect& rect)
             : fX{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight}
-            , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom}
-            , fType(GrQuadType::kRect) {}
+            , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom} {}
 
-    GrQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys, GrQuadType type)
-            : fType(type) {
+    GrQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys) {
         xs.store(fX);
         ys.store(fY);
     }
 
+    explicit GrQuad(const SkPoint pts[4])
+            : fX{pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX}
+            , fY{pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY} {}
+
     /** Sets the quad to the rect as transformed by the matrix. */
     static GrQuad MakeFromRect(const SkRect&, const SkMatrix&);
 
@@ -86,8 +95,6 @@
     skvx::Vec<4, float> x4f() const { return skvx::Vec<4, float>::Load(fX); }
     skvx::Vec<4, float> y4f() const { return skvx::Vec<4, float>::Load(fY); }
 
-    GrQuadType quadType() const { return fType; }
-
     // True if anti-aliasing affects this quad. Only valid when quadType == kRect_QuadType
     bool aaHasEffectOnRect() const;
 
@@ -97,8 +104,6 @@
 
     float fX[4];
     float fY[4];
-
-    GrQuadType fType;
 };
 
 class GrPerspQuad {
@@ -108,21 +113,16 @@
     explicit GrPerspQuad(const SkRect& rect)
             : fX{rect.fLeft, rect.fLeft, rect.fRight, rect.fRight}
             , fY{rect.fTop, rect.fBottom, rect.fTop, rect.fBottom}
-            , fW{1.f, 1.f, 1.f, 1.f}
-            , fType(GrQuadType::kRect) {}
+            , fW{1.f, 1.f, 1.f, 1.f} {}
 
-    GrPerspQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys,
-                GrQuadType type)
-            : fType(type) {
-        SkASSERT(type != GrQuadType::kPerspective);
+    GrPerspQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys) {
         xs.store(fX);
         ys.store(fY);
         fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
     }
 
     GrPerspQuad(const skvx::Vec<4, float>& xs, const skvx::Vec<4, float>& ys,
-                const skvx::Vec<4, float>& ws, GrQuadType type)
-            : fType(type) {
+                const skvx::Vec<4, float>& ws) {
         xs.store(fX);
         ys.store(fY);
         ws.store(fW);
@@ -139,10 +139,10 @@
 
     SkPoint3 point(int i) const { return {fX[i], fY[i], fW[i]}; }
 
-    SkRect bounds() const {
+    SkRect bounds(GrQuadType type) const {
         auto x = this->x4f();
         auto y = this->y4f();
-        if (fType == GrQuadType::kPerspective) {
+        if (type == GrQuadType::kPerspective) {
             auto iw = this->iw4f();
             x *= iw;
             y *= iw;
@@ -161,9 +161,7 @@
     skvx::Vec<4, float> w4f() const { return skvx::Vec<4, float>::Load(fW); }
     skvx::Vec<4, float> iw4f() const { return 1.f / this->w4f(); }
 
-    GrQuadType quadType() const { return fType; }
-
-    bool hasPerspective() const { return fType == GrQuadType::kPerspective; }
+    bool hasPerspective() const { return any(w4f() != 1.f); }
 
     // True if anti-aliasing affects this quad. Only valid when quadType == kRect_QuadType
     bool aaHasEffectOnRect() const;
@@ -173,13 +171,11 @@
     friend class GrQuadListBase;
 
     // Copy 4 values from each of the arrays into the quad's components
-    GrPerspQuad(const float xs[4], const float ys[4], const float ws[4], GrQuadType type);
+    GrPerspQuad(const float xs[4], const float ys[4], const float ws[4]);
 
     float fX[4];
     float fY[4];
     float fW[4];
-
-    GrQuadType fType;
 };
 
 // Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
@@ -209,9 +205,9 @@
 
     GrQuadType quadType() const { return fType; }
 
-    void reserve(int count, bool needsPerspective) {
+    void reserve(int count, GrQuadType forType) {
         fXYs.reserve(count);
-        if (needsPerspective || fType == GrQuadType::kPerspective) {
+        if (forType == GrQuadType::kPerspective || fType == GrQuadType::kPerspective) {
             fWs.reserve(4 * count);
         }
     }
@@ -223,11 +219,11 @@
         const QuadData<T>& item = fXYs[i];
         if (fType == GrQuadType::kPerspective) {
             // Read the explicit ws
-            return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i, fType);
+            return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i);
         } else {
             // Ws are implicitly 1s.
             static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
-            return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs, fType);
+            return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs);
         }
     }
 
@@ -253,8 +249,8 @@
     }
 
     // Returns the added item data so that its metadata can be initialized if T is not void
-    QuadData<T>& pushBackImpl(const GrQuad& quad) {
-        this->upgradeType(quad.quadType());
+    QuadData<T>& pushBackImpl(const GrQuad& quad, GrQuadType type) {
+        this->upgradeType(type);
         QuadData<T>& item = fXYs.push_back();
         memcpy(item.fX, quad.fX, 4 * sizeof(float));
         memcpy(item.fY, quad.fY, 4 * sizeof(float));
@@ -264,8 +260,8 @@
         return item;
     }
 
-    QuadData<T>& pushBackImpl(const GrPerspQuad& quad) {
-        this->upgradeType(quad.quadType());
+    QuadData<T>& pushBackImpl(const GrPerspQuad& quad, GrQuadType type) {
+        this->upgradeType(type);
         QuadData<T>& item = fXYs.push_back();
         memcpy(item.fX, quad.fX, 4 * sizeof(float));
         memcpy(item.fY, quad.fY, 4 * sizeof(float));
@@ -313,12 +309,12 @@
         this->concatImpl(that);
     }
 
-    void push_back(const GrQuad& quad) {
-        this->pushBackImpl(quad);
+    void push_back(const GrQuad& quad, GrQuadType type) {
+        this->pushBackImpl(quad, type);
     }
 
-    void push_back(const GrPerspQuad& quad) {
-        this->pushBackImpl(quad);
+    void push_back(const GrPerspQuad& quad, GrQuadType type) {
+        this->pushBackImpl(quad, type);
     }
 
 private:
@@ -337,13 +333,13 @@
     }
 
     // Adding to the list requires metadata
-    void push_back(const GrQuad& quad, T&& metadata) {
-        QuadData<T>& item = this->pushBackImpl(quad);
+    void push_back(const GrQuad& quad, GrQuadType type, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad, type);
         item.fMetadata = std::move(metadata);
     }
 
-    void push_back(const GrPerspQuad& quad, T&& metadata) {
-        QuadData<T>& item = this->pushBackImpl(quad);
+    void push_back(const GrPerspQuad& quad, GrQuadType type, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad, type);
         item.fMetadata = std::move(metadata);
     }
 
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index aad26c1..1889b0b 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -64,32 +64,35 @@
                                           GrQuadAAFlags edgeAA,
                                           const GrUserStencilSettings* stencilSettings,
                                           const GrPerspQuad& deviceQuad,
-                                          const GrPerspQuad& localQuad) {
+                                          GrQuadType deviceQuadType,
+                                          const GrPerspQuad& localQuad,
+                                          GrQuadType localQuadType) {
         // Clean up deviations between aaType and edgeAA
-        GrResolveAATypeForQuad(aaType, edgeAA, deviceQuad, &aaType, &edgeAA);
+        GrResolveAATypeForQuad(aaType, edgeAA, deviceQuad, deviceQuadType, &aaType, &edgeAA);
         return Helper::FactoryHelper<FillRectOp>(context, std::move(paint), aaType, edgeAA,
-                stencilSettings, deviceQuad, localQuad);
+                stencilSettings, deviceQuad, deviceQuadType, localQuad, localQuadType);
     }
 
     // aaType is passed to Helper in the initializer list, so incongruities between aaType and
     // edgeFlags must be resolved prior to calling this constructor.
     FillRectOp(Helper::MakeArgs args, SkPMColor4f paintColor, GrAAType aaType,
                GrQuadAAFlags edgeFlags, const GrUserStencilSettings* stencil,
-               const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad)
+               const GrPerspQuad& deviceQuad, GrQuadType deviceQuadType,
+               const GrPerspQuad& localQuad, GrQuadType localQuadType)
             : INHERITED(ClassID())
             , fHelper(args, aaType, stencil) {
         // The color stored with the quad is the clear color if a scissor-clear is decided upon
         // when executing the op.
-        fDeviceQuads.push_back(deviceQuad, { paintColor, edgeFlags });
+        fDeviceQuads.push_back(deviceQuad, deviceQuadType, { paintColor, edgeFlags });
 
         if (!fHelper.isTrivial()) {
             // Conservatively keep track of the local coordinates; it may be that the paint doesn't
             // need them after analysis is finished. If the paint is known to be solid up front they
             // can be skipped entirely.
-            fLocalQuads.push_back(localQuad);
+            fLocalQuads.push_back(localQuad, localQuadType);
         }
-        this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
-                        IsZeroArea::kNo);
+        this->setBounds(deviceQuad.bounds(deviceQuadType),
+                        HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
     }
 
     const char* name() const override { return "FillRectOp"; }
@@ -294,7 +297,8 @@
     // used with quad sets, which uses the same view matrix for each quad so this assumes that the
     // device quad type of the new quad is the same as the op's.
     void addQuad(const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad,
-                 const SkPMColor4f& color, GrQuadAAFlags edgeAA, GrAAType aaType) {
+                 GrQuadType localQuadType, const SkPMColor4f& color, GrQuadAAFlags edgeAA,
+                 GrAAType aaType) {
         // The new quad's aa type should be the same as the first quad's or none, except when the
         // first quad's aa type was already downgraded to none, in which case the stored type must
         // be lifted to back to the requested type.
@@ -310,12 +314,12 @@
 
         // Update the bounds and add the quad to this op's storage
         SkRect newBounds = this->bounds();
-        newBounds.joinPossiblyEmptyRect(deviceQuad.bounds());
+        newBounds.joinPossiblyEmptyRect(deviceQuad.bounds(fDeviceQuads.quadType()));
         this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
                         IsZeroArea::kNo);
-        fDeviceQuads.push_back(deviceQuad, { color, edgeAA });
+        fDeviceQuads.push_back(deviceQuad, fDeviceQuads.quadType(), { color, edgeAA });
         if (!fHelper.isTrivial()) {
-            fLocalQuads.push_back(localQuad);
+            fLocalQuads.push_back(localQuad, localQuadType);
         }
     }
 
@@ -352,8 +356,10 @@
                                       const SkMatrix& viewMatrix,
                                       const SkRect& rect,
                                       const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad::MakeFromRect(rect, viewMatrix), GrPerspQuad(rect));
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix),  dstQuadType,
+                            GrPerspQuad(rect), GrQuadType::kRect);
 }
 
 std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrRecordingContext* context,
@@ -364,9 +370,11 @@
                                                      const SkMatrix& localMatrix,
                                                      const SkRect& rect,
                                                      const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
+    GrQuadType localQuadType = GrQuadTypeForTransformedRect(localMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad::MakeFromRect(rect, viewMatrix),
-                            GrPerspQuad::MakeFromRect(rect, localMatrix));
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix), dstQuadType,
+                            GrPerspQuad::MakeFromRect(rect, localMatrix), localQuadType);
 }
 
 std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrRecordingContext* context,
@@ -377,8 +385,10 @@
                                                    const SkRect& rect,
                                                    const SkRect& localRect,
                                                    const GrUserStencilSettings* stencilSettings) {
+    GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad::MakeFromRect(rect, viewMatrix), GrPerspQuad(localRect));
+                            GrPerspQuad::MakeFromRect(rect, viewMatrix), dstQuadType,
+                            GrPerspQuad(localRect), GrQuadType::kRect);
 }
 
 std::unique_ptr<GrDrawOp> MakePerEdgeQuad(GrRecordingContext* context,
@@ -389,10 +399,12 @@
                                           const SkPoint quad[4],
                                           const SkPoint localQuad[4],
                                           const GrUserStencilSettings* stencilSettings) {
+    GrQuadType deviceType = GrQuadTypeForPoints(quad, viewMatrix);
+    GrQuadType localType = GrQuadTypeForPoints(localQuad ? localQuad : quad, SkMatrix::I());
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
-                            GrPerspQuad::MakeFromSkQuad(quad, viewMatrix),
+                            GrPerspQuad::MakeFromSkQuad(quad, viewMatrix), deviceType,
                             GrPerspQuad::MakeFromSkQuad(localQuad ? localQuad : quad,
-                                                        SkMatrix::I()));
+                                                        SkMatrix::I()), localType);
 }
 
 std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
@@ -404,12 +416,14 @@
                                   const GrUserStencilSettings* stencilSettings) {
     // First make a draw op for the first quad in the set
     SkASSERT(cnt > 0);
+    GrQuadType deviceQuadType = GrQuadTypeForTransformedRect(viewMatrix);
 
     paint.setColor4f(quads[0].fColor);
     std::unique_ptr<GrDrawOp> op = FillRectOp::Make(context, std::move(paint), aaType,
             quads[0].fAAFlags, stencilSettings,
-            GrPerspQuad::MakeFromRect(quads[0].fRect, viewMatrix),
-            GrPerspQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix));
+            GrPerspQuad::MakeFromRect(quads[0].fRect, viewMatrix), deviceQuadType,
+            GrPerspQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix),
+            GrQuadTypeForTransformedRect(quads[0].fLocalMatrix));
     auto* fillRects = op->cast<FillRectOp>();
 
     // Accumulate remaining quads similar to onCombineIfPossible() without creating an op
@@ -418,12 +432,13 @@
 
         GrAAType resolvedAA;
         GrQuadAAFlags resolvedEdgeFlags;
-        GrResolveAATypeForQuad(aaType, quads[i].fAAFlags, deviceQuad,
+        GrResolveAATypeForQuad(aaType, quads[i].fAAFlags, deviceQuad, deviceQuadType,
                                &resolvedAA, &resolvedEdgeFlags);
 
         fillRects->addQuad(deviceQuad,
                            GrPerspQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
-                           quads[i].fColor, resolvedEdgeFlags,resolvedAA);
+                           GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), quads[i].fColor,
+                           resolvedEdgeFlags,resolvedAA);
     }
 
     return op;
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 38c5955..62509d1 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -742,9 +742,6 @@
 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
                  const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
                  GrQuadAAFlags aaFlags) {
-    SkASSERT(deviceQuad.quadType() <= spec.deviceQuadType());
-    SkASSERT(!spec.hasLocalCoords() || localQuad.quadType() <= spec.localQuadType());
-
     CoverageMode mode = get_mode_for_spec(spec);
 
     // Load position data into V4fs (always x, y, and load w to avoid branching down the road)
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index fb28f43..a39535a 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -91,9 +91,6 @@
     // based on the configuration in the vertex spec; if that attribute is disabled in the spec,
     // then its corresponding function argument is ignored.
     //
-    // Tessellation is based on the quad type of the vertex spec, not the provided GrPerspQuad's
-    // so that all quads in a batch are tessellated the same.
-    //
     // Returns the advanced pointer in vertices.
     void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
                      const SkPMColor4f& color, const GrPerspQuad& localQuad, const SkRect& domain,
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 491704f..15e73a0 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -99,7 +99,7 @@
     if (origin == kBottomLeft_GrSurfaceOrigin) {
         ys = h - ys;
     }
-    return GrPerspQuad(xs, ys, GrQuadType::kStandard);
+    return GrPerspQuad(xs, ys);
 }
 
 /**
@@ -120,8 +120,9 @@
                                           const SkMatrix& viewMatrix,
                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
         GrPerspQuad dstQuad = GrPerspQuad::MakeFromRect(dstRect, viewMatrix);
+        GrQuadType dstQuadType = GrQuadTypeForTransformedRect(viewMatrix);
 
-        if (dstQuad.quadType() == GrQuadType::kRect) {
+        if (dstQuadType == GrQuadType::kRect) {
             // Disable filtering if possible (note AA optimizations for rects are automatically
             // handled above in GrResolveAATypeForQuad).
             if (filter != GrSamplerState::Filter::kNearest &&
@@ -133,8 +134,8 @@
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
         // srcRect provides both local coords and domain (if needed), so use nullptr for srcQuad
         return pool->allocate<TextureOp>(
-                std::move(proxy), filter, color, dstQuad, srcRect, constraint,
-                nullptr, aaType, aaFlags, std::move(textureColorSpaceXform));
+                std::move(proxy), filter, color, dstQuad, dstQuadType, srcRect, constraint,
+                nullptr, GrQuadType::kRect, aaType, aaFlags, std::move(textureColorSpaceXform));
     }
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           sk_sp<GrTextureProxy> proxy,
@@ -148,7 +149,9 @@
                                           const SkMatrix& viewMatrix,
                                           sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
         GrPerspQuad grDstQuad = GrPerspQuad::MakeFromSkQuad(dstQuad, viewMatrix);
+        GrQuadType dstQuadType = GrQuadTypeForPoints(dstQuad, viewMatrix);
         GrPerspQuad grSrcQuad = GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I());
+        GrQuadType srcQuadType = GrQuadTypeForPoints(srcQuad, SkMatrix::I());
 
         // If constraint remains fast, the value in srcRect will be ignored since srcQuads provides
         // the local coordinates and a domain won't be used.
@@ -162,8 +165,9 @@
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
         // Pass domain as srcRect if provided, but send srcQuad as a GrPerspQuad for local coords
         return pool->allocate<TextureOp>(
-                std::move(proxy), filter, color, grDstQuad, srcRect, constraint, &grSrcQuad,
-                aaType, aaFlags, std::move(textureColorSpaceXform));
+                std::move(proxy), filter, color, grDstQuad, dstQuadType, srcRect, constraint,
+                &grSrcQuad, srcQuadType, aaType, aaFlags,
+                std::move(textureColorSpaceXform));
     }
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           const GrRenderTargetContext::TextureSetEntry set[],
@@ -255,8 +259,9 @@
     // If srcQuad is provided, it will be used for the local coords instead of srcRect, although
     // srcRect will still specify the domain constraint if needed.
     TextureOp(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, const SkPMColor4f& color,
-              const GrPerspQuad& dstQuad, const SkRect& srcRect,
-              SkCanvas::SrcRectConstraint constraint, const GrPerspQuad* srcQuad, GrAAType aaType,
+              const GrPerspQuad& dstQuad, GrQuadType dstQuadType,
+              const SkRect& srcRect, SkCanvas::SrcRectConstraint constraint,
+              const GrPerspQuad* srcQuad, GrQuadType srcQuadType, GrAAType aaType,
               GrQuadAAFlags aaFlags, sk_sp<GrColorSpaceXform> textureColorSpaceXform)
             : INHERITED(ClassID())
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
@@ -264,7 +269,7 @@
             , fFinalized(0) {
         // Clean up disparities between the overall aa type and edge configuration and apply
         // optimizations based on the rect and matrix when appropriate
-        GrResolveAATypeForQuad(aaType, aaFlags, dstQuad, &aaType, &aaFlags);
+        GrResolveAATypeForQuad(aaType, aaFlags, dstQuad, dstQuadType, &aaType, &aaFlags);
         fAAType = static_cast<unsigned>(aaType);
 
         // We expect our caller to have already caught this optimization.
@@ -283,14 +288,14 @@
         Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes
                                                                           : Domain::kNo;
         // Initially, if srcQuad is provided it will always be at index 0 of fSrcQuads
-        fQuads.push_back(dstQuad, {color, srcRect, srcQuad ? 0 : -1, domain, aaFlags});
+        fQuads.push_back(dstQuad, dstQuadType, {color, srcRect, srcQuad ? 0 : -1, domain, aaFlags});
         if (srcQuad) {
-            fSrcQuads.push_back(*srcQuad);
+            fSrcQuads.push_back(*srcQuad, srcQuadType);
         }
         fProxyCnt = 1;
         fProxies[0] = {proxy.release(), 1};
-        this->setBounds(dstQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
-                        IsZeroArea::kNo);
+        auto bounds = dstQuad.bounds(dstQuadType);
+        this->setBounds(bounds, HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
         fDomain = static_cast<unsigned>(domain);
     }
     TextureOp(const GrRenderTargetContext::TextureSetEntry set[], int cnt,
@@ -308,7 +313,7 @@
         // Most dst rects are transformed by the same view matrix, so their quad types start
         // identical, unless an entry provides a dstClip or additional transform that changes it.
         // The quad list will automatically adapt to that.
-        fQuads.reserve(cnt, viewMatrix.hasPerspective());
+        fQuads.reserve(cnt, GrQuadTypeForTransformedRect(viewMatrix));
         bool allOpaque = true;
         Domain netDomain = Domain::kNo;
         for (unsigned p = 0; p < fProxyCnt; ++p) {
@@ -326,19 +331,22 @@
             auto quad = set[p].fDstClipQuad == nullptr ?
                     GrPerspQuad::MakeFromRect(set[p].fDstRect, ctm) :
                     GrPerspQuad::MakeFromSkQuad(set[p].fDstClipQuad, ctm);
+            GrQuadType quadType =
+                    set[p].fDstClipQuad ? GrQuadTypeForPoints(set[p].fDstClipQuad, ctm)
+                                        : GrQuadTypeForTransformedRect(ctm);
 
-            bounds.joinPossiblyEmptyRect(quad.bounds());
+            bounds.joinPossiblyEmptyRect(quad.bounds(quadType));
             GrQuadAAFlags aaFlags;
             // Don't update the overall aaType, might be inappropriate for some of the quads
             GrAAType aaForQuad;
-            GrResolveAATypeForQuad(aaType, set[p].fAAFlags, quad, &aaForQuad, &aaFlags);
+            GrResolveAATypeForQuad(aaType, set[p].fAAFlags, quad, quadType, &aaForQuad, &aaFlags);
             // Resolve sets aaForQuad to aaType or None, there is never a change between aa methods
             SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType);
             if (overallAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) {
                 overallAAType = aaType;
             }
             if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
-                mustFilter = quad.quadType() != GrQuadType::kRect ||
+                mustFilter = quadType != GrQuadType::kRect ||
                              GrTextureOp::GetFilterHasEffect(ctm, set[p].fSrcRect,
                                                              set[p].fDstRect);
             }
@@ -362,10 +370,12 @@
                 // but with respect to srcRect
                 SkPoint srcQuad[4];
                 GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcQuad, 4);
-                fSrcQuads.push_back(GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()));
+                fSrcQuads.push_back(GrPerspQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()),
+                                    GrQuadTypeForPoints(srcQuad, SkMatrix::I()));
                 srcQuadIndex = fSrcQuads.count() - 1;
             }
-            fQuads.push_back(quad, {color, set[p].fSrcRect, srcQuadIndex, domainForQuad, aaFlags});
+            fQuads.push_back(quad, quadType,
+                             {color, set[p].fSrcRect, srcQuadIndex, domainForQuad, aaFlags});
         }
         fAAType = static_cast<unsigned>(overallAAType);
         if (!mustFilter) {
diff --git a/tests/GrQuadListTest.cpp b/tests/GrQuadListTest.cpp
index 3100c37..a9cd9b3 100644
--- a/tests/GrQuadListTest.cpp
+++ b/tests/GrQuadListTest.cpp
@@ -55,8 +55,8 @@
     GrQuadList list2D;
     // Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
     // the coordinates make sense (including that the type was lifted to perspective).
-    list2D.push_back(make_2d_quad());
-    list2D.push_back(make_2d_persp_quad());
+    list2D.push_back(make_2d_quad(), GrQuadType::kRect);
+    list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
 
     // Check 2D state of the list
     ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
@@ -66,7 +66,7 @@
     ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1");
 
     // Force the 2D quads to be updated to store ws by adding a perspective quad
-    list2D.push_back(make_3d_persp_quad());
+    list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
     ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
             "Expected 2D list to be upgraded to perspective");
 
@@ -81,9 +81,9 @@
     // Now make a list that starts with a 3D persp quad, then has conventional quads added to it
     // and make sure its state is correct
     GrQuadList list3D;
-    list3D.push_back(make_3d_persp_quad());
-    list3D.push_back(make_2d_persp_quad());
-    list3D.push_back(make_2d_quad());
+    list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+    list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
+    list3D.push_back(make_2d_quad(), GrQuadType::kRect);
 
     ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
     ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
@@ -96,8 +96,8 @@
     GrTQuadList<TestData> list2D;
     // Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
     // the coordinates make sense (including that the type was lifted to perspective).
-    list2D.push_back(make_2d_quad(), {1, 1.f});
-    list2D.push_back(make_2d_persp_quad(), {2, 2.f});
+    list2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+    list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
 
     // Check 2D state of the list
     ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
@@ -111,7 +111,7 @@
             "Incorrect metadata at i=1");
 
     // Force the 2D quads to be updated to store ws by adding a perspective quad
-    list2D.push_back(make_3d_persp_quad(), {3, 3.f});
+    list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
     ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
             "Expected 2D list to be upgraded to perspective");
 
@@ -132,9 +132,9 @@
     // Now make a list that starts with a 3D persp quad, then has conventional quads added to it
     // and make sure its state is correct
     GrTQuadList<TestData> list3D;
-    list3D.push_back(make_3d_persp_quad(), {3, 3.f});
-    list3D.push_back(make_2d_persp_quad(), {2, 2.f});
-    list3D.push_back(make_2d_quad(), {1, 1.f});
+    list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
+    list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
+    list3D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
 
     ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
     ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
@@ -150,9 +150,9 @@
 
 TEST(Concat2DWith2D) {
     GrQuadList a2D;
-    a2D.push_back(make_2d_quad());
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect);
     GrQuadList b2D;
-    b2D.push_back(make_2d_persp_quad());
+    b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
 
     a2D.concat(b2D);
 
@@ -163,9 +163,9 @@
 
 TEST(Concat2DWith3D) {
     GrQuadList a2D;
-    a2D.push_back(make_2d_quad());
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect);
     GrQuadList b3D;
-    b3D.push_back(make_3d_persp_quad());
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
 
     a2D.concat(b3D);
 
@@ -176,9 +176,9 @@
 
 TEST(Concat3DWith2D) {
     GrQuadList a3D;
-    a3D.push_back(make_3d_persp_quad());
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
     GrQuadList b2D;
-    b2D.push_back(make_2d_quad());
+    b2D.push_back(make_2d_quad(), GrQuadType::kRect);
 
     a3D.concat(b2D);
 
@@ -189,9 +189,9 @@
 
 TEST(Concat3DWith3D) {
     GrQuadList a3D;
-    a3D.push_back(make_3d_persp_quad());
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
     GrQuadList b3D;
-    b3D.push_back(make_3d_persp_quad());
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
 
     a3D.concat(b3D);
 
@@ -202,9 +202,9 @@
 
 TEST(Concat2DWith2DMetadata) {
     GrTQuadList<TestData> a2D;
-    a2D.push_back(make_2d_quad(), {1, 1.f});
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
     GrTQuadList<TestData> b2D;
-    b2D.push_back(make_2d_persp_quad(), {2, 2.f});
+    b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
 
     a2D.concat(b2D);
 
@@ -219,9 +219,9 @@
 
 TEST(Concat2DWith3DMetadata) {
     GrTQuadList<TestData> a2D;
-    a2D.push_back(make_2d_quad(), {1, 1.f});
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
     GrTQuadList<TestData> b3D;
-    b3D.push_back(make_3d_persp_quad(), {2, 2.f});
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
 
     a2D.concat(b3D);
 
@@ -236,9 +236,9 @@
 
 TEST(Concat3DWith2DMetadata) {
     GrTQuadList<TestData> a3D;
-    a3D.push_back(make_3d_persp_quad(), {1, 1.f});
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
     GrTQuadList<TestData> b2D;
-    b2D.push_back(make_2d_quad(), {2, 2.f});
+    b2D.push_back(make_2d_quad(), GrQuadType::kRect, {2, 2.f});
 
     a3D.concat(b2D);
 
@@ -253,9 +253,9 @@
 
 TEST(Concat3DWith3DMetadata) {
     GrTQuadList<TestData> a3D;
-    a3D.push_back(make_3d_persp_quad(), {1, 1.f});
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
     GrTQuadList<TestData> b3D;
-    b3D.push_back(make_3d_persp_quad(), {2, 2.f});
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
 
     a3D.concat(b3D);
 
@@ -270,7 +270,7 @@
 
 TEST(WriteMetadata) {
     GrTQuadList<TestData> list;
-    list.push_back(make_2d_quad(), {1, 1.f});
+    list.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
     ASSERTF(list.metadata(0).fItem1 == 1 && list.metadata(0).fItem2 == 1.f,
             "Incorrect metadata at i=0"); // Sanity check