Reland "Centralize geometry processor vertex shader transform code"

This is a reland of 0426947243e1d6b470830c4d3b1c5704ed1f23e1

Original change's description:
> 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>

Bug: skia:10396
Change-Id: If1347bcacb7c405a66f9d4c5b0059e9d735b3f9a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/298062
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@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;