SkSL sample() now permits specification of coordinates

Bug: skia:
Change-Id: I16073008ac852f1864bd1d2bd38087a5b661d05a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/232581
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/gm/fpcoordinateoverride.cpp b/gm/fpcoordinateoverride.cpp
new file mode 100644
index 0000000..721a783
--- /dev/null
+++ b/gm/fpcoordinateoverride.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm/gm.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkSize.h"
+#include "include/core/SkString.h"
+#include "include/core/SkTileMode.h"
+#include "include/core/SkTypes.h"
+#include "include/gpu/GrContext.h"
+#include "src/gpu/GrCaps.h"
+#include "src/gpu/GrContextPriv.h"
+#include "src/gpu/GrCoordTransform.h"
+#include "src/gpu/GrFragmentProcessor.h"
+#include "src/gpu/GrRenderTargetContext.h"
+#include "src/gpu/GrRenderTargetContextPriv.h"
+#include "src/gpu/effects/GrRRectEffect.h"
+#include "src/gpu/effects/GrSkSLFP.h"
+#include "src/gpu/ops/GrFillRectOp.h"
+#include "tools/Resources.h"
+#include "tools/ToolUtils.h"
+
+class SampleCoordEffect : public GrFragmentProcessor {
+public:
+    static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 0;
+
+    SampleCoordEffect(std::unique_ptr<GrFragmentProcessor> child)
+        : INHERITED(CLASS_ID, kNone_OptimizationFlags) {
+        child->setComputeLocalCoordsInVertexShader(false);
+        this->registerChildProcessor(std::move(child));
+    }
+
+    const char* name() const override { return "SampleCoordEffect"; }
+
+    std::unique_ptr<GrFragmentProcessor> clone() const override {
+        SkASSERT(false);
+        return nullptr;
+    }
+
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {
+    }
+
+    bool onIsEqual(const GrFragmentProcessor&) const override {
+        SkASSERT(false);
+        return true;
+    }
+
+private:
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    typedef GrFragmentProcessor INHERITED;
+};
+
+class GLSLSampleCoordEffect : public GrGLSLFragmentProcessor {
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        SkString sample1("sample1");
+        SkString sample2("sample2");
+        this->invokeChild(0, &sample1, args, "float2(sk_FragCoord.x, sk_FragCoord.y)");
+        this->invokeChild(0, &sample2, args, "float2(sk_FragCoord.x, 512 - sk_FragCoord.y)");
+        fragBuilder->codeAppendf("%s = (%s + %s) / 2;\n", args.fOutputColor, sample1.c_str(),
+                                 sample2.c_str());
+    }
+};
+
+GrGLSLFragmentProcessor* SampleCoordEffect::onCreateGLSLInstance() const {
+    return new GLSLSampleCoordEffect();
+}
+
+DEF_SIMPLE_GPU_GM_BG(fpcoordinateoverride, ctx, rtCtx, canvas, 512, 512,
+                     ToolUtils::color_to_565(0xFF66AA99)) {
+    SkRect bounds = SkRect::MakeIWH(512, 512);
+
+    SkBitmap bmp;
+    GetResourceAsBitmap("images/mandrill_512_q075.jpg", &bmp);
+    GrProxyProvider* proxyProvider = ctx->priv().proxyProvider();
+    sk_sp<GrTextureProxy> texture = proxyProvider->createProxyFromBitmap(bmp, GrMipMapped::kNo);
+    std::unique_ptr<GrFragmentProcessor> imgFP = GrSimpleTextureEffect::Make(texture, SkMatrix());
+    auto fp = std::unique_ptr<GrFragmentProcessor>(new SampleCoordEffect(std::move(imgFP)));
+
+    GrPaint grPaint;
+    grPaint.addCoverageFragmentProcessor(std::move(fp));
+
+    rtCtx->priv().testingOnly_addDrawOp(GrFillRectOp::MakeNonAARect(ctx,
+                                                                    std::move(grPaint),
+                                                                    SkMatrix::I(),
+                                                                    bounds));
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index 8f7df05..8df253b 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -165,6 +165,7 @@
   "$_gm/fontregen.cpp",
   "$_gm/fontscaler.cpp",
   "$_gm/fontscalerdistortable.cpp",
+  "$_gm/fpcoordinateoverride.cpp",
   "$_gm/fwidth_squircle.cpp",
   "$_gm/gamma.cpp",
   "$_gm/gammatext.cpp",
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 019b684..257d736 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -16,10 +16,11 @@
   "$_src/sksl/SkSLJIT.cpp",
   "$_src/sksl/SkSLLexer.cpp",
   "$_src/sksl/SkSLParser.cpp",
+  "$_src/sksl/SkSLSectionAndParameterHelper.cpp",
   "$_src/sksl/SkSLString.cpp",
   "$_src/sksl/SkSLUtil.cpp",
-  "$_src/sksl/ir/SkSLSymbolTable.cpp",
   "$_src/sksl/ir/SkSLSetting.cpp",
+  "$_src/sksl/ir/SkSLSymbolTable.cpp",
   "$_src/sksl/ir/SkSLType.cpp",
   "$_src/sksl/ir/SkSLVariableReference.cpp",
 ]
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index 9b23a57..cad1cbd 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -586,15 +586,16 @@
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     fragBuilder->codeAppendf("\t\thalf4 %s = ", dColor);
-    fragBuilder->appendTextureLookup(args.fTexSamplers[0], args.fTransformedCoords[0].c_str(),
-                                     args.fTransformedCoords[0].getType());
+    fragBuilder->appendTextureLookup(args.fTexSamplers[0],
+                                     args.fTransformedCoords[0].fVaryingPoint.c_str(),
+                                     args.fTransformedCoords[0].fVaryingPoint.getType());
     fragBuilder->codeAppend(";\n");
 
     // Unpremultiply the displacement
     fragBuilder->codeAppendf(
         "\t\t%s.rgb = (%s.a < %s) ? half3(0.0) : saturate(%s.rgb / %s.a);",
         dColor, dColor, nearZero, dColor, dColor);
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[1]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[1].fVaryingPoint);
     fragBuilder->codeAppendf("\t\tfloat2 %s = %s + %s*(%s.",
                              cCoords, coords2D.c_str(), scaleUni, dColor);
 
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index db5aac4..f6db850 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -1752,7 +1752,7 @@
         GrShaderVar("scale", kHalf_GrSLType),
     };
     SkString sobelFuncName;
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
     fragBuilder->emitFunction(kHalf_GrSLType,
                               "sobel",
diff --git a/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
index 6518d3c..0009c0a 100644
--- a/src/effects/imagefilters/SkMorphologyImageFilter.cpp
+++ b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
@@ -286,7 +286,7 @@
     const char* range = uniformHandler->getUniformCStr(fRangeUni);
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
     const char* func;
     switch (me.type()) {
         case MorphType::kErode:
diff --git a/src/gpu/GrCoordTransform.h b/src/gpu/GrCoordTransform.h
index fac7844..2451363 100644
--- a/src/gpu/GrCoordTransform.h
+++ b/src/gpu/GrCoordTransform.h
@@ -23,7 +23,8 @@
     GrCoordTransform()
             : fProxy(nullptr)
             , fNormalize(false)
-            , fReverseY(false) {
+            , fReverseY(false)
+            , fComputeInVertexShader(true) {
         SkDEBUGCODE(fInProcessor = false);
     }
 
@@ -100,6 +101,12 @@
     // successfully instantiated
     GrTexture* peekTexture() const { return fProxy->peekTexture(); }
 
+    bool computeInVertexShader() const { return fComputeInVertexShader; }
+
+    void setComputeInVertexShader(bool computeInVertexShader) {
+        fComputeInVertexShader = computeInVertexShader;
+    }
+
 private:
     void reset(const SkMatrix& m, GrTextureProxy* proxy = nullptr) {
         SkASSERT(!fInProcessor);
@@ -108,6 +115,7 @@
         fProxy = proxy;
         fNormalize = proxy && proxy->textureType() != GrTextureType::kRectangle;
         fReverseY = proxy && kBottomLeft_GrSurfaceOrigin == proxy->origin();
+        fComputeInVertexShader = true;
     }
 
     // The textures' effect is to optionally normalize the final matrix, so a blind
@@ -119,6 +127,7 @@
     const GrTextureProxy*   fProxy;
     bool                    fNormalize;
     bool                    fReverseY;
+    bool                    fComputeInVertexShader;
 
 #ifdef SK_DEBUG
 public:
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 41975b3..2e6b1a1 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -69,7 +69,8 @@
     return this->onTextureSampler(i);
 }
 
-void GrFragmentProcessor::addCoordTransform(const GrCoordTransform* transform) {
+void GrFragmentProcessor::addCoordTransform(GrCoordTransform* transform) {
+    transform->setComputeInVertexShader(this->computeLocalCoordsInVertexShader());
     fCoordTransforms.push_back(transform);
     fFlags |= kUsesLocalCoords_Flag;
     SkDEBUGCODE(transform->setInProcessor();)
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 1e19997..b9035ff 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -8,10 +8,10 @@
 #ifndef GrFragmentProcessor_DEFINED
 #define GrFragmentProcessor_DEFINED
 
+#include "src/gpu/GrCoordTransform.h"
 #include "src/gpu/GrProcessor.h"
 #include "src/gpu/ops/GrOp.h"
 
-class GrCoordTransform;
 class GrGLSLFragmentProcessor;
 class GrPaint;
 class GrPipeline;
@@ -114,7 +114,7 @@
         numTransforms(). */
     const GrCoordTransform& coordTransform(int index) const { return *fCoordTransforms[index]; }
 
-    const SkTArray<const GrCoordTransform*, true>& coordTransforms() const {
+    const SkTArray<GrCoordTransform*, true>& coordTransforms() const {
         return fCoordTransforms;
     }
 
@@ -127,6 +127,24 @@
     /** Do any of the coordtransforms for this processor require local coords? */
     bool usesLocalCoords() const { return SkToBool(fFlags & kUsesLocalCoords_Flag); }
 
+    bool computeLocalCoordsInVertexShader() const {
+        return SkToBool(fFlags & kComputeLocalCoordsInVertexShader_Flag);
+    }
+
+    void setComputeLocalCoordsInVertexShader(bool value) const {
+        if (value) {
+            fFlags |= kComputeLocalCoordsInVertexShader_Flag;
+        } else {
+            fFlags &= ~kComputeLocalCoordsInVertexShader_Flag;
+        }
+        for (GrCoordTransform* transform : fCoordTransforms) {
+            transform->setComputeInVertexShader(value);
+        }
+        for (const auto& child : fChildProcessors) {
+            child->setComputeLocalCoordsInVertexShader(value);
+        }
+    }
+
     /**
      * A GrDrawOp may premultiply its antialiasing coverage into its GrGeometryProcessor's color
      * output under the following scenario:
@@ -284,8 +302,8 @@
 
     GrFragmentProcessor(ClassID classID, OptimizationFlags optimizationFlags)
             : INHERITED(classID)
-            , fFlags(optimizationFlags) {
-        SkASSERT((fFlags & ~kAll_OptimizationFlags) == 0);
+            , fFlags(optimizationFlags | kComputeLocalCoordsInVertexShader_Flag) {
+        SkASSERT((optimizationFlags & ~kAll_OptimizationFlags) == 0);
     }
 
     OptimizationFlags optimizationFlags() const {
@@ -325,7 +343,7 @@
      * transforms in a consistent order. The non-virtual implementation of isEqual() automatically
      * compares transforms and will assume they line up across the two processor instances.
      */
-    void addCoordTransform(const GrCoordTransform*);
+    void addCoordTransform(GrCoordTransform*);
 
     /**
      * FragmentProcessor subclasses call this from their constructor to register any child
@@ -382,13 +400,14 @@
     enum PrivateFlags {
         kFirstPrivateFlag = kAll_OptimizationFlags + 1,
         kUsesLocalCoords_Flag = kFirstPrivateFlag,
+        kComputeLocalCoordsInVertexShader_Flag = kFirstPrivateFlag << 1,
     };
 
-    mutable uint32_t fFlags = 0;
+    mutable uint32_t fFlags = kComputeLocalCoordsInVertexShader_Flag;
 
     int fTextureSamplerCnt = 0;
 
-    SkSTArray<4, const GrCoordTransform*, true> fCoordTransforms;
+    SkSTArray<4, GrCoordTransform*, true> fCoordTransforms;
 
     SkSTArray<1, std::unique_ptr<GrFragmentProcessor>, true> fChildProcessors;
 
diff --git a/src/gpu/GrPathProcessor.cpp b/src/gpu/GrPathProcessor.cpp
index 7e36968..861ff1b 100644
--- a/src/gpu/GrPathProcessor.cpp
+++ b/src/gpu/GrPathProcessor.cpp
@@ -49,6 +49,11 @@
         fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
     }
 
+    SkString matrix_to_sksl(const SkMatrix& m) {
+        return SkStringPrintf("float3x3(%f, %f, %f, %f, %f, %f, %f, %f, %f)", m[0], m[1], m[2],
+                              m[3], m[4], m[5], m[6], m[7], m[8]);
+    }
+
     void emitTransforms(GrGLSLVaryingHandler* varyingHandler,
                         FPCoordTransformHandler* transformHandler) {
         int i = 0;
@@ -66,7 +71,11 @@
                                                                &v).toIndex();
             fInstalledTransforms.back().fType = varyingType;
 
-            transformHandler->specifyCoordsForCurrCoordTransform(SkString(v.fsIn()), varyingType);
+            transformHandler->specifyCoordsForCurrCoordTransform(
+                                                        matrix_to_sksl(coordTransform->getMatrix()),
+                                                        UniformHandle(),
+                                                        GrShaderVar(SkString(v.fsIn()),
+                                                                             varyingType));
             ++i;
         }
     }
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index 1ce6ca2..70a812b 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -25,7 +25,7 @@
 }
 
 uint32_t
-GrPrimitiveProcessor::getTransformKey(const SkTArray<const GrCoordTransform*, true>& coords,
+GrPrimitiveProcessor::getTransformKey(const SkTArray<GrCoordTransform*, true>& coords,
                                       int numCoords) const {
     uint32_t totalKey = 0;
     for (int t = 0; t < numCoords; ++t) {
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index ca79985..7145688 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -174,7 +174,7 @@
      *
      * TODO: A better name for this function  would be "compute" instead of "get".
      */
-    uint32_t getTransformKey(const SkTArray<const GrCoordTransform*, true>& coords,
+    uint32_t getTransformKey(const SkTArray<GrCoordTransform*, true>& coords,
                              int numCoords) const;
 
     /**
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index a33ce64..e59ed01 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -46,7 +46,7 @@
     const char* dims = uniformHandler->getUniformCStr(fDimensions);
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
     /*
      * Filter weights come from Don Mitchell & Arun Netravali's 'Reconstruction Filters in Computer
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index 33d916c..1c7e4db 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -56,7 +56,7 @@
                                                  "Kernel", arrayCount);
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
     fragBuilder->codeAppendf("%s = half4(0, 0, 0, 0);", args.fOutputColor);
 
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index f4ec11d..86df7a0 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -63,7 +63,7 @@
     const char* bias = uniformHandler->getUniformCStr(fBiasUni);
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
     fragBuilder->codeAppend("half4 sum = half4(0, 0, 0, 0);");
     fragBuilder->codeAppendf("float2 coord = %s - %s * %s;", coords2D.c_str(), kernelOffset, imgInc);
     fragBuilder->codeAppend("half4 c;");
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index 01722ee..c9ae676 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -167,7 +167,7 @@
         int substringStartIndex = 0;
         int formatArgIndex = 0;
         SkString coords = args.fTransformedCoords.count()
-            ? fragBuilder->ensureCoords2D(args.fTransformedCoords[0])
+            ? fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint)
             : SkString("sk_FragCoord");
         for (size_t i = 0; i < fGLSL.length(); ++i) {
             char c = fGLSL[i];
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index 65411f1..4bd1538 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -315,7 +315,8 @@
             const GrTextureDomain& domain = tde.fTextureDomain;
 
             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-            SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+            SkString coords2D =
+                              fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
             fGLDomain.sampleTexture(fragBuilder,
                                     args.fUniformHandler,
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index 44033ad..a82aec7 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -98,7 +98,7 @@
 
             SkString coords[4];
             for (int i = 0; i < numSamplers; ++i) {
-                coords[i] = fragBuilder->ensureCoords2D(args.fTransformedCoords[i]);
+                coords[i] = fragBuilder->ensureCoords2D(args.fTransformedCoords[i].fVaryingPoint);
             }
 
             for (int i = 0; i < numSamplers; ++i) {
diff --git a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
index 8661e24..506cedb 100644
--- a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
+++ b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
@@ -41,7 +41,8 @@
                                                              "innerThreshold");
         outerThresholdVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType,
                                                              "outerThreshold");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "half4 color = %s;\nhalf4 mask_color = sample(%s, %s).%s;\nif (mask_color.w < 0.5) "
                 "{\n    if (color.w > %s) {\n        half scale = %s / color.w;\n        color.xyz "
@@ -50,7 +51,8 @@
                 "color.w = %s;\n}\n%s = color;\n",
                 args.fInputColor,
                 fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]),
-                sk_TransformedCoords2D_0.c_str(),
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
                 fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str(),
                 args.fUniformHandler->getUniformCStr(outerThresholdVar),
                 args.fUniformHandler->getUniformCStr(outerThresholdVar),
diff --git a/src/gpu/effects/generated/GrMagnifierEffect.cpp b/src/gpu/effects/generated/GrMagnifierEffect.cpp
index 0526e83..4f7a2ea 100644
--- a/src/gpu/effects/generated/GrMagnifierEffect.cpp
+++ b/src/gpu/effects/generated/GrMagnifierEffect.cpp
@@ -47,7 +47,8 @@
                 kFragment_GrShaderFlag, kFloat_GrSLType, "yInvInset");
         offsetVar =
                 args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "offset");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "float2 coord = %s;\nfloat2 zoom_coord = float2(%s) + coord * float2(%s, "
                 "%s);\nfloat2 delta = (coord - %s.xy) * %s.zw;\ndelta = min(delta, "
@@ -56,7 +57,8 @@
                 "- delta;\n    float dist = length(delta);\n    dist = max(2.0 - dist, 0.0);\n    "
                 "weight = min(dist * dist, 1.0);\n} else {\n    float2 delta_squared = delta * "
                 "delta;\n    weight = min(min(delta_squared.x, delta_square",
-                sk_TransformedCoords2D_0.c_str(),
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
                 args.fUniformHandler->getUniformCStr(offsetVar),
                 args.fUniformHandler->getUniformCStr(xInvZoomVar),
                 args.fUniformHandler->getUniformCStr(yInvZoomVar),
diff --git a/src/gpu/effects/generated/GrMixerEffect.cpp b/src/gpu/effects/generated/GrMixerEffect.cpp
index 9914d3a..88ca234 100644
--- a/src/gpu/effects/generated/GrMixerEffect.cpp
+++ b/src/gpu/effects/generated/GrMixerEffect.cpp
@@ -27,14 +27,14 @@
         (void)weight;
         weightVar =
                 args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "weight");
-        SkString _input0 = SkStringPrintf("%s", args.fInputColor);
+        SkString _input1278 = SkStringPrintf("%s", args.fInputColor);
         SkString _sample1278("_sample1278");
-        this->invokeChild(_outer.fp0_index, _input0.c_str(), &_sample1278, args);
+        this->invokeChild(_outer.fp0_index, _input1278.c_str(), &_sample1278, args);
         fragBuilder->codeAppendf("half4 in0 = %s;", _sample1278.c_str());
-        SkString _input1 = SkStringPrintf("%s", args.fInputColor);
+        SkString _input1335 = SkStringPrintf("%s", args.fInputColor);
         SkString _sample1335("_sample1335");
         if (_outer.fp1_index >= 0) {
-            this->invokeChild(_outer.fp1_index, _input1.c_str(), &_sample1335, args);
+            this->invokeChild(_outer.fp1_index, _input1335.c_str(), &_sample1335, args);
         } else {
             fragBuilder->codeAppendf("half4 %s;", _sample1335.c_str());
         }
diff --git a/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp b/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp
index 852111d..56a55dc 100644
--- a/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp
+++ b/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.cpp
@@ -42,9 +42,9 @@
                                           : "half4(0)",
                 _outer.literalColor.fR, _outer.literalColor.fG, _outer.literalColor.fB,
                 _outer.literalColor.fA);
-        SkString _input0("constColor");
+        SkString _input1992("constColor");
         SkString _sample1992("_sample1992");
-        this->invokeChild(_outer.fp_index, _input0.c_str(), &_sample1992, args);
+        this->invokeChild(_outer.fp_index, _input1992.c_str(), &_sample1992, args);
         fragBuilder->codeAppendf("\n%s = %s;\n", args.fOutputColor, _sample1992.c_str());
     }
 
diff --git a/src/gpu/effects/generated/GrSimpleTextureEffect.cpp b/src/gpu/effects/generated/GrSimpleTextureEffect.cpp
index 693c5f6..5f57e2e 100644
--- a/src/gpu/effects/generated/GrSimpleTextureEffect.cpp
+++ b/src/gpu/effects/generated/GrSimpleTextureEffect.cpp
@@ -25,11 +25,13 @@
         (void)_outer;
         auto matrix = _outer.matrix;
         (void)matrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "%s = %s * sample(%s, %s).%s;\n", args.fOutputColor, args.fInputColor,
                 fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]),
-                sk_TransformedCoords2D_0.c_str(),
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
                 fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str());
     }
 
diff --git a/src/gpu/gl/GrGLUniformHandler.h b/src/gpu/gl/GrGLUniformHandler.h
index 829e016..817be54 100644
--- a/src/gpu/gl/GrGLUniformHandler.h
+++ b/src/gpu/gl/GrGLUniformHandler.h
@@ -38,6 +38,10 @@
                                           int arrayCount,
                                           const char** outName) override;
 
+    void updateUniformVisibility(UniformHandle u, uint32_t visibility) override {
+        fUniforms[u.toIndex()].fVisibility |= visibility;
+    }
+
     SamplerHandle addSampler(const GrTexture*, const GrSamplerState&, const GrSwizzle&,
                              const char* name, const GrShaderCaps*) override;
 
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index dec2c96..e5e97de 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "src/gpu/GrCoordTransform.h"
 #include "src/gpu/GrFragmentProcessor.h"
 #include "src/gpu/GrProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
@@ -16,15 +17,36 @@
     this->onSetData(pdman, processor);
 }
 
-void GrGLSLFragmentProcessor::invokeChild(int childIndex, const char* inputColor, EmitArgs& args) {
+void GrGLSLFragmentProcessor::invokeChild(int childIndex, const char* inputColor, EmitArgs& args,
+                                          SkSL::String skslCoords) {
     while (childIndex >= (int) fFunctionNames.size()) {
         fFunctionNames.emplace_back();
     }
-    this->internalInvokeChild(childIndex, inputColor, args.fOutputColor, args);
+    this->internalInvokeChild(childIndex, inputColor, args.fOutputColor, args, skslCoords);
+}
+
+void GrGLSLFragmentProcessor::writeChildCall(GrGLSLFPFragmentBuilder* fragBuilder, int childIndex,
+                                             TransformedCoordVars coordVars, const char* inputColor,
+                                             const char* outputColor, EmitArgs& args,
+                                             SkSL::String skslCoords) {
+    std::vector<SkString> coordParams;
+    for (int i = 0; i < coordVars.count(); ++i) {
+        coordParams.push_back(fragBuilder->ensureCoords2D(coordVars[i].fVaryingPoint));
+    }
+    // if the fragment processor is invoked with overridden coordinates, it must *always* be invoked
+    // with overridden coords
+    SkASSERT(args.fFp.computeLocalCoordsInVertexShader() == (skslCoords.length() == 0));
+    fragBuilder->codeAppendf("%s = %s(%s", outputColor, fFunctionNames[childIndex].c_str(),
+                             inputColor ? inputColor : "half4(1)");
+    if (skslCoords.length()) {
+        fragBuilder->codeAppendf(", %s", skslCoords.c_str());
+    }
+    fragBuilder->codeAppend(");\n");
 }
 
 void GrGLSLFragmentProcessor::invokeChild(int childIndex, const char* inputColor,
-                                          SkString* outputColor, EmitArgs& args) {
+                                          SkString* outputColor, EmitArgs& args,
+                                          SkSL::String skslCoords) {
     SkASSERT(outputColor);
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
     outputColor->append(fragBuilder->getMangleString());
@@ -32,17 +54,32 @@
     while (childIndex >= (int) fFunctionNames.size()) {
         fFunctionNames.emplace_back();
     }
+    if (!args.fFp.computeLocalCoordsInVertexShader() && skslCoords.length() == 0) {
+        skslCoords = "_coords";
+    }
     if (fFunctionNames[childIndex].size() == 0) {
-        this->internalInvokeChild(childIndex, inputColor, outputColor->c_str(), args);
+        this->internalInvokeChild(childIndex, inputColor, outputColor->c_str(), args, skslCoords);
     } else {
-        fragBuilder->codeAppendf("%s = %s(%s);", outputColor->c_str(),
-                                 fFunctionNames[childIndex].c_str(),
-                                 inputColor ? inputColor : "half4(1)");
+        const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex);
+
+        TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex);
+        TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex);
+        EmitArgs childArgs(fragBuilder,
+                           args.fUniformHandler,
+                           args.fShaderCaps,
+                           childProc,
+                           outputColor->c_str(),
+                           "_input",
+                           coordVars,
+                           textureSamplers);
+        this->writeChildCall(fragBuilder, childIndex, coordVars, inputColor, outputColor->c_str(),
+                             childArgs, skslCoords);
     }
 }
 
 void GrGLSLFragmentProcessor::internalInvokeChild(int childIndex, const char* inputColor,
-                                                  const char* outputColor, EmitArgs& args) {
+                                                  const char* outputColor, EmitArgs& args,
+                                                  SkSL::String skslCoords) {
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
     fragBuilder->onBeforeChildProcEmitCode();  // call first so mangleString is updated
@@ -59,7 +96,6 @@
     }
 
     const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex);
-
     TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex);
     TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex);
 
@@ -74,11 +110,8 @@
     fFunctionNames[childIndex] = fragBuilder->writeProcessorFunction(
                                                                this->childProcessor(childIndex),
                                                                childArgs);
-    fragBuilder->codeAppendf("%s = %s(%s);\n", outputColor,
-                                               fFunctionNames[childIndex].c_str(),
-                                               inputName.size() > 0 ? inputName.c_str()
-                                                                    : "half4(1)");
-
+    this->writeChildCall(fragBuilder, childIndex, coordVars, inputColor, outputColor, childArgs,
+                         skslCoords);
     fragBuilder->onAfterChildProcEmitCode();
 }
 
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.h b/src/gpu/glsl/GrGLSLFragmentProcessor.h
index c9971a2..ad441d8 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.h
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.h
@@ -10,8 +10,10 @@
 
 #include "src/gpu/GrFragmentProcessor.h"
 #include "src/gpu/GrShaderVar.h"
+#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h"
 #include "src/gpu/glsl/GrGLSLProgramDataManager.h"
 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
+#include "src/sksl/SkSLString.h"
 
 class GrProcessor;
 class GrProcessorKeyBuilder;
@@ -69,8 +71,8 @@
     };
 
 public:
-    using TransformedCoordVars =
-            BuilderInputProvider<GrShaderVar, &GrFragmentProcessor::numCoordTransforms>;
+    using TransformedCoordVars = BuilderInputProvider<GrGLSLPrimitiveProcessor::TransformVar,
+                                                      &GrFragmentProcessor::numCoordTransforms>;
     using TextureSamplers =
             BuilderInputProvider<SamplerHandle, &GrFragmentProcessor::numTextureSamplers>;
 
@@ -137,8 +139,9 @@
     }
 
     // Invoke the child with the default input color (solid white)
-    inline void invokeChild(int childIndex, SkString* outputColor, EmitArgs& parentArgs) {
-        this->invokeChild(childIndex, nullptr, outputColor, parentArgs);
+    inline void invokeChild(int childIndex, SkString* outputColor, EmitArgs& parentArgs,
+                            SkSL::String skslCoords = "") {
+        this->invokeChild(childIndex, nullptr, outputColor, parentArgs, skslCoords);
     }
 
     /** Invokes a child proc in its own scope. Pass in the parent's EmitArgs and invokeChild will
@@ -150,17 +153,18 @@
      *  color.
      */
     void invokeChild(int childIndex, const char* inputColor, SkString* outputColor,
-                     EmitArgs& parentArgs);
+                     EmitArgs& parentArgs, SkSL::String skslCoords = "");
 
     // Use the parent's output color to hold child's output, and use the
     // default input color of solid white
-    inline void invokeChild(int childIndex, EmitArgs& args) {
+    inline void invokeChild(int childIndex, EmitArgs& args, SkSL::String skslCoords = "") {
         // null pointer cast required to disambiguate the function call
-        this->invokeChild(childIndex, (const char*) nullptr, args);
+        this->invokeChild(childIndex, (const char*) nullptr, args, skslCoords);
     }
 
     /** Variation that uses the parent's output color variable to hold the child's output.*/
-    void invokeChild(int childIndex, const char* inputColor, EmitArgs& parentArgs);
+    void invokeChild(int childIndex, const char* inputColor, EmitArgs& parentArgs,
+                     SkSL::String skslCoords = "");
 
     /**
      * Pre-order traversal of a GLSLFP hierarchy, or of multiple trees with roots in an array of
@@ -189,7 +193,12 @@
     virtual void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) {}
 
 private:
-    void internalInvokeChild(int, const char*, const char*, EmitArgs&);
+    void writeChildCall(GrGLSLFPFragmentBuilder* fragBuilder, int childIndex,
+                        TransformedCoordVars coordVars, const char* inputColor,
+                        const char* outputColor, EmitArgs& args,
+                        SkSL::String skslCoords);
+
+    void internalInvokeChild(int, const char*, const char*, EmitArgs&, SkSL::String);
 
     // one per child; either not present or empty string if not yet emitted
     SkTArray<SkString> fFunctionNames;
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index 4590d57..877a742 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -160,15 +160,27 @@
                                                          GrGLSLFragmentProcessor::EmitArgs& args) {
     this->onBeforeChildProcEmitCode();
     this->nextStage();
+    if (!args.fFp.computeLocalCoordsInVertexShader() && args.fTransformedCoords.count() > 0) {
+        // we currently only support overriding a single coordinate pair
+        SkASSERT(args.fTransformedCoords.count() == 1);
+        const GrGLSLProgramDataManager::UniformHandle& mat =
+                                                          args.fTransformedCoords[0].fUniformMatrix;
+        if (mat.isValid()) {
+            args.fUniformHandler->updateUniformVisibility(mat, kFragment_GrShaderFlag);
+            this->codeAppendf("_coords = (float3(_coords, 1) * %s).xy;\n",
+                              args.fTransformedCoords[0].fMatrixCode.c_str());
+        }
+    }
     this->codeAppendf("half4 %s;\n", args.fOutputColor);
     fp->emitCode(args);
-    this->codeAppendf("return %s;", args.fOutputColor);
-    GrShaderVar inColor(args.fInputColor, kHalf4_GrSLType);
+    this->codeAppendf("return %s;\n", args.fOutputColor);
+    GrShaderVar params[] = { GrShaderVar(args.fInputColor, kHalf4_GrSLType),
+                             GrShaderVar("_coords", kFloat2_GrSLType) };
     SkString result;
     this->emitFunction(kHalf4_GrSLType,
                        "stage",
-                       1,
-                       &inColor,
+                       args.fFp.computeLocalCoordsInVertexShader() ? 1 : 2,
+                       params,
                        this->code().c_str(),
                        &result);
     this->deleteStage();
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
index af9bd5c..d99239e 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
@@ -84,15 +84,18 @@
         SkString strVaryingName;
         strVaryingName.printf("TransformedCoords_%d", i);
         GrGLSLVarying v(varyingType);
-        varyingHandler->addVarying(strVaryingName.c_str(), &v);
+        if (coordTransform->computeInVertexShader()) {
+            varyingHandler->addVarying(strVaryingName.c_str(), &v);
 
-        handler->specifyCoordsForCurrCoordTransform(SkString(v.fsIn()), varyingType);
-
-        if (kFloat2_GrSLType == varyingType) {
-            vb->codeAppendf("%s = (%s * %s).xy;", v.vsOut(), uniName, localCoords.c_str());
-        } else {
-            vb->codeAppendf("%s = %s * %s;", v.vsOut(), uniName, localCoords.c_str());
+            if (kFloat2_GrSLType == varyingType) {
+                vb->codeAppendf("%s = (%s * %s).xy;", v.vsOut(), uniName, localCoords.c_str());
+            } else {
+                vb->codeAppendf("%s = %s * %s;", v.vsOut(), uniName, localCoords.c_str());
+            }
         }
+        handler->specifyCoordsForCurrCoordTransform(SkString(uniName),
+                                                    fInstalledTransforms.back().fHandle,
+                                                    GrShaderVar(SkString(v.fsIn()), varyingType));
         ++i;
     }
 }
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
index 5c02647..12fb74f 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
@@ -23,12 +23,29 @@
 
 class GrGLSLPrimitiveProcessor {
 public:
+    using UniformHandle        = GrGLSLProgramDataManager::UniformHandle;
+    using SamplerHandle        = GrGLSLUniformHandler::SamplerHandle;
     using FPCoordTransformIter = GrFragmentProcessor::CoordTransformIter;
 
-    virtual ~GrGLSLPrimitiveProcessor() {}
+    struct TransformVar {
+        TransformVar() = default;
 
-    using UniformHandle      = GrGLSLProgramDataManager::UniformHandle;
-    using SamplerHandle      = GrGLSLUniformHandler::SamplerHandle;
+        TransformVar(SkString matrixCode, UniformHandle uniformMatrix, GrShaderVar varyingPoint)
+            : fMatrixCode(std::move(matrixCode))
+            , fUniformMatrix(uniformMatrix)
+            , fVaryingPoint(varyingPoint) {}
+
+        // a string of SkSL code which resolves to the transformation matrix
+        SkString fMatrixCode;
+        // the variable containing the matrix, if any, otherwise an invalid handle
+        UniformHandle fUniformMatrix;
+        // the transformed coordinate output by the vertex shader and consumed by the fragment
+        // shader
+        GrShaderVar fVaryingPoint;
+    };
+
+
+    virtual ~GrGLSLPrimitiveProcessor() {}
 
     /**
      * This class provides access to the GrCoordTransforms across all GrFragmentProcessors in a
@@ -40,7 +57,7 @@
     class FPCoordTransformHandler : public SkNoncopyable {
     public:
         FPCoordTransformHandler(const GrPipeline& pipeline,
-                                SkTArray<GrShaderVar>* transformedCoordVars)
+                                SkTArray<TransformVar>* transformedCoordVars)
                 : fIter(pipeline)
                 , fTransformedCoordVars(transformedCoordVars) {}
 
@@ -60,7 +77,7 @@
         GrFragmentProcessor::CoordTransformIter fIter;
         SkDEBUGCODE(bool                        fAddedCoord = false;)
         SkDEBUGCODE(const GrCoordTransform*     fCurr = nullptr;)
-        SkTArray<GrShaderVar>*                  fTransformedCoordVars;
+        SkTArray<TransformVar>*                 fTransformedCoordVars;
     };
 
     struct EmitArgs {
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index fb90fc7..c71071b 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -196,7 +196,8 @@
         }
     }
 
-    const GrShaderVar* coordVars = fTransformedCoordVars.begin() + transformedCoordVarsIdx;
+    const GrGLSLPrimitiveProcessor::TransformVar* coordVars = fTransformedCoordVars.begin() +
+                                                              transformedCoordVarsIdx;
     GrGLSLFragmentProcessor::TransformedCoordVars coords(&fp, coordVars);
     GrGLSLFragmentProcessor::TextureSamplers textureSamplers(&fp, texSamplers.begin());
     GrGLSLFragmentProcessor::EmitArgs args(&fFS,
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index 44db251..9f5e3a3 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -169,8 +169,8 @@
 #endif
 
     // These are used to check that we don't excede the allowable number of resources in a shader.
-    int                         fNumFragmentSamplers;
-    SkSTArray<4, GrShaderVar>   fTransformedCoordVars;
+    int fNumFragmentSamplers;
+    SkSTArray<4, GrGLSLPrimitiveProcessor::TransformVar> fTransformedCoordVars;
 };
 
 #endif
diff --git a/src/gpu/glsl/GrGLSLUniformHandler.h b/src/gpu/glsl/GrGLSLUniformHandler.h
index 3380e70..c3232cd 100644
--- a/src/gpu/glsl/GrGLSLUniformHandler.h
+++ b/src/gpu/glsl/GrGLSLUniformHandler.h
@@ -64,6 +64,11 @@
     virtual const GrShaderVar& getUniformVariable(UniformHandle u) const = 0;
 
     /**
+     * 'Or's the visibility parameter with the current uniform visibililty.
+     */
+    virtual void updateUniformVisibility(UniformHandle u, uint32_t visibility) = 0;
+
+    /**
      * Shortcut for getUniformVariable(u).c_str()
      */
     virtual const char* getUniformCStr(UniformHandle u) const = 0;
diff --git a/src/gpu/gradients/generated/GrClampedGradientEffect.cpp b/src/gpu/gradients/generated/GrClampedGradientEffect.cpp
index 658f6b8..268fc6f 100644
--- a/src/gpu/gradients/generated/GrClampedGradientEffect.cpp
+++ b/src/gpu/gradients/generated/GrClampedGradientEffect.cpp
@@ -46,9 +46,9 @@
                 args.fOutputColor, args.fOutputColor,
                 args.fUniformHandler->getUniformCStr(leftBorderColorVar), args.fOutputColor,
                 args.fUniformHandler->getUniformCStr(rightBorderColorVar));
-        SkString _input0("t");
+        SkString _input1767("t");
         SkString _sample1767("_sample1767");
-        this->invokeChild(_outer.colorizer_index, _input0.c_str(), &_sample1767, args);
+        this->invokeChild(_outer.colorizer_index, _input1767.c_str(), &_sample1767, args);
         fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
                                  args.fOutputColor, _sample1767.c_str(),
                                  (_outer.makePremul ? "true" : "false"), args.fOutputColor,
diff --git a/src/gpu/gradients/generated/GrLinearGradientLayout.cpp b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
index 28e7da8..a20b0e5 100644
--- a/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
@@ -25,10 +25,13 @@
         (void)_outer;
         auto gradientMatrix = _outer.gradientMatrix;
         (void)gradientMatrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "half t = half(%s.x) + 9.9999997473787516e-06;\n%s = half4(t, 1.0, 0.0, 0.0);\n",
-                sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                args.fOutputColor);
     }
 
 private:
diff --git a/src/gpu/gradients/generated/GrRadialGradientLayout.cpp b/src/gpu/gradients/generated/GrRadialGradientLayout.cpp
index 213be21..f163943 100644
--- a/src/gpu/gradients/generated/GrRadialGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrRadialGradientLayout.cpp
@@ -25,9 +25,13 @@
         (void)_outer;
         auto gradientMatrix = _outer.gradientMatrix;
         (void)gradientMatrix;
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf("half t = half(length(%s));\n%s = half4(t, 1.0, 0.0, 0.0);\n",
-                                 sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
+                                 _outer.computeLocalCoordsInVertexShader()
+                                         ? sk_TransformedCoords2D_0.c_str()
+                                         : "_coords",
+                                 args.fOutputColor);
     }
 
 private:
diff --git a/src/gpu/gradients/generated/GrSweepGradientLayout.cpp b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
index 18b4739..6abcbb1 100644
--- a/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
@@ -32,15 +32,24 @@
         biasVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "bias");
         scaleVar =
                 args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf_GrSLType, "scale");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "half angle;\nif (sk_Caps.atan2ImplementedAsAtanYOverX) {\n    angle = half(2.0 * "
                 "atan(-%s.y, length(%s) - %s.x));\n} else {\n    angle = half(atan(-%s.y, "
                 "-%s.x));\n}\nhalf t = ((angle * 0.15915493667125702 + 0.5) + %s) * %s;\n%s = "
                 "half4(t, 1.0, 0.0, 0.0);\n",
-                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
-                sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
-                sk_TransformedCoords2D_0.c_str(), args.fUniformHandler->getUniformCStr(biasVar),
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                args.fUniformHandler->getUniformCStr(biasVar),
                 args.fUniformHandler->getUniformCStr(scaleVar), args.fOutputColor);
     }
 
diff --git a/src/gpu/gradients/generated/GrTiledGradientEffect.cpp b/src/gpu/gradients/generated/GrTiledGradientEffect.cpp
index 4c85ed6..9d0637f 100644
--- a/src/gpu/gradients/generated/GrTiledGradientEffect.cpp
+++ b/src/gpu/gradients/generated/GrTiledGradientEffect.cpp
@@ -41,9 +41,9 @@
                 (_outer.childProcessor(_outer.gradLayout_index).preservesOpaqueInput() ? "true"
                                                                                        : "false"),
                 args.fOutputColor, (_outer.mirror ? "true" : "false"));
-        SkString _input0("t");
+        SkString _input1464("t");
         SkString _sample1464("_sample1464");
-        this->invokeChild(_outer.colorizer_index, _input0.c_str(), &_sample1464, args);
+        this->invokeChild(_outer.colorizer_index, _input1464.c_str(), &_sample1464, args);
         fragBuilder->codeAppendf("\n    %s = %s;\n}\n@if (%s) {\n    %s.xyz *= %s.w;\n}\n",
                                  args.fOutputColor, _sample1464.c_str(),
                                  (_outer.makePremul ? "true" : "false"), args.fOutputColor,
diff --git a/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp
index 5421d2c..804a42c 100644
--- a/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp
@@ -42,7 +42,8 @@
         (void)focalParams;
         focalParamsVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
                                                           "focalParams");
-        SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+        SkString sk_TransformedCoords2D_0 =
+                fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
         fragBuilder->codeAppendf(
                 "float2 p = %s;\nfloat t = -1.0;\nhalf v = 1.0;\n@switch (%d) {\n    case 1:\n     "
                 "   {\n            half r0_2 = %s.y;\n            t = float(r0_2) - p.y * p.y;\n   "
@@ -51,8 +52,9 @@
                 "0:\n        {\n            half r0 = %s.x;\n            @if (%s) {\n              "
                 "  t = length(p) - float(r0);\n            } else {\n                t = "
                 "-length(p) - float(r0);\n       ",
-                sk_TransformedCoords2D_0.c_str(), (int)_outer.type,
-                args.fUniformHandler->getUniformCStr(focalParamsVar),
+                _outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str()
+                                                          : "_coords",
+                (int)_outer.type, args.fUniformHandler->getUniformCStr(focalParamsVar),
                 args.fUniformHandler->getUniformCStr(focalParamsVar),
                 (_outer.isRadiusIncreasing ? "true" : "false"));
         fragBuilder->codeAppendf(
diff --git a/src/gpu/mtl/GrMtlUniformHandler.h b/src/gpu/mtl/GrMtlUniformHandler.h
index 407540b..9c789ff 100644
--- a/src/gpu/mtl/GrMtlUniformHandler.h
+++ b/src/gpu/mtl/GrMtlUniformHandler.h
@@ -57,6 +57,10 @@
                                           int arrayCount,
                                           const char** outName) override;
 
+    void updateUniformVisibility(UniformHandle u, uint32_t visibility) override {
+        fUniforms[u.toIndex()].fVisibility |= visibility;
+    }
+
     SamplerHandle addSampler(const GrTexture*,
                              const GrSamplerState&,
                              const GrSwizzle&,
diff --git a/src/gpu/vk/GrVkUniformHandler.h b/src/gpu/vk/GrVkUniformHandler.h
index 6e594c8..de6a3e4 100644
--- a/src/gpu/vk/GrVkUniformHandler.h
+++ b/src/gpu/vk/GrVkUniformHandler.h
@@ -73,6 +73,10 @@
                                           int arrayCount,
                                           const char** outName) override;
 
+    void updateUniformVisibility(UniformHandle u, uint32_t visibility) override {
+        fUniforms[u.toIndex()].fVisibility |= visibility;
+    }
+
     SamplerHandle addSampler(const GrTexture* texture,
                              const GrSamplerState&,
                              const GrSwizzle&,
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index f72a631..812dc16 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -837,7 +837,7 @@
 
     GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-    SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
     fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
                                                    "baseFrequency");
@@ -1254,7 +1254,7 @@
     const GrImprovedPerlinNoiseEffect& pne = args.fFp.cast<GrImprovedPerlinNoiseEffect>();
     GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-    SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
+    SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
     fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
                                                    "baseFrequency");
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index df08f07..6e4320e 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -25,7 +25,7 @@
 : INHERITED(context, program, errors, out)
 , fName(std::move(name))
 , fFullName(String::printf("Gr%s", fName.c_str()))
-, fSectionAndParameterHelper(*program, *errors) {
+, fSectionAndParameterHelper(program, *errors) {
     fLineEnding = "\\n";
     fTextureFunctionOverride = "sample";
 }
@@ -122,11 +122,12 @@
             }
             int64_t index = ((IntLiteral&) *i.fIndex).fValue;
             String name = "sk_TransformedCoords2D_" + to_string(index);
-            fFormatArgs.push_back(name + ".c_str()");
+            fFormatArgs.push_back("_outer.computeLocalCoordsInVertexShader() ? " + name +
+                                  ".c_str() : \"_coords\"");
             if (fWrittenTransformedCoords.find(index) == fWrittenTransformedCoords.end()) {
                 addExtraEmitCodeLine("SkString " + name +
                                      " = fragBuilder->ensureCoords2D(args.fTransformedCoords[" +
-                                     to_string(index) + "]);");
+                                     to_string(index) + "].fVaryingPoint);");
                 fWrittenTransformedCoords.insert(index);
             }
             return;
@@ -408,7 +409,7 @@
     if (c.fFunction.fBuiltin && c.fFunction.fName == "sample" &&
         c.fArguments[0]->fType.kind() != Type::Kind::kSampler_Kind) {
         // Sanity checks that are detected by function definition in sksl_fp.inc
-        SkASSERT(c.fArguments.size() == 1 || c.fArguments.size() == 2);
+        SkASSERT(c.fArguments.size() >= 1 && c.fArguments.size() <= 3);
         SkASSERT("fragmentProcessor"  == c.fArguments[0]->fType.name() ||
                  "fragmentProcessor?" == c.fArguments[0]->fType.name());
 
@@ -420,12 +421,7 @@
                     "sample()'s fragmentProcessor argument must be a variable reference\n");
             return;
         }
-        if (c.fArguments.size() > 1) {
-            // Second argument must also be a half4 expression
-            SkASSERT("half4" == c.fArguments[1]->fType.name());
-        }
         const Variable& child = ((const VariableReference&) *c.fArguments[0]).fVariable;
-        int index = getChildFPIndex(child);
 
         // Start a new extra emit code section so that the emitted child processor can depend on
         // sksl variables defined in earlier sksl code.
@@ -435,25 +431,38 @@
         // must be properly formatted with a prefixed comma when the parameter should be inserted
         // into the invokeChild() parameter list.
         String inputArg;
-        if (c.fArguments.size() > 1) {
-            SkASSERT(c.fArguments.size() == 2);
+        if (c.fArguments.size() > 1 && c.fArguments[1]->fType.name() == "half4") {
             // Use the invokeChild() variant that accepts an input color, so convert the 2nd
             // argument's expression into C++ code that produces sksl stored in an SkString.
-            String inputName = "_input" + to_string(index);
+            String inputName = "_input" + to_string(c.fOffset);
             addExtraEmitCodeLine(convertSKSLExpressionToCPP(*c.fArguments[1], inputName));
 
             // invokeChild() needs a char*
             inputArg = ", " + inputName + ".c_str()";
         }
 
+        bool hasCoords = c.fArguments.back()->fType.name() == "float2";
+
         // Write the output handling after the possible input handling
         String childName = "_sample" + to_string(c.fOffset);
         addExtraEmitCodeLine("SkString " + childName + "(\"" + childName + "\");");
+        String coordsName;
+        if (hasCoords) {
+            coordsName = "_coords" + to_string(c.fOffset);
+            addExtraEmitCodeLine(convertSKSLExpressionToCPP(*c.fArguments.back(), coordsName));
+        }
         if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) {
             addExtraEmitCodeLine("if (_outer." + String(child.fName) + "_index >= 0) {\n    ");
         }
-        addExtraEmitCodeLine("this->invokeChild(_outer." + String(child.fName) + "_index" +
-                             inputArg + ", &" + childName + ", args);");
+        if (hasCoords) {
+            addExtraEmitCodeLine("this->invokeChild(_outer." + String(child.fName) + "_index" +
+                                 inputArg + ", &" + childName + ", args, " + coordsName +
+                                 ".c_str());");
+        } else {
+            addExtraEmitCodeLine("this->invokeChild(_outer." + String(child.fName) + "_index" +
+                                 inputArg + ", &" + childName + ", args);");
+        }
+
         if (c.fArguments[0]->fType.kind() == Type::kNullable_Kind) {
             // Null FPs are not emitted, but their output can still be referenced in dependent
             // expressions - thus we always declare the variable.
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index f43aa17..31cc890 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -25,7 +25,7 @@
 , fContext(*context)
 , fName(std::move(name))
 , fFullName(String::printf("Gr%s", fName.c_str()))
-, fSectionAndParameterHelper(*program, *errors) {}
+, fSectionAndParameterHelper(program, *errors) {}
 
 String HCodeGenerator::ParameterType(const Context& context, const Type& type,
                                      const Layout& layout) {
@@ -281,6 +281,10 @@
             }
             this->writef("            %s_index = this->numChildProcessors();",
                          FieldName(String(param->fName).c_str()).c_str());
+            if (fSectionAndParameterHelper.hasCoordOverrides(*param)) {
+                this->writef("            %s->setComputeLocalCoordsInVertexShader(false);",
+                             String(param->fName).c_str());
+            }
             this->writef("            this->registerChildProcessor(std::move(%s));",
                          String(param->fName).c_str());
             if (param->fType.kind() == Type::kNullable_Kind) {
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index 428b130..37434d4 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -21,7 +21,7 @@
 : INHERITED(context, program, errors, out)
 , fName("Temp")
 , fFullName(String::printf("Gr%s", fName.c_str()))
-, fSectionAndParameterHelper(*program, *errors)
+, fSectionAndParameterHelper(program, *errors)
 , fFormatArgs(outFormatArgs) {}
 
 void PipelineStageCodeGenerator::writef(const char* s, va_list va) {
diff --git a/src/sksl/SkSLSectionAndParameterHelper.cpp b/src/sksl/SkSLSectionAndParameterHelper.cpp
new file mode 100644
index 0000000..08299bb
--- /dev/null
+++ b/src/sksl/SkSLSectionAndParameterHelper.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLSectionAndParameterHelper.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpressionStatement.h"
+#include "src/sksl/ir/SkSLFieldAccess.h"
+#include "src/sksl/ir/SkSLForStatement.h"
+#include "src/sksl/ir/SkSLFunctionCall.h"
+#include "src/sksl/ir/SkSLIfStatement.h"
+#include "src/sksl/ir/SkSLIndexExpression.h"
+#include "src/sksl/ir/SkSLPostfixExpression.h"
+#include "src/sksl/ir/SkSLPrefixExpression.h"
+#include "src/sksl/ir/SkSLReturnStatement.h"
+#include "src/sksl/ir/SkSLSwitchStatement.h"
+#include "src/sksl/ir/SkSLSwizzle.h"
+#include "src/sksl/ir/SkSLTernaryExpression.h"
+#include "src/sksl/ir/SkSLVarDeclarationsStatement.h"
+#include "src/sksl/ir/SkSLWhileStatement.h"
+
+namespace SkSL {
+
+SectionAndParameterHelper::SectionAndParameterHelper(const Program* program, ErrorReporter& errors)
+    : fProgram(*program) {
+    for (const auto& p : fProgram) {
+        switch (p.fKind) {
+            case ProgramElement::kVar_Kind: {
+                const VarDeclarations& decls = (const VarDeclarations&) p;
+                for (const auto& raw : decls.fVars) {
+                    const VarDeclaration& decl = (VarDeclaration&) *raw;
+                    if (IsParameter(*decl.fVar)) {
+                        fParameters.push_back(decl.fVar);
+                    }
+                }
+                break;
+            }
+            case ProgramElement::kSection_Kind: {
+                const Section& s = (const Section&) p;
+                if (IsSupportedSection(s.fName.c_str())) {
+                    if (SectionRequiresArgument(s.fName.c_str()) && !s.fArgument.size()) {
+                        errors.error(s.fOffset,
+                                     ("section '@" + s.fName +
+                                      "' requires one parameter").c_str());
+                    }
+                    if (!SectionAcceptsArgument(s.fName.c_str()) && s.fArgument.size()) {
+                        errors.error(s.fOffset,
+                                     ("section '@" + s.fName + "' has no parameters").c_str());
+                    }
+                } else {
+                    errors.error(s.fOffset,
+                                 ("unsupported section '@" + s.fName + "'").c_str());
+                }
+                if (!SectionPermitsDuplicates(s.fName.c_str()) &&
+                        fSections.find(s.fName) != fSections.end()) {
+                    errors.error(s.fOffset,
+                                 ("duplicate section '@" + s.fName + "'").c_str());
+                }
+                fSections[s.fName].push_back(&s);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+}
+
+bool SectionAndParameterHelper::hasCoordOverrides(const Variable& fp) {
+    for (const auto& pe : fProgram) {
+        if (this->hasCoordOverrides(pe, fp)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool SectionAndParameterHelper::hasCoordOverrides(const ProgramElement& pe, const Variable& fp) {
+    if (pe.fKind == ProgramElement::kFunction_Kind) {
+        return this->hasCoordOverrides(*((const FunctionDefinition&) pe).fBody, fp);
+    }
+    return false;
+}
+
+bool SectionAndParameterHelper::hasCoordOverrides(const Expression& e, const Variable& fp) {
+    switch (e.fKind) {
+        case Expression::kFunctionCall_Kind: {
+            const FunctionCall& fc = (const FunctionCall&) e;
+            const FunctionDeclaration& f = fc.fFunction;
+            if (f.fBuiltin && f.fName == "sample" && fc.fArguments.size() >= 2 &&
+                fc.fArguments.back()->fType == *fProgram.fContext->fFloat2_Type &&
+                fc.fArguments[0]->fKind == Expression::kVariableReference_Kind &&
+                &((VariableReference&) *fc.fArguments[0]).fVariable == &fp) {
+                return true;
+            }
+            for (const auto& e : fc.fArguments) {
+                if (this->hasCoordOverrides(*e, fp)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        case Expression::kConstructor_Kind: {
+            const Constructor& c = (const Constructor&) e;
+            for (const auto& e : c.fArguments) {
+                if (this->hasCoordOverrides(*e, fp)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        case Expression::kFieldAccess_Kind: {
+            return this->hasCoordOverrides(*((const FieldAccess&) e).fBase, fp);
+        }
+        case Expression::kSwizzle_Kind:
+            return this->hasCoordOverrides(*((const Swizzle&) e).fBase, fp);
+        case Expression::kBinary_Kind: {
+            const BinaryExpression& b = (const BinaryExpression&) e;
+            return this->hasCoordOverrides(*b.fLeft, fp) ||
+                   this->hasCoordOverrides(*b.fRight, fp);
+        }
+        case Expression::kIndex_Kind: {
+            const IndexExpression& idx = (const IndexExpression&) e;
+            return this->hasCoordOverrides(*idx.fBase, fp) ||
+                   this->hasCoordOverrides(*idx.fIndex, fp);
+        }
+        case Expression::kPrefix_Kind:
+            return this->hasCoordOverrides(*((const PrefixExpression&) e).fOperand, fp);
+        case Expression::kPostfix_Kind:
+            return this->hasCoordOverrides(*((const PostfixExpression&) e).fOperand, fp);
+        case Expression::kTernary_Kind: {
+            const TernaryExpression& t = (const TernaryExpression&) e;
+            return this->hasCoordOverrides(*t.fTest, fp) ||
+                   this->hasCoordOverrides(*t.fIfTrue, fp) ||
+                   this->hasCoordOverrides(*t.fIfFalse, fp);
+        }
+        case Expression::kVariableReference_Kind:
+            return false;
+        case Expression::kAppendStage_Kind:
+        case Expression::kBoolLiteral_Kind:
+        case Expression::kDefined_Kind:
+        case Expression::kExternalFunctionCall_Kind:
+        case Expression::kExternalValue_Kind:
+        case Expression::kFloatLiteral_Kind:
+        case Expression::kFunctionReference_Kind:
+        case Expression::kIntLiteral_Kind:
+        case Expression::kNullLiteral_Kind:
+        case Expression::kSetting_Kind:
+        case Expression::kTypeReference_Kind:
+            return false;
+    }
+    SkASSERT(false);
+    return false;
+}
+
+bool SectionAndParameterHelper::hasCoordOverrides(const Statement& s, const Variable& fp) {
+    switch (s.fKind) {
+        case Statement::kBlock_Kind: {
+            for (const auto& child : ((const Block&) s).fStatements) {
+                if (this->hasCoordOverrides(*child, fp)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        case Statement::kVarDeclaration_Kind: {
+            const VarDeclaration& var = (const VarDeclaration&) s;
+            if (var.fValue) {
+                return hasCoordOverrides(*var.fValue, fp);
+            }
+            return false;
+        }
+        case Statement::kVarDeclarations_Kind: {
+            const VarDeclarations& decls = *((const VarDeclarationsStatement&) s).fDeclaration;
+            for (const auto& stmt : decls.fVars) {
+                if (this->hasCoordOverrides(*stmt, fp)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        case Statement::kExpression_Kind:
+            return this->hasCoordOverrides(*((const ExpressionStatement&) s).fExpression, fp);
+        case Statement::kReturn_Kind: {
+            const ReturnStatement& r = (const ReturnStatement&) s;
+            if (r.fExpression) {
+                return this->hasCoordOverrides(*r.fExpression, fp);
+            }
+            return false;
+        }
+        case Statement::kIf_Kind: {
+            const IfStatement& i = (const IfStatement&) s;
+            return this->hasCoordOverrides(*i.fTest, fp) ||
+                   this->hasCoordOverrides(*i.fIfTrue, fp) ||
+                   (i.fIfFalse && this->hasCoordOverrides(*i.fIfFalse, fp));
+        }
+        case Statement::kFor_Kind: {
+            const ForStatement& f = (const ForStatement&) s;
+            return this->hasCoordOverrides(*f.fInitializer, fp) ||
+                   this->hasCoordOverrides(*f.fTest, fp) ||
+                   this->hasCoordOverrides(*f.fNext, fp) ||
+                   this->hasCoordOverrides(*f.fStatement, fp);
+        }
+        case Statement::kWhile_Kind: {
+            const WhileStatement& w = (const WhileStatement&) s;
+            return this->hasCoordOverrides(*w.fTest, fp) ||
+                   this->hasCoordOverrides(*w.fStatement, fp);
+        }
+        case Statement::kDo_Kind: {
+            const DoStatement& d = (const DoStatement&) s;
+            return this->hasCoordOverrides(*d.fTest, fp) ||
+                   this->hasCoordOverrides(*d.fStatement, fp);
+        }
+        case Statement::kSwitch_Kind: {
+            const SwitchStatement& sw = (const SwitchStatement&) s;
+            for (const auto& c : sw.fCases) {
+                for (const auto& st : c->fStatements) {
+                    if (this->hasCoordOverrides(*st, fp)) {
+                        return true;
+                    }
+                }
+            }
+            return this->hasCoordOverrides(*sw.fValue, fp);
+        }
+        case Statement::kBreak_Kind:
+        case Statement::kContinue_Kind:
+        case Statement::kDiscard_Kind:
+        case Statement::kGroup_Kind:
+        case Statement::kNop_Kind:
+            return false;
+    }
+    SkASSERT(false);
+    return false;
+}
+
+}
diff --git a/src/sksl/SkSLSectionAndParameterHelper.h b/src/sksl/SkSLSectionAndParameterHelper.h
index b09ee3c..3251024 100644
--- a/src/sksl/SkSLSectionAndParameterHelper.h
+++ b/src/sksl/SkSLSectionAndParameterHelper.h
@@ -38,48 +38,7 @@
 
 class SectionAndParameterHelper {
 public:
-    SectionAndParameterHelper(const Program& program, ErrorReporter& errors) {
-        for (const auto& p : program) {
-            switch (p.fKind) {
-                case ProgramElement::kVar_Kind: {
-                    const VarDeclarations& decls = (const VarDeclarations&) p;
-                    for (const auto& raw : decls.fVars) {
-                        const VarDeclaration& decl = (VarDeclaration&) *raw;
-                        if (IsParameter(*decl.fVar)) {
-                            fParameters.push_back(decl.fVar);
-                        }
-                    }
-                    break;
-                }
-                case ProgramElement::kSection_Kind: {
-                    const Section& s = (const Section&) p;
-                    if (IsSupportedSection(s.fName.c_str())) {
-                        if (SectionRequiresArgument(s.fName.c_str()) && !s.fArgument.size()) {
-                            errors.error(s.fOffset,
-                                         ("section '@" + s.fName +
-                                          "' requires one parameter").c_str());
-                        }
-                        if (!SectionAcceptsArgument(s.fName.c_str()) && s.fArgument.size()) {
-                            errors.error(s.fOffset,
-                                         ("section '@" + s.fName + "' has no parameters").c_str());
-                        }
-                    } else {
-                        errors.error(s.fOffset,
-                                     ("unsupported section '@" + s.fName + "'").c_str());
-                    }
-                    if (!SectionPermitsDuplicates(s.fName.c_str()) &&
-                            fSections.find(s.fName) != fSections.end()) {
-                        errors.error(s.fOffset,
-                                     ("duplicate section '@" + s.fName + "'").c_str());
-                    }
-                    fSections[s.fName].push_back(&s);
-                    break;
-                }
-                default:
-                    break;
-            }
-        }
-    }
+    SectionAndParameterHelper(const Program* program, ErrorReporter& errors);
 
     const Section* getSection(const char* name) {
         SkASSERT(!SectionPermitsDuplicates(name));
@@ -103,6 +62,8 @@
         return fParameters;
     }
 
+    bool hasCoordOverrides(const Variable& fp);
+
     static bool IsParameter(const Variable& var) {
         return (var.fModifiers.fFlags & Modifiers::kIn_Flag) &&
                -1 == var.fModifiers.fLayout.fBuiltin;
@@ -148,6 +109,13 @@
     }
 
 private:
+    bool hasCoordOverrides(const Statement& s, const Variable& fp);
+
+    bool hasCoordOverrides(const Expression& e, const Variable& fp);
+
+    bool hasCoordOverrides(const ProgramElement& p, const Variable& fp);
+
+    const Program& fProgram;
     std::vector<const Variable*> fParameters;
     std::unordered_map<String, std::vector<const Section*>> fSections;
 };
diff --git a/src/sksl/sksl_fp.inc b/src/sksl/sksl_fp.inc
index 048a004..0608230 100644
--- a/src/sksl/sksl_fp.inc
+++ b/src/sksl/sksl_fp.inc
@@ -24,7 +24,11 @@
 layout(builtin=10012) half sk_Height;
 
 half4 sample(fragmentProcessor fp);
+half4 sample(fragmentProcessor fp, float2 coords);
 half4 sample(fragmentProcessor fp, half4 input);
+half4 sample(fragmentProcessor fp, half4 input, float2 coords);
 half4 sample(fragmentProcessor? fp);
+half4 sample(fragmentProcessor? fp, float2 coords);
 half4 sample(fragmentProcessor? fp, half4 input);
+half4 sample(fragmentProcessor? fp, half4 input, float2 coords);
 )
diff --git a/tests/SkSLFPTest.cpp b/tests/SkSLFPTest.cpp
index f15d71f..aba444b 100644
--- a/tests/SkSLFPTest.cpp
+++ b/tests/SkSLFPTest.cpp
@@ -452,9 +452,11 @@
          {},
          {
             "SkString sk_TransformedCoords2D_0 = "
-                                         "fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);",
+                           "fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);",
             "fragBuilder->codeAppendf(\"%s = half4(%s, %s);\\n\", args.fOutputColor, "
-                              "sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str());"
+                    "_outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() :"
+                    " \"_coords\", _outer.computeLocalCoordsInVertexShader() ? "
+                    "sk_TransformedCoords2D_0.c_str() : \"_coords\");"
          });
 
 }
@@ -513,13 +515,13 @@
             "this->registerChildProcessor(std::move(child2));"
          },
          {
-            "SkString _input0(\"childIn\");",
+            "SkString _input128(\"childIn\");",
             "SkString _sample128(\"_sample128\");",
-            "this->invokeChild(_outer.child1_index, _input0.c_str(), &_sample128, args);",
+            "this->invokeChild(_outer.child1_index, _input128.c_str(), &_sample128, args);",
             "fragBuilder->codeAppendf(\"\\nhalf4 childOut1 = %s;\", _sample128.c_str());",
-            "SkString _input1(\"childOut1\");",
+            "SkString _input174(\"childOut1\");",
             "SkString _sample174(\"_sample174\");",
-            "this->invokeChild(_outer.child2_index, _input1.c_str(), &_sample174, args);",
+            "this->invokeChild(_outer.child2_index, _input174.c_str(), &_sample174, args);",
             "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
             "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
          });
@@ -536,9 +538,9 @@
             "this->registerChildProcessor(std::move(child));",
          },
          {
-            "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
+            "SkString _input64 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
             "SkString _sample64(\"_sample64\");",
-            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample64, args);",
+            "this->invokeChild(_outer.child_index, _input64.c_str(), &_sample64, args);",
             "fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample64.c_str());",
             "this->registerChildProcessor(src.childProcessor(child_index).clone());",
          });
@@ -557,12 +559,12 @@
             "this->registerChildProcessor(std::move(child2));"
          },
          {
-            "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
+            "SkString _input121 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
             "SkString _sample121(\"_sample121\");",
-            "this->invokeChild(_outer.child1_index, _input0.c_str(), &_sample121, args);",
-            "SkString _input1 = SkStringPrintf(\"%s * %s\", args.fInputColor, _sample121.c_str());",
+            "this->invokeChild(_outer.child1_index, _input121.c_str(), &_sample121, args);",
+            "SkString _input93 = SkStringPrintf(\"%s * %s\", args.fInputColor, _sample121.c_str());",
             "SkString _sample93(\"_sample93\");",
-            "this->invokeChild(_outer.child2_index, _input1.c_str(), &_sample93, args);",
+            "this->invokeChild(_outer.child2_index, _input93.c_str(), &_sample93, args);",
             "fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample93.c_str());",
             "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
             "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
@@ -588,12 +590,11 @@
             "hasCap = sk_Caps.externalTextureSupport;",
             "fragBuilder->codeAppendf(\"bool hasCap = %s;\\nif (hasCap) {\", (hasCap ? \"true\" : "
                                      "\"false\"));",
-            "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
+            "SkString _input130 = SkStringPrintf(\"%s\", args.fInputColor);",
             "SkString _sample130(\"_sample130\");",
-            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample130, args);",
+            "this->invokeChild(_outer.child_index, _input130.c_str(), &_sample130, args);",
             "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}\\n\","
                                      " args.fOutputColor, _sample130.c_str(), args.fOutputColor);",
-
             "this->registerChildProcessor(src.childProcessor(child_index).clone());"
          });
 }
@@ -615,9 +616,9 @@
          {
             "fragBuilder->codeAppendf(\"if (%s) {\", "
                     "(_outer.childProcessor(_outer.child_index).preservesOpaqueInput() ? ",
-            "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
+            "SkString _input105 = SkStringPrintf(\"%s\", args.fInputColor);",
             "SkString _sample105(\"_sample105\");",
-            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample105, args);",
+            "this->invokeChild(_outer.child_index, _input105.c_str(), &_sample105, args);",
             "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}\\n\","
                                      " args.fOutputColor, _sample105.c_str(), args.fOutputColor);",
             "this->registerChildProcessor(src.childProcessor(child_index).clone());"
@@ -685,3 +686,27 @@
          "error: 1: 'in' variable must be either 'uniform' or 'layout(key)', or there must be a "
          "custom @setData function\n1 error\n");
 }
+
+DEF_TEST(SkSLFPSampleCoords, r) {
+    test(r,
+         "in fragmentProcessor child;"
+         "@coordTransform { SkMatrix() }"
+         "void main() {"
+         "    sk_OutColor = sample(child) + sample(child, sk_TransformedCoords2D[0] / 2);"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {},
+         {
+            "SkString _sample94(\"_sample94\");\n",
+            "this->invokeChild(_outer.child_index, &_sample94, args);\n",
+            "SkString _sample110(\"_sample110\");\n",
+            "SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D("
+                                                     "args.fTransformedCoords[0].fVaryingPoint);\n",
+            "SkString _coords110 = SkStringPrintf(\"%s / 2.0\", "
+                    "_outer.computeLocalCoordsInVertexShader() ? sk_TransformedCoords2D_0.c_str() :"
+                    " \"_coords\");\n",
+            "this->invokeChild(_outer.child_index, &_sample110, args, _coords110.c_str());\n",
+            "fragBuilder->codeAppendf(\"%s = %s + %s;\\n\", args.fOutputColor, _sample94.c_str(), "
+                                     "_sample110.c_str());\n"
+         });
+}