Centralize geometry processor vertex shader transform code

GrGLSLGeometryProcessors no longer have to call emitTransforms() in
their onEmitCode() function. Instead, the GpArgs struct allows them to
set a GrShaderVar that holds the computed or explicitly provided local
coordinates in the vertex shader.

The base GrGLSLGeometryProcessor now automatically uses that to collect
all of the transforms that can then be lifted out of FPs to the vertex
shader, and base their computation on the GP provided local coordinate.

As part of this, there is no more built-in magic concatenation of a
local matrix / inverse view matrix to these coordinate transforms. GP
implementations that relied on this now manage their own uniform for this
matrix and compute the local coordinate before assigning to GpArgs.

The base GrGLSLGeometryProcessor is updated to provide helpers for this
pattern.

Bug: skia:10396
Change-Id: I56afb3fff4b806f6015ab13626ac1afde9ef5c2b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297027
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 4f93238..94ba184 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -57,6 +57,7 @@
     public:
         GLSLProcessor()
             : fViewMatrix(SkMatrix::InvalidMatrix())
+            , fLocalMatrix(SkMatrix::InvalidMatrix())
             , fColor(SK_PMColor4fILLEGAL)
             , fCoverage(0xff) {}
 
@@ -111,14 +112,14 @@
                                       &fViewMatrixUniform);
 
             // emit transforms using either explicit local coords or positions
-            const auto& coordsAttr = gp.fInLocalCoords.isInitialized() ? gp.fInLocalCoords
-                                                                       : gp.fInPosition;
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 coordsAttr.asShaderVar(),
-                                 gp.localMatrix(),
-                                 args.fFPCoordTransformHandler);
+            if (gp.fInLocalCoords.isInitialized()) {
+                SkASSERT(gp.localMatrix().isIdentity());
+                gpArgs->fLocalCoordVar = gp.fInLocalCoords.asShaderVar();
+            } else if (gp.fLocalCoordsWillBeRead) {
+                this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                      gp.fInPosition.asShaderVar(), gp.localMatrix(),
+                                      &fLocalMatrixUniform);
+            }
 
             // Setup coverage as pass through
             if (gp.hasVertexCoverage() && !tweakAlpha) {
@@ -144,8 +145,12 @@
             const DefaultGeoProc& def = gp.cast<DefaultGeoProc>();
             uint32_t key = def.fFlags;
             key |= (def.coverage() == 0xff) ? 0x80 : 0;
-            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x100 : 0;
-            key |= ComputePosKey(def.viewMatrix()) << 20;
+            key |= def.localCoordsWillBeRead() ? 0x100 : 0;
+
+            bool usesLocalMatrix = def.localCoordsWillBeRead() &&
+                                   !def.fInLocalCoords.isInitialized();
+            key = AddMatrixKeys(key, def.viewMatrix(),
+                                usesLocalMatrix ? def.localMatrix() : SkMatrix::I());
             b->add32(key);
         }
 
@@ -154,12 +159,9 @@
                      const CoordTransformRange& transformRange) override {
             const DefaultGeoProc& dgp = gp.cast<DefaultGeoProc>();
 
-            if (!dgp.viewMatrix().isIdentity() &&
-                !SkMatrixPriv::CheapEqual(fViewMatrix, dgp.viewMatrix()))
-            {
-                fViewMatrix = dgp.viewMatrix();
-                pdman.setSkMatrix(fViewMatrixUniform, fViewMatrix);
-            }
+            this->setTransform(pdman, fViewMatrixUniform, dgp.viewMatrix(), &fViewMatrix);
+            this->setTransform(pdman, fLocalMatrixUniform, dgp.localMatrix(), &fLocalMatrix);
+            this->setTransformDataHelper(pdman, transformRange);
 
             if (!dgp.hasVertexColor() && dgp.color() != fColor) {
                 pdman.set4fv(fColorUniform, 1, dgp.color().vec());
@@ -170,14 +172,15 @@
                 pdman.set1f(fCoverageUniform, GrNormalizeByteToFloat(dgp.coverage()));
                 fCoverage = dgp.coverage();
             }
-            this->setTransformDataHelper(dgp.fLocalMatrix, pdman, transformRange);
         }
 
     private:
         SkMatrix fViewMatrix;
+        SkMatrix fLocalMatrix;
         SkPMColor4f fColor;
         uint8_t fCoverage;
         UniformHandle fViewMatrixUniform;
+        UniformHandle fLocalMatrixUniform;
         UniformHandle fColorUniform;
         UniformHandle fCoverageUniform;
 
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index 40d4c93..75af35a 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -56,12 +56,14 @@
         constexpr Attribute(const char* name,
                             GrVertexAttribType cpuType,
                             GrSLType gpuType)
-            : fName(name), fCPUType(cpuType), fGPUType(gpuType) {}
+                : fName(name), fCPUType(cpuType), fGPUType(gpuType) {
+            SkASSERT(name && gpuType != kVoid_GrSLType);
+        }
         constexpr Attribute(const Attribute&) = default;
 
         Attribute& operator=(const Attribute&) = default;
 
-        constexpr bool isInitialized() const { return SkToBool(fName); }
+        constexpr bool isInitialized() const { return fGPUType != kVoid_GrSLType; }
 
         constexpr const char* name() const { return fName; }
         constexpr GrVertexAttribType cpuType() const { return fCPUType; }
@@ -77,7 +79,7 @@
     private:
         const char* fName = nullptr;
         GrVertexAttribType fCPUType = kFloat_GrVertexAttribType;
-        GrSLType fGPUType = kFloat_GrSLType;
+        GrSLType fGPUType = kVoid_GrSLType;
     };
 
     class Iter {
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index d4c1d8b..e5c99c0 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -103,6 +103,10 @@
 public:
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
 
+    static void GenKey(const GrCCPathProcessor& cc, GrProcessorKeyBuilder* b) {
+        b->add32(AddMatrixKeys((uint32_t) cc.fCoverageMode, SkMatrix::I(), cc.fLocalMatrix));
+    }
+
 private:
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                  const CoordTransformRange& transformRange) override {
@@ -110,14 +114,21 @@
         pdman.set2f(fAtlasAdjustUniform,
                     1.0f / proc.fAtlasDimensions.fWidth,
                     1.0f / proc.fAtlasDimensions.fHeight);
-        this->setTransformDataHelper(proc.fLocalMatrix, pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
+        this->setTransform(pdman, fLocalMatrixUni, proc.fLocalMatrix, &fLocalMatrix);
     }
 
     GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform;
+    GrGLSLUniformHandler::UniformHandle fLocalMatrixUni;
+    SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
 
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
+void GrCCPathProcessor::getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const  {
+    GrCCPathProcessor::Impl::GenKey(*this, b);
+}
+
 GrGLSLPrimitiveProcessor* GrCCPathProcessor::createGLSLInstance(const GrShaderCaps&) const {
     return new Impl();
 }
@@ -218,8 +229,8 @@
     }
 
     gpArgs->fPositionVar.set(kFloat2_GrSLType, "octocoord");
-    this->emitTransforms(v, varyingHandler, uniHandler, gpArgs->fPositionVar, proc.fLocalMatrix,
-                         args.fFPCoordTransformHandler);
+    this->writeLocalCoord(v, args.fUniformHandler, gpArgs, gpArgs->fPositionVar, proc.fLocalMatrix,
+                          &fLocalMatrixUni);
 
     // Fragment shader.
     GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index cb5f227..789ac2e 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -64,9 +64,8 @@
                       const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I());
 
     const char* name() const override { return "GrCCPathProcessor"; }
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        b->add32((uint32_t)fCoverageMode);
-    }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override;
+
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
     void drawPaths(GrOpFlushState*, const GrPipeline&, const GrSurfaceProxy& atlasProxy,
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
index 8f0a7ee..bb09fe4 100644
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ b/src/gpu/ccpr/GrCCStroker.cpp
@@ -103,7 +103,6 @@
 
 void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
     GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
 
     varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
 
@@ -137,8 +136,7 @@
                    edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
 
     gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
-    this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
-                         SkMatrix::I(), args.fFPCoordTransformHandler);
+    // Leave fLocalCoordVar uninitialized; this GP is not combined with frag processors
 
     // Use the 4 edge distances to calculate coverage in the fragment shader.
     GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
@@ -194,7 +192,6 @@
 
 void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
     GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
 
     varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
 
@@ -259,8 +256,7 @@
                    coverages.vsOut());
 
     gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
-    this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
-                         SkMatrix::I(), args.fFPCoordTransformHandler);
+    // Leave fLocalCoordVar uninitialized; this GP is not combined with frag processors
 
     // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
     GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
diff --git a/src/gpu/ccpr/GrGSCoverageProcessor.cpp b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
index feaac1a..44e2def 100644
--- a/src/gpu/ccpr/GrGSCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
@@ -25,7 +25,7 @@
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
                  const CoordTransformRange& transformRange) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
diff --git a/src/gpu/ccpr/GrVSCoverageProcessor.cpp b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
index ee3ce64..0270cbf 100644
--- a/src/gpu/ccpr/GrVSCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
@@ -20,7 +20,7 @@
 private:
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
                  const CoordTransformRange& transformRange) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     void onEmitCode(EmitArgs&, GrGPArgs*) override;
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index cbd8baa..e127ab4 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -28,12 +28,8 @@
                  const CoordTransformRange& transformRange) override {
         const GrConicEffect& ce = primProc.cast<GrConicEffect>();
 
-        if (!ce.viewMatrix().isIdentity() &&
-            !SkMatrixPriv::CheapEqual(fViewMatrix, ce.viewMatrix()))
-        {
-            fViewMatrix = ce.viewMatrix();
-            pdman.setSkMatrix(fViewMatrixUniform, fViewMatrix);
-        }
+        this->setTransform(pdman, fViewMatrixUniform, ce.viewMatrix(), &fViewMatrix);
+        this->setTransform(pdman, fLocalMatrixUniform, ce.localMatrix(), &fLocalMatrix);
 
         if (ce.color() != fColor) {
             pdman.set4fv(fColorUniform, 1, ce.color().vec());
@@ -44,24 +40,27 @@
             pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(ce.coverageScale()));
             fCoverageScale = ce.coverageScale();
         }
-        this->setTransformDataHelper(ce.localMatrix(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
 private:
     SkMatrix fViewMatrix;
+    SkMatrix fLocalMatrix;
     SkPMColor4f fColor;
     uint8_t fCoverageScale;
     UniformHandle fColorUniform;
     UniformHandle fCoverageScaleUniform;
     UniformHandle fViewMatrixUniform;
+    UniformHandle fLocalMatrixUniform;
 
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
 GrGLConicEffect::GrGLConicEffect(const GrGeometryProcessor& processor)
-    : fViewMatrix(SkMatrix::InvalidMatrix())
-    , fColor(SK_PMColor4fILLEGAL)
-    , fCoverageScale(0xff) {}
+        : fViewMatrix(SkMatrix::InvalidMatrix())
+        , fLocalMatrix(SkMatrix::InvalidMatrix())
+        , fColor(SK_PMColor4fILLEGAL)
+        , fCoverageScale(0xff) {}
 
 void GrGLConicEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
     GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
@@ -87,14 +86,10 @@
                               gp.inPosition().name(),
                               gp.viewMatrix(),
                               &fViewMatrixUniform);
-
-    // emit transforms with position
-    this->emitTransforms(vertBuilder,
-                         varyingHandler,
-                         uniformHandler,
-                         gp.inPosition().asShaderVar(),
-                         gp.localMatrix(),
-                         args.fFPCoordTransformHandler);
+    if (gp.usesLocalCoords()) {
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, gp.inPosition().asShaderVar(),
+                              gp.localMatrix(), &fLocalMatrixUniform);
+    }
 
     // TODO: we should check on the number of bits float and half provide and use the smallest one
     // that suffices. Additionally we should assert that the upstream code only lets us get here if
@@ -165,8 +160,9 @@
     const GrConicEffect& ce = gp.cast<GrConicEffect>();
     uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2;
     key |= 0xff != ce.coverageScale() ? 0x8 : 0x0;
-    key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0;
-    key |= ComputePosKey(ce.viewMatrix()) << 5;
+    key |= ce.usesLocalCoords() ? 0x10 : 0x0;
+    key = AddMatrixKeys(key, ce.viewMatrix(), ce.usesLocalCoords() ? ce.localMatrix()
+                                                                   : SkMatrix::I());
     b->add32(key);
 }
 
@@ -227,12 +223,8 @@
                  const CoordTransformRange& transformRange) override {
         const GrQuadEffect& qe = primProc.cast<GrQuadEffect>();
 
-        if (!qe.viewMatrix().isIdentity() &&
-            !SkMatrixPriv::CheapEqual(fViewMatrix, qe.viewMatrix()))
-        {
-            fViewMatrix = qe.viewMatrix();
-            pdman.setSkMatrix(fViewMatrixUniform, fViewMatrix);
-        }
+        this->setTransform(pdman, fViewMatrixUniform, qe.viewMatrix(), &fViewMatrix);
+        this->setTransform(pdman, fLocalMatrixUniform, qe.localMatrix(), &fLocalMatrix);
 
         if (qe.color() != fColor) {
             pdman.set4fv(fColorUniform, 1, qe.color().vec());
@@ -243,24 +235,28 @@
             pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(qe.coverageScale()));
             fCoverageScale = qe.coverageScale();
         }
-        this->setTransformDataHelper(qe.localMatrix(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
 private:
     SkMatrix fViewMatrix;
+    SkMatrix fLocalMatrix;
     SkPMColor4f fColor;
     uint8_t fCoverageScale;
+
     UniformHandle fColorUniform;
     UniformHandle fCoverageScaleUniform;
     UniformHandle fViewMatrixUniform;
+    UniformHandle fLocalMatrixUniform;
 
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
 GrGLQuadEffect::GrGLQuadEffect(const GrGeometryProcessor& processor)
-    : fViewMatrix(SkMatrix::InvalidMatrix())
-    , fColor(SK_PMColor4fILLEGAL)
-    , fCoverageScale(0xff) {}
+        : fViewMatrix(SkMatrix::InvalidMatrix())
+        , fLocalMatrix(SkMatrix::InvalidMatrix())
+        , fColor(SK_PMColor4fILLEGAL)
+        , fCoverageScale(0xff) {}
 
 void GrGLQuadEffect::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
     GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
@@ -286,14 +282,10 @@
                               gp.inPosition().name(),
                               gp.viewMatrix(),
                               &fViewMatrixUniform);
-
-    // emit transforms with position
-    this->emitTransforms(vertBuilder,
-                         varyingHandler,
-                         uniformHandler,
-                         gp.inPosition().asShaderVar(),
-                         gp.localMatrix(),
-                         args.fFPCoordTransformHandler);
+    if (gp.usesLocalCoords()) {
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, gp.inPosition().asShaderVar(),
+                              gp.localMatrix(), &fLocalMatrixUniform);
+    }
 
     fragBuilder->codeAppendf("half edgeAlpha;");
 
@@ -329,8 +321,9 @@
     const GrQuadEffect& ce = gp.cast<GrQuadEffect>();
     uint32_t key = ce.isAntiAliased() ? (ce.isFilled() ? 0x0 : 0x1) : 0x2;
     key |= ce.coverageScale() != 0xff ? 0x8 : 0x0;
-    key |= ce.usesLocalCoords() && ce.localMatrix().hasPerspective() ? 0x10 : 0x0;
-    key |= ComputePosKey(ce.viewMatrix()) << 5;
+    key |= ce.usesLocalCoords()? 0x10 : 0x0;
+    key = AddMatrixKeys(key, ce.viewMatrix(), ce.usesLocalCoords() ? ce.localMatrix()
+                                                                   : SkMatrix::I());
     b->add32(key);
 }
 
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 8761212..4a9f94e 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -20,7 +20,10 @@
 
 class GrGLBitmapTextGeoProc : public GrGLSLGeometryProcessor {
 public:
-    GrGLBitmapTextGeoProc() : fColor(SK_PMColor4fILLEGAL), fAtlasDimensions{0,0} {}
+    GrGLBitmapTextGeoProc()
+            : fColor(SK_PMColor4fILLEGAL)
+            , fAtlasDimensions{0,0}
+            , fLocalMatrix(SkMatrix::InvalidMatrix()) {}
 
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
         const GrBitmapTextGeoProc& btgp = args.fGP.cast<GrBitmapTextGeoProc>();
@@ -53,14 +56,8 @@
 
         // Setup position
         gpArgs->fPositionVar = btgp.inPosition().asShaderVar();
-
-        // emit transforms
-        this->emitTransforms(vertBuilder,
-                             varyingHandler,
-                             uniformHandler,
-                             btgp.inPosition().asShaderVar(),
-                             btgp.localMatrix(),
-                             args.fFPCoordTransformHandler);
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, btgp.inPosition().asShaderVar(),
+                              btgp.localMatrix(), &fLocalMatrixUniform);
 
         fragBuilder->codeAppend("half4 texColor;");
         append_multitexture_lookup(args, btgp.numTextureSamplers(),
@@ -92,7 +89,9 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(btgp.localMatrix(), pdman, transformRange);
+
+        this->setTransform(pdman, fLocalMatrixUniform, btgp.localMatrix(), &fLocalMatrix);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     static inline void GenKey(const GrGeometryProcessor& proc,
@@ -102,6 +101,7 @@
         uint32_t key = 0;
         key |= btgp.usesW() ? 0x1 : 0x0;
         key |= btgp.maskFormat() << 1;
+        key |= ComputeMatrixKey(btgp.localMatrix()) << 2;
         b->add32(key);
         b->add32(btgp.numTextureSamplers());
     }
@@ -113,6 +113,9 @@
     SkISize       fAtlasDimensions;
     UniformHandle fAtlasDimensionsInvUniform;
 
+    SkMatrix      fLocalMatrix;
+    UniformHandle fLocalMatrixUniform;
+
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 3bf74e1..19c75f3 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -57,14 +57,8 @@
 
         // Setup position
         gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar();
-
-        // emit transforms
-        this->emitTransforms(vertBuilder,
-                             varyingHandler,
-                             uniformHandler,
-                             dfTexEffect.inPosition().asShaderVar(),
-                             dfTexEffect.localMatrix(),
-                             args.fFPCoordTransformHandler);
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, gpArgs->fPositionVar,
+                              dfTexEffect.localMatrix(), &fLocalMatrixUniform);
 
         // add varyings
         GrGLSLVarying uv(kFloat2_GrSLType);
@@ -185,7 +179,8 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
+        this->setTransform(pdman, fLocalMatrixUniform, dfa8gp.localMatrix(), &fLocalMatrix);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -193,6 +188,7 @@
                               GrProcessorKeyBuilder* b) {
         const GrDistanceFieldA8TextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldA8TextGeoProc>();
         uint32_t key = dfTexEffect.getFlags();
+        key |= ComputeMatrixKey(dfTexEffect.localMatrix()) << 16;
         b->add32(key);
         b->add32(dfTexEffect.numTextureSamplers());
     }
@@ -202,9 +198,12 @@
     float fDistanceAdjust = -1.f;
     UniformHandle fDistanceAdjustUni;
 #endif
-    SkISize fAtlasDimensions = {0, 0};
+    SkISize       fAtlasDimensions = {0, 0};
     UniformHandle fAtlasDimensionsInvUniform;
 
+    SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
+    UniformHandle fLocalMatrixUniform;
+
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
@@ -354,31 +353,20 @@
         varyingHandler->addPassThroughAttribute(dfPathEffect.inColor(), args.fOutputColor);
 
         if (dfPathEffect.matrix().hasPerspective()) {
-            // Setup position
+            // Setup position (output position is transformed, local coords are pass through)
             this->writeOutputPosition(vertBuilder,
                                       uniformHandler,
                                       gpArgs,
                                       dfPathEffect.inPosition().name(),
                                       dfPathEffect.matrix(),
                                       &fMatrixUniform);
-
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 dfPathEffect.inPosition().asShaderVar(),
-                                 args.fFPCoordTransformHandler);
+            gpArgs->fLocalCoordVar = dfPathEffect.inPosition().asShaderVar();
         } else {
-            // Setup position
+            // Setup position (output position is pass through, local coords are transformed)
             this->writeOutputPosition(vertBuilder, gpArgs, dfPathEffect.inPosition().name());
-
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 dfPathEffect.inPosition().asShaderVar(),
-                                 dfPathEffect.matrix(),
-                                 args.fFPCoordTransformHandler);
+            this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                  dfPathEffect.inPosition().asShaderVar(), dfPathEffect.matrix(),
+                                  &fMatrixUniform);
         }
 
         // Use highp to work around aliasing issues
@@ -463,10 +451,9 @@
                  const CoordTransformRange& transformRange) override {
         const GrDistanceFieldPathGeoProc& dfpgp = proc.cast<GrDistanceFieldPathGeoProc>();
 
-        if (dfpgp.matrix().hasPerspective() && !SkMatrixPriv::CheapEqual(fMatrix, dfpgp.matrix())) {
-            fMatrix = dfpgp.matrix();
-            pdman.setSkMatrix(fMatrixUniform, fMatrix);
-        }
+        // We always set the matrix uniform; it's either used to transform from local to device
+        // for the output position, or from device to local for the local coord variable.
+        this->setTransform(pdman, fMatrixUniform, dfpgp.matrix(), &fMatrix);
 
         const SkISize& atlasDimensions = dfpgp.atlasDimensions();
         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
@@ -477,11 +464,7 @@
             fAtlasDimensions = atlasDimensions;
         }
 
-        if (dfpgp.matrix().hasPerspective()) {
-            this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
-        } else {
-            this->setTransformDataHelper(dfpgp.matrix(), pdman, transformRange);
-        }
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -490,7 +473,7 @@
         const GrDistanceFieldPathGeoProc& dfTexEffect = gp.cast<GrDistanceFieldPathGeoProc>();
 
         uint32_t key = dfTexEffect.getFlags();
-        key |= ComputePosKey(dfTexEffect.matrix()) << 16;
+        key |= ComputeMatrixKey(dfTexEffect.matrix()) << 16;
         b->add32(key);
         b->add32(dfTexEffect.matrix().hasPerspective());
         b->add32(dfTexEffect.numTextureSamplers());
@@ -605,7 +588,9 @@
 
 class GrGLDistanceFieldLCDTextGeoProc : public GrGLSLGeometryProcessor {
 public:
-    GrGLDistanceFieldLCDTextGeoProc() : fAtlasDimensions({0, 0}) {
+    GrGLDistanceFieldLCDTextGeoProc()
+            : fAtlasDimensions({0, 0})
+            , fLocalMatrix(SkMatrix::InvalidMatrix()) {
         fDistanceAdjust = GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
     }
 
@@ -634,14 +619,9 @@
 
         // Setup position
         gpArgs->fPositionVar = dfTexEffect.inPosition().asShaderVar();
-
-        // emit transforms
-        this->emitTransforms(vertBuilder,
-                             varyingHandler,
-                             uniformHandler,
-                             dfTexEffect.inPosition().asShaderVar(),
-                             dfTexEffect.localMatrix(),
-                             args.fFPCoordTransformHandler);
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                              dfTexEffect.inPosition().asShaderVar(), dfTexEffect.localMatrix(),
+                              &fLocalMatrixUniform);
 
         // set up varyings
         GrGLSLVarying uv(kFloat2_GrSLType);
@@ -801,7 +781,8 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(dflcd.localMatrix(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
+        this->setTransform(pdman, fLocalMatrixUniform, dflcd.localMatrix(), &fLocalMatrix);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -809,7 +790,8 @@
                               GrProcessorKeyBuilder* b) {
         const GrDistanceFieldLCDTextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldLCDTextGeoProc>();
 
-        uint32_t key = dfTexEffect.getFlags();
+        uint32_t key = (dfTexEffect.getFlags() << 16) |
+                       ComputeMatrixKey(dfTexEffect.localMatrix());
         b->add32(key);
         b->add32(dfTexEffect.numTextureSamplers());
     }
@@ -821,6 +803,9 @@
     SkISize                                       fAtlasDimensions;
     UniformHandle                                 fAtlasDimensionsInvUniform;
 
+    SkMatrix                                      fLocalMatrix;
+    UniformHandle                                 fLocalMatrixUniform;
+
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
index de6db26..d129a7d 100644
--- a/src/gpu/effects/GrShadowGeoProc.cpp
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -22,7 +22,6 @@
         const GrRRectShadowGeoProc& rsgp = args.fGP.cast<GrRRectShadowGeoProc>();
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-        GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         // emit attributes
@@ -35,13 +34,7 @@
 
         // Setup position
         this->writeOutputPosition(vertBuilder, gpArgs, rsgp.inPosition().name());
-
-        // emit transforms
-        this->emitTransforms(vertBuilder,
-                             varyingHandler,
-                             uniformHandler,
-                             rsgp.inPosition().asShaderVar(),
-                             args.fFPCoordTransformHandler);
+        // No need for local coordinates, this GP does not combine with fragment processors
 
         fragBuilder->codeAppend("half d = length(shadowParams.xy);");
         fragBuilder->codeAppend("float2 uv = float2(shadowParams.z * (1.0 - d), 0.5);");
@@ -53,7 +46,7 @@
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
                  const CoordTransformRange& transformRange) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
 private:
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
index 0d63776..334b35b 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
@@ -20,6 +20,13 @@
     GrGPArgs gpArgs;
     this->onEmitCode(args, &gpArgs);
 
+    // FIXME This must always be called at the moment, even when fLocalCoordVar is uninitialized
+    // and void because collectTransforms registers the uniforms for legacy coord transforms, which
+    // still need to be added even if the FPs are sampled explicitly. When they are gone, we only
+    // need to call this if the local coord isn't void (plus verify that FPs really don't need it).
+    this->collectTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
+                            gpArgs.fLocalCoordVar, args.fFPCoordTransformHandler);
+
     if (args.fGP.willUseTessellationShaders()) {
         // Tessellation shaders are temporarily responsible for integrating their own code strings
         // while we work out full support.
@@ -62,12 +69,11 @@
     }
 }
 
-void GrGLSLGeometryProcessor::emitTransforms(GrGLSLVertexBuilder* vb,
-                                             GrGLSLVaryingHandler* varyingHandler,
-                                             GrGLSLUniformHandler* uniformHandler,
-                                             const GrShaderVar& localCoordsVar,
-                                             const SkMatrix& localMatrix,
-                                             FPCoordTransformHandler* handler) {
+void GrGLSLGeometryProcessor::collectTransforms(GrGLSLVertexBuilder* vb,
+                                                GrGLSLVaryingHandler* varyingHandler,
+                                                GrGLSLUniformHandler* uniformHandler,
+                                                const GrShaderVar& localCoordsVar,
+                                                FPCoordTransformHandler* handler) {
     // We only require localCoordsVar to be valid if there is a coord transform that needs
     // it. CTs on FPs called with explicit coords do not require a local coord.
     auto getLocalCoords = [&localCoordsVar,
@@ -120,8 +126,7 @@
         if (!fp.isSampledWithExplicitCoords()) {
             auto [localCoordsStr, localCoordLength] = getLocalCoords();
             GrGLSLVarying v(kFloat2_GrSLType);
-            if (localMatrix.hasPerspective() || coordTransform.matrix().hasPerspective() ||
-                localCoordLength == 3) {
+            if (coordTransform.matrix().hasPerspective() || localCoordLength == 3) {
                 v = GrGLSLVarying(kFloat3_GrSLType);
             }
             SkString strVaryingName;
@@ -182,18 +187,12 @@
     }
 }
 
-void GrGLSLGeometryProcessor::setTransformDataHelper(const SkMatrix& localMatrix,
-                                                     const GrGLSLProgramDataManager& pdman,
+void GrGLSLGeometryProcessor::setTransformDataHelper(const GrGLSLProgramDataManager& pdman,
                                                      const CoordTransformRange& transformRange) {
     int i = 0;
     for (auto [transform, fp] : transformRange) {
         if (fInstalledTransforms[i].fHandle.isValid()) {
-            SkMatrix m;
-            if (fp.isSampledWithExplicitCoords()) {
-                m = GetTransformMatrix(transform, SkMatrix::I());
-            } else {
-                m = GetTransformMatrix(transform, localMatrix);
-            }
+            SkMatrix m = GetTransformMatrix(transform, SkMatrix::I());
             if (!SkMatrixPriv::CheapEqual(fInstalledTransforms[i].fCurrentValue, m)) {
                 if (fInstalledTransforms[i].fType == kFloat4_GrSLType) {
                     float values[4] = {m.getScaleX(), m.getTranslateX(),
@@ -213,11 +212,94 @@
     SkASSERT(i == fInstalledTransforms.count());
 }
 
+void GrGLSLGeometryProcessor::setTransform(const GrGLSLProgramDataManager& pdman,
+                                           const UniformHandle& uniform,
+                                           const SkMatrix& matrix,
+                                           SkMatrix* state) const {
+    if (!uniform.isValid() || (state && SkMatrixPriv::CheapEqual(*state, matrix))) {
+        // No update needed
+        return;
+    }
+    if (state) {
+        *state = matrix;
+    }
+    if (matrix.isScaleTranslate()) {
+        // ComputeMatrixKey and writeX() assume the uniform is a float4 (can't assert since nothing
+        // is exposed on a handle, but should be caught lower down).
+        float values[4] = {matrix.getScaleX(), matrix.getTranslateX(),
+                           matrix.getScaleY(), matrix.getTranslateY()};
+        pdman.set4fv(uniform, 1, values);
+    } else {
+        pdman.setSkMatrix(uniform, matrix);
+    }
+}
+
+static void write_vertex_position(GrGLSLVertexBuilder* vertBuilder,
+                                  GrGLSLUniformHandler* uniformHandler,
+                                  const GrShaderVar& inPos,
+                                  const SkMatrix& matrix,
+                                  const char* matrixName,
+                                  GrShaderVar* outPos,
+                                  GrGLSLGeometryProcessor::UniformHandle* matrixUniform) {
+    SkASSERT(inPos.getType() == kFloat3_GrSLType || inPos.getType() == kFloat2_GrSLType);
+    SkString outName = vertBuilder->newTmpVarName(inPos.getName().c_str());
+
+    if (matrix.isIdentity()) {
+        // Direct assignment, we won't use a uniform for the matrix.
+        outPos->set(inPos.getType(), outName.c_str());
+        vertBuilder->codeAppendf("float%d %s = %s;", GrSLTypeVecLength(inPos.getType()),
+                                                     outName.c_str(), inPos.getName().c_str());
+    } else {
+        SkASSERT(matrixUniform);
+
+        bool useCompactTransform = matrix.isScaleTranslate();
+        const char* mangledMatrixName;
+        *matrixUniform = uniformHandler->addUniform(nullptr,
+                                                        kVertex_GrShaderFlag,
+                                                        useCompactTransform ? kFloat4_GrSLType
+                                                                            : kFloat3x3_GrSLType,
+                                                        matrixName,
+                                                        &mangledMatrixName);
+
+        if (inPos.getType() == kFloat3_GrSLType) {
+            // A float3 stays a float3 whether or not the matrix adds perspective
+            if (useCompactTransform) {
+                vertBuilder->codeAppendf("float3 %s = %s.xz1 * %s + %s.yw0;\n",
+                                         outName.c_str(), mangledMatrixName,
+                                         inPos.getName().c_str(), mangledMatrixName);
+            } else {
+                vertBuilder->codeAppendf("float3 %s = %s * %s;\n", outName.c_str(),
+                                         mangledMatrixName, inPos.getName().c_str());
+            }
+            outPos->set(kFloat3_GrSLType, outName.c_str());
+        } else if (matrix.hasPerspective()) {
+            // A float2 is promoted to a float3 if we add perspective via the matrix
+            SkASSERT(!useCompactTransform);
+            vertBuilder->codeAppendf("float3 %s = (%s * %s.xy1);",
+                                     outName.c_str(), mangledMatrixName, inPos.getName().c_str());
+            outPos->set(kFloat3_GrSLType, outName.c_str());
+        } else {
+            if (useCompactTransform) {
+                vertBuilder->codeAppendf("float2 %s = %s.xz * %s + %s.yw;\n",
+                                         outName.c_str(), mangledMatrixName,
+                                         inPos.getName().c_str(), mangledMatrixName);
+            } else {
+                vertBuilder->codeAppendf("float2 %s = (%s * %s.xy1).xy;\n",
+                                         outName.c_str(), mangledMatrixName,
+                                         inPos.getName().c_str());
+            }
+            outPos->set(kFloat2_GrSLType, outName.c_str());
+        }
+    }
+}
+
 void GrGLSLGeometryProcessor::writeOutputPosition(GrGLSLVertexBuilder* vertBuilder,
                                                   GrGPArgs* gpArgs,
                                                   const char* posName) {
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2");
-    vertBuilder->codeAppendf("float2 %s = %s;", gpArgs->fPositionVar.c_str(), posName);
+    // writeOutputPosition assumes the incoming pos name points to a float2 variable
+    GrShaderVar inPos(posName, kFloat2_GrSLType);
+    write_vertex_position(vertBuilder, nullptr, inPos, SkMatrix::I(), "viewMatrix",
+                          &gpArgs->fPositionVar, nullptr);
 }
 
 void GrGLSLGeometryProcessor::writeOutputPosition(GrGLSLVertexBuilder* vertBuilder,
@@ -226,24 +308,17 @@
                                                   const char* posName,
                                                   const SkMatrix& mat,
                                                   UniformHandle* viewMatrixUniform) {
-    if (mat.isIdentity()) {
-        gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2");
-        vertBuilder->codeAppendf("float2 %s = %s;", gpArgs->fPositionVar.c_str(), posName);
-    } else {
-        const char* viewMatrixName;
-        *viewMatrixUniform = uniformHandler->addUniform(nullptr,
-                                                        kVertex_GrShaderFlag,
-                                                        kFloat3x3_GrSLType,
-                                                        "uViewM",
-                                                        &viewMatrixName);
-        if (!mat.hasPerspective()) {
-            gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos2");
-            vertBuilder->codeAppendf("float2 %s = (%s * float3(%s, 1)).xy;",
-                                     gpArgs->fPositionVar.c_str(), viewMatrixName, posName);
-        } else {
-            gpArgs->fPositionVar.set(kFloat3_GrSLType, "pos3");
-            vertBuilder->codeAppendf("float3 %s = %s * float3(%s, 1);",
-                                     gpArgs->fPositionVar.c_str(), viewMatrixName, posName);
-        }
-    }
+    GrShaderVar inPos(posName, kFloat2_GrSLType);
+    write_vertex_position(vertBuilder, uniformHandler, inPos, mat, "viewMatrix",
+                          &gpArgs->fPositionVar, viewMatrixUniform);
+}
+
+void GrGLSLGeometryProcessor::writeLocalCoord(GrGLSLVertexBuilder* vertBuilder,
+                                              GrGLSLUniformHandler* uniformHandler,
+                                              GrGPArgs* gpArgs,
+                                              GrShaderVar localVar,
+                                              const SkMatrix& localMatrix,
+                                              UniformHandle* localMatrixUniform) {
+    write_vertex_position(vertBuilder, uniformHandler, localVar, localMatrix, "localMatrix",
+                          &gpArgs->fLocalCoordVar, localMatrixUniform);
 }
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.h b/src/gpu/glsl/GrGLSLGeometryProcessor.h
index f23bd0e..10e2137 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.h
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.h
@@ -22,41 +22,33 @@
     /* Any general emit code goes in the base class emitCode.  Subclasses override onEmitCode */
     void emitCode(EmitArgs&) final;
 
-protected:
-    // A helper which subclasses can use to upload coord transform matrices in setData().
-    void setTransformDataHelper(const SkMatrix& localMatrix,
-                                const GrGLSLProgramDataManager& pdman,
-                                const CoordTransformRange&);
-
-    // Emit transformed local coords from the vertex shader as a uniform matrix and varying per
-    // 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*,
-                        const GrShaderVar& localCoordsVar,
-                        const SkMatrix& localMatrix,
-                        FPCoordTransformHandler*);
-
-    // Version of above that assumes identity for the local matrix.
-    void emitTransforms(GrGLSLVertexBuilder* vb,
-                        GrGLSLVaryingHandler* varyingHandler,
-                        GrGLSLUniformHandler* uniformHandler,
-                        const GrShaderVar& localCoordsVar,
-                        FPCoordTransformHandler* handler) {
-        this->emitTransforms(vb, varyingHandler, uniformHandler, localCoordsVar, SkMatrix::I(),
-                             handler);
-    }
-
-    // TODO: doc
+    // Generate the final code for assigning transformed coordinates to the varyings recorded in
+    // the call to collectTransforms(). This must happen after FP code emission so that it has
+    // access to any uniforms the FPs registered for const/uniform sample matrix invocations.
     void emitTransformCode(GrGLSLVertexBuilder* vb,
                            GrGLSLUniformHandler* uniformHandler) override;
 
+protected:
+    // A helper which subclasses can use to upload coord transform matrices in setData().
+    void setTransformDataHelper(const GrGLSLProgramDataManager& pdman,
+                                const CoordTransformRange&);
+
+    // A helper for setting the matrix on a uniform handle initialized through
+    // writeOutputPosition or writeLocalCoord. Automatically handles elided uniforms,
+    // scale+translate matrices, and state tracking (if provided state pointer is non-null).
+    void setTransform(const GrGLSLProgramDataManager& pdman, const UniformHandle& uniform,
+                      const SkMatrix& matrix, SkMatrix* state=nullptr) const;
+
     struct GrGPArgs {
         // Used to specify the output variable used by the GP to store its device position. It can
         // either be a float2 or a float3 (in order to handle perspective). The subclass sets this
         // in its onEmitCode().
         GrShaderVar fPositionVar;
+        // Used to specify the variable storing the draw's local coordinates. It can be either a
+        // float2, float3, or void. It can only be void when no FP needs local coordinates. This
+        // variable can be an attribute or local variable, but should not itself be a varying.
+        // GrGLSLGeometryProcessor automatically determines if this must be passed to a FS.
+        GrShaderVar fLocalCoordVar;
     };
 
     // Helpers for adding code to write the transformed vertex position. The first simple version
@@ -73,19 +65,54 @@
                              const SkMatrix& mat,
                              UniformHandle* viewMatrixUniform);
 
-    static uint32_t ComputePosKey(const SkMatrix& mat) {
+    // Helper to transform an existing variable by a given local matrix (e.g. the inverse view
+    // matrix). It will declare the transformed local coord variable and will set
+    // GrGPArgs::fLocalCoordVar.
+    void writeLocalCoord(GrGLSLVertexBuilder*, GrGLSLUniformHandler*, GrGPArgs*,
+                         GrShaderVar localVar, const SkMatrix& localMatrix,
+                         UniformHandle* localMatrixUniform);
+
+    // GPs that use writeOutputPosition and/or writeLocalCoord must incorporate the matrix type
+    // into their key, and should use this function or one of the other related helpers.
+    static uint32_t ComputeMatrixKey(const SkMatrix& mat) {
         if (mat.isIdentity()) {
-            return 0x0;
+            return 0b00;
+        } else if (mat.isScaleTranslate()) {
+            return 0b01;
         } else if (!mat.hasPerspective()) {
-            return 0x01;
+            return 0b10;
         } else {
-            return 0x02;
+            return 0b11;
         }
     }
+    static uint32_t ComputeMatrixKeys(const SkMatrix& viewMatrix, const SkMatrix& localMatrix) {
+        return (ComputeMatrixKey(viewMatrix) << kMatrixKeyBits) | ComputeMatrixKey(localMatrix);
+    }
+    static uint32_t AddMatrixKeys(uint32_t flags, const SkMatrix& viewMatrix,
+                                  const SkMatrix& localMatrix) {
+        // Shifting to make room for the matrix keys shouldn't lose bits
+        SkASSERT(((flags << (2 * kMatrixKeyBits)) >> (2 * kMatrixKeyBits)) == flags);
+        return (flags << (2 * kMatrixKeyBits)) | ComputeMatrixKeys(viewMatrix, localMatrix);
+    }
+    static constexpr int kMatrixKeyBits = 2;
 
 private:
     virtual void onEmitCode(EmitArgs&, GrGPArgs*) = 0;
 
+    // Iterates over the FPs in 'handler' to register additional varyings and uniforms to support
+    // VS-promoted local coord evaluation for the FPs. Subclasses must call this with
+    // 'localCoordsVar' set to an SkSL variable expression of type 'float2' or 'float3' representing
+    // the original local coordinates of the draw.
+    //
+    // This must happen before FP code emission so that the FPs can find the appropriate varying
+    // handles they use in place of explicit coord sampling; it is automatically called after
+    // onEmitCode() returns using the value stored in GpArgs::fLocalCoordVar.
+    void collectTransforms(GrGLSLVertexBuilder* vb,
+                           GrGLSLVaryingHandler* varyingHandler,
+                           GrGLSLUniformHandler* uniformHandler,
+                           const GrShaderVar& localCoordsVar,
+                           FPCoordTransformHandler* handler);
+
     struct TransformUniform {
         UniformHandle  fHandle;
         GrSLType       fType = kVoid_GrSLType;
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index 5b27431..e5a9c14 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -20,7 +20,8 @@
     , fOutputs(GrGLSLProgramBuilder::kVarsPerBlock)
     , fFeaturesAddedMask(0)
     , fCodeIndex(kCode)
-    , fFinalized(false) {
+    , fFinalized(false)
+    , fTmpVariableCounter(0) {
     // We push back some dummy pointers which will later become our header
     for (int i = 0; i <= kCode; i++) {
         fShaderStrings.push_back();
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index b69abe8..b766280 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -84,6 +84,13 @@
 
     void declareGlobal(const GrShaderVar&);
 
+    // Generates a unique variable name for holding the result of a temporary expression when it's
+    // not reasonable to just add a new block for scoping. Does not declare anything.
+    SkString newTmpVarName(const char* suffix) {
+        int tmpIdx = fTmpVariableCounter++;
+        return SkStringPrintf("_tmp_%d_%s", tmpIdx, suffix);
+    }
+
     /**
     * Called by GrGLSLProcessors to add code to one of the shaders.
     */
@@ -238,6 +245,9 @@
     int fCodeIndex;
     bool fFinalized;
 
+    // Counter for generating unique scratch variable names in a shader.
+    int fTmpVariableCounter;
+
     friend class GrCCCoverageProcessor; // to access code().
     friend class GrGLSLProgramBuilder;
     friend class GrGLProgramBuilder;
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index ca11c0d..2dc3e1a 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -577,14 +577,11 @@
 
             // Setup position
             this->writeOutputPosition(vertBuilder, gpArgs, qe.fInPosition.name());
-
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 qe.fInPosition.asShaderVar(),
-                                 qe.fLocalMatrix,
-                                 args.fFPCoordTransformHandler);
+            if (qe.fUsesLocalCoords) {
+                this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                      qe.fInPosition.asShaderVar(), qe.fLocalMatrix,
+                                      &fLocalMatrixUniform);
+            }
 
             fragBuilder->codeAppendf("half edgeAlpha;");
 
@@ -611,18 +608,24 @@
                                   const GrShaderCaps&,
                                   GrProcessorKeyBuilder* b) {
             const QuadEdgeEffect& qee = gp.cast<QuadEdgeEffect>();
-            b->add32(SkToBool(qee.fUsesLocalCoords && qee.fLocalMatrix.hasPerspective()));
+            uint32_t key = (uint32_t) qee.fUsesLocalCoords;
+            key |= ComputeMatrixKey(qee.fLocalMatrix) << 1;
+            b->add32(key);
         }
 
         void setData(const GrGLSLProgramDataManager& pdman,
                      const GrPrimitiveProcessor& gp,
                      const CoordTransformRange& transformRange) override {
             const QuadEdgeEffect& qe = gp.cast<QuadEdgeEffect>();
-            this->setTransformDataHelper(qe.fLocalMatrix, pdman, transformRange);
+            this->setTransformDataHelper(pdman, transformRange);
+            this->setTransform(pdman, fLocalMatrixUniform, qe.fLocalMatrix, &fLocalMatrix);
         }
 
     private:
         typedef GrGLSLGeometryProcessor INHERITED;
+
+        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
+        UniformHandle fLocalMatrixUniform;
     };
 
     void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 6f663f6..5ee04a4 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -876,14 +876,19 @@
 private:
     UniformHandle fParamUniform;
     UniformHandle fColorUniform;
+    UniformHandle fLocalMatrixUniform;
+
+    SkMatrix      fLocalMatrix;
     SkPMColor4f   fColor;
     SkScalar      fPrevRadius;
     SkScalar      fPrevCenterX;
     SkScalar      fPrevIntervalLength;
+
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
 GLDashingCircleEffect::GLDashingCircleEffect() {
+    fLocalMatrix = SkMatrix::InvalidMatrix();
     fColor = SK_PMColor4fILLEGAL;
     fPrevRadius = SK_ScalarMin;
     fPrevCenterX = SK_ScalarMin;
@@ -915,14 +920,10 @@
 
     // Setup position
     this->writeOutputPosition(vertBuilder, gpArgs, dce.fInPosition.name());
-
-    // emit transforms
-    this->emitTransforms(vertBuilder,
-                         varyingHandler,
-                         uniformHandler,
-                         dce.fInPosition.asShaderVar(),
-                         dce.localMatrix(),
-                         args.fFPCoordTransformHandler);
+    if (dce.usesLocalCoords()) {
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, dce.fInPosition.asShaderVar(),
+                              dce.localMatrix(), &fLocalMatrixUniform);
+    }
 
     // transforms all points so that we can compare them to our test circle
     fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);",
@@ -951,7 +952,8 @@
         pdman.set4fv(fColorUniform, 1, dce.color().vec());
         fColor = dce.color();
     }
-    this->setTransformDataHelper(dce.localMatrix(), pdman, transformRange);
+    this->setTransformDataHelper(pdman, transformRange);
+    this->setTransform(pdman, fLocalMatrixUniform, dce.localMatrix(), &fLocalMatrix);
 }
 
 void GLDashingCircleEffect::GenKey(const GrGeometryProcessor& gp,
@@ -959,8 +961,9 @@
                                    GrProcessorKeyBuilder* b) {
     const DashingCircleEffect& dce = gp.cast<DashingCircleEffect>();
     uint32_t key = 0;
-    key |= dce.usesLocalCoords() && dce.localMatrix().hasPerspective() ? 0x1 : 0x0;
+    key |= dce.usesLocalCoords() ? 0x1 : 0x0;
     key |= static_cast<uint32_t>(dce.aaMode()) << 1;
+    key |= ComputeMatrixKey(dce.localMatrix()) << 3;
     b->add32(key);
 }
 
@@ -1086,6 +1089,10 @@
 private:
     SkPMColor4f   fColor;
     UniformHandle fColorUniform;
+
+    SkMatrix      fLocalMatrix;
+    UniformHandle fLocalMatrixUniform;
+
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
@@ -1118,14 +1125,10 @@
 
     // Setup position
     this->writeOutputPosition(vertBuilder, gpArgs, de.fInPosition.name());
-
-    // emit transforms
-    this->emitTransforms(vertBuilder,
-                         varyingHandler,
-                         uniformHandler,
-                         de.fInPosition.asShaderVar(),
-                         de.localMatrix(),
-                         args.fFPCoordTransformHandler);
+    if (de.usesLocalCoords()) {
+        this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs, de.fInPosition.asShaderVar(),
+                              de.localMatrix(), &fLocalMatrixUniform);
+    }
 
     // transforms all points so that we can compare them to our test rect
     fragBuilder->codeAppendf("half xShifted = half(%s.x - floor(%s.x / %s.z) * %s.z);",
@@ -1178,7 +1181,8 @@
         pdman.set4fv(fColorUniform, 1, de.color().vec());
         fColor = de.color();
     }
-    this->setTransformDataHelper(de.localMatrix(), pdman, transformRange);
+    this->setTransformDataHelper(pdman, transformRange);
+    this->setTransform(pdman, fLocalMatrixUniform, de.localMatrix(), &fLocalMatrix);
 }
 
 void GLDashingLineEffect::GenKey(const GrGeometryProcessor& gp,
@@ -1186,8 +1190,9 @@
                                  GrProcessorKeyBuilder* b) {
     const DashingLineEffect& de = gp.cast<DashingLineEffect>();
     uint32_t key = 0;
-    key |= de.usesLocalCoords() && de.localMatrix().hasPerspective() ? 0x1 : 0x0;
-    key |= static_cast<int>(de.aaMode()) << 8;
+    key |= de.usesLocalCoords() ? 0x1 : 0x0;
+    key |= static_cast<int>(de.aaMode()) << 1;
+    key |= ComputeMatrixKey(de.localMatrix()) << 3;
     b->add32(key);
 }
 
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index d538805..111cc7e 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -185,12 +185,7 @@
             // emit transforms using either explicit local coords or positions
             const auto& coordsAttr = gp.localCoordsAttr().isInitialized() ? gp.localCoordsAttr()
                                                                           : gp.positionAttr();
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 coordsAttr.asShaderVar(),
-                                 SkMatrix::I(),
-                                 args.fFPCoordTransformHandler);
+            gpArgs->fLocalCoordVar = coordsAttr.asShaderVar();
 
             // Add varyings and globals for all custom attributes
             using Usage = SkVertices::Attribute::Usage;
@@ -306,7 +301,7 @@
             const VerticesGP& vgp = gp.cast<VerticesGP>();
             uint32_t key = 0;
             key |= (vgp.fColorArrayType == ColorArrayType::kSkColor) ? 0x1 : 0;
-            key |= ComputePosKey(vgp.viewMatrix()) << 20;
+            key |= ComputeMatrixKey(vgp.viewMatrix()) << 20;
             b->add32(key);
             b->add32(GrColorSpaceXform::XformKey(vgp.fColorSpaceXform.get()));
 
@@ -323,19 +318,14 @@
                      const CoordTransformRange& transformRange) override {
             const VerticesGP& vgp = gp.cast<VerticesGP>();
 
-            if (!vgp.viewMatrix().isIdentity() &&
-                !SkMatrixPriv::CheapEqual(fViewMatrix, vgp.viewMatrix())) {
-                fViewMatrix = vgp.viewMatrix();
-                pdman.setSkMatrix(fViewMatrixUniform, fViewMatrix);
-            }
+            this->setTransform(pdman, fViewMatrixUniform, vgp.viewMatrix(), &fViewMatrix);
+            this->setTransformDataHelper(pdman, transformRange);
 
             if (!vgp.colorAttr().isInitialized() && vgp.color() != fColor) {
                 pdman.set4fv(fColorUniform, 1, vgp.color().vec());
                 fColor = vgp.color();
             }
 
-            this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
-
             fColorSpaceHelper.setData(pdman, vgp.fColorSpaceXform.get());
 
             for (const auto& matrixUni : fCustomMatrixUniforms) {
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index 76122e5..630955f 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -680,15 +680,13 @@
         v->codeAppend("float2 aa_outset = aa_bloat_direction.xy * aa_bloatradius;");
         v->codeAppend("float2 vertexpos = corner + radius_outset * radii + aa_outset;");
 
-        // Emit transforms.
+        // Write positions
         GrShaderVar localCoord("", kFloat2_GrSLType);
         if (proc.fFlags & ProcessorFlags::kHasLocalCoords) {
             v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
                                                "local_rect.zw * (1 + vertexpos)) * .5;");
-            localCoord.set(kFloat2_GrSLType, "localcoord");
+            gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
         }
-        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
-                             args.fFPCoordTransformHandler);
 
         // Transform to device space.
         SkASSERT(!(proc.fFlags & ProcessorFlags::kHasPerspective));
@@ -743,7 +741,7 @@
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
                  const CoordTransformRange& transformRange) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 };
 
@@ -781,15 +779,13 @@
         // [-1,-1,+1,+1] space.
         v->codeAppend("float2 vertexpos = corner + radius_outset * radii;");
 
-        // Emit transforms.
+        // Write positions
         GrShaderVar localCoord("", kFloat2_GrSLType);
         if (hasLocalCoords) {
             v->codeAppend("float2 localcoord = (local_rect.xy * (1 - vertexpos) + "
                                                "local_rect.zw * (1 + vertexpos)) * .5;");
-            localCoord.set(kFloat2_GrSLType, "localcoord");
+            gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
         }
-        this->emitTransforms(v, varyings, args.fUniformHandler, localCoord,
-                             args.fFPCoordTransformHandler);
 
         // Transform to device space.
         if (!hasPerspective) {
@@ -849,7 +845,7 @@
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
                  const CoordTransformRange& transformRange) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 };
 
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 7efe3a3..fe9983f 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -49,7 +49,7 @@
             void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
                          const CoordTransformRange& transformRange) override {
                 const auto& latticeGP = proc.cast<LatticeGP>();
-                this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+                this->setTransformDataHelper(pdman, transformRange);
                 fColorSpaceXformHelper.setData(pdman, latticeGP.fColorSpaceXform.get());
             }
 
@@ -62,11 +62,8 @@
 
                 args.fVaryingHandler->emitAttributes(latticeGP);
                 this->writeOutputPosition(args.fVertBuilder, gpArgs, latticeGP.fInPosition.name());
-                this->emitTransforms(args.fVertBuilder,
-                                     args.fVaryingHandler,
-                                     args.fUniformHandler,
-                                     latticeGP.fInTextureCoords.asShaderVar(),
-                                     args.fFPCoordTransformHandler);
+                gpArgs->fLocalCoordVar = latticeGP.fInTextureCoords.asShaderVar();
+
                 args.fFragBuilder->codeAppend("float2 textureCoords;");
                 args.fVaryingHandler->addPassThroughAttribute(latticeGP.fInTextureCoords,
                                                               "textureCoords");
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index d857557..f720f63 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -155,14 +155,9 @@
 
             // Setup position
             this->writeOutputPosition(vertBuilder, gpArgs, cgp.fInPosition.name());
-
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 cgp.fInPosition.asShaderVar(),
-                                 cgp.fLocalMatrix,
-                                 args.fFPCoordTransformHandler);
+            this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                  cgp.fInPosition.asShaderVar(), cgp.fLocalMatrix,
+                                  &fLocalMatrixUniform);
 
             fragBuilder->codeAppend("float d = length(circleEdge.xy);");
             fragBuilder->codeAppend("half distanceToOuterEdge = half(circleEdge.z * (1.0 - d));");
@@ -210,24 +205,29 @@
                            const GrShaderCaps&,
                            GrProcessorKeyBuilder* b) {
             const CircleGeometryProcessor& cgp = gp.cast<CircleGeometryProcessor>();
-            uint16_t key;
+            uint32_t key;
             key = cgp.fStroke ? 0x01 : 0x0;
-            key |= cgp.fLocalMatrix.hasPerspective() ? 0x02 : 0x0;
-            key |= cgp.fInClipPlane.isInitialized() ? 0x04 : 0x0;
-            key |= cgp.fInIsectPlane.isInitialized() ? 0x08 : 0x0;
-            key |= cgp.fInUnionPlane.isInitialized() ? 0x10 : 0x0;
-            key |= cgp.fInRoundCapCenters.isInitialized() ? 0x20 : 0x0;
+            key |= cgp.fInClipPlane.isInitialized() ? 0x02 : 0x0;
+            key |= cgp.fInIsectPlane.isInitialized() ? 0x04 : 0x0;
+            key |= cgp.fInUnionPlane.isInitialized() ? 0x08 : 0x0;
+            key |= cgp.fInRoundCapCenters.isInitialized() ? 0x10 : 0x0;
+            key |= (ComputeMatrixKey(cgp.fLocalMatrix) << 16);
             b->add32(key);
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                      const CoordTransformRange& transformRange) override {
-            this->setTransformDataHelper(primProc.cast<CircleGeometryProcessor>().fLocalMatrix,
-                                         pdman, transformRange);
+            this->setTransformDataHelper(pdman, transformRange);
+            this->setTransform(pdman, fLocalMatrixUniform,
+                               primProc.cast<CircleGeometryProcessor>().fLocalMatrix,
+                               &fLocalMatrix);
         }
 
     private:
         typedef GrGLSLGeometryProcessor INHERITED;
+
+        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
+        UniformHandle fLocalMatrixUniform;
     };
 
     SkMatrix fLocalMatrix;
@@ -389,14 +389,10 @@
 
             // Setup position
             this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition.name());
+            this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                  bcscgp.fInPosition.asShaderVar(), bcscgp.fLocalMatrix,
+                                  &fLocalMatrixUniform);
 
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 bcscgp.fInPosition.asShaderVar(),
-                                 bcscgp.fLocalMatrix,
-                                 args.fFPCoordTransformHandler);
             GrShaderVar fnArgs[] = {
                     GrShaderVar("angleToEdge", kFloat_GrSLType),
                     GrShaderVar("diameter", kFloat_GrSLType),
@@ -477,18 +473,22 @@
                            GrProcessorKeyBuilder* b) {
             const ButtCapDashedCircleGeometryProcessor& bcscgp =
                     gp.cast<ButtCapDashedCircleGeometryProcessor>();
-            b->add32(bcscgp.fLocalMatrix.hasPerspective());
+            b->add32(ComputeMatrixKey(bcscgp.fLocalMatrix));
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                      const CoordTransformRange& transformRange) override {
-            this->setTransformDataHelper(
-                    primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, pdman,
-                    transformRange);
+            this->setTransformDataHelper(pdman, transformRange);
+            this->setTransform(pdman, fLocalMatrixUniform,
+                               primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix,
+                               &fLocalMatrix);
         }
 
     private:
         typedef GrGLSLGeometryProcessor INHERITED;
+
+        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
+        UniformHandle fLocalMatrixUniform;
     };
 
     SkMatrix fLocalMatrix;
@@ -588,14 +588,10 @@
 
             // Setup position
             this->writeOutputPosition(vertBuilder, gpArgs, egp.fInPosition.name());
+            this->writeLocalCoord(vertBuilder, uniformHandler, gpArgs,
+                                  egp.fInPosition.asShaderVar(), egp.fLocalMatrix,
+                                  &fLocalMatrixUniform);
 
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 egp.fInPosition.asShaderVar(),
-                                 egp.fLocalMatrix,
-                                 args.fFPCoordTransformHandler);
             // For stroked ellipses, we use the full ellipse equation (x^2/a^2 + y^2/b^2 = 1)
             // to compute both the edges because we need two separate test equations for
             // the single offset.
@@ -666,19 +662,23 @@
                            const GrShaderCaps&,
                            GrProcessorKeyBuilder* b) {
             const EllipseGeometryProcessor& egp = gp.cast<EllipseGeometryProcessor>();
-            uint16_t key = egp.fStroke ? 0x1 : 0x0;
-            key |= egp.fLocalMatrix.hasPerspective() ? 0x2 : 0x0;
+            uint32_t key = egp.fStroke ? 0x1 : 0x0;
+            key |= ComputeMatrixKey(egp.fLocalMatrix) << 1;
             b->add32(key);
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                      const CoordTransformRange& transformRange) override {
             const EllipseGeometryProcessor& egp = primProc.cast<EllipseGeometryProcessor>();
-            this->setTransformDataHelper(egp.fLocalMatrix, pdman, transformRange);
+            this->setTransformDataHelper(pdman, transformRange);
+            this->setTransform(pdman, fLocalMatrixUniform, egp.fLocalMatrix, &fLocalMatrix);
         }
 
     private:
         typedef GrGLSLGeometryProcessor INHERITED;
+
+        SkMatrix      fLocalMatrix = SkMatrix::InvalidMatrix();
+        UniformHandle fLocalMatrixUniform;
     };
 
     Attribute fInPosition;
@@ -791,13 +791,7 @@
                                       diegp.fInPosition.name(),
                                       diegp.fViewMatrix,
                                       &fViewMatrixUniform);
-
-            // emit transforms
-            this->emitTransforms(vertBuilder,
-                                 varyingHandler,
-                                 uniformHandler,
-                                 diegp.fInPosition.asShaderVar(),
-                                 args.fFPCoordTransformHandler);
+            gpArgs->fLocalCoordVar = diegp.fInPosition.asShaderVar();
 
             // for outer curve
             fragBuilder->codeAppendf("float2 scaledOffset = %s.xy;", offsets0.fsIn());
@@ -862,8 +856,8 @@
                            const GrShaderCaps&,
                            GrProcessorKeyBuilder* b) {
             const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
-            uint16_t key = static_cast<uint16_t>(diegp.fStyle);
-            key |= ComputePosKey(diegp.fViewMatrix) << 10;
+            uint32_t key = static_cast<uint32_t>(diegp.fStyle);
+            key |= ComputeMatrixKey(diegp.fViewMatrix) << 10;
             b->add32(key);
         }
 
@@ -871,17 +865,12 @@
                      const CoordTransformRange& transformRange) override {
             const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
 
-            if (!diegp.fViewMatrix.isIdentity() &&
-                !SkMatrixPriv::CheapEqual(fViewMatrix, diegp.fViewMatrix))
-            {
-                fViewMatrix = diegp.fViewMatrix;
-                pdman.setSkMatrix(fViewMatrixUniform, fViewMatrix);
-            }
-            this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+            this->setTransform(pdman, fViewMatrixUniform, diegp.fViewMatrix, &fViewMatrix);
+            this->setTransformDataHelper(pdman, transformRange);
         }
 
     private:
-        SkMatrix fViewMatrix;
+        SkMatrix      fViewMatrix;
         UniformHandle fViewMatrixUniform;
 
         typedef GrGLSLGeometryProcessor INHERITED;
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index bc5de24..ca420dd 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -571,7 +571,7 @@
             void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
                          const CoordTransformRange& transformRange) override {
                 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
-                this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+                this->setTransformDataHelper(pdman, transformRange);
                 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
             }
 
@@ -604,18 +604,10 @@
                     gpArgs->fPositionVar = gp.fPosition.asShaderVar();
                 }
 
-                // Handle local coordinates if they exist. This is required even when the op
-                // isn't providing local coords but there are FPs called with explicit coords.
-                // It installs the uniforms that transform their coordinates in the fragment
-                // shader.
-                // NOTE: If the only usage of local coordinates is for the inline texture fetch
-                // before FPs, then there are no registered FPCoordTransforms and this ends up
-                // emitting nothing, so there isn't a duplication of local coordinates
-                this->emitTransforms(args.fVertBuilder,
-                                     args.fVaryingHandler,
-                                     args.fUniformHandler,
-                                     gp.fLocalCoord.asShaderVar(),
-                                     args.fFPCoordTransformHandler);
+                // This attribute will be uninitialized if earlier FP analysis determined no
+                // local coordinates are needed (and this will not include the inline texture
+                // fetch this GP does before invoking FPs).
+                gpArgs->fLocalCoordVar = gp.fLocalCoord.asShaderVar();
 
                 // Solid color before any texturing gets modulated in
                 if (gp.fColor.isInitialized()) {
diff --git a/src/gpu/tessellate/GrDrawAtlasPathOp.cpp b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
index 2e456f5..cda13be 100644
--- a/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
+++ b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
@@ -85,15 +85,12 @@
 
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
 
-        GrShaderVar localCoord = gpArgs->fPositionVar;
         if (shader.fUsesLocalCoords) {
             args.fVertBuilder->codeAppendf(R"(
                     float2x2 M = float2x2(viewmatrix_scaleskew);
                     float2 localcoord = inverse(M) * (devcoord - viewmatrix_trans);)");
-            localCoord.set(kFloat2_GrSLType, "localcoord");
+            gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
         }
-        this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
-                             localCoord, args.fFPCoordTransformHandler);
 
         args.fFragBuilder->codeAppendf("%s = ", args.fOutputCoverage);
         args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], atlasCoord.fsIn());
@@ -104,7 +101,7 @@
                  const CoordTransformRange& transformRange) override {
         const SkISize& dimensions = primProc.cast<DrawAtlasPathShader>().fAtlasDimensions;
         pdman.set2f(fAtlasAdjustUniform, 1.f / dimensions.width(), 1.f / dimensions.height());
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform;
diff --git a/src/gpu/tessellate/GrFillPathShader.cpp b/src/gpu/tessellate/GrFillPathShader.cpp
index f87ac4a..93e6e59 100644
--- a/src/gpu/tessellate/GrFillPathShader.cpp
+++ b/src/gpu/tessellate/GrFillPathShader.cpp
@@ -26,11 +26,8 @@
         args.fVertBuilder->codeAppend("float2 localcoord, vertexpos;");
         shader.emitVertexCode(this, args.fVertBuilder, viewMatrix, args.fUniformHandler);
 
-        this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
-                             GrShaderVar("localcoord", kFloat2_GrSLType),
-                             args.fFPCoordTransformHandler);
-
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
+        gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
 
         const char* color;
         fColorUniform = args.fUniformHandler->addUniform(
@@ -53,7 +50,7 @@
             pdman.set4f(fPathBoundsUniform, b.left(), b.top(), b.right(), b.bottom());
         }
 
-        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+        this->setTransformDataHelper(pdman, transformRange);
     }
 
     GrGLSLUniformHandler::UniformHandle fViewMatrixUniform;