Transform vertices for distance field glyphs on CPU.

This allows batching of DF draws with different view matrices.

For perspective matrices this means the transformed position vertex
attribute must have w values. Currently, non-perspective DF draws still
use 2 component positions, though this could be changed in the future.
Consequently, perspective draws can batch with other perspective draws
but not non-perspective draws.

Adds a GM to test batching and reusing the same blobs with both perspective
and non-perspective matrices.

Change-Id: I0e42c5449ebf3a5a54025dbcdec824d904d5bd9e
Reviewed-on: https://skia-review.googlesource.com/79900
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/gm/dftext_blob_persp.cpp b/gm/dftext_blob_persp.cpp
new file mode 100644
index 0000000..cfa681d
--- /dev/null
+++ b/gm/dftext_blob_persp.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "Resources.h"
+#include "SkCanvas.h"
+#include "SkSurface.h"
+#include "SkTextBlob.h"
+#include "SkTypeface.h"
+#include "sk_tool_utils.h"
+
+/**
+ * This GM tests reusing the same text blobs with distance fields rendering using various
+ * combinations of perspective and non-perspetive matrices, scissor clips, and different x,y params
+ * passed to the draw.
+ */
+class DFTextBlobPerspGM : public skiagm::GM {
+public:
+    DFTextBlobPerspGM() { this->setBGColor(0xFFFFFFFF); }
+
+protected:
+    SkString onShortName() override {
+        SkString name("dftext_blob_persp");
+        name.append(sk_tool_utils::platform_font_manager());
+        return name;
+    }
+
+    SkISize onISize() override { return SkISize::Make(900, 350); }
+
+    void onOnceBeforeDraw() override {
+        for (int i = 0; i < 3; ++i) {
+            SkPaint paint;
+            paint.setTextSize(32);
+            paint.setAntiAlias(i > 0);
+            paint.setLCDRenderText(i > 1);
+            paint.setSubpixelText(true);
+            SkTextBlobBuilder builder;
+            sk_tool_utils::add_to_text_blob(&builder, "SkiaText", paint, 0, 0);
+            fBlobs.emplace_back(builder.make());
+        }
+    }
+
+    virtual void onDraw(SkCanvas* inputCanvas) override {
+    // set up offscreen rendering with distance field text
+#if SK_SUPPORT_GPU
+        GrContext* ctx = inputCanvas->getGrContext();
+        SkISize size = this->onISize();
+        if (!inputCanvas->getBaseLayerSize().isEmpty()) {
+            size = inputCanvas->getBaseLayerSize();
+        }
+        SkImageInfo info = SkImageInfo::MakeN32(size.width(), size.height(), kPremul_SkAlphaType,
+                                                inputCanvas->imageInfo().refColorSpace());
+        SkSurfaceProps props(SkSurfaceProps::kUseDeviceIndependentFonts_Flag,
+                             SkSurfaceProps::kLegacyFontHost_InitType);
+        auto surface = SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, 0, &props);
+        SkCanvas* canvas = surface ? surface->getCanvas() : inputCanvas;
+        // init our new canvas with the old canvas's matrix
+        canvas->setMatrix(inputCanvas->getTotalMatrix());
+#else
+        SkCanvas* canvas = inputCanvas;
+#endif
+        SkScalar x = 0, y = 0;
+        SkScalar maxH = 0;
+        for (auto twm : {TranslateWithMatrix::kNo, TranslateWithMatrix::kYes}) {
+            for (auto pm : {PerspMode::kNone, PerspMode::kX, PerspMode::kY, PerspMode::kXY}) {
+                for (auto& blob : fBlobs) {
+                    for (bool clip : {false, true}) {
+                        canvas->save();
+                        SkScalar w = blob->bounds().width();
+                        SkScalar h = blob->bounds().height();
+                        if (clip) {
+                            auto rect =
+                                    SkRect::MakeXYWH(x + 5, y + 5, w * 3.f / 4.f, h * 3.f / 4.f);
+                            canvas->clipRect(rect, false);
+                        }
+                        this->drawBlob(canvas, blob.get(), SK_ColorBLACK, x, y + h, pm, twm);
+                        x += w + 20.f;
+                        maxH = SkTMax(h, maxH);
+                        canvas->restore();
+                    }
+                }
+                x = 0;
+                y += maxH + 20.f;
+                maxH = 0;
+            }
+        }
+#if SK_SUPPORT_GPU
+        // render offscreen buffer
+        if (surface) {
+            SkAutoCanvasRestore acr(inputCanvas, true);
+            // since we prepended this matrix already, we blit using identity
+            inputCanvas->resetMatrix();
+            inputCanvas->drawImage(surface->makeImageSnapshot().get(), 0, 0, nullptr);
+        }
+#endif
+    }
+
+private:
+    enum class PerspMode { kNone, kX, kY, kXY };
+
+    enum class TranslateWithMatrix : bool { kNo, kYes };
+
+    void drawBlob(SkCanvas* canvas, SkTextBlob* blob, SkColor color, SkScalar x, SkScalar y,
+                  PerspMode perspMode, TranslateWithMatrix translateWithMatrix) {
+        canvas->save();
+        SkMatrix persp = SkMatrix::I();
+        switch (perspMode) {
+            case PerspMode::kNone:
+                break;
+            case PerspMode::kX:
+                persp.setPerspX(0.005f);
+                break;
+            case PerspMode::kY:
+                persp.setPerspY(00.005f);
+                break;
+            case PerspMode::kXY:
+                persp.setPerspX(-0.001f);
+                persp.setPerspY(-0.0015f);
+                break;
+        }
+        persp = SkMatrix::Concat(persp, SkMatrix::MakeTrans(-x, -y));
+        persp = SkMatrix::Concat(SkMatrix::MakeTrans(x, y), persp);
+        canvas->concat(persp);
+        if (TranslateWithMatrix::kYes == translateWithMatrix) {
+            canvas->translate(x, y);
+            x = 0;
+            y = 0;
+        }
+        SkPaint paint;
+        paint.setColor(color);
+        canvas->drawTextBlob(blob, x, y, paint);
+        canvas->restore();
+    }
+
+    SkTArray<sk_sp<SkTextBlob>, true> fBlobs;
+    typedef skiagm::GM INHERITED;
+};
+
+DEF_GM(return new DFTextBlobPerspGM;)
diff --git a/gn/gm.gni b/gn/gm.gni
index 4d4833f..62be999 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -101,6 +101,7 @@
   "$_gm/dashing.cpp",
   "$_gm/degeneratesegments.cpp",
   "$_gm/dftext.cpp",
+  "$_gm/dftext_blob_persp.cpp",
   "$_gm/discard.cpp",
   "$_gm/displacement.cpp",
   "$_gm/distantclip.cpp",
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index e882328..b527ab5 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -1337,6 +1337,9 @@
         @param count  items in SkPoint3 array to transform
     */
     void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const;
+    /** Same as above but with a variable offset between successive points. */
+    void mapHomogeneousPointsWithStride(SkPoint3 dst[], const SkPoint3 src[], size_t stride,
+                                        int count) const;
 
     /** Maps SkPoint (x, y) to result. SkPoint is mapped by multiplying by SkMatrix. Given:
 
diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp
index d025240..4851aea 100644
--- a/src/core/SkMatrix.cpp
+++ b/src/core/SkMatrix.cpp
@@ -1037,32 +1037,48 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const {
+void SkMatrix::mapHomogeneousPointsWithStride(SkPoint3 dst[], const SkPoint3 src[], size_t stride,
+                                              int count) const {
     SkASSERT((dst && src && count > 0) || 0 == count);
     // no partial overlap
     SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]);
 
     if (count > 0) {
         if (this->isIdentity()) {
-            memcpy(dst, src, count * sizeof(SkPoint3));
+            if (src != dst) {
+                if (stride == sizeof(SkPoint3)) {
+                    memcpy(dst, src, count * sizeof(SkPoint3));
+                } else {
+                    for (int i = 0; i < count; ++i) {
+                        *dst = *src;
+                        dst = reinterpret_cast<SkPoint3*>(reinterpret_cast<char*>(dst) + stride);
+                        src = reinterpret_cast<const SkPoint3*>(reinterpret_cast<const char*>(src) +
+                                                                stride);
+                    }
+                }
+            }
             return;
         }
         do {
             SkScalar sx = src->fX;
             SkScalar sy = src->fY;
             SkScalar sw = src->fZ;
-            src++;
+            src = reinterpret_cast<const SkPoint3*>(reinterpret_cast<const char*>(src) + stride);
 
             SkScalar x = sdot(sx, fMat[kMScaleX], sy, fMat[kMSkewX],  sw, fMat[kMTransX]);
             SkScalar y = sdot(sx, fMat[kMSkewY],  sy, fMat[kMScaleY], sw, fMat[kMTransY]);
             SkScalar w = sdot(sx, fMat[kMPersp0], sy, fMat[kMPersp1], sw, fMat[kMPersp2]);
 
             dst->set(x, y, w);
-            dst++;
+            dst = reinterpret_cast<SkPoint3*>(reinterpret_cast<char*>(dst) + stride);
         } while (--count);
     }
 }
 
+void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const {
+    this->mapHomogeneousPointsWithStride(dst, src, sizeof(SkPoint3), count);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkMatrix::mapVectors(SkPoint dst[], const SkPoint src[], int count) const {
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 2747f55..b8548bb 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "GrDistanceFieldGeoProc.h"
-
 #include "GrAtlasedShaderHelpers.h"
 #include "GrTexture.h"
 #include "SkDistanceFieldGen.h"
@@ -23,13 +22,7 @@
 
 class GrGLDistanceFieldA8TextGeoProc : public GrGLSLGeometryProcessor {
 public:
-    GrGLDistanceFieldA8TextGeoProc()
-            : fViewMatrix(SkMatrix::InvalidMatrix())
-    #ifdef SK_GAMMA_APPLY_TO_A8
-            , fDistanceAdjust(-1.0f)
-    #endif
-            , fAtlasSize({0,0}) {
-    }
+    GrGLDistanceFieldA8TextGeoProc() = default;
 
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
         const GrDistanceFieldA8TextGeoProc& dfTexEffect =
@@ -61,18 +54,14 @@
         varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
-        this->writeOutputPosition(vertBuilder,
-                                  uniformHandler,
-                                  gpArgs,
-                                  dfTexEffect.inPosition()->fName,
-                                  dfTexEffect.viewMatrix(),
-                                  &fViewMatrixUniform);
+        gpArgs->fPositionVar = dfTexEffect.inPosition()->asShaderVar();
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
                              dfTexEffect.inPosition()->asShaderVar(),
+                             dfTexEffect.localMatrix(),
                              args.fFPCoordTransformHandler);
 
         // add varyings
@@ -182,13 +171,6 @@
         }
 #endif
 
-        if (!dfa8gp.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dfa8gp.viewMatrix())) {
-            fViewMatrix = dfa8gp.viewMatrix();
-            float viewMatrix[3 * 3];
-            GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
-            pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
-        }
-
         SkASSERT(dfa8gp.numTextureSamplers() >= 1);
         GrTexture* atlas = dfa8gp.textureSampler(0).peekTexture();
         SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
@@ -198,7 +180,7 @@
             pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
         }
 
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+        this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, &transformIter);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -206,19 +188,16 @@
                               GrProcessorKeyBuilder* b) {
         const GrDistanceFieldA8TextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldA8TextGeoProc>();
         uint32_t key = dfTexEffect.getFlags();
-        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
         b->add32(key);
         b->add32(dfTexEffect.numTextureSamplers());
     }
 
 private:
-    SkMatrix      fViewMatrix;
-    UniformHandle fViewMatrixUniform;
 #ifdef SK_GAMMA_APPLY_TO_A8
-    float         fDistanceAdjust;
+    float fDistanceAdjust = -1.f;
     UniformHandle fDistanceAdjustUni;
 #endif
-    SkISize       fAtlasSize;
+    SkISize fAtlasSize = {0, 0};
     UniformHandle fAtlasSizeInvUniform;
 
     typedef GrGLSLGeometryProcessor INHERITED;
@@ -227,26 +206,28 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(
-                                                 GrColor color,
-                                                 const SkMatrix& viewMatrix,
-                                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
-                                                 const GrSamplerState& params,
+        GrColor color,
+        const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+        const GrSamplerState& params,
 #ifdef SK_GAMMA_APPLY_TO_A8
-                                                 float distanceAdjust,
+        float distanceAdjust,
 #endif
-                                                 uint32_t flags,
-                                                 bool usesLocalCoords)
+        uint32_t flags,
+        const SkMatrix& localMatrix)
         : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
         , fColor(color)
-        , fViewMatrix(viewMatrix)
 #ifdef SK_GAMMA_APPLY_TO_A8
         , fDistanceAdjust(distanceAdjust)
 #endif
         , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
         , fInColor(nullptr)
-        , fUsesLocalCoords(usesLocalCoords) {
+        , fLocalMatrix(localMatrix) {
     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
-    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    if (flags & kPerspective_DistanceFieldEffectFlag) {
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat3_GrVertexAttribType);
+    } else {
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    }
     fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < kMaxTextures; ++i) {
@@ -303,14 +284,16 @@
     if (flags & kSimilarity_DistanceFieldEffectFlag) {
         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
     }
-
-    return GrDistanceFieldA8TextGeoProc::Make(GrRandomColor(d->fRandom),
-                                              GrTest::TestMatrix(d->fRandom), proxies,
+    SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
+    GrColor color = GrRandomColor(d->fRandom);
+    float lum = d->fRandom->nextF();
+    return GrDistanceFieldA8TextGeoProc::Make(color,
+                                              proxies,
                                               samplerState,
 #ifdef SK_GAMMA_APPLY_TO_A8
-                                              d->fRandom->nextF(),
+                                              lum,
 #endif
-                                              flags, d->fRandom->nextBool());
+                                              flags, localMatrix);
 }
 #endif
 
@@ -324,7 +307,8 @@
     }
 
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
-        const GrDistanceFieldPathGeoProc& dfTexEffect = args.fGP.cast<GrDistanceFieldPathGeoProc>();
+        const GrDistanceFieldPathGeoProc& dfPathEffect =
+                args.fGP.cast<GrDistanceFieldPathGeoProc>();
 
         GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
@@ -333,7 +317,7 @@
         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
 
         // emit attributes
-        varyingHandler->emitAttributes(dfTexEffect);
+        varyingHandler->emitAttributes(dfPathEffect);
 
         const char* atlasSizeInvName;
         fAtlasSizeInvUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
@@ -345,55 +329,55 @@
         GrGLSLVarying uv(kFloat2_GrSLType);
         GrGLSLVarying texIdx(kHalf_GrSLType);
         GrGLSLVarying st(kFloat2_GrSLType);
-        append_index_uv_varyings(args, dfTexEffect.inTextureCoords()->fName, atlasSizeInvName,
-                                 &uv, &texIdx, &st);
+        append_index_uv_varyings(args, dfPathEffect.inTextureCoords()->fName, atlasSizeInvName, &uv,
+                                 &texIdx, &st);
 
         // setup pass through color
-        varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
+        varyingHandler->addPassThroughAttribute(dfPathEffect.inColor(), args.fOutputColor);
 
-        if (dfTexEffect.matrix().hasPerspective()) {
+        if (dfPathEffect.matrix().hasPerspective()) {
             // Setup position
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
-                                      dfTexEffect.inPosition()->fName,
-                                      dfTexEffect.matrix(),
+                                      dfPathEffect.inPosition()->fName,
+                                      dfPathEffect.matrix(),
                                       &fMatrixUniform);
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 dfTexEffect.inPosition()->asShaderVar(),
+                                 dfPathEffect.inPosition()->asShaderVar(),
                                  args.fFPCoordTransformHandler);
         } else {
             // Setup position
-            this->writeOutputPosition(vertBuilder, gpArgs, dfTexEffect.inPosition()->fName);
+            this->writeOutputPosition(vertBuilder, gpArgs, dfPathEffect.inPosition()->fName);
 
             // emit transforms
             this->emitTransforms(vertBuilder,
                                  varyingHandler,
                                  uniformHandler,
-                                 dfTexEffect.inPosition()->asShaderVar(),
-                                 dfTexEffect.matrix(),
+                                 dfPathEffect.inPosition()->asShaderVar(),
+                                 dfPathEffect.matrix(),
                                  args.fFPCoordTransformHandler);
         }
 
         // Use highp to work around aliasing issues
         fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
         fragBuilder->codeAppend("half4 texColor;");
-        append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
-                                   texIdx, "uv", "texColor");
+        append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
+                                   "texColor");
 
         fragBuilder->codeAppend("half distance = "
             SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
 
         fragBuilder->codeAppend("half afwidth;");
-        bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
-                               kUniformScale_DistanceFieldEffectMask;
-        bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
+        bool isUniformScale = (dfPathEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
+                              kUniformScale_DistanceFieldEffectMask;
+        bool isSimilarity = SkToBool(dfPathEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
         bool isGammaCorrect =
-            SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag);
+                SkToBool(dfPathEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag);
         if (isUniformScale) {
             // For uniform scale, we adjust for the effect of the transformation on the distance
             // by using the length of the gradient of the t coordinate in the y direction.
@@ -589,9 +573,7 @@
 
 class GrGLDistanceFieldLCDTextGeoProc : public GrGLSLGeometryProcessor {
 public:
-    GrGLDistanceFieldLCDTextGeoProc()
-            : fViewMatrix(SkMatrix::InvalidMatrix())
-            , fAtlasSize({0,0}) {
+    GrGLDistanceFieldLCDTextGeoProc() : fAtlasSize({0, 0}) {
         fDistanceAdjust = GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
     }
 
@@ -619,18 +601,14 @@
         varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
 
         // Setup position
-        this->writeOutputPosition(vertBuilder,
-                                  uniformHandler,
-                                  gpArgs,
-                                  dfTexEffect.inPosition()->fName,
-                                  dfTexEffect.viewMatrix(),
-                                  &fViewMatrixUniform);
+        gpArgs->fPositionVar = dfTexEffect.inPosition()->asShaderVar();
 
         // emit transforms
         this->emitTransforms(vertBuilder,
                              varyingHandler,
                              uniformHandler,
                              dfTexEffect.inPosition()->asShaderVar(),
+                             dfTexEffect.localMatrix(),
                              args.fFPCoordTransformHandler);
 
         // set up varyings
@@ -778,13 +756,6 @@
             fDistanceAdjust = wa;
         }
 
-        if (!dflcd.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dflcd.viewMatrix())) {
-            fViewMatrix = dflcd.viewMatrix();
-            float viewMatrix[3 * 3];
-            GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
-            pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
-        }
-
         SkASSERT(dflcd.numTextureSamplers() >= 1);
         GrTexture* atlas = dflcd.textureSampler(0).peekTexture();
         SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
@@ -793,8 +764,7 @@
             fAtlasSize.set(atlas->width(), atlas->height());
             pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
         }
-
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+        this->setTransformDataHelper(dflcd.localMatrix(), pdman, &transformIter);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -803,15 +773,11 @@
         const GrDistanceFieldLCDTextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldLCDTextGeoProc>();
 
         uint32_t key = dfTexEffect.getFlags();
-        key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
         b->add32(key);
         b->add32(dfTexEffect.numTextureSamplers());
     }
 
 private:
-    SkMatrix                                      fViewMatrix;
-    UniformHandle                                 fViewMatrixUniform;
-
     GrDistanceFieldLCDTextGeoProc::DistanceAdjust fDistanceAdjust;
     UniformHandle                                 fDistanceAdjustUni;
 
@@ -824,19 +790,22 @@
 ///////////////////////////////////////////////////////////////////////////////
 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(
                                                  GrColor color,
-                                                 const SkMatrix& viewMatrix,
                                                  const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                                  const GrSamplerState& params,
                                                  DistanceAdjust distanceAdjust,
-                                                 uint32_t flags, bool usesLocalCoords)
+                                                 uint32_t flags,
+                                                 const SkMatrix& localMatrix)
         : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
         , fColor(color)
-        , fViewMatrix(viewMatrix)
         , fDistanceAdjust(distanceAdjust)
         , fFlags(flags & kLCD_DistanceFieldEffectMask)
-        , fUsesLocalCoords(usesLocalCoords) {
+        , fLocalMatrix(localMatrix) {
     SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
-    fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    if (fFlags & kPerspective_DistanceFieldEffectFlag) {
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat3_GrVertexAttribType);
+    } else {
+        fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
+    }
     fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kUShort2_GrVertexAttribType);
     for (int i = 0; i < kMaxTextures; ++i) {
@@ -893,8 +862,9 @@
         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
     }
     flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
-    return GrDistanceFieldLCDTextGeoProc::Make(GrRandomColor(d->fRandom),
-                                               GrTest::TestMatrix(d->fRandom), proxies,
-                                               samplerState, wa, flags, d->fRandom->nextBool());
+    GrColor color = GrRandomColor(d->fRandom);
+    SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
+    return GrDistanceFieldLCDTextGeoProc::Make(color, proxies, samplerState, wa, flags,
+                                               localMatrix);
 }
 #endif
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index a099382..f3fd676 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -19,24 +19,27 @@
 enum GrDistanceFieldEffectFlags {
     kSimilarity_DistanceFieldEffectFlag   = 0x01, // ctm is similarity matrix
     kScaleOnly_DistanceFieldEffectFlag    = 0x02, // ctm has only scale and translate
-    kUseLCD_DistanceFieldEffectFlag       = 0x04, // use lcd text
-    kBGR_DistanceFieldEffectFlag          = 0x08, // lcd display has bgr order
-    kPortrait_DistanceFieldEffectFlag     = 0x10, // lcd display is in portrait mode (not used yet)
-    kGammaCorrect_DistanceFieldEffectFlag = 0x20, // assume gamma-correct output (linear blending)
-    kAliased_DistanceFieldEffectFlag      = 0x40, // monochrome output
+    kPerspective_DistanceFieldEffectFlag  = 0x04, // ctm has perspective (and positions are x,y,w)
+    kUseLCD_DistanceFieldEffectFlag       = 0x08, // use lcd text
+    kBGR_DistanceFieldEffectFlag          = 0x10, // lcd display has bgr order
+    kPortrait_DistanceFieldEffectFlag     = 0x20, // lcd display is in portrait mode (not used yet)
+    kGammaCorrect_DistanceFieldEffectFlag = 0x40, // assume gamma-correct output (linear blending)
+    kAliased_DistanceFieldEffectFlag      = 0x80, // monochrome output
 
-    kInvalid_DistanceFieldEffectFlag    = 0x80,   // invalid state (for initialization)
+    kInvalid_DistanceFieldEffectFlag      = 0x100,   // invalid state (for initialization)
 
     kUniformScale_DistanceFieldEffectMask = kSimilarity_DistanceFieldEffectFlag |
                                             kScaleOnly_DistanceFieldEffectFlag,
     // The subset of the flags relevant to GrDistanceFieldA8TextGeoProc
     kNonLCD_DistanceFieldEffectMask       = kSimilarity_DistanceFieldEffectFlag |
                                             kScaleOnly_DistanceFieldEffectFlag |
+                                            kPerspective_DistanceFieldEffectFlag |
                                             kGammaCorrect_DistanceFieldEffectFlag |
                                             kAliased_DistanceFieldEffectFlag,
     // The subset of the flags relevant to GrDistanceFieldLCDTextGeoProc
     kLCD_DistanceFieldEffectMask          = kSimilarity_DistanceFieldEffectFlag |
                                             kScaleOnly_DistanceFieldEffectFlag |
+                                            kPerspective_DistanceFieldEffectFlag |
                                             kUseLCD_DistanceFieldEffectFlag |
                                             kBGR_DistanceFieldEffectFlag |
                                             kGammaCorrect_DistanceFieldEffectFlag,
@@ -52,23 +55,22 @@
 public:
     static constexpr int kMaxTextures = 4;
 
+    /** The local matrix should be identity if local coords are not required by the GrPipeline. */
 #ifdef SK_GAMMA_APPLY_TO_A8
-    static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& viewMatrix,
+    static sk_sp<GrGeometryProcessor> Make(GrColor color,
                                            const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, float lum, uint32_t flags,
-                                           bool usesLocalCoords) {
-        return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, proxies,
-                                             params, lum, flags, usesLocalCoords));
+                                           const SkMatrix& localMatrixIfUsesLocalCoords) {
+        return sk_sp<GrGeometryProcessor>(new GrDistanceFieldA8TextGeoProc(
+                color, proxies, params, lum, flags, localMatrixIfUsesLocalCoords));
     }
 #else
-    static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& viewMatrix,
+    static sk_sp<GrGeometryProcessor> Make(GrColor color,
                                            const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, uint32_t flags,
-                                           bool usesLocalCoords) {
-        return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, proxies,
-                                             params, flags, usesLocalCoords));
+                                           const SkMatrix& localMatrixIfUsesLocalCoords) {
+        return sk_sp<GrGeometryProcessor>(new GrDistanceFieldA8TextGeoProc(
+                color, proxies, params, flags, localMatrixIfUsesLocalCoords));
     }
 #endif
 
@@ -80,8 +82,7 @@
     const Attribute* inColor() const { return fInColor; }
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     GrColor color() const { return fColor; }
-    const SkMatrix& viewMatrix() const { return fViewMatrix; }
-    bool usesLocalCoords() const { return fUsesLocalCoords; }
+    const SkMatrix& localMatrix() const { return fLocalMatrix; }
 #ifdef SK_GAMMA_APPLY_TO_A8
     float getDistanceAdjust() const { return fDistanceAdjust; }
 #endif
@@ -94,16 +95,14 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    GrDistanceFieldA8TextGeoProc(GrColor, const SkMatrix& viewMatrix,
-                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+    GrDistanceFieldA8TextGeoProc(GrColor, const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                  const GrSamplerState& params,
 #ifdef SK_GAMMA_APPLY_TO_A8
                                  float distanceAdjust,
 #endif
-                                 uint32_t flags, bool usesLocalCoords);
+                                 uint32_t flags, const SkMatrix& localMatrix);
 
     GrColor          fColor;
-    SkMatrix         fViewMatrix;
     TextureSampler   fTextureSamplers[kMaxTextures];
 #ifdef SK_GAMMA_APPLY_TO_A8
     float            fDistanceAdjust;
@@ -112,7 +111,7 @@
     const Attribute* fInPosition;
     const Attribute* fInColor;
     const Attribute* fInTextureCoords;
-    bool             fUsesLocalCoords;
+    SkMatrix         fLocalMatrix;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
@@ -129,6 +128,7 @@
 public:
     static constexpr int kMaxTextures = 4;
 
+    /** The local matrix should be identity if local coords are not required by the GrPipeline. */
     static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& matrix,
                                            const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, uint32_t flags) {
@@ -197,16 +197,13 @@
     static constexpr int kMaxTextures = 4;
 
     static sk_sp<GrGeometryProcessor> Make(GrColor color,
-                                           const SkMatrix& viewMatrix,
                                            const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params,
                                            DistanceAdjust distanceAdjust,
                                            uint32_t flags,
-                                           bool usesLocalCoords) {
-        return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldLCDTextGeoProc(color, viewMatrix, proxies,
-                                              params, distanceAdjust,
-                                              flags, usesLocalCoords));
+                                           const SkMatrix& localMatrixIfUsesLocalCoords) {
+        return sk_sp<GrGeometryProcessor>(new GrDistanceFieldLCDTextGeoProc(
+                color, proxies, params, distanceAdjust, flags, localMatrixIfUsesLocalCoords));
     }
 
     ~GrDistanceFieldLCDTextGeoProc() override {}
@@ -218,9 +215,8 @@
     const Attribute* inTextureCoords() const { return fInTextureCoords; }
     DistanceAdjust getDistanceAdjust() const { return fDistanceAdjust; }
     GrColor color() const { return fColor; }
-    const SkMatrix& viewMatrix() const { return fViewMatrix; }
     uint32_t getFlags() const { return fFlags; }
-    bool usesLocalCoords() const { return fUsesLocalCoords; }
+    const SkMatrix& localMatrix() const { return fLocalMatrix; }
 
     void addNewProxies(const sk_sp<GrTextureProxy> proxies[kMaxTextures], const GrSamplerState& p);
 
@@ -229,20 +225,18 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    GrDistanceFieldLCDTextGeoProc(GrColor, const SkMatrix& viewMatrix,
-                                  const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+    GrDistanceFieldLCDTextGeoProc(GrColor, const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                   const GrSamplerState& params, DistanceAdjust wa, uint32_t flags,
-                                  bool usesLocalCoords);
+                                  const SkMatrix& localMatrix);
 
     GrColor          fColor;
-    SkMatrix         fViewMatrix;
     TextureSampler   fTextureSamplers[kMaxTextures];
     DistanceAdjust   fDistanceAdjust;
     uint32_t         fFlags;
     const Attribute* fInPosition;
     const Attribute* fInColor;
     const Attribute* fInTextureCoords;
-    bool             fUsesLocalCoords;
+    const SkMatrix   fLocalMatrix;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST
 
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
index 9c71042..9a768c2 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
@@ -57,40 +57,43 @@
                                              const SkMatrix& localMatrix,
                                              FPCoordTransformHandler* handler) {
     SkASSERT(GrSLTypeIsFloatType(localCoordsVar.getType()));
-    SkASSERT(2 == GrSLTypeVecLength(localCoordsVar.getType()));
+    SkASSERT(2 == GrSLTypeVecLength(localCoordsVar.getType()) ||
+             3 == GrSLTypeVecLength(localCoordsVar.getType()));
 
+    bool threeComponentLocalCoords = 3 == GrSLTypeVecLength(localCoordsVar.getType());
+    SkString localCoords;
+    if (threeComponentLocalCoords) {
+        localCoords = localCoordsVar.getName();
+    } else {
+        localCoords.printf("float3(%s, 1)", localCoordsVar.c_str());
+    }
     int i = 0;
     while (const GrCoordTransform* coordTransform = handler->nextCoordTransform()) {
         SkString strUniName;
         strUniName.printf("CoordTransformMatrix_%d", i);
-        GrSLType varyingType;
-
-        uint32_t type = coordTransform->getMatrix().getType();
-        type |= localMatrix.getType();
-
-        varyingType = SkToBool(SkMatrix::kPerspective_Mask & type) ? kFloat3_GrSLType :
-                                                                     kFloat2_GrSLType;
         const char* uniName;
-
-
         fInstalledTransforms.push_back().fHandle = uniformHandler->addUniform(kVertex_GrShaderFlag,
                                                                               kFloat3x3_GrSLType,
                                                                               strUniName.c_str(),
                                                                               &uniName).toIndex();
+        GrSLType varyingType = kFloat2_GrSLType;
+        if (localMatrix.hasPerspective() || coordTransform->getMatrix().hasPerspective()) {
+            varyingType = kFloat3_GrSLType;
+        }
         SkString strVaryingName;
         strVaryingName.printf("TransformedCoords_%d", i);
-
         GrGLSLVarying v(varyingType);
         varyingHandler->addVarying(strVaryingName.c_str(), &v);
 
-        SkASSERT(kFloat2_GrSLType == varyingType || kFloat3_GrSLType == varyingType);
         handler->specifyCoordsForCurrCoordTransform(SkString(v.fsIn()), varyingType);
 
         if (kFloat2_GrSLType == varyingType) {
-            vb->codeAppendf("%s = (%s * float3(%s, 1)).xy;", v.vsOut(), uniName,
-                            localCoordsVar.c_str());
+            vb->codeAppendf("%s = (%s * %s).xy;", v.vsOut(), uniName, localCoords.c_str());
+            if (threeComponentLocalCoords) {
+                vb->codeAppendf("%s /= %s.z;", v.vsOut(), localCoords.c_str());
+            }
         } else {
-            vb->codeAppendf("%s = %s * float3(%s, 1);", v.vsOut(), uniName, localCoordsVar.c_str());
+            vb->codeAppendf("%s = %s * %s;", v.vsOut(), uniName, localCoords.c_str());
         }
         ++i;
     }
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.h b/src/gpu/glsl/GrGLSLGeometryProcessor.h
index de1e4fd..36cc546 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.h
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.h
@@ -29,7 +29,8 @@
                                 FPCoordTransformIter*);
 
     // Emit transformed local coords from the vertex shader as a uniform matrix and varying per
-    // coord-transform.
+    // coord-transform. localCoordsVar must be a 2- or 3-component vector. If it is 3 then it is
+    // assumed to be a 2D homogeneous coordinate.
     void emitTransforms(GrGLSLVertexBuilder*,
                         GrGLSLVaryingHandler*,
                         GrGLSLUniformHandler*,
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 6743fea..d71cd92 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -6,14 +6,12 @@
  */
 
 #include "GrAtlasTextOp.h"
-
 #include "GrContext.h"
 #include "GrOpFlushState.h"
 #include "GrResourceProvider.h"
-
 #include "SkGlyphCache.h"
 #include "SkMathPriv.h"
-
+#include "SkPoint3.h"
 #include "effects/GrBitmapTextGeoProc.h"
 #include "effects/GrDistanceFieldGeoProc.h"
 #include "text/GrAtlasGlyphCache.h"
@@ -22,6 +20,35 @@
 
 static const int kDistanceAdjustLumShift = 5;
 
+void GrAtlasTextOp::init() {
+    const Geometry& geo = fGeoData[0];
+    fColor = geo.fColor;
+    SkRect bounds;
+    geo.fBlob->computeSubRunBounds(&bounds, geo.fRun, geo.fSubRun, geo.fViewMatrix, geo.fX, geo.fY);
+    // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
+    // we treat this as a set of non-AA rects rendered with a texture.
+    this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
+    if (this->usesDistanceFields()) {
+        bool isLCD = this->isLCD();
+
+        const SkMatrix& viewMatrix = geo.fViewMatrix;
+
+        fDFGPFlags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
+        fDFGPFlags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
+        fDFGPFlags |= viewMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
+        fDFGPFlags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
+        fDFGPFlags |= (kAliasedDistanceField_MaskType == fMaskType)
+                              ? kAliased_DistanceFieldEffectFlag
+                              : 0;
+
+        if (isLCD) {
+            fDFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
+            fDFGPFlags |=
+                    (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0;
+        }
+    }
+}
+
 SkString GrAtlasTextOp::dumpInfo() const {
     SkString str;
 
@@ -183,7 +210,7 @@
     // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
     // TODO actually only invert if we don't have RGBA
     SkMatrix localMatrix;
-    if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
+    if (this->usesLocalCoords() && !fGeoData[0].fViewMatrix.invert(&localMatrix)) {
         SkDebugf("Cannot invert viewmatrix\n");
         return;
     }
@@ -200,8 +227,10 @@
     FlushInfo flushInfo;
     flushInfo.fPipeline =
             target->makePipeline(fSRGBFlags, std::move(fProcessors), target->detachAppliedClip());
+    SkDEBUGCODE(bool dfPerspective = false);
     if (this->usesDistanceFields()) {
         flushInfo.fGeometryProcessor = this->setupDfProcessor();
+        SkDEBUGCODE(dfPerspective = fGeoData[0].fViewMatrix.hasPerspective());
     } else {
         flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
             this->color(), proxies, GrSamplerState::ClampNearest(), maskFormat,
@@ -210,7 +239,7 @@
 
     flushInfo.fGlyphsToFlush = 0;
     size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
-    SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat));
+    SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat, dfPerspective));
 
     int glyphCount = this->numGlyphs();
     const GrBuffer* vertexBuffer;
@@ -242,9 +271,24 @@
             if (args.fClipRect.isEmpty()) {
                 memcpy(currVertex, result.fFirstVertex, vertexBytes);
             } else {
+                SkASSERT(!dfPerspective);
                 clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
                            result.fGlyphsRegenerated);
             }
+            if (this->usesDistanceFields() && !args.fViewMatrix.isIdentity()) {
+                // We always do the distance field view matrix transformation after copying rather
+                // than during blob vertex generation time in the blob as handling successive
+                // arbitrary transformations would be complicated and accumulate error.
+                if (args.fViewMatrix.hasPerspective()) {
+                    auto* pos = reinterpret_cast<SkPoint3*>(currVertex);
+                    args.fViewMatrix.mapHomogeneousPointsWithStride(
+                            pos, pos, vertexStride, result.fGlyphsRegenerated * kVerticesPerGlyph);
+                } else {
+                    auto* pos = reinterpret_cast<SkPoint*>(currVertex);
+                    args.fViewMatrix.mapPointsWithStride(
+                            pos, vertexStride, result.fGlyphsRegenerated * kVerticesPerGlyph);
+                }
+            }
             flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated;
             if (!result.fFinished) {
                 this->flush(target, &flushInfo);
@@ -300,21 +344,25 @@
         return false;
     }
 
-    if (!this->usesDistanceFields()) {
-        if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) {
-            return false;
-        }
-        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
-            return false;
-        }
-    } else {
-        if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+    const SkMatrix& thisFirstMatrix = fGeoData[0].fViewMatrix;
+    const SkMatrix& thatFirstMatrix = that->fGeoData[0].fViewMatrix;
+
+    if (this->usesLocalCoords() && !thisFirstMatrix.cheapEqualTo(thatFirstMatrix)) {
+        return false;
+    }
+
+    if (this->usesDistanceFields()) {
+        if (fDFGPFlags != that->fDFGPFlags) {
             return false;
         }
 
         if (fLuminanceColor != that->fLuminanceColor) {
             return false;
         }
+    } else {
+        if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) {
+            return false;
+        }
     }
 
     // Keep the batch vertex buffer size below 32K so we don't have to create a special one
@@ -358,20 +406,18 @@
 // TODO trying to figure out why lcd is so whack
 // (see comments in GrAtlasTextContext::ComputeCanonicalColor)
 sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor() const {
-    const SkMatrix& viewMatrix = this->viewMatrix();
     const sk_sp<GrTextureProxy>* p = fFontCache->getProxies(this->maskFormat());
     bool isLCD = this->isLCD();
-    // set up any flags
-    uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
-    flags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
-    flags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
-    flags |= (kAliasedDistanceField_MaskType == fMaskType) ? kAliased_DistanceFieldEffectFlag : 0;
+
+    SkMatrix localMatrix = SkMatrix::I();
+    if (this->usesLocalCoords()) {
+        // If this fails we'll just use I().
+        bool result = fGeoData[0].fViewMatrix.invert(&localMatrix);
+        (void)result;
+    }
 
     // see if we need to create a new effect
     if (isLCD) {
-        flags |= kUseLCD_DistanceFieldEffectFlag;
-        flags |= (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0;
-
         float redCorrection = fDistanceAdjustTable->getAdjustment(
                 SkColorGetR(fLuminanceColor) >> kDistanceAdjustLumShift,
                 fUseGammaCorrectDistanceTable);
@@ -384,10 +430,8 @@
         GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
                 GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
                         redCorrection, greenCorrection, blueCorrection);
-
-        return GrDistanceFieldLCDTextGeoProc::Make(this->color(), viewMatrix, p,
-                                                   GrSamplerState::ClampBilerp(), widthAdjust,
-                                                   flags, this->usesLocalCoords());
+        return GrDistanceFieldLCDTextGeoProc::Make(this->color(), p, GrSamplerState::ClampBilerp(),
+                                                   widthAdjust, fDFGPFlags, localMatrix);
     } else {
 #ifdef SK_GAMMA_APPLY_TO_A8
         float correction = 0;
@@ -397,13 +441,11 @@
             correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
                                                              fUseGammaCorrectDistanceTable);
         }
-        return GrDistanceFieldA8TextGeoProc::Make(this->color(), viewMatrix, p,
-                                                  GrSamplerState::ClampBilerp(), correction, flags,
-                                                  this->usesLocalCoords());
+        return GrDistanceFieldA8TextGeoProc::Make(this->color(), p, GrSamplerState::ClampBilerp(),
+                                                  correction, fDFGPFlags, localMatrix);
 #else
-        return GrDistanceFieldA8TextGeoProc::Make(this->color(), viewMatrix, p,
-                                                  GrSamplerState::ClampBilerp(), flags,
-                                                  this->usesLocalCoords());
+        return GrDistanceFieldA8TextGeoProc::Make(this->color(), p, GrSamplerState::ClampBilerp(),
+                                                  fDFGPFlags, localMatrix);
 #endif
     }
 }
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index d31a329..cb15c36 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -87,16 +87,8 @@
     // init() so the op can initialize itself
     Geometry& geometry() { return fGeoData[0]; }
 
-    void init() {
-        const Geometry& geo = fGeoData[0];
-        fColor = geo.fColor;
-        SkRect bounds;
-        geo.fBlob->computeSubRunBounds(&bounds, geo.fRun, geo.fSubRun, geo.fViewMatrix, geo.fX,
-                                       geo.fY);
-        // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
-        // we treat this as a set of non-AA rects rendered with a texture.
-        this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
-    }
+    /** Called after this->geometry() has been configured. */
+    void init();
 
     const char* name() const override { return "AtlasTextOp"; }
 
@@ -187,7 +179,6 @@
     inline void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const;
 
     GrColor color() const { return fColor; }
-    const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; }
     bool usesLocalCoords() const { return fUsesLocalCoords; }
     int numGlyphs() const { return fNumGlyphs; }
 
@@ -212,6 +203,7 @@
     sk_sp<const GrDistanceFieldAdjustTable> fDistanceAdjustTable;
     SkColor fLuminanceColor;
     bool fUseGammaCorrectDistanceTable;
+    uint32_t fDFGPFlags = 0;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
index 0b25a34..02f3ab0 100644
--- a/src/gpu/text/GrAtlasTextBlob.cpp
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -96,7 +96,13 @@
 
     run.fInitialized = true;
 
-    size_t vertexStride = GetVertexStride(format);
+    bool hasW = subRun->hasWCoord();
+    // DF glyphs drawn in perspective must always have a w coord.
+    SkASSERT(hasW || !subRun->drawAsDistanceFields() || !fInitialViewMatrix.hasPerspective());
+    // Non-DF glyphs should never have a w coord.
+    SkASSERT(!hasW || subRun->drawAsDistanceFields());
+
+    size_t vertexStride = GetVertexStride(format, hasW);
 
     subRun->setMaskFormat(format);
 
@@ -105,53 +111,29 @@
 
     intptr_t vertex = reinterpret_cast<intptr_t>(this->fVertices + subRun->vertexEndIndex());
 
-    if (kARGB_GrMaskFormat != glyph->fMaskFormat) {
-        // V0
-        SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fLeft, positions.fTop);
-        SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
-        *colorPtr = color;
-        vertex += vertexStride;
+    // We always write the third position component used by SDFs. If it is unused it gets
+    // overwritten. Similarly, we always write the color and the blob will later overwrite it
+    // with texture coords if it is unused.
+    size_t colorOffset = hasW ? sizeof(SkPoint3) : sizeof(SkPoint);
+    // V0
+    *reinterpret_cast<SkPoint3*>(vertex) = {positions.fLeft, positions.fTop, 1.f};
+    *reinterpret_cast<GrColor*>(vertex + colorOffset) = color;
+    vertex += vertexStride;
 
-        // V1
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fLeft, positions.fBottom);
-        colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
-        *colorPtr = color;
-        vertex += vertexStride;
+    // V1
+    *reinterpret_cast<SkPoint3*>(vertex) = {positions.fLeft, positions.fBottom, 1.f};
+    *reinterpret_cast<GrColor*>(vertex + colorOffset) = color;
+    vertex += vertexStride;
 
-        // V2
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fRight, positions.fTop);
-        colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
-        *colorPtr = color;
-        vertex += vertexStride;
+    // V2
+    *reinterpret_cast<SkPoint3*>(vertex) = {positions.fRight, positions.fTop, 1.f};
+    *reinterpret_cast<GrColor*>(vertex + colorOffset) = color;
+    vertex += vertexStride;
 
-        // V3
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fRight, positions.fBottom);
-        colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
-        *colorPtr = color;
-    } else {
-        // V0
-        SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fLeft, positions.fTop);
-        vertex += vertexStride;
+    // V3
+    *reinterpret_cast<SkPoint3*>(vertex) = {positions.fRight, positions.fBottom, 1.f};
+    *reinterpret_cast<GrColor*>(vertex + colorOffset) = color;
 
-        // V1
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fLeft, positions.fBottom);
-        vertex += vertexStride;
-
-        // V2
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fRight, positions.fTop);
-        vertex += vertexStride;
-
-        // V3
-        position = reinterpret_cast<SkPoint*>(vertex);
-        position->set(positions.fRight, positions.fBottom);
-    }
     subRun->appendVertices(vertexStride);
     fGlyphs[subRun->glyphEndIndex()] = glyph;
     subRun->glyphAppended();
@@ -185,6 +167,7 @@
         return true;
     }
 
+    /** This could be relaxed for blobs with only distance field glyphs. */
     if (fInitialViewMatrix.hasPerspective() && !fInitialViewMatrix.cheapEqualTo(viewMatrix)) {
         return true;
     }
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index 450e256..282fa98 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -17,6 +17,7 @@
 #include "SkMaskFilter.h"
 #include "SkOpts.h"
 #include "SkPathEffect.h"
+#include "SkPoint3.h"
 #include "SkRasterizer.h"
 #include "SkSurfaceProps.h"
 #include "SkTInternalLList.h"
@@ -53,6 +54,12 @@
 
     static sk_sp<GrAtlasTextBlob> Make(GrMemoryPool* pool, int glyphCount, int runCount);
 
+    /**
+     * We currently force regeneration of a blob if old or new matrix differ in having perspective.
+     * If we ever change that then the key must contain the perspectiveness when there are distance
+     * fields as perspective distance field use 3 component vertex positions and non-perspective
+     * uses 2.
+     */
     struct Key {
         Key() {
             sk_bzero(this, sizeof(Key));
@@ -126,12 +133,13 @@
     }
 
     // sets the last subrun of runIndex to use distance field text
-    void setSubRunHasDistanceFields(int runIndex, bool hasLCD, bool isAntiAlias) {
+    void setSubRunHasDistanceFields(int runIndex, bool hasLCD, bool isAntiAlias, bool hasWCoord) {
         Run& run = fRuns[runIndex];
         Run::SubRunInfo& subRun = run.fSubRunInfo.back();
         subRun.setUseLCDText(hasLCD);
         subRun.setAntiAliased(isAntiAlias);
         subRun.setDrawAsDistanceFields();
+        subRun.setHasWCoord(hasWCoord);
     }
 
     void setRunDrawAsPaths(int runIndex) {
@@ -169,13 +177,15 @@
                      SkGlyphCache*, const SkGlyph& skGlyph,
                      SkScalar x, SkScalar y, SkScalar scale, bool treatAsBMP);
 
-    static size_t GetVertexStride(GrMaskFormat maskFormat) {
+    static size_t GetVertexStride(GrMaskFormat maskFormat, bool isDistanceFieldWithWCoord) {
         switch (maskFormat) {
             case kA8_GrMaskFormat:
-                return kGrayTextVASize;
+                return isDistanceFieldWithWCoord ? kGrayTextDFPerspectiveVASize : kGrayTextVASize;
             case kARGB_GrMaskFormat:
+                SkASSERT(!isDistanceFieldWithWCoord);
                 return kColorTextVASize;
             default:
+                SkASSERT(!isDistanceFieldWithWCoord);
                 return kLCDTextVASize;
         }
     }
@@ -232,8 +242,10 @@
     // position + local coord
     static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16);
     static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16);
+    static const size_t kGrayTextDFPerspectiveVASize =
+            sizeof(SkPoint3) + sizeof(GrColor) + sizeof(SkIPoint16);
     static const size_t kLCDTextVASize = kGrayTextVASize;
-    static const size_t kMaxVASize = kGrayTextVASize;
+    static const size_t kMaxVASize = kGrayTextDFPerspectiveVASize;
     static const int kVerticesPerGlyph = 4;
 
     static void AssertEqual(const GrAtlasTextBlob&, const GrAtlasTextBlob&);
@@ -419,7 +431,7 @@
 
             // This function assumes the translation will be applied before it is called again
             void computeTranslation(const SkMatrix& viewMatrix, SkScalar x, SkScalar y,
-                                    SkScalar*transX, SkScalar* transY);
+                                    SkScalar* transX, SkScalar* transY);
 
             // df properties
             void setDrawAsDistanceFields() { fFlags |= kDrawAsSDF_Flag; }
@@ -432,12 +444,17 @@
                 fFlags = antiAliased ? fFlags | kAntiAliased_Flag : fFlags & ~kAntiAliased_Flag;
             }
             bool isAntiAliased() const { return SkToBool(fFlags & kAntiAliased_Flag); }
+            void setHasWCoord(bool hasW) {
+                fFlags  = hasW ? (fFlags | kHasWCoord_Flag) : fFlags & ~kHasWCoord_Flag;
+            }
+            bool hasWCoord() const { return SkToBool(fFlags & kHasWCoord_Flag); }
 
         private:
             enum Flag {
                 kDrawAsSDF_Flag = 0x1,
                 kUseLCDText_Flag = 0x2,
-                kAntiAliased_Flag = 0x4
+                kAntiAliased_Flag = 0x4,
+                kHasWCoord_Flag = 0x8
             };
 
             GrDrawOpAtlas::BulkUseTokenUpdater fBulkUseToken;
diff --git a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
index 28b2e0f..1be5ae3 100644
--- a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
+++ b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
@@ -71,8 +71,8 @@
 
     // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
     // vertices, hence vertexStride - sizeof(SkIPoint16)
-    intptr_t colorOffset = sizeof(SkPoint);
     intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
+    intptr_t colorOffset = texCoordOffset - sizeof(GrColor);
 
     // V0
     if (regenPos) {
@@ -211,8 +211,9 @@
         }
     }
 
+    bool hasW = fSubRun->hasWCoord();
     Result result;
-    auto vertexStride = GetVertexStride(fSubRun->maskFormat());
+    auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
     char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
                        fCurrGlyph * kVerticesPerGlyph * vertexStride;
     result.fFirstVertex = currVertex;
@@ -300,7 +301,8 @@
             return this->doRegen<false, true, true, true>();
         case kNoRegen: {
             Result result;
-            auto vertexStride = GetVertexStride(fSubRun->maskFormat());
+            bool hasW = fSubRun->hasWCoord();
+            auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
             result.fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
             result.fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
                                   fCurrGlyph * kVerticesPerGlyph * vertexStride;
diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp
index d617786..3faa0f7 100644
--- a/src/gpu/text/GrAtlasTextContext.cpp
+++ b/src/gpu/text/GrAtlasTextContext.cpp
@@ -646,13 +646,14 @@
     SkTDArray<char> fallbackTxt;
     SkTDArray<SkScalar> fallbackPos;
 
+    bool hasWCoord = viewMatrix.hasPerspective();
     // Setup distance field paint and text ratio
     SkScalar textRatio;
     SkPaint dfPaint(paint);
     this->initDistanceFieldPaint(blob, &dfPaint, &textRatio, viewMatrix);
     blob->setHasDistanceField();
     blob->setSubRunHasDistanceFields(runIndex, paint.skPaint().isLCDRenderText(),
-                                     paint.skPaint().isAntiAlias());
+                                     paint.skPaint().isAntiAlias(), hasWCoord);
 
     GrAtlasTextStrike* currStrike = nullptr;