Make GrTextureOp capable of edge antialiasing.

Bug: skia:
Change-Id: I0088bdbb6a76811611fa4628656bf9513c5bf04a
Reviewed-on: https://skia-review.googlesource.com/91105
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/bench/MultitextureImageBench.cpp b/bench/MultitextureImageBench.cpp
index d001cac..2c87efb 100644
--- a/bench/MultitextureImageBench.cpp
+++ b/bench/MultitextureImageBench.cpp
@@ -16,12 +16,16 @@
 
 class MultitextureImages : public Benchmark {
 public:
-    MultitextureImages(int imageSize, int dstRectSize, bool disableMultitexturing)
+    MultitextureImages(int imageSize, int dstRectSize, bool disableMultitexturing, bool aa)
             : fImageSize(imageSize)
             , fDstRectSize(dstRectSize)
-            , fDisableMultitexturing(disableMultitexturing) {
+            , fDisableMultitexturing(disableMultitexturing)
+            , fAA(aa) {
         fName.appendf("multitexture_images_%dx%d_image_%dx%d_rect", imageSize, imageSize,
                       dstRectSize, dstRectSize);
+        if (aa) {
+            fName.append("_aa");
+        }
         if (disableMultitexturing) {
             fName.append("_disable_multitexturing");
         }
@@ -60,6 +64,7 @@
         SkPaint paint;
         paint.setAlpha(0x40);
         paint.setFilterQuality(kLow_SkFilterQuality);
+        paint.setAntiAlias(fAA);
         for (int i = 0; i < loops; i++) {
             for (int j = 0; j < kNumImages; ++j) {
                 SkVector translate = this->translation(i * kNumImages + j);
@@ -87,8 +92,9 @@
 
     SkVector translation(int i) const {
         SkVector offset;
-        offset.fX = i % kNumColumns * kTranslate;
-        offset.fY = (i / kNumColumns) % kNumRows * kTranslate;
+        // Fractional offsets to ensure we can't ignore antialiasing.
+        offset.fX = i % kNumColumns * kTranslate + 0.1f;
+        offset.fY = (i / kNumColumns) % kNumRows * kTranslate + 0.1f;
         return offset;
     }
 
@@ -102,24 +108,32 @@
     int fImageSize;
     int fDstRectSize;
     bool fDisableMultitexturing;
+    bool fAA;
 
     typedef Benchmark INHERITED;
 };
 
-DEF_BENCH(return new MultitextureImages(128, 32, false));
-DEF_BENCH(return new MultitextureImages(128, 32, true));
-DEF_BENCH(return new MultitextureImages(128, 128, false));
-DEF_BENCH(return new MultitextureImages(128, 128, true));
-DEF_BENCH(return new MultitextureImages(128, 256, false));
-DEF_BENCH(return new MultitextureImages(128, 256, true));
+// Non-AA
+DEF_BENCH(return new MultitextureImages(128, 32, false, false));
+DEF_BENCH(return new MultitextureImages(128, 32, true, false));
+DEF_BENCH(return new MultitextureImages(128, 128, false, false));
+DEF_BENCH(return new MultitextureImages(128, 128, true, false));
+DEF_BENCH(return new MultitextureImages(128, 256, false, false));
+DEF_BENCH(return new MultitextureImages(128, 256, true, false));
 
-DEF_BENCH(return new MultitextureImages(512, 32, false));
-DEF_BENCH(return new MultitextureImages(512, 32, true));
-DEF_BENCH(return new MultitextureImages(512, 128, false));
-DEF_BENCH(return new MultitextureImages(512, 128, true));
-DEF_BENCH(return new MultitextureImages(512, 256, false));
-DEF_BENCH(return new MultitextureImages(512, 256, true));
-DEF_BENCH(return new MultitextureImages(512, 512, false));
-DEF_BENCH(return new MultitextureImages(512, 512, true));
+DEF_BENCH(return new MultitextureImages(512, 32, false, false));
+DEF_BENCH(return new MultitextureImages(512, 32, true, false));
+DEF_BENCH(return new MultitextureImages(512, 128, false, false));
+DEF_BENCH(return new MultitextureImages(512, 128, true, false));
+DEF_BENCH(return new MultitextureImages(512, 256, false, false));
+DEF_BENCH(return new MultitextureImages(512, 256, true, false));
+DEF_BENCH(return new MultitextureImages(512, 512, false, false));
+DEF_BENCH(return new MultitextureImages(512, 512, true, false));
+
+// AA
+DEF_BENCH(return new MultitextureImages(512, 512, true, true));
+DEF_BENCH(return new MultitextureImages(512, 512, false, true));
+DEF_BENCH(return new MultitextureImages(128, 32, true, true));
+DEF_BENCH(return new MultitextureImages(128, 32, false, true));
 
 #endif
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index f480110..22c65fa 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -49,7 +49,11 @@
             return GrShaderVar(fName, GrVertexAttribTypeToSLType(fType),
                                GrShaderVar::kIn_TypeModifier);
         }
-        const char*          fName;
+        bool isInitialized() const { return SkToBool(fName); }
+        Attribute() = default;
+        Attribute(const char* name, GrVertexAttribType type, int offset, InputRate rate)
+                : fName(name), fType(type), fOffsetInRecord(offset), fInputRate(rate) {}
+        const char*          fName = nullptr;
         GrVertexAttribType   fType;
         int                  fOffsetInRecord;
         InputRate            fInputRate;
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index c985e96..b0b117f 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -766,7 +766,7 @@
 
 void GrRenderTargetContext::drawTextureAffine(const GrClip& clip, sk_sp<GrTextureProxy> proxy,
                                               GrSamplerState::Filter filter, GrColor color,
-                                              const SkRect& srcRect, const SkRect& dstRect,
+                                              const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
                                               const SkMatrix& viewMatrix,
                                               sk_sp<GrColorSpaceXform> colorSpaceXform) {
     ASSERT_SINGLE_OWNER
@@ -785,9 +785,9 @@
     }
 
     bool allowSRGB = SkToBool(this->colorSpaceInfo().colorSpace());
-    this->addDrawOp(clip, GrTextureOp::Make(std::move(proxy), filter, color, clippedSrcRect,
-                                            clippedDstRect, viewMatrix, std::move(colorSpaceXform),
-                                            allowSRGB));
+    this->addDrawOp(
+            clip, GrTextureOp::Make(std::move(proxy), filter, color, clippedSrcRect, clippedDstRect,
+                                    aa, viewMatrix, std::move(colorSpaceXform), allowSRGB));
 }
 
 void GrRenderTargetContext::fillRectWithLocalMatrix(const GrClip& clip,
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 6215262..2ae52b9 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -145,11 +145,10 @@
      * Creates an op that draws a subrectangle of a texture. The passed color is modulated by the
      * texture's color. 'srcRect' specifies the rectangle of the texture to draw. 'dstRect'
      * specifies the rectangle to draw in local coords which will be transformed by 'viewMatrix' to
-     * device space. The edges of the rendered rectangle are not antialiased. This asserts that the
-     * view matrix does not have perspective.
+     * device space. This asserts that the view matrix does not have perspective.
      */
     void drawTextureAffine(const GrClip& clip, sk_sp<GrTextureProxy>, GrSamplerState::Filter,
-                           GrColor, const SkRect& srcRect, const SkRect& dstRect,
+                           GrColor, const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
                            const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform>);
 
     /**
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index bedd292..f8e5f74 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -94,14 +94,13 @@
 static bool can_use_draw_texture_affine(const SkPaint& paint, const SkMatrix& ctm,
                                         SkCanvas::SrcRectConstraint constraint) {
     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
-            !paint.getImageFilter() && !paint.isAntiAlias() &&
-            paint.getFilterQuality() < kMedium_SkFilterQuality &&
+            !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality &&
             paint.getBlendMode() == SkBlendMode::kSrcOver && !ctm.hasPerspective() &&
             SkCanvas::kFast_SrcRectConstraint == constraint);
 }
 
 static void draw_texture_affine(const SkPaint& paint, const SkMatrix& ctm, const SkRect* src,
-                                const SkRect* dst, sk_sp<GrTextureProxy> proxy,
+                                const SkRect* dst, GrAA aa, sk_sp<GrTextureProxy> proxy,
                                 SkColorSpace* colorSpace, const GrClip& clip,
                                 GrRenderTargetContext* rtc) {
     SkASSERT(!(SkToBool(src) && !SkToBool(dst)));
@@ -131,7 +130,7 @@
     GrColor color = GrPixelConfigIsAlphaOnly(proxy->config())
                             ? SkColorToPremulGrColor(paint.getColor())
                             : SkColorAlphaToGrColor(paint.getColor());
-    rtc->drawTextureAffine(clip, std::move(proxy), filter, color, srcRect, dstRect, ctm,
+    rtc->drawTextureAffine(clip, std::move(proxy), filter, color, srcRect, dstRect, aa, ctm,
                            std::move(csxf));
 }
 
@@ -143,8 +142,8 @@
                                          SkCanvas::SrcRectConstraint constraint,
                                          const SkMatrix& viewMatrix, const SkPaint& paint) {
     if (can_use_draw_texture_affine(paint, this->ctm(), constraint)) {
-        draw_texture_affine(paint, viewMatrix, srcRect, dstRect, std::move(proxy), colorSpace,
-                            this->clip(), fRenderTargetContext.get());
+        draw_texture_affine(paint, viewMatrix, srcRect, dstRect, GrAA(paint.isAntiAlias()),
+                            std::move(proxy), colorSpace, this->clip(), fRenderTargetContext.get());
         return;
     }
     GrTextureAdjuster adjuster(this->context(), std::move(proxy), alphaType, pinnedUniqueID,
@@ -166,8 +165,8 @@
         if (!proxy) {
             return;
         }
-        draw_texture_affine(paint, viewMatrix, srcRect, dstRect, std::move(proxy), cs.get(),
-                            this->clip(), fRenderTargetContext.get());
+        draw_texture_affine(paint, viewMatrix, srcRect, dstRect, GrAA(paint.isAntiAlias()),
+                            std::move(proxy), cs.get(), this->clip(), fRenderTargetContext.get());
         return;
     }
     this->drawTextureProducer(maker, srcRect, dstRect, constraint, viewMatrix, paint);
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 5ed02cc..6d735f5 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -20,9 +20,13 @@
 #include "GrTextureProxy.h"
 #include "SkGr.h"
 #include "SkMathPriv.h"
+#include "SkPoint.h"
+#include "SkPoint3.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
 
 namespace {
 
@@ -38,12 +42,25 @@
         SkPoint fTextureCoords;
         GrColor fColor;
     };
+    struct AAVertex {
+        SkPoint fPosition;
+        SkPoint fTextureCoords;
+        SkPoint3 fEdges[4];
+        GrColor fColor;
+    };
     struct MultiTextureVertex {
         SkPoint fPosition;
         int fTextureIdx;
         SkPoint fTextureCoords;
         GrColor fColor;
     };
+    struct AAMultiTextureVertex {
+        SkPoint fPosition;
+        int fTextureIdx;
+        SkPoint fTextureCoords;
+        SkPoint3 fEdges[4];
+        GrColor fColor;
+    };
 
     // Maximum number of textures supported by this op. Must also be checked against the caps
     // limit. These numbers were based on some limited experiments on a HP Z840 and Pixel XL 2016
@@ -59,7 +76,7 @@
     }
 
     static sk_sp<GrGeometryProcessor> Make(sk_sp<GrTextureProxy> proxies[], int proxyCnt,
-                                           sk_sp<GrColorSpaceXform> csxf,
+                                           sk_sp<GrColorSpaceXform> csxf, GrAA aa,
                                            const GrSamplerState::Filter filters[],
                                            const GrShaderCaps& caps) {
         // We use placement new to avoid always allocating space for kMaxTextures TextureSampler
@@ -68,7 +85,7 @@
         size_t size = sizeof(TextureGeometryProcessor) + sizeof(TextureSampler) * (samplerCnt - 1);
         void* mem = GrGeometryProcessor::operator new(size);
         return sk_sp<TextureGeometryProcessor>(new (mem) TextureGeometryProcessor(
-                proxies, proxyCnt, samplerCnt, std::move(csxf), filters, caps));
+                proxies, proxyCnt, samplerCnt, std::move(csxf), aa, filters, caps));
     }
 
     ~TextureGeometryProcessor() override {
@@ -82,6 +99,7 @@
 
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
         b->add32(GrColorSpaceXform::XformKey(fColorSpaceXform.get()));
+        b->add32(static_cast<uint32_t>(this->aa()));
     }
 
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
@@ -149,13 +167,36 @@
                                                                       &fColorSpaceXformHelper);
                 }
                 args.fFragBuilder->codeAppend(";");
-                args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
+                if (GrAA::kYes == textureGP.aa()) {
+                    GrGLSLVarying aaDistVarying(kFloat4_GrSLType,
+                                                GrGLSLVarying::Scope::kVertToFrag);
+                    args.fVaryingHandler->addVarying("aaDists", &aaDistVarying);
+                    args.fVertBuilder->codeAppendf(
+                            R"(%s = float4(dot(aaEdge0.xy, %s.xy) + aaEdge0.z,
+                                           dot(aaEdge1.xy, %s.xy) + aaEdge1.z,
+                                           dot(aaEdge2.xy, %s.xy) + aaEdge2.z,
+                                           dot(aaEdge3.xy, %s.xy) + aaEdge3.z);)",
+                            aaDistVarying.vsOut(), textureGP.fPositions.fName,
+                            textureGP.fPositions.fName, textureGP.fPositions.fName,
+                            textureGP.fPositions.fName);
+
+                    args.fFragBuilder->codeAppendf(
+                            "float mindist = min(min(%s.x, %s.y), min(%s.z, %s.w));",
+                            aaDistVarying.fsIn(), aaDistVarying.fsIn(), aaDistVarying.fsIn(),
+                            aaDistVarying.fsIn());
+                    args.fFragBuilder->codeAppendf("%s = float4(clamp(mindist, 0, 1));",
+                                                   args.fOutputCoverage);
+                } else {
+                    args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
+                }
             }
             GrGLSLColorSpaceXformHelper fColorSpaceXformHelper;
         };
         return new GLSLProcessor;
     }
 
+    GrAA aa() const { return GrAA(fAAEdges[0].isInitialized()); }
+
 private:
     // This exists to reduce the number of shaders generated. It does some rounding of sampler
     // counts.
@@ -173,10 +214,9 @@
     }
 
     TextureGeometryProcessor(sk_sp<GrTextureProxy> proxies[], int proxyCnt, int samplerCnt,
-                             sk_sp<GrColorSpaceXform> csxf, const GrSamplerState::Filter filters[],
-                             const GrShaderCaps& caps)
-            : INHERITED(kTextureGeometryProcessor_ClassID)
-            , fColorSpaceXform(std::move(csxf)) {
+                             sk_sp<GrColorSpaceXform> csxf, GrAA aa,
+                             const GrSamplerState::Filter filters[], const GrShaderCaps& caps)
+            : INHERITED(kTextureGeometryProcessor_ClassID), fColorSpaceXform(std::move(csxf)) {
         SkASSERT(proxyCnt > 0 && samplerCnt >= proxyCnt);
         fPositions = this->addVertexAttrib("position", kFloat2_GrVertexAttribType);
         fSamplers[0].reset(std::move(proxies[0]), filters[0]);
@@ -200,6 +240,12 @@
         }
 
         fTextureCoords = this->addVertexAttrib("textureCoords", kFloat2_GrVertexAttribType);
+        if (GrAA::kYes == aa) {
+            fAAEdges[0] = this->addVertexAttrib("aaEdge0", kFloat3_GrVertexAttribType);
+            fAAEdges[1] = this->addVertexAttrib("aaEdge1", kFloat3_GrVertexAttribType);
+            fAAEdges[2] = this->addVertexAttrib("aaEdge2", kFloat3_GrVertexAttribType);
+            fAAEdges[3] = this->addVertexAttrib("aaEdge3", kFloat3_GrVertexAttribType);
+        }
         fColors = this->addVertexAttrib("color", kUByte4_norm_GrVertexAttribType);
     }
 
@@ -207,12 +253,148 @@
     Attribute fTextureIdx;
     Attribute fTextureCoords;
     Attribute fColors;
+    Attribute fAAEdges[4];
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
     TextureSampler fSamplers[1];
 
     typedef GrGeometryProcessor INHERITED;
 };
 
+namespace {
+// This is a class soley so it can be partially specialized (functions cannot be).
+template<GrAA, typename Vertex> class VertexAAHandler;
+
+template<typename Vertex> class VertexAAHandler<GrAA::kNo, Vertex> {
+public:
+    static void AssignPositionsAndTexCoords(Vertex* vertices, const GrQuad& quad,
+                                            const SkRect& texRect) {
+        vertices[0].fPosition = quad.point(0);
+        vertices[0].fTextureCoords = {texRect.fLeft, texRect.fTop};
+        vertices[1].fPosition = quad.point(1);
+        vertices[1].fTextureCoords = {texRect.fLeft, texRect.fBottom};
+        vertices[2].fPosition = quad.point(2);
+        vertices[2].fTextureCoords = {texRect.fRight, texRect.fTop};
+        vertices[3].fPosition = quad.point(3);
+        vertices[3].fTextureCoords = {texRect.fRight, texRect.fBottom};
+    }
+};
+
+template<typename Vertex> class VertexAAHandler<GrAA::kYes, Vertex> {
+public:
+    static void AssignPositionsAndTexCoords(Vertex* vertices, const GrQuad& quad,
+                                            const SkRect& texRect) {
+        // We compute the four edge equations for quad, then outset them and compute a new quad
+        // as the intersection points of the outset edges.
+
+        // GrQuad is in tristip order but we want the points to be in a fan order, so swap 2 and 3.
+        Sk4f xs(quad.point(0).fX, quad.point(1).fX, quad.point(3).fX, quad.point(2).fX);
+        Sk4f ys(quad.point(0).fY, quad.point(1).fY, quad.point(3).fY, quad.point(2).fY);
+        Sk4f xsrot = SkNx_shuffle<1, 2, 3, 0>(xs);
+        Sk4f ysrot = SkNx_shuffle<1, 2, 3, 0>(ys);
+        Sk4f normXs = ysrot - ys;
+        Sk4f normYs = xs - xsrot;
+        Sk4f ds = xsrot * ys - ysrot * xs;
+        Sk4f invNormLengths = (normXs * normXs + normYs * normYs).rsqrt();
+        float test = normXs[0] * xs[2] + normYs[0] * ys[2] + ds[0];
+        // Make sure the edge equations have their normals facing into the quad in device space
+        if (test < 0) {
+            invNormLengths = -invNormLengths;
+        }
+        normXs *= invNormLengths;
+        normYs *= invNormLengths;
+        ds *= invNormLengths;
+
+        // Here is the bloat. This makes our edge equations compute coverage without requiring a
+        // half pixel offset and is also used to compute the bloated quad that will cover all
+        // pixels.
+        ds += Sk4f(0.5f);
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 4; ++j) {
+                vertices[j].fEdges[i].fX = normXs[i];
+                vertices[j].fEdges[i].fY = normYs[i];
+                vertices[j].fEdges[i].fZ = ds[i];
+            }
+        }
+
+        // Reverse the process to compute the points of the bloated quad from the edge equations.
+        // This time the inputs don't have 1s as their third coord and we want to homogenize rather
+        // than normalize the output since we need a GrQuad with 2D points.
+        xsrot = SkNx_shuffle<3, 0, 1, 2>(normXs);
+        ysrot = SkNx_shuffle<3, 0, 1, 2>(normYs);
+        Sk4f dsrot = SkNx_shuffle<3, 0, 1, 2>(ds);
+        xs = ysrot * ds - normYs * dsrot;
+        ys = normXs * dsrot - xsrot * ds;
+        ds = xsrot * normYs - ysrot * normXs;
+        ds = ds.invert();
+        xs *= ds;
+        ys *= ds;
+
+        // Go back to tri strip order when writing out the bloated quad to vertex positions.
+        vertices[0].fPosition = {xs[0], ys[0]};
+        vertices[1].fPosition = {xs[1], ys[1]};
+        vertices[3].fPosition = {xs[2], ys[2]};
+        vertices[2].fPosition = {xs[3], ys[3]};
+
+        AssignTexCoords(vertices, quad, texRect);
+    }
+
+private:
+    static void AssignTexCoords(Vertex* vertices, const GrQuad& quad, const SkRect& tex) {
+        SkMatrix q = SkMatrix::MakeAll(quad.point(0).fX, quad.point(1).fX, quad.point(2).fX,
+                                       quad.point(0).fY, quad.point(1).fY, quad.point(2).fY,
+                                                    1.f,              1.f,              1.f);
+        SkMatrix qinv;
+        if (!q.invert(&qinv)) {
+            return;
+        }
+        SkMatrix t = SkMatrix::MakeAll(tex.fLeft,    tex.fLeft, tex.fRight,
+                                        tex.fTop,  tex.fBottom,   tex.fTop,
+                                             1.f,          1.f,        1.f);
+        SkMatrix map;
+        map.setConcat(t, qinv);
+        SkMatrixPriv::MapPointsWithStride(map, &vertices[0].fTextureCoords, sizeof(Vertex),
+                                          &vertices[0].fPosition, sizeof(Vertex), 4);
+    }
+};
+
+template <typename Vertex, bool IsMultiTex> struct TexIdAssigner;
+
+template <typename Vertex> struct TexIdAssigner<Vertex, true> {
+    static void Assign(Vertex* vertices, int textureIdx) {
+        vertices[0].fTextureIdx = textureIdx;
+        vertices[1].fTextureIdx = textureIdx;
+        vertices[2].fTextureIdx = textureIdx;
+        vertices[3].fTextureIdx = textureIdx;
+    }
+};
+
+template <typename Vertex> struct TexIdAssigner<Vertex, false> {
+    static void Assign(Vertex* vertices, int textureIdx) {}
+};
+}  // anonymous namespace
+
+template <typename Vertex, bool IsMultiTex, GrAA AA>
+static void tessellate_quad(const GrQuad& devQuad, const SkRect& srcRect, GrColor color,
+                            GrSurfaceOrigin origin, Vertex* vertices, SkScalar iw, SkScalar ih,
+                            int textureIdx) {
+    SkRect texRect = {
+            iw * srcRect.fLeft,
+            ih * srcRect.fTop,
+            iw * srcRect.fRight,
+            ih * srcRect.fBottom
+    };
+    if (origin == kBottomLeft_GrSurfaceOrigin) {
+        texRect.fTop = 1.f - texRect.fTop;
+        texRect.fBottom = 1.f - texRect.fBottom;
+    }
+    VertexAAHandler<AA, Vertex>::AssignPositionsAndTexCoords(vertices, devQuad, texRect);
+    vertices[0].fColor = color;
+    vertices[1].fColor = color;
+    vertices[2].fColor = color;
+    vertices[3].fColor = color;
+    TexIdAssigner<Vertex, IsMultiTex>::Assign(vertices, textureIdx);
+}
 /**
  * Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a
  * the texture by color. The blend with the destination is always src-over. The edges are non-AA.
@@ -221,11 +403,11 @@
 public:
     static std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy> proxy,
                                           GrSamplerState::Filter filter, GrColor color,
-                                          const SkRect srcRect, const SkRect dstRect,
+                                          const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
                                           const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> csxf,
                                           bool allowSRBInputs) {
         return std::unique_ptr<GrDrawOp>(new TextureOp(std::move(proxy), filter, color, srcRect,
-                                                       dstRect, viewMatrix, std::move(csxf),
+                                                       dstRect, aa, viewMatrix, std::move(csxf),
                                                        allowSRBInputs));
     }
 
@@ -298,22 +480,23 @@
 #if defined(__clang__) && (__clang_major__ * 1000 + __clang_minor__) >= 3007
 __attribute__((no_sanitize("float-cast-overflow")))
 #endif
-    size_t RectSizeAsSizeT(const SkRect &rect) {;
+    size_t RectSizeAsSizeT(const SkRect& rect) {;
         return static_cast<size_t>(SkTMax(rect.width(), 1.f) * SkTMax(rect.height(), 1.f));
     }
 
     static constexpr int kMaxTextures = TextureGeometryProcessor::kMaxTextures;
 
     TextureOp(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, GrColor color,
-              const SkRect& srcRect, const SkRect& dstRect, const SkMatrix& viewMatrix,
+              const SkRect& srcRect, const SkRect& dstRect, GrAA aa, const SkMatrix& viewMatrix,
               sk_sp<GrColorSpaceXform> csxf, bool allowSRGBInputs)
             : INHERITED(ClassID())
             , fColorSpaceXform(std::move(csxf))
             , fProxy0(proxy.release())
             , fFilter0(filter)
             , fProxyCnt(1)
-            , fFinalized(false)
-            , fAllowSRGBInputs(allowSRGBInputs) {
+            , fAA(aa == GrAA::kYes ? 1 : 0)
+            , fFinalized(0)
+            , fAllowSRGBInputs(allowSRGBInputs ? 1 : 0) {
         Draw& draw = fDraws.push_back();
         draw.fSrcRect = srcRect;
         draw.fTextureIdx = 0;
@@ -339,7 +522,7 @@
 
         sk_sp<GrGeometryProcessor> gp =
                 TextureGeometryProcessor::Make(proxiesSPs, fProxyCnt, std::move(fColorSpaceXform),
-                                               filters, *target->caps().shaderCaps());
+                                               this->aa(), filters, *target->caps().shaderCaps());
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
         args.fCaps = &target->caps();
@@ -356,35 +539,28 @@
             return;
         }
         if (1 == fProxyCnt) {
-            SkASSERT(gp->getVertexStride() == sizeof(TextureGeometryProcessor::Vertex));
-            for (int i = 0; i < fDraws.count(); ++i) {
-                auto vertices = static_cast<TextureGeometryProcessor::Vertex*>(vdata);
-                GrTexture* texture = proxies[0]->priv().peekTexture();
-                float iw = 1.f / texture->width();
-                float ih = 1.f / texture->height();
-                float tl = iw * fDraws[i].fSrcRect.fLeft;
-                float tr = iw * fDraws[i].fSrcRect.fRight;
-                float tt = ih * fDraws[i].fSrcRect.fTop;
-                float tb = ih * fDraws[i].fSrcRect.fBottom;
-                if (proxies[0]->origin() == kBottomLeft_GrSurfaceOrigin) {
-                    tt = 1.f - tt;
-                    tb = 1.f - tb;
+            GrSurfaceOrigin origin = proxies[0]->origin();
+            GrTexture* texture = proxies[0]->priv().peekTexture();
+            float iw = 1.f / texture->width();
+            float ih = 1.f / texture->height();
+            if (GrAA::kYes == this->aa()) {
+                SkASSERT(gp->getVertexStride() == sizeof(TextureGeometryProcessor::AAVertex));
+                auto vertices = static_cast<TextureGeometryProcessor::AAVertex*>(vdata);
+                for (int i = 0; i < fDraws.count(); ++i) {
+                    tessellate_quad<TextureGeometryProcessor::AAVertex, false, GrAA::kYes>(
+                            fDraws[i].fQuad, fDraws[i].fSrcRect, fDraws[i].fColor, origin,
+                            vertices + 4 * i, iw, ih, 0);
                 }
-                vertices[0 + 4 * i].fPosition = fDraws[i].fQuad.points()[0];
-                vertices[0 + 4 * i].fTextureCoords = {tl, tt};
-                vertices[0 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[1 + 4 * i].fPosition = fDraws[i].fQuad.points()[1];
-                vertices[1 + 4 * i].fTextureCoords = {tl, tb};
-                vertices[1 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[2 + 4 * i].fPosition = fDraws[i].fQuad.points()[2];
-                vertices[2 + 4 * i].fTextureCoords = {tr, tt};
-                vertices[2 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[3 + 4 * i].fPosition = fDraws[i].fQuad.points()[3];
-                vertices[3 + 4 * i].fTextureCoords = {tr, tb};
-                vertices[3 + 4 * i].fColor = fDraws[i].fColor;
+            } else {
+                SkASSERT(gp->getVertexStride() == sizeof(TextureGeometryProcessor::Vertex));
+                auto vertices = static_cast<TextureGeometryProcessor::Vertex*>(vdata);
+                for (int i = 0; i < fDraws.count(); ++i) {
+                    tessellate_quad<TextureGeometryProcessor::Vertex, false, GrAA::kNo>(
+                            fDraws[i].fQuad, fDraws[i].fSrcRect, fDraws[i].fColor, origin,
+                            vertices + 4 * i, iw, ih, 0);
+                }
             }
         } else {
-            SkASSERT(gp->getVertexStride() == sizeof(TextureGeometryProcessor::MultiTextureVertex));
             GrTexture* textures[kMaxTextures];
             float iw[kMaxTextures];
             float ih[kMaxTextures];
@@ -393,33 +569,29 @@
                 iw[t] = 1.f / textures[t]->width();
                 ih[t] = 1.f / textures[t]->height();
             }
-            for (int i = 0; i < fDraws.count(); ++i) {
-                int t = fDraws[i].fTextureIdx;
-                auto vertices = static_cast<TextureGeometryProcessor::MultiTextureVertex*>(vdata);
-                float tl = iw[t] * fDraws[i].fSrcRect.fLeft;
-                float tr = iw[t] * fDraws[i].fSrcRect.fRight;
-                float tt = ih[t] * fDraws[i].fSrcRect.fTop;
-                float tb = ih[t] * fDraws[i].fSrcRect.fBottom;
-                if (proxies[t]->origin() == kBottomLeft_GrSurfaceOrigin) {
-                    tt = 1.f - tt;
-                    tb = 1.f - tb;
+            if (GrAA::kYes == this->aa()) {
+                SkASSERT(gp->getVertexStride() ==
+                         sizeof(TextureGeometryProcessor::AAMultiTextureVertex));
+                auto vertices = static_cast<TextureGeometryProcessor::AAMultiTextureVertex*>(vdata);
+                for (int i = 0; i < fDraws.count(); ++i) {
+                    auto tidx = fDraws[i].fTextureIdx;
+                    GrSurfaceOrigin origin = proxies[tidx]->origin();
+                    tessellate_quad<TextureGeometryProcessor::AAMultiTextureVertex, true,
+                                    GrAA::kYes>(fDraws[i].fQuad, fDraws[i].fSrcRect,
+                                                fDraws[i].fColor, origin, vertices + 4 * i,
+                                                iw[tidx], ih[tidx], tidx);
                 }
-                vertices[0 + 4 * i].fPosition = fDraws[i].fQuad.points()[0];
-                vertices[0 + 4 * i].fTextureIdx = t;
-                vertices[0 + 4 * i].fTextureCoords = {tl, tt};
-                vertices[0 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[1 + 4 * i].fPosition = fDraws[i].fQuad.points()[1];
-                vertices[1 + 4 * i].fTextureIdx = t;
-                vertices[1 + 4 * i].fTextureCoords = {tl, tb};
-                vertices[1 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[2 + 4 * i].fPosition = fDraws[i].fQuad.points()[2];
-                vertices[2 + 4 * i].fTextureIdx = t;
-                vertices[2 + 4 * i].fTextureCoords = {tr, tt};
-                vertices[2 + 4 * i].fColor = fDraws[i].fColor;
-                vertices[3 + 4 * i].fPosition = fDraws[i].fQuad.points()[3];
-                vertices[3 + 4 * i].fTextureIdx = t;
-                vertices[3 + 4 * i].fTextureCoords = {tr, tb};
-                vertices[3 + 4 * i].fColor = fDraws[i].fColor;
+            } else {
+                SkASSERT(gp->getVertexStride() ==
+                         sizeof(TextureGeometryProcessor::MultiTextureVertex));
+                auto vertices = static_cast<TextureGeometryProcessor::MultiTextureVertex*>(vdata);
+                for (int i = 0; i < fDraws.count(); ++i) {
+                    auto tidx = fDraws[i].fTextureIdx;
+                    GrSurfaceOrigin origin = proxies[tidx]->origin();
+                    tessellate_quad<TextureGeometryProcessor::MultiTextureVertex, true, GrAA::kNo>(
+                            fDraws[i].fQuad, fDraws[i].fSrcRect, fDraws[i].fColor, origin,
+                            vertices + 4 * i, iw[tidx], ih[tidx], tidx);
+                }
             }
         }
         GrPrimitiveType primitiveType =
@@ -446,6 +618,9 @@
         if (!GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get())) {
             return false;
         }
+        if (this->fAA != that->fAA) {
+            return false;
+        }
         // Because of an issue where GrColorSpaceXform adds the same function every time it is used
         // in a texture lookup, we only allow multiple textures when there is no transform.
         if (TextureGeometryProcessor::SupportsMultitexture(shaderCaps) && !fColorSpaceXform &&
@@ -560,6 +735,8 @@
         return newProxyCnt;
     }
 
+    GrAA aa() const { return static_cast<GrAA>(fAA); }
+
     GrTextureProxy* const* proxies() const { return fProxyCnt > 1 ? fProxyArray : &fProxy0; }
 
     const GrSamplerState::Filter* filters() const {
@@ -585,12 +762,12 @@
         GrTextureProxy** fProxyArray;
     };
     size_t fMaxApproxDstPixelArea;
-    // The next four members should pack.
     GrSamplerState::Filter fFilter0;
     uint8_t fProxyCnt;
+    unsigned fAA : 1;
     // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
-    uint8_t fFinalized;
-    uint8_t fAllowSRGBInputs;
+    unsigned fFinalized : 1;
+    unsigned fAllowSRGBInputs : 1;
 
     typedef GrMeshDrawOp INHERITED;
 };
@@ -603,11 +780,11 @@
 namespace GrTextureOp {
 
 std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter,
-                               GrColor color, const SkRect& srcRect, const SkRect& dstRect,
+                               GrColor color, const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
                                const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> csxf,
                                bool allowSRGBInputs) {
     SkASSERT(!viewMatrix.hasPerspective());
-    return TextureOp::Make(std::move(proxy), filter, color, srcRect, dstRect, viewMatrix,
+    return TextureOp::Make(std::move(proxy), filter, color, srcRect, dstRect, aa, viewMatrix,
                            std::move(csxf), allowSRGBInputs);
 }
 
@@ -639,7 +816,8 @@
             static_cast<uint32_t>(GrSamplerState::Filter::kMipMap) + 1);
     auto csxf = GrTest::TestColorXform(random);
     bool allowSRGBInputs = random->nextBool();
-    return GrTextureOp::Make(std::move(proxy), filter, color, srcRect, rect, viewMatrix,
+    GrAA aa = GrAA(random->nextBool());
+    return GrTextureOp::Make(std::move(proxy), filter, color, srcRect, rect, aa, viewMatrix,
                              std::move(csxf), allowSRGBInputs);
 }
 
diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h
index d5a766b..9a03c8c 100644
--- a/src/gpu/ops/GrTextureOp.h
+++ b/src/gpu/ops/GrTextureOp.h
@@ -23,7 +23,7 @@
  * space. 'viewMatrix' must be affine.
  */
 std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy>, GrSamplerState::Filter, GrColor,
-                               const SkRect& srcRect, const SkRect& dstRect,
+                               const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
                                const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform>,
                                bool allowSRGBInputs);
 }