Use DrawQuad struct to group device+local coords

This is part of a series to make it easier to manipulate the device and
local coordinates as the ops are being created. By instantiating a
single DrawQuad and allowing the functions to avoid having to copy the
GrQuads before making modifications (e.g. cropping, normalization,
or perspective clipping).

Bug: skia:9779
Change-Id: I0c6eefaee10638bc7483049d1993addeddc97005
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/269141
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index ea78d13..655739f 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -605,17 +605,18 @@
 
 GrRenderTargetContext::QuadOptimization GrRenderTargetContext::attemptQuadOptimization(
         const GrClip& clip, const SkPMColor4f* constColor,
-        const GrUserStencilSettings* stencilSettings, GrAA* aa, GrQuadAAFlags* edgeFlags,
-        GrQuad* deviceQuad, GrQuad* localQuad) {
+        const GrUserStencilSettings* stencilSettings, GrAA* aa, DrawQuad* quad) {
     // Optimization requirements:
     // 1. kDiscard applies when clip bounds and quad bounds do not intersect
-    // 2. kClear applies when constColor and final geom is pixel aligned rect;
-    //       pixel aligned rect requires rect clip and (rect quad or quad covers clip)
-    // 3. kRRect applies when constColor and rrect clip and quad covers clip
-    // 4. kExplicitClip applies when rect clip and (rect quad or quad covers clip)
-    // 5. kCropped applies when rect quad (currently)
-    // 6. kNone always applies
-    GrQuadAAFlags newFlags = *edgeFlags;
+    // 2a. kSubmitted applies when constColor and final geom is pixel aligned rect;
+    //       pixel aligned rect requires rect clip and (rect quad or quad covers clip) OR
+    // 2b. kSubmitted applies when constColor and rrect clip and quad covers clip
+    // 4. kClipApplied applies when rect clip and (rect quad or quad covers clip)
+    // 5. kCropped in all other scenarios (although a crop may be a no-op)
+
+    // Save the old AA flags since CropToRect will modify 'quad' and if kCropped is returned, it's
+    // better to just keep the old flags instead of introducing mixed edge flags.
+    GrQuadAAFlags oldFlags = quad->fEdgeFlags;
 
     SkRect rtRect;
     if (stencilSettings) {
@@ -628,27 +629,25 @@
         rtRect = SkRect::MakeWH(this->width(), this->height());
     }
 
-    SkRect drawBounds = deviceQuad->bounds();
+    SkRect drawBounds = quad->fDevice.bounds();
     if (constColor) {
-        // Don't bother updating local coordinates when the paint will ignore them anyways
-        localQuad = nullptr;
         // If the device quad is not finite, coerce into a finite quad. This is acceptable since it
         // will be cropped to the finite 'clip' or render target and there is no local space mapping
-        if (!deviceQuad->isFinite()) {
+        if (!quad->fDevice.isFinite()) {
             for (int i = 0; i < 4; ++i) {
-                if (!make_vertex_finite(deviceQuad->xs() + i) ||
-                    !make_vertex_finite(deviceQuad->ys() + i) ||
-                    !make_vertex_finite(deviceQuad->ws() + i)) {
+                if (!make_vertex_finite(quad->fDevice.xs() + i) ||
+                    !make_vertex_finite(quad->fDevice.ys() + i) ||
+                    !make_vertex_finite(quad->fDevice.ws() + i)) {
                     // Discard if we see a nan
                     return QuadOptimization::kDiscarded;
                 }
             }
-            SkASSERT(deviceQuad->isFinite());
+            SkASSERT(quad->fDevice.isFinite());
         }
     } else {
         // CropToRect requires the quads to be finite. If they are not finite and we have local
         // coordinates, the mapping from local space to device space is poorly defined so drop it
-        if (!deviceQuad->isFinite()) {
+        if (!quad->fDevice.isFinite()) {
             return QuadOptimization::kDiscarded;
         }
     }
@@ -684,11 +683,11 @@
 
         if (clipRRect.isRect()) {
             // No rounded corners, so the kClear and kExplicitClip optimizations are possible
-            if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad)) {
+            if (GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /*compute local*/ !constColor)) {
                 if (!stencilSettings && constColor &&
-                    deviceQuad->quadType() == GrQuad::Type::kAxisAligned) {
+                    quad->fDevice.quadType() == GrQuad::Type::kAxisAligned) {
                     // Clear optimization is possible
-                    drawBounds = deviceQuad->bounds();
+                    drawBounds = quad->fDevice.bounds();
                     if (drawBounds.contains(rtRect)) {
                         // Fullscreen clear
                         this->clear(nullptr, *constColor, CanClearFullscreen::kYes);
@@ -704,9 +703,8 @@
                 }
 
                 // Update overall AA setting.
-                *edgeFlags = newFlags;
                 if (*aa == GrAA::kNo && clipAA == GrAA::kYes &&
-                    newFlags != GrQuadAAFlags::kNone) {
+                    quad->fEdgeFlags != GrQuadAAFlags::kNone) {
                     // The clip was anti-aliased and now the draw needs to be upgraded to AA to
                     // properly reflect the smooth edge of the clip.
                     *aa = GrAA::kYes;
@@ -721,14 +719,15 @@
             } else {
                 // The quads have been updated to better fit the clip bounds, but can't get rid of
                 // the clip entirely
+                quad->fEdgeFlags = oldFlags;
                 return QuadOptimization::kCropped;
             }
         } else if (!stencilSettings && constColor) {
             // Rounded corners and constant filled color (limit ourselves to solid colors because
             // there is no way to use custom local coordinates with drawRRect).
-            if (GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad) &&
-                deviceQuad->quadType() == GrQuad::Type::kAxisAligned &&
-                deviceQuad->bounds().contains(clipBounds)) {
+            if (GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /* compute local */ false) &&
+                quad->fDevice.quadType() == GrQuad::Type::kAxisAligned &&
+                quad->fDevice.bounds().contains(clipBounds)) {
                 // Since the cropped quad became a rectangle which covered the bounds of the rrect,
                 // we can draw the rrect directly and ignore the edge flags
                 GrPaint paint;
@@ -738,6 +737,7 @@
                 return QuadOptimization::kSubmitted;
             } else {
                 // The quad has been updated to better fit clip bounds, but can't remove the clip
+                quad->fEdgeFlags = oldFlags;
                 return QuadOptimization::kCropped;
             }
         }
@@ -755,7 +755,8 @@
 
     // Even if this were to return true, the crop rect does not exactly match the clip, so can not
     // report explicit-clip. Since these edges aren't visible, don't update the final edge flags.
-    GrQuadUtils::CropToRect(clipBounds, clipAA, &newFlags, deviceQuad, localQuad);
+    GrQuadUtils::CropToRect(clipBounds, clipAA, quad, /* compute local */ !constColor);
+    quad->fEdgeFlags = oldFlags;
 
     return QuadOptimization::kCropped;
 }
@@ -763,9 +764,7 @@
 void GrRenderTargetContext::drawFilledQuad(const GrClip& clip,
                                            GrPaint&& paint,
                                            GrAA aa,
-                                           GrQuadAAFlags edgeFlags,
-                                           const GrQuad& deviceQuad,
-                                           const GrQuad& localQuad,
+                                           DrawQuad* quad,
                                            const GrUserStencilSettings* ss) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
@@ -782,18 +781,15 @@
         constColor = &paintColor;
     }
 
-    GrQuad croppedDeviceQuad = deviceQuad;
-    GrQuad croppedLocalQuad = localQuad;
-    QuadOptimization opt = this->attemptQuadOptimization(clip, constColor, ss, &aa, &edgeFlags,
-                                                         &croppedDeviceQuad, &croppedLocalQuad);
+    QuadOptimization opt = this->attemptQuadOptimization(clip, constColor, ss, &aa, quad);
     if (opt >= QuadOptimization::kClipApplied) {
         // These optimizations require caller to add an op themselves
         const GrClip& finalClip = opt == QuadOptimization::kClipApplied ? GrFixedClip::Disabled()
                                                                         : clip;
         GrAAType aaType = ss ? (aa == GrAA::kYes ? GrAAType::kMSAA : GrAAType::kNone)
                              : this->chooseAAType(aa);
-        this->addDrawOp(finalClip, GrFillRectOp::Make(fContext, std::move(paint), aaType, edgeFlags,
-                                                      croppedDeviceQuad, croppedLocalQuad, ss));
+        this->addDrawOp(finalClip, GrFillRectOp::Make(fContext, std::move(paint), aaType,
+                                                      quad, ss));
     }
     // All other optimization levels were completely handled inside attempt(), so no extra op needed
 }
@@ -806,9 +802,7 @@
                                              const SkPMColor4f& color,
                                              SkBlendMode blendMode,
                                              GrAA aa,
-                                             GrQuadAAFlags edgeFlags,
-                                             const GrQuad& deviceQuad,
-                                             const GrQuad& localQuad,
+                                             DrawQuad* quad,
                                              const SkRect* domain) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
@@ -820,10 +814,7 @@
 
     // Functionally this is very similar to drawFilledQuad except that there's no constColor to
     // enable the kSubmitted optimizations, no stencil settings support, and its a GrTextureOp.
-    GrQuad croppedDeviceQuad = deviceQuad;
-    GrQuad croppedLocalQuad = localQuad;
-    QuadOptimization opt = this->attemptQuadOptimization(clip, nullptr, nullptr, &aa, &edgeFlags,
-                                                         &croppedDeviceQuad, &croppedLocalQuad);
+    QuadOptimization opt = this->attemptQuadOptimization(clip, nullptr, nullptr, &aa, quad);
 
     SkASSERT(opt != QuadOptimization::kSubmitted);
     if (opt != QuadOptimization::kDiscarded) {
@@ -836,11 +827,10 @@
                                                           : GrTextureOp::Saturate::kNo;
         // Use the provided domain, although hypothetically we could detect that the cropped local
         // quad is sufficiently inside the domain and the constraint could be dropped.
-        this->addDrawOp(
-                finalClip,
-                GrTextureOp::Make(fContext, std::move(proxyView), srcAlphaType,
-                                  std::move(textureXform), filter, color, saturate, blendMode,
-                                  aaType, edgeFlags, croppedDeviceQuad, croppedLocalQuad, domain));
+        this->addDrawOp(finalClip,
+                        GrTextureOp::Make(fContext, std::move(proxyView), srcAlphaType,
+                                          std::move(textureXform), filter, color, saturate,
+                                          blendMode, aaType, quad, domain));
     }
 }
 
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 1799ee9..f1c74ae 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -174,9 +174,9 @@
                         const SkMatrix& viewMatrix,
                         const SkRect& rectToDraw,
                         const SkRect& localRect) {
-        this->drawFilledQuad(clip, std::move(paint), aa,
-                             aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
-                             GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect));
+        DrawQuad quad{GrQuad::MakeFromRect(rectToDraw, viewMatrix), GrQuad(localRect),
+                      aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone};
+        this->drawFilledQuad(clip, std::move(paint), aa, &quad);
     }
 
     /**
@@ -188,10 +188,10 @@
                                  const SkMatrix& viewMatrix,
                                  const SkRect& rect,
                                  const SkMatrix& localMatrix) {
-        this->drawFilledQuad(clip, std::move(paint), aa,
-                             aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
-                             GrQuad::MakeFromRect(rect, viewMatrix),
-                             GrQuad::MakeFromRect(rect, localMatrix));
+        DrawQuad quad{GrQuad::MakeFromRect(rect, viewMatrix),
+                      GrQuad::MakeFromRect(rect, localMatrix),
+                      aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone};
+        this->drawFilledQuad(clip, std::move(paint), aa, &quad);
     }
 
     /**
@@ -204,8 +204,8 @@
                             const SkMatrix& viewMatrix, const SkRect& rect,
                             const SkRect* optionalLocalRect = nullptr) {
         const SkRect& localRect = optionalLocalRect ? *optionalLocalRect : rect;
-        this->drawFilledQuad(clip, std::move(paint), aa, edgeAA,
-                             GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(localRect));
+        DrawQuad quad{GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(localRect), edgeAA};
+        this->drawFilledQuad(clip, std::move(paint), aa, &quad);
     }
 
     /**
@@ -221,12 +221,12 @@
      * necessary.
      */
     void fillQuadWithEdgeAA(const GrClip& clip, GrPaint&& paint, GrAA aa, GrQuadAAFlags edgeAA,
-                            const SkMatrix& viewMatrix, const SkPoint quad[4],
-                            const SkPoint optionalLocalQuad[4]) {
-        const SkPoint* localQuad = optionalLocalQuad ? optionalLocalQuad : quad;
-        this->drawFilledQuad(clip, std::move(paint), aa, edgeAA,
-                             GrQuad::MakeFromSkQuad(quad, viewMatrix),
-                             GrQuad::MakeFromSkQuad(localQuad, SkMatrix::I()));
+                            const SkMatrix& viewMatrix, const SkPoint points[4],
+                            const SkPoint optionalLocalPoints[4]) {
+        const SkPoint* localPoints = optionalLocalPoints ? optionalLocalPoints : points;
+        DrawQuad quad{GrQuad::MakeFromSkQuad(points, viewMatrix),
+                      GrQuad::MakeFromSkQuad(localPoints, SkMatrix::I()), edgeAA};
+        this->drawFilledQuad(clip, std::move(paint), aa, &quad);
     }
 
     /** Used with drawQuadSet */
@@ -254,9 +254,10 @@
                      sk_sp<GrColorSpaceXform> texXform) {
         const SkRect* domain = constraint == SkCanvas::kStrict_SrcRectConstraint ?
                 &srcRect : nullptr;
+        DrawQuad quad{GrQuad::MakeFromRect(dstRect, viewMatrix), GrQuad(srcRect), edgeAA};
+
         this->drawTexturedQuad(clip, std::move(view), srcAlphaType, std::move(texXform),
-                               filter, color, mode, aa, edgeAA,
-                               GrQuad::MakeFromRect(dstRect, viewMatrix), GrQuad(srcRect), domain);
+                               filter, color, mode, aa, &quad, domain);
     }
 
     /**
@@ -274,10 +275,10 @@
         GrSurfaceOrigin origin = proxy->origin();
         const GrSwizzle& swizzle = proxy->textureSwizzle();
         GrSurfaceProxyView proxyView(std::move(proxy), origin, swizzle);
+        DrawQuad quad{GrQuad::MakeFromSkQuad(dstQuad, viewMatrix),
+                      GrQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()), edgeAA};
         this->drawTexturedQuad(clip, std::move(proxyView), srcAlphaType, std::move(texXform),
-                               filter, color, mode, aa, edgeAA,
-                               GrQuad::MakeFromSkQuad(dstQuad, viewMatrix),
-                               GrQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()), domain);
+                               filter, color, mode, aa, &quad, domain);
     }
 
     /** Used with drawTextureSet */
@@ -620,23 +621,20 @@
                                              const SkPMColor4f* constColor,
                                              const GrUserStencilSettings* stencilSettings,
                                              GrAA* aa,
-                                             GrQuadAAFlags* edgeFlags,
-                                             GrQuad* deviceQuad,
-                                             GrQuad* localQuad);
+                                             DrawQuad* quad);
 
     // If stencil settings, 'ss', are non-null, AA controls MSAA or no AA. If they are null, then AA
     // can choose between coverage, MSAA as per chooseAAType(). This will always attempt to apply
     // quad optimizations, so all quad/rect public APIs should rely on this function for consistent
-    // clipping behavior.
+    // clipping behavior. 'quad' will be modified in place to reflect final rendered geometry.
     void drawFilledQuad(const GrClip& clip,
                         GrPaint&& paint,
                         GrAA aa,
-                        GrQuadAAFlags edgeFlags,
-                        const GrQuad& deviceQuad,
-                        const GrQuad& localQuad,
+                        DrawQuad* quad,
                         const GrUserStencilSettings* ss = nullptr);
 
-    // Like drawFilledQuad but does not require using a GrPaint or FP for texturing
+    // Like drawFilledQuad but does not require using a GrPaint or FP for texturing.
+    // 'quad' may be modified in place to reflect final geometry.
     void drawTexturedQuad(const GrClip& clip,
                           GrSurfaceProxyView proxyView,
                           SkAlphaType alphaType,
@@ -645,9 +643,7 @@
                           const SkPMColor4f& color,
                           SkBlendMode blendMode,
                           GrAA aa,
-                          GrQuadAAFlags edgeFlags,
-                          const GrQuad& deviceQuad,
-                          const GrQuad& localQuad,
+                          DrawQuad* quad,
                           const SkRect* domain = nullptr);
 
     void drawShapeUsingPathRenderer(const GrClip&, GrPaint&&, GrAA, const SkMatrix&,
diff --git a/src/gpu/GrRenderTargetContextPriv.h b/src/gpu/GrRenderTargetContextPriv.h
index ecc5cd9..188d251 100644
--- a/src/gpu/GrRenderTargetContextPriv.h
+++ b/src/gpu/GrRenderTargetContextPriv.h
@@ -58,10 +58,10 @@
             const SkMatrix* localMatrix = nullptr) {
         // Since this provides stencil settings to drawFilledQuad, it performs a different AA type
         // resolution compared to regular rect draws, which is the main reason it remains separate.
-        GrQuad localQuad = localMatrix ? GrQuad::MakeFromRect(rect, *localMatrix) : GrQuad(rect);
-        fRenderTargetContext->drawFilledQuad(
-                clip, std::move(paint), doStencilMSAA, GrQuadAAFlags::kNone,
-                GrQuad::MakeFromRect(rect, viewMatrix), localQuad, ss);
+        DrawQuad quad{GrQuad::MakeFromRect(rect, viewMatrix),
+                      localMatrix ? GrQuad::MakeFromRect(rect, *localMatrix) : GrQuad(rect),
+                      GrQuadAAFlags::kNone};
+        fRenderTargetContext->drawFilledQuad(clip, std::move(paint), doStencilMSAA, &quad, ss);
     }
 
     void stencilPath(
diff --git a/src/gpu/geometry/GrQuad.h b/src/gpu/geometry/GrQuad.h
index 2094a4b..58f59f0 100644
--- a/src/gpu/geometry/GrQuad.h
+++ b/src/gpu/geometry/GrQuad.h
@@ -13,6 +13,8 @@
 #include "include/core/SkPoint3.h"
 #include "include/private/SkVx.h"
 
+enum class GrQuadAAFlags;
+
 /**
  * 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).
@@ -161,4 +163,12 @@
     Type fType = Type::kAxisAligned;
 };
 
+// A simple struct representing the common work unit of a pair of device and local coordinates, as
+// well as the edge flags controlling anti-aliasing for the quadrilateral when drawn.
+struct DrawQuad {
+    GrQuad        fDevice;
+    GrQuad        fLocal;
+    GrQuadAAFlags fEdgeFlags;
+};
+
 #endif
diff --git a/src/gpu/geometry/GrQuadUtils.cpp b/src/gpu/geometry/GrQuadUtils.cpp
index 7450c31..7b53a01 100644
--- a/src/gpu/geometry/GrQuadUtils.cpp
+++ b/src/gpu/geometry/GrQuadUtils.cpp
@@ -381,28 +381,28 @@
     }
 }
 
-bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad,
-                GrQuad* local) {
-    SkASSERT(quad->isFinite());
+bool CropToRect(const SkRect& cropRect, GrAA cropAA, DrawQuad* quad, bool computeLocal) {
+    SkASSERT(quad->fDevice.isFinite());
 
-    if (quad->quadType() == GrQuad::Type::kAxisAligned) {
+    if (quad->fDevice.quadType() == GrQuad::Type::kAxisAligned) {
         // crop_rect and crop_rect_simple keep the rectangles as rectangles, so the intersection
         // of the crop and quad can be calculated exactly. Some care must be taken if the quad
         // is axis-aligned but does not satisfy asRect() due to flips, etc.
         GrQuadAAFlags clippedEdges;
-        if (local) {
-            if (is_simple_rect(*quad) && is_simple_rect(*local)) {
-                clippedEdges = crop_simple_rect(cropRect, quad->xs(), quad->ys(),
-                                                local->xs(), local->ys());
+        if (computeLocal) {
+            if (is_simple_rect(quad->fDevice) && is_simple_rect(quad->fLocal)) {
+                clippedEdges = crop_simple_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
+                                                quad->fLocal.xs(), quad->fLocal.ys());
             } else {
-                clippedEdges = crop_rect(cropRect, quad->xs(), quad->ys(),
-                                         local->xs(), local->ys(), local->ws());
+                clippedEdges = crop_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
+                                         quad->fLocal.xs(), quad->fLocal.ys(), quad->fLocal.ws());
             }
         } else {
-            if (is_simple_rect(*quad)) {
-                clippedEdges = crop_simple_rect(cropRect, quad->xs(), quad->ys(), nullptr, nullptr);
+            if (is_simple_rect(quad->fDevice)) {
+                clippedEdges = crop_simple_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
+                                                nullptr, nullptr);
             } else {
-                clippedEdges = crop_rect(cropRect, quad->xs(), quad->ys(),
+                clippedEdges = crop_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
                                          nullptr, nullptr, nullptr);
             }
         }
@@ -410,26 +410,31 @@
         // Apply the clipped edge updates to the original edge flags
         if (cropAA == GrAA::kYes) {
             // Turn on all edges that were clipped
-            *edgeFlags |= clippedEdges;
+            quad->fEdgeFlags |= clippedEdges;
         } else {
             // Turn off all edges that were clipped
-            *edgeFlags &= ~clippedEdges;
+            quad->fEdgeFlags &= ~clippedEdges;
         }
         return true;
     }
 
-    if (local) {
+    if (computeLocal) {
         // FIXME (michaelludwig) Calculate cropped local coordinates when not kAxisAligned
         return false;
     }
 
-    V4f devX = quad->x4f();
-    V4f devY = quad->y4f();
-    V4f devIW = quad->iw4f();
+    V4f devX = quad->fDevice.x4f();
+    V4f devY = quad->fDevice.y4f();
     // Project the 3D coordinates to 2D
-    if (quad->quadType() == GrQuad::Type::kPerspective) {
-        devX *= devIW;
-        devY *= devIW;
+    if (quad->fDevice.quadType() == GrQuad::Type::kPerspective) {
+        V4f devW = quad->fDevice.w4f();
+        if (any(devW < SkPathPriv::kW0PlaneDistance)) {
+            // The rest of this function assumes the quad is in front of w = 0
+            return false;
+        }
+        devW = 1.f / devW;
+        devX *= devW;
+        devY *= devW;
     }
 
     V4f clipX = {cropRect.fLeft, cropRect.fLeft, cropRect.fRight, cropRect.fRight};
@@ -460,21 +465,17 @@
         // FIXME (michaelludwig) - once we have local coordinates handled, it may be desirable to
         // keep the draw as perspective so that the hardware does perspective interpolation instead
         // of pushing it into a local coord w and having the shader do an extra divide.
-        clipX.store(quad->xs());
-        clipY.store(quad->ys());
-        quad->ws()[0] = 1.f;
-        quad->ws()[1] = 1.f;
-        quad->ws()[2] = 1.f;
-        quad->ws()[3] = 1.f;
-        quad->setQuadType(GrQuad::Type::kAxisAligned);
+        clipX.store(quad->fDevice.xs());
+        clipY.store(quad->fDevice.ys());
+        quad->fDevice.setQuadType(GrQuad::Type::kAxisAligned);
 
         // Update the edge flags to match the clip setting since all 4 edges have been clipped
-        *edgeFlags = cropAA == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
+        quad->fEdgeFlags = cropAA == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
 
         return true;
     }
 
-    // FIXME (michaelludwig) - use the GrQuadPerEdgeAA tessellation inset/outset math to move
+    // FIXME (michaelludwig) - use TessellationHelper's inset/outset math to move
     // edges to the closest clip corner they are outside of
 
     return false;
diff --git a/src/gpu/geometry/GrQuadUtils.h b/src/gpu/geometry/GrQuadUtils.h
index 63d3415..48be862 100644
--- a/src/gpu/geometry/GrQuadUtils.h
+++ b/src/gpu/geometry/GrQuadUtils.h
@@ -33,12 +33,9 @@
      * based on cropAA policy). If provided, the local coordinates will be updated to reflect the
      * updated device coordinates of this quad.
      *
-     * 'local' may be null, in which case the new local coordinates will not be calculated. This is
-     * useful when it's known a paint does not require local coordinates. However, neither
-     * 'edgeFlags' nore 'quad' can be null.
+     * If 'computeLocal' is false, the local coordinates in 'quad' will not be modified.
      */
-    bool CropToRect(const SkRect& cropRect, GrAA cropAA, GrQuadAAFlags* edgeFlags, GrQuad* quad,
-                    GrQuad* local=nullptr);
+    bool CropToRect(const SkRect& cropRect, GrAA cropAA, DrawQuad* quad, bool computeLocal=true);
 
     class TessellationHelper {
     public:
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index 4c6bfda..a65ab8e 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -64,30 +64,28 @@
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           GrAAType aaType,
-                                          GrQuadAAFlags edgeAA,
-                                          const GrUserStencilSettings* stencilSettings,
-                                          const GrQuad& deviceQuad,
-                                          const GrQuad& localQuad) {
+                                          DrawQuad* quad,
+                                          const GrUserStencilSettings* stencilSettings) {
         // Clean up deviations between aaType and edgeAA
-        GrQuadUtils::ResolveAAType(aaType, edgeAA, deviceQuad, &aaType, &edgeAA);
-        return Helper::FactoryHelper<FillRectOp>(context, std::move(paint), aaType, edgeAA,
-                stencilSettings, deviceQuad, localQuad);
+        GrQuadUtils::ResolveAAType(aaType, quad->fEdgeFlags, quad->fDevice,
+                                   &aaType, &quad->fEdgeFlags);
+        return Helper::FactoryHelper<FillRectOp>(context, std::move(paint), aaType, quad,
+                                                 stencilSettings);
     }
 
     // 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 GrQuad& deviceQuad, const GrQuad& localQuad)
+               DrawQuad* quad, const GrUserStencilSettings* stencil)
             : INHERITED(ClassID())
             , fHelper(args, aaType, stencil)
             , fQuads(1, !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.
-        fQuads.append(deviceQuad, { paintColor, edgeFlags },
-                      fHelper.isTrivial() ? nullptr : &localQuad);
-        this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
+        fQuads.append(quad->fDevice, {paintColor, quad->fEdgeFlags},
+                      fHelper.isTrivial() ? nullptr : &quad->fLocal);
+        this->setBounds(quad->fDevice.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
                         IsHairline::kNo);
     }
 
@@ -335,8 +333,7 @@
     // But since it's avoiding the op list management, it must update the op's bounds. This is only
     // 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.
-    bool addQuad(const GrQuad& deviceQuad, const GrQuad& localQuad,
-                 const SkPMColor4f& color, GrQuadAAFlags edgeAA, GrAAType aaType) {
+    bool addQuad(DrawQuad* quad, const SkPMColor4f& color, 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.
@@ -362,10 +359,11 @@
 
         // Update the bounds and add the quad to this op's storage
         SkRect newBounds = this->bounds();
-        newBounds.joinPossiblyEmptyRect(deviceQuad.bounds());
+        newBounds.joinPossiblyEmptyRect(quad->fDevice.bounds());
         this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
                         IsHairline::kNo);
-        fQuads.append(deviceQuad, { color, edgeAA }, fHelper.isTrivial() ? nullptr : &localQuad);
+        fQuads.append(quad->fDevice, { color, quad->fEdgeFlags },
+                      fHelper.isTrivial() ? nullptr : &quad->fLocal);
         return true;
     }
 
@@ -388,12 +386,9 @@
 std::unique_ptr<GrDrawOp> GrFillRectOp::Make(GrRecordingContext* context,
                                              GrPaint&& paint,
                                              GrAAType aaType,
-                                             GrQuadAAFlags aaFlags,
-                                             const GrQuad& deviceQuad,
-                                             const GrQuad& localQuad,
+                                             DrawQuad* quad,
                                              const GrUserStencilSettings* stencil) {
-    return FillRectOp::Make(context, std::move(paint), aaType, aaFlags, stencil,
-                            deviceQuad, localQuad);
+    return FillRectOp::Make(context, std::move(paint), aaType, std::move(quad), stencil);
 }
 
 std::unique_ptr<GrDrawOp> GrFillRectOp::MakeNonAARect(GrRecordingContext* context,
@@ -401,8 +396,8 @@
                                                       const SkMatrix& view,
                                                       const SkRect& rect,
                                                       const GrUserStencilSettings* stencil) {
-    return FillRectOp::Make(context, std::move(paint), GrAAType::kNone, GrQuadAAFlags::kNone,
-                            stencil, GrQuad::MakeFromRect(rect, view), GrQuad(rect));
+    DrawQuad quad{GrQuad::MakeFromRect(rect, view), GrQuad(rect), GrQuadAAFlags::kNone};
+    return FillRectOp::Make(context, std::move(paint), GrAAType::kNone, &quad, stencil);
 }
 
 std::unique_ptr<GrDrawOp> GrFillRectOp::MakeOp(GrRecordingContext* context,
@@ -416,27 +411,26 @@
     // First make a draw op for the first quad in the set
     SkASSERT(cnt > 0);
 
+    DrawQuad quad{GrQuad::MakeFromRect(quads[0].fRect, viewMatrix),
+                  GrQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix),
+                  quads[0].fAAFlags};
     paint.setColor4f(quads[0].fColor);
-    std::unique_ptr<GrDrawOp> op = FillRectOp::Make(
-            context, std::move(paint), aaType,
-            quads[0].fAAFlags, stencilSettings,
-            GrQuad::MakeFromRect(quads[0].fRect, viewMatrix),
-            GrQuad::MakeFromRect(quads[0].fRect, quads[0].fLocalMatrix));
+    std::unique_ptr<GrDrawOp> op = FillRectOp::Make(context, std::move(paint), aaType,
+                                                    &quad, stencilSettings);
     FillRectOp* fillRects = op->cast<FillRectOp>();
 
     *numConsumed = 1;
     // Accumulate remaining quads similar to onCombineIfPossible() without creating an op
     for (int i = 1; i < cnt; ++i) {
-        GrQuad deviceQuad = GrQuad::MakeFromRect(quads[i].fRect, viewMatrix);
+        quad = {GrQuad::MakeFromRect(quads[i].fRect, viewMatrix),
+                GrQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
+                quads[i].fAAFlags};
 
         GrAAType resolvedAA;
-        GrQuadAAFlags resolvedEdgeFlags;
-        GrQuadUtils::ResolveAAType(aaType, quads[i].fAAFlags, deviceQuad,
-                                   &resolvedAA, &resolvedEdgeFlags);
+        GrQuadUtils::ResolveAAType(aaType, quads[i].fAAFlags, quad.fDevice,
+                                   &resolvedAA, &quad.fEdgeFlags);
 
-        if (!fillRects->addQuad(deviceQuad,
-                                GrQuad::MakeFromRect(quads[i].fRect, quads[i].fLocalMatrix),
-                                quads[i].fColor, resolvedEdgeFlags, resolvedAA)) {
+        if (!fillRects->addQuad(&quad, quads[i].fColor, resolvedAA)) {
             break;
         }
 
@@ -504,21 +498,20 @@
         if (random->nextBool()) {
             // Single local matrix
             SkMatrix localMatrix = GrTest::TestMatrixInvertible(random);
-            return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags,
-                                      GrQuad::MakeFromRect(rect, viewMatrix),
-                                      GrQuad::MakeFromRect(rect, localMatrix), stencil);
+            DrawQuad quad = {GrQuad::MakeFromRect(rect, viewMatrix),
+                             GrQuad::MakeFromRect(rect, localMatrix), aaFlags};
+            return GrFillRectOp::Make(context, std::move(paint), aaType, &quad, stencil);
         } else {
             // Pass local rect directly
             SkRect localRect = GrTest::TestRect(random);
-            return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags,
-                                      GrQuad::MakeFromRect(rect, viewMatrix),
-                                      GrQuad(localRect), stencil);
+            DrawQuad quad = {GrQuad::MakeFromRect(rect, viewMatrix),
+                             GrQuad(localRect), aaFlags};
+            return GrFillRectOp::Make(context, std::move(paint), aaType, &quad, stencil);
         }
     } else {
         // The simplest constructor
-        return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags,
-                                  GrQuad::MakeFromRect(rect, viewMatrix),
-                                  GrQuad(rect), stencil);
+        DrawQuad quad = {GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(rect), aaFlags};
+        return GrFillRectOp::Make(context, std::move(paint), aaType, &quad, stencil);
     }
 }
 
diff --git a/src/gpu/ops/GrFillRectOp.h b/src/gpu/ops/GrFillRectOp.h
index 600e87b..e44dae52 100644
--- a/src/gpu/ops/GrFillRectOp.h
+++ b/src/gpu/ops/GrFillRectOp.h
@@ -31,9 +31,7 @@
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
                                           GrPaint&& paint,
                                           GrAAType aaType,
-                                          GrQuadAAFlags aaFlags,
-                                          const GrQuad& deviceQuad,
-                                          const GrQuad& localQuad,
+                                          DrawQuad* quad,
                                           const GrUserStencilSettings* stencil = nullptr);
 
     // Utility function to create a non-AA rect transformed by view. This is used commonly enough
diff --git a/src/gpu/ops/GrStrokeRectOp.cpp b/src/gpu/ops/GrStrokeRectOp.cpp
index 81ce82b..c45185c 100644
--- a/src/gpu/ops/GrStrokeRectOp.cpp
+++ b/src/gpu/ops/GrStrokeRectOp.cpp
@@ -798,10 +798,9 @@
         if (devOutside.isEmpty()) {
             return nullptr;
         }
-        return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage,
-                                  GrQuadAAFlags::kAll,
-                                  GrQuad::MakeFromRect(rects[0], viewMatrix),
-                                  GrQuad(rects[0]));
+        DrawQuad quad{GrQuad::MakeFromRect(rects[0], viewMatrix), GrQuad(rects[0]),
+                      GrQuadAAFlags::kAll};
+        return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, &quad);
     }
 
     SkVector devHalfStrokeSize{ SkScalarHalf(devOutside.fRight - devInside.fRight),
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 49b1590..550b648 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -201,14 +201,11 @@
                                           const SkPMColor4f& color,
                                           GrTextureOp::Saturate saturate,
                                           GrAAType aaType,
-                                          GrQuadAAFlags aaFlags,
-                                          const GrQuad& deviceQuad,
-                                          const GrQuad& localQuad,
+                                          DrawQuad* quad,
                                           const SkRect* domain) {
         GrOpMemoryPool* pool = context->priv().opMemoryPool();
         return pool->allocate<TextureOp>(std::move(proxyView), std::move(textureXform), filter,
-                                         color, saturate, aaType, aaFlags, deviceQuad, localQuad,
-                                         domain);
+                                         color, saturate, aaType, quad, domain);
     }
 
     static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
@@ -451,17 +448,14 @@
 
     };
 
-    // dstQuad should be the geometry transformed by the view matrix. If domainRect
-    // is not null it will be used to apply the strict src rect constraint.
+    // If domainRect is not null it will be used to apply a strict src rect-style constraint.
     TextureOp(GrSurfaceProxyView proxyView,
               sk_sp<GrColorSpaceXform> textureColorSpaceXform,
               GrSamplerState::Filter filter,
               const SkPMColor4f& color,
               GrTextureOp::Saturate saturate,
               GrAAType aaType,
-              GrQuadAAFlags aaFlags,
-              const GrQuad& dstQuad,
-              const GrQuad& srcQuad,
+              DrawQuad* quad,
               const SkRect* domainRect)
             : INHERITED(ClassID())
             , fQuads(1, true /* includes locals */)
@@ -471,7 +465,8 @@
 
         // Clean up disparities between the overall aa type and edge configuration and apply
         // optimizations based on the rect and matrix when appropriate
-        GrQuadUtils::ResolveAAType(aaType, aaFlags, dstQuad, &aaType, &aaFlags);
+        GrQuadUtils::ResolveAAType(aaType, quad->fEdgeFlags, quad->fDevice,
+                                   &aaType, &quad->fEdgeFlags);
         fMetadata.fAAType = static_cast<uint16_t>(aaType);
 
         // We expect our caller to have already caught this optimization.
@@ -490,14 +485,13 @@
         // Normalize src coordinates and the domain (if set)
         NormalizationParams params = proxy_normalization_params(proxyView.proxy(),
                                                                 proxyView.origin());
-        GrQuad normalizedSrcQuad = srcQuad;
-        normalize_src_quad(params, &normalizedSrcQuad);
+        normalize_src_quad(params, &quad->fLocal);
         SkRect domain = normalize_domain(filter, params, domainRect);
 
-        fQuads.append(dstQuad, {color, domain, aaFlags}, &normalizedSrcQuad);
+        fQuads.append(quad->fDevice, {color, domain, quad->fEdgeFlags}, &quad->fLocal);
         fViewCountPairs[0] = {proxyView.detachProxy(), 1};
 
-        this->setBounds(dstQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
+        this->setBounds(quad->fDevice.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
                         IsHairline::kNo);
     }
 
@@ -560,20 +554,20 @@
 
             // Use dstRect/srcRect unless dstClip is provided, in which case derive new source
             // coordinates by mapping dstClipQuad by the dstRect to srcRect transform.
-            GrQuad quad, srcQuad;
+            DrawQuad quad;
             if (set[q].fDstClipQuad) {
-                quad = GrQuad::MakeFromSkQuad(set[q].fDstClipQuad, ctm);
+                quad.fDevice = GrQuad::MakeFromSkQuad(set[q].fDstClipQuad, ctm);
 
                 SkPoint srcPts[4];
                 GrMapRectPoints(set[q].fDstRect, set[q].fSrcRect, set[q].fDstClipQuad, srcPts, 4);
-                srcQuad = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
+                quad.fLocal = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
             } else {
-                quad = GrQuad::MakeFromRect(set[q].fDstRect, ctm);
-                srcQuad = GrQuad(set[q].fSrcRect);
+                quad.fDevice = GrQuad::MakeFromRect(set[q].fDstRect, ctm);
+                quad.fLocal = GrQuad(set[q].fSrcRect);
             }
 
             // Before normalizing the source coordinates, determine if bilerp is actually needed
-            if (netFilter != filter && filter_has_effect(srcQuad, quad)) {
+            if (netFilter != filter && filter_has_effect(quad.fLocal, quad.fDevice)) {
                 // The only way netFilter != filter is if bilerp is requested and we haven't yet
                 // found a quad that requires bilerp (so net is still nearest).
                 SkASSERT(netFilter == GrSamplerState::Filter::kNearest &&
@@ -587,15 +581,15 @@
             // Normalize the src quads and apply origin
             NormalizationParams proxyParams = proxy_normalization_params(
                     curProxy, set[q].fProxyView.origin());
-            normalize_src_quad(proxyParams, &srcQuad);
+            normalize_src_quad(proxyParams, &quad.fLocal);
 
             // Update overall bounds of the op as the union of all quads
-            bounds.joinPossiblyEmptyRect(quad.bounds());
+            bounds.joinPossiblyEmptyRect(quad.fDevice.bounds());
 
             // Determine the AA type for the quad, then merge with net AA type
-            GrQuadAAFlags aaFlags;
             GrAAType aaForQuad;
-            GrQuadUtils::ResolveAAType(aaType, set[q].fAAFlags, quad, &aaForQuad, &aaFlags);
+            GrQuadUtils::ResolveAAType(aaType, set[q].fAAFlags, quad.fDevice,
+                                       &aaForQuad, &quad.fEdgeFlags);
             // Resolve sets aaForQuad to aaType or None, there is never a change between aa methods
             SkASSERT(aaForQuad == GrAAType::kNone || aaForQuad == aaType);
             if (netAAType == GrAAType::kNone && aaForQuad != GrAAType::kNone) {
@@ -621,7 +615,8 @@
             // (this frequently happens when Chrome draws 9-patches).
             SkRect domain = normalize_domain(filter, proxyParams, domainForQuad);
             float alpha = SkTPin(set[q].fAlpha, 0.f, 1.f);
-            fQuads.append(quad, {{alpha, alpha, alpha, alpha}, domain, aaFlags}, &srcQuad);
+            fQuads.append(quad.fDevice, {{alpha, alpha, alpha, alpha}, domain, quad.fEdgeFlags},
+                          &quad.fLocal);
             fViewCountPairs[p].fQuadCnt++;
         }
         // The # of proxy switches should match what was provided (+1 because we incremented p
@@ -1046,9 +1041,7 @@
                                             Saturate saturate,
                                             SkBlendMode blendMode,
                                             GrAAType aaType,
-                                            GrQuadAAFlags aaFlags,
-                                            const GrQuad& deviceQuad,
-                                            const GrQuad& localQuad,
+                                            DrawQuad* quad,
                                             const SkRect* domain) {
     // Apply optimizations that are valid whether or not using GrTextureOp or GrFillRectOp
     if (domain && domain->contains(proxyView.proxy()->backingStoreBoundsRect())) {
@@ -1056,13 +1049,14 @@
         domain = nullptr;
     }
 
-    if (filter != GrSamplerState::Filter::kNearest && !filter_has_effect(localQuad, deviceQuad)) {
+    if (filter != GrSamplerState::Filter::kNearest &&
+        !filter_has_effect(quad->fLocal, quad->fDevice)) {
         filter = GrSamplerState::Filter::kNearest;
     }
 
     if (blendMode == SkBlendMode::kSrcOver) {
         return TextureOp::Make(context, std::move(proxyView), std::move(textureXform), filter,
-                               color, saturate, aaType, aaFlags, deviceQuad, localQuad, domain);
+                               color, saturate, aaType, std::move(quad), domain);
     } else {
         // Emulate complex blending using GrFillRectOp
         GrPaint paint;
@@ -1073,7 +1067,7 @@
         if (domain) {
             const auto& caps = *context->priv().caps();
             SkRect localRect;
-            if (localQuad.asRect(&localRect)) {
+            if (quad->fLocal.asRect(&localRect)) {
                 fp = GrTextureEffect::MakeSubset(std::move(proxyView), alphaType, SkMatrix::I(), filter,
                                                  *domain, localRect, caps);
             } else {
@@ -1089,8 +1083,7 @@
             paint.addColorFragmentProcessor(GrClampFragmentProcessor::Make(false));
         }
 
-        return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, deviceQuad,
-                                  localQuad);
+        return GrFillRectOp::Make(context, std::move(paint), aaType, quad);
     }
 }
 
@@ -1181,16 +1174,17 @@
                 ctm.preConcat(*set[i].fPreViewMatrix);
             }
 
-            GrQuad quad, srcQuad;
+            DrawQuad quad;
+            quad.fEdgeFlags = set[i].fAAFlags;
             if (set[i].fDstClipQuad) {
-                quad = GrQuad::MakeFromSkQuad(set[i].fDstClipQuad, ctm);
+                quad.fDevice = GrQuad::MakeFromSkQuad(set[i].fDstClipQuad, ctm);
 
                 SkPoint srcPts[4];
                 GrMapRectPoints(set[i].fDstRect, set[i].fSrcRect, set[i].fDstClipQuad, srcPts, 4);
-                srcQuad = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
+                quad.fLocal = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
             } else {
-                quad = GrQuad::MakeFromRect(set[i].fDstRect, ctm);
-                srcQuad = GrQuad(set[i].fSrcRect);
+                quad.fDevice = GrQuad::MakeFromRect(set[i].fDstRect, ctm);
+                quad.fLocal = GrQuad(set[i].fSrcRect);
             }
 
             const SkRect* domain = constraint == SkCanvas::kStrict_SrcRectConstraint
@@ -1198,7 +1192,7 @@
 
             auto op = Make(context, set[i].fProxyView, set[i].fSrcAlphaType, textureColorSpaceXform,
                            filter, {alpha, alpha, alpha, alpha}, saturate, blendMode, aaType,
-                           set[i].fAAFlags, quad, srcQuad, domain);
+                           &quad, domain);
             rtc->addDrawOp(clip, std::move(op));
         }
         return;
@@ -1333,10 +1327,10 @@
     auto alphaType = static_cast<SkAlphaType>(
             random->nextRangeU(kUnknown_SkAlphaType + 1, kLastEnum_SkAlphaType));
 
+    DrawQuad quad = {GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(srcRect), aaFlags};
     return GrTextureOp::Make(context, std::move(proxyView), alphaType, std::move(texXform), filter,
-                             color, saturate, SkBlendMode::kSrcOver, aaType, aaFlags,
-                             GrQuad::MakeFromRect(rect, viewMatrix), GrQuad(srcRect),
-                             useDomain ? &srcRect : nullptr);
+                             color, saturate, SkBlendMode::kSrcOver, aaType,
+                             &quad, useDomain ? &srcRect : nullptr);
 }
 
 #endif
diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h
index 079159b..21d9b9a 100644
--- a/src/gpu/ops/GrTextureOp.h
+++ b/src/gpu/ops/GrTextureOp.h
@@ -49,9 +49,7 @@
                                           Saturate,
                                           SkBlendMode,
                                           GrAAType,
-                                          GrQuadAAFlags,
-                                          const GrQuad& deviceQuad,
-                                          const GrQuad& localQuad,
+                                          DrawQuad*,
                                           const SkRect* domain = nullptr);
 
     // Automatically falls back to using one GrFillRectOp per entry if dynamic states are not
diff --git a/tests/GrQuadCropTest.cpp b/tests/GrQuadCropTest.cpp
index 36fafe4..255cc77 100644
--- a/tests/GrQuadCropTest.cpp
+++ b/tests/GrQuadCropTest.cpp
@@ -26,21 +26,20 @@
     // Should use run_crop_fully_covers_test for non-rect matrices
     SkASSERT(viewMatrix.rectStaysRect());
 
-    GrQuad drawQuad = GrQuad::MakeFromRect(kDrawRect, viewMatrix);
-    GrQuad localQuad = GrQuad::MakeFromRect(kDrawRect, localMatrix ? *localMatrix : SkMatrix::I());
-    GrQuad* localQuadPtr = localMatrix ? &localQuad : nullptr;
-    GrQuadAAFlags edgeFlags = clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll;
+    DrawQuad quad = {GrQuad::MakeFromRect(kDrawRect, viewMatrix),
+                     GrQuad::MakeFromRect(kDrawRect, localMatrix ? *localMatrix : SkMatrix::I()),
+                     clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll};
 
-    bool exact = GrQuadUtils::CropToRect(clipRect, clipAA, &edgeFlags, &drawQuad, localQuadPtr);
+    bool exact = GrQuadUtils::CropToRect(clipRect, clipAA, &quad, /* calc. locals */ !!localMatrix);
     ASSERTF(exact, "Expected exact crop");
-    ASSERTF(drawQuad.quadType() == GrQuad::Type::kAxisAligned,
+    ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned,
             "Expected quad to remain axis-aligned");
 
     // Since we remained a rectangle, the bounds will exactly match the coordinates
     SkRect expectedBounds = viewMatrix.mapRect(kDrawRect);
     SkAssertResult(expectedBounds.intersect(clipRect));
 
-    SkRect actualBounds = drawQuad.bounds();
+    SkRect actualBounds = quad.fDevice.bounds();
     ASSERT_NEARLY_EQUAL(expectedBounds.fLeft, actualBounds.fLeft);
     ASSERT_NEARLY_EQUAL(expectedBounds.fTop, actualBounds.fTop);
     ASSERT_NEARLY_EQUAL(expectedBounds.fRight, actualBounds.fRight);
@@ -54,9 +53,9 @@
         SkMatrix toLocal = SkMatrix::Concat(*localMatrix, invViewMatrix);
 
         for (int p = 0; p < 4; ++p) {
-            SkPoint expectedPoint = drawQuad.point(p);
+            SkPoint expectedPoint = quad.fDevice.point(p);
             toLocal.mapPoints(&expectedPoint, 1);
-            SkPoint actualPoint = localQuad.point(p);
+            SkPoint actualPoint = quad.fLocal.point(p);
 
             ASSERT_NEARLY_EQUAL(expectedPoint.fX, actualPoint.fX);
             ASSERT_NEARLY_EQUAL(expectedPoint.fY, actualPoint.fY);
@@ -68,30 +67,30 @@
     SkRect drawClip = invViewMatrix.mapRect(clipRect);
     if (drawClip.fLeft > kDrawRect.fLeft) {
         if (clipAA == GrAA::kYes) {
-            ASSERTF(edgeFlags & GrQuadAAFlags::kLeft, "Expected left edge AA set");
+            ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kLeft, "Expected left edge AA set");
         } else {
-            ASSERTF(!(edgeFlags & GrQuadAAFlags::kLeft), "Expected left edge AA unset");
+            ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kLeft), "Expected left edge AA unset");
         }
     }
     if (drawClip.fRight < kDrawRect.fRight) {
         if (clipAA == GrAA::kYes) {
-            ASSERTF(edgeFlags & GrQuadAAFlags::kRight, "Expected right edge AA set");
+            ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kRight, "Expected right edge AA set");
         } else {
-            ASSERTF(!(edgeFlags & GrQuadAAFlags::kRight),  "Expected right edge AA unset");
+            ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kRight),  "Expected right edge AA unset");
         }
     }
     if (drawClip.fTop > kDrawRect.fTop) {
         if (clipAA == GrAA::kYes) {
-            ASSERTF(edgeFlags & GrQuadAAFlags::kTop, "Expected top edge AA set");
+            ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kTop, "Expected top edge AA set");
         } else {
-            ASSERTF(!(edgeFlags & GrQuadAAFlags::kTop), "Expected top edge AA unset");
+            ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kTop), "Expected top edge AA unset");
         }
     }
     if (drawClip.fBottom < kDrawRect.fBottom) {
         if (clipAA == GrAA::kYes) {
-            ASSERTF(edgeFlags & GrQuadAAFlags::kBottom, "Expected bottom edge AA set");
+            ASSERTF(quad.fEdgeFlags & GrQuadAAFlags::kBottom, "Expected bottom edge AA set");
         } else {
-            ASSERTF(!(edgeFlags & GrQuadAAFlags::kBottom), "Expected bottom edge AA unset");
+            ASSERTF(!(quad.fEdgeFlags & GrQuadAAFlags::kBottom), "Expected bottom edge AA unset");
         }
     }
 }
@@ -110,57 +109,56 @@
     containsCrop.outset(10.f, 10.f);
     SkRect drawRect = invViewMatrix.mapRect(containsCrop);
 
-    GrQuad drawQuad = GrQuad::MakeFromRect(drawRect, viewMatrix);
-    GrQuadAAFlags edgeFlags = clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll;
+    DrawQuad quad = {GrQuad::MakeFromRect(drawRect, viewMatrix),
+                     GrQuad::MakeFromRect(drawRect, localMatrix ? *localMatrix : SkMatrix::I()),
+                     clipAA == GrAA::kYes ? GrQuadAAFlags::kNone : GrQuadAAFlags::kAll};
 
     if (localMatrix) {
-        GrQuad localQuad = GrQuad::MakeFromRect(drawRect, *localMatrix);
+        DrawQuad originalQuad = quad;
 
-        GrQuad originalDrawQuad = drawQuad;
-        GrQuad originalLocalQuad = localQuad;
-        GrQuadAAFlags originalEdgeFlags = edgeFlags;
-
-        bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &edgeFlags, &drawQuad, &localQuad);
+        bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad);
         // Currently non-rect matrices don't know how to update local coordinates, so the crop
         // doesn't know how to restrict itself and should leave the inputs unmodified
         ASSERTF(!exact, "Expected crop to be not exact");
-        ASSERTF(edgeFlags == originalEdgeFlags, "Expected edge flags not to be modified");
+        ASSERTF(quad.fEdgeFlags == originalQuad.fEdgeFlags,
+                "Expected edge flags not to be modified");
 
         for (int i = 0; i < 4; ++i) {
-            ASSERT_NEARLY_EQUAL(originalDrawQuad.x(i), drawQuad.x(i));
-            ASSERT_NEARLY_EQUAL(originalDrawQuad.y(i), drawQuad.y(i));
-            ASSERT_NEARLY_EQUAL(originalDrawQuad.w(i), drawQuad.w(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fDevice.x(i), quad.fDevice.x(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fDevice.y(i), quad.fDevice.y(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fDevice.w(i), quad.fDevice.w(i));
 
-            ASSERT_NEARLY_EQUAL(originalLocalQuad.x(i), localQuad.x(i));
-            ASSERT_NEARLY_EQUAL(originalLocalQuad.y(i), localQuad.y(i));
-            ASSERT_NEARLY_EQUAL(originalLocalQuad.w(i), localQuad.w(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fLocal.x(i), quad.fLocal.x(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fLocal.y(i), quad.fLocal.y(i));
+            ASSERT_NEARLY_EQUAL(originalQuad.fLocal.w(i), quad.fLocal.w(i));
         }
     } else {
         // Since no local coordinates were provided, and the input draw geometry is known to
         // fully cover the crop rect, the quad should be updated to match cropRect exactly
-        bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &edgeFlags, &drawQuad, nullptr);
+        bool exact = GrQuadUtils::CropToRect(kDrawRect, clipAA, &quad, /* calc. local */ false);
         ASSERTF(exact, "Expected crop to be exact");
 
         GrQuadAAFlags expectedFlags = clipAA == GrAA::kYes ? GrQuadAAFlags::kAll
                                                            : GrQuadAAFlags::kNone;
-        ASSERTF(expectedFlags == edgeFlags, "Expected edge flags do not match clip AA setting");
-        ASSERTF(drawQuad.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type");
+        ASSERTF(expectedFlags == quad.fEdgeFlags,
+                "Expected edge flags do not match clip AA setting");
+        ASSERTF(quad.fDevice.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type");
 
-        ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, drawQuad.x(0));
-        ASSERT_NEARLY_EQUAL(kDrawRect.fTop, drawQuad.y(0));
-        ASSERT_NEARLY_EQUAL(1.f, drawQuad.w(0));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(0));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(0));
+        ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(0));
 
-        ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, drawQuad.x(1));
-        ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, drawQuad.y(1));
-        ASSERT_NEARLY_EQUAL(1.f, drawQuad.w(1));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fLeft, quad.fDevice.x(1));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(1));
+        ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(1));
 
-        ASSERT_NEARLY_EQUAL(kDrawRect.fRight, drawQuad.x(2));
-        ASSERT_NEARLY_EQUAL(kDrawRect.fTop, drawQuad.y(2));
-        ASSERT_NEARLY_EQUAL(1.f, drawQuad.w(2));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(2));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fTop, quad.fDevice.y(2));
+        ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(2));
 
-        ASSERT_NEARLY_EQUAL(kDrawRect.fRight, drawQuad.x(3));
-        ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, drawQuad.y(3));
-        ASSERT_NEARLY_EQUAL(1.f, drawQuad.w(3));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fRight, quad.fDevice.x(3));
+        ASSERT_NEARLY_EQUAL(kDrawRect.fBottom, quad.fDevice.y(3));
+        ASSERT_NEARLY_EQUAL(1.f, quad.fDevice.w(3));
     }
 }