Change SampleMatrix to SampleUsage

It now tracks all sample calls of a child (matrix, explicit coords,
pass through). There is now just one registerChild() call, and the
sampling pattern of that child is fully determined by the SampleUsage
parameter.

Change-Id: Iaadcd325fca64a59f24192aadd06923c66362181
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/299875
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index ae0daf0..6dc8bf7 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -93,7 +93,7 @@
     size_t offset = 0, uniformSize = 0;
     std::vector<Variable> inAndUniformVars;
     std::vector<SkString> children;
-    std::vector<SkSL::SampleMatrix> sampleMatrices;
+    std::vector<SkSL::SampleUsage> sampleUsages;
     std::vector<Varying> varyings;
     const SkSL::Context& ctx(compiler->context());
 
@@ -138,8 +138,7 @@
                     if (var.fModifiers.fFlags & flag) {
                         if (&var.fType == ctx.fFragmentProcessor_Type.get()) {
                             children.push_back(var.fName);
-                            sampleMatrices.push_back(
-                                    SkSL::Analysis::GetSampleMatrix(*program, var));
+                            sampleUsages.push_back(SkSL::Analysis::GetSampleUsage(*program, var));
                             continue;
                         }
 
@@ -264,7 +263,7 @@
                                                       std::move(program),
                                                       std::move(inAndUniformVars),
                                                       std::move(children),
-                                                      std::move(sampleMatrices),
+                                                      std::move(sampleUsages),
                                                       std::move(varyings),
                                                       uniformSize,
                                                       mainHasSampleCoords));
@@ -294,7 +293,7 @@
                                  std::unique_ptr<SkSL::Program> baseProgram,
                                  std::vector<Variable>&& inAndUniformVars,
                                  std::vector<SkString>&& children,
-                                 std::vector<SkSL::SampleMatrix>&& sampleMatrices,
+                                 std::vector<SkSL::SampleUsage>&& sampleUsages,
                                  std::vector<Varying>&& varyings,
                                  size_t uniformSize,
                                  bool mainHasSampleCoords)
@@ -303,14 +302,14 @@
         , fBaseProgram(std::move(baseProgram))
         , fInAndUniformVars(std::move(inAndUniformVars))
         , fChildren(std::move(children))
-        , fSampleMatrices(std::move(sampleMatrices))
+        , fSampleUsages(std::move(sampleUsages))
         , fVaryings(std::move(varyings))
         , fUniformSize(uniformSize)
         , fMainFunctionHasSampleCoords(mainHasSampleCoords) {
     SkASSERT(fBaseProgram);
     SkASSERT(SkIsAlign4(fUniformSize));
     SkASSERT(fUniformSize <= this->inputSize());
-    SkASSERT(fChildren.size() == fSampleMatrices.size());
+    SkASSERT(fChildren.size() == fSampleUsages.size());
 }
 
 SkRuntimeEffect::~SkRuntimeEffect() = default;
diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp
index 39b7062..06ea770 100644
--- a/src/effects/SkTableColorFilter.cpp
+++ b/src/effects/SkTableColorFilter.cpp
@@ -308,7 +308,7 @@
         // Not bothering with table-specific optimizations.
         : INHERITED(kColorTableEffect_ClassID, kNone_OptimizationFlags) {
     auto te = GrTextureEffect::Make(std::move(view), kUnknown_SkAlphaType);
-    this->registerExplicitlySampledChild(std::move(te));
+    this->registerChild(std::move(te), SkSL::SampleUsage::Explicit());
 }
 
 ColorTableEffect::ColorTableEffect(const ColorTableEffect& that)
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index d50d2ad..015a0b7 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -506,7 +506,7 @@
         , fYChannelSelector(yChannelSelector)
         , fScale(scale) {
     this->registerChild(std::move(displacement));
-    this->registerExplicitlySampledChild(std::move(color));
+    this->registerChild(std::move(color), SkSL::SampleUsage::Explicit());
     this->setUsesSampleCoordsDirectly();
 }
 
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index 4b3232b..c243592 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -1629,7 +1629,7 @@
         child = GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, SkMatrix::I(), kSampler,
                                       caps);
     }
-    this->registerExplicitlySampledChild(std::move(child));
+    this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
     this->setUsesSampleCoordsDirectly();
 }
 
diff --git a/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
index bdeb6e3..c6aa8c4 100644
--- a/src/effects/imagefilters/SkMorphologyImageFilter.cpp
+++ b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
@@ -340,7 +340,7 @@
         , fUseRange(SkToBool(range)) {
     this->setUsesSampleCoordsDirectly();
     auto te = GrTextureEffect::Make(std::move(view), srcAlphaType);
-    this->registerExplicitlySampledChild(std::move(te));
+    this->registerChild(std::move(te), SkSL::SampleUsage::Explicit());
     if (fUseRange) {
         fRange[0] = range[0];
         fRange[1] = range[1];
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index f358a47..a6c7cc6 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -71,7 +71,7 @@
 int GrFragmentProcessor::numCoordTransforms() const {
     if (SkToBool(fFlags & kUsesSampleCoordsDirectly_Flag) && !this->isSampledWithExplicitCoords()) {
         // coordTransform(0) will return an implicitly defined coord transform so that varyings are
-        // added for this FP in order to support const/uniform sample matrix lifting.
+        // added for this FP in order to support uniform sample matrix lifting.
         return 1;
     } else {
         return 0;
@@ -87,23 +87,6 @@
     return kImplicitIdentity;
 }
 
-void GrFragmentProcessor::setSampleMatrix(SkSL::SampleMatrix newMatrix) {
-    SkASSERT(!newMatrix.isNoOp());
-    SkASSERT(fMatrix.isNoOp());
-
-    fMatrix = newMatrix;
-    // When an FP is sampled using variable matrix expressions, it is effectively being sampled
-    // explicitly, except that the call site will automatically evaluate the matrix expression to
-    // produce the float2 passed into this FP.
-    if (fMatrix.isVariable()) {
-        this->addAndPushFlagToChildren(kSampledWithExplicitCoords_Flag);
-    }
-    // Push perspective matrix type to children
-    if (fMatrix.fHasPerspective) {
-        this->addAndPushFlagToChildren(kNetTransformHasPerspective_Flag);
-    }
-}
-
 void GrFragmentProcessor::addAndPushFlagToChildren(PrivateFlags flag) {
     // This propagates down, so if we've already marked it, all our children should have it too
     if (!(fFlags & flag)) {
@@ -138,24 +121,35 @@
 #endif
 
 int GrFragmentProcessor::registerChild(std::unique_ptr<GrFragmentProcessor> child,
-                                       SkSL::SampleMatrix sampleMatrix,
-                                       bool explicitlySampled) {
+                                       SkSL::SampleUsage sampleUsage) {
     // The child should not have been attached to another FP already and not had any sampling
     // strategy set on it.
-    SkASSERT(child && !child->fParent && child->sampleMatrix().isNoOp() &&
+    SkASSERT(child && !child->fParent && !child->sampleUsage().isSampled() &&
              !child->isSampledWithExplicitCoords() && !child->hasPerspectiveTransform());
 
+    // If a child is sampled directly (sample(child)), and with a single uniform matrix, we need to
+    // treat it as if it were sampled with multiple matrices (eg variable).
+    bool variableMatrix = sampleUsage.hasVariableMatrix() ||
+                          (sampleUsage.fPassThrough && sampleUsage.hasUniformMatrix());
+
     // Configure child's sampling state first
-    if (explicitlySampled) {
+    child->fUsage = sampleUsage;
+
+    // When an FP is sampled using variable matrix expressions, it is effectively being sampled
+    // explicitly, except that the call site will automatically evaluate the matrix expression to
+    // produce the float2 passed into this FP.
+    if (sampleUsage.fExplicitCoords || variableMatrix) {
         child->addAndPushFlagToChildren(kSampledWithExplicitCoords_Flag);
     }
-    if (sampleMatrix.fKind != SkSL::SampleMatrix::Kind::kNone) {
-        child->setSampleMatrix(sampleMatrix);
+
+    // Push perspective matrix type to children
+    if (sampleUsage.fHasPerspective) {
+        child->addAndPushFlagToChildren(kNetTransformHasPerspective_Flag);
     }
 
-    if (child->sampleMatrix().fKind == SkSL::SampleMatrix::Kind::kVariable) {
-        // Since the child is sampled with a variable matrix expression, auto-generated code in
-        // invokeChildWithMatrix() for this FP will refer to the local coordinates.
+    // If the child is sampled with a variable matrix expression, auto-generated code in
+    // invokeChildWithMatrix() for this FP will refer to the local coordinates.
+    if (variableMatrix) {
         this->setUsesSampleCoordsDirectly();
     }
 
@@ -181,14 +175,13 @@
 
     // Sanity check: our sample strategy comes from a parent we shouldn't have yet.
     SkASSERT(!this->isSampledWithExplicitCoords() && !this->hasPerspectiveTransform() &&
-             fMatrix.isNoOp() && !fParent);
+             !fUsage.isSampled() && !fParent);
     return index;
 }
 
 int GrFragmentProcessor::cloneAndRegisterChildProcessor(const GrFragmentProcessor& fp) {
     std::unique_ptr<GrFragmentProcessor> clone = fp.clone();
-    return this->registerChild(std::move(clone), fp.sampleMatrix(),
-                               fp.isSampledWithExplicitCoords());
+    return this->registerChild(std::move(clone), fp.sampleUsage());
 }
 
 void GrFragmentProcessor::cloneAndRegisterAllChildProcessors(const GrFragmentProcessor& src) {
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 17cba88..fc2360a 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -10,7 +10,7 @@
 
 #include <tuple>
 
-#include "include/private/SkSLSampleMatrix.h"
+#include "include/private/SkSLSampleUsage.h"
 #include "src/gpu/GrProcessor.h"
 #include "src/gpu/ops/GrOp.h"
 
@@ -171,10 +171,10 @@
         return SkToBool(fFlags & kNetTransformHasPerspective_Flag);
     }
 
-    // The SampleMatrix describing how this FP is invoked by its parent using 'sample(matrix)'
+    // The SampleUsage describing how this FP is invoked by its parent using 'sample(matrix)'
     // This only reflects the immediate sampling from parent to this FP
-    const SkSL::SampleMatrix& sampleMatrix() const {
-        return fMatrix;
+    const SkSL::SampleUsage& sampleUsage() const {
+        return fUsage;
     }
 
     /**
@@ -397,27 +397,14 @@
      * FragmentProcessors they have. This must be called AFTER all texture accesses and coord
      * transforms have been added.
      * This is for processors whose shader code will be composed of nested processors whose output
-     * colors will be combined somehow to produce its output color.  Registering these child
+     * colors will be combined somehow to produce its output color. Registering these child
      * processors will allow the ProgramBuilder to automatically handle their transformed coords and
      * texture accesses and mangle their uniform and output color names.
      *
-     * Depending on the 2nd and 3rd parameters, this corresponds to the following SkSL sample calls:
-     *  - sample(child): Keep default arguments
-     *  - sample(child, matrix): Provide approprate SampleMatrix matching SkSL
-     *  - sample(child, float2): SampleMatrix() and 'true', or use 'registerExplicitlySampledChild'
-     *  - sample(child, matrix)+sample(child, float2): Appropriate SampleMatrix and 'true'
+     * The SampleUsage parameter describes all of the ways that the child is sampled by the parent.
      */
     int registerChild(std::unique_ptr<GrFragmentProcessor> child,
-                      SkSL::SampleMatrix sampleMatrix = SkSL::SampleMatrix(),
-                      bool explicitlySampled = false);
-
-    /**
-     * A helper for use when the child is only invoked with sample(float2), and not sample()
-     * or sample(matrix).
-     */
-    int registerExplicitlySampledChild(std::unique_ptr<GrFragmentProcessor> child) {
-        return this->registerChild(std::move(child), SkSL::SampleMatrix(), true);
-    }
+                      SkSL::SampleUsage sampleUsage = SkSL::SampleUsage::PassThrough());
 
     /**
      * This method takes an existing fragment processor, clones it, registers it as a child of this
@@ -482,8 +469,6 @@
 
     bool hasSameTransforms(const GrFragmentProcessor&) const;
 
-    void setSampleMatrix(SkSL::SampleMatrix matrix);
-
     enum PrivateFlags {
         kFirstPrivateFlag = kAll_OptimizationFlags + 1,
 
@@ -505,7 +490,7 @@
 
     SkSTArray<1, std::unique_ptr<GrFragmentProcessor>, true> fChildProcessors;
     const GrFragmentProcessor* fParent = nullptr;
-    SkSL::SampleMatrix fMatrix;
+    SkSL::SampleUsage fUsage;
 
     typedef GrProcessor INHERITED;
 };
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index ca384f0..6a844da 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -15,17 +15,17 @@
  * the transform code is applied.
  */
 enum SampleFlag {
-    kExplicitlySampled_Flag          = 0b00001,  // GrFP::isSampledWithExplicitCoords()
+    kExplicitlySampled_Flag      = 0b00001,  // GrFP::isSampledWithExplicitCoords()
 
-    kLegacyCoordTransform_Flag       = 0b00010, // !GrFP::coordTransform(i)::isNoOp()
+    kLegacyCoordTransform_Flag   = 0b00010, // !GrFP::coordTransform(i)::isNoOp()
 
-    kNone_SampleMatrix_Flag          = 0b00100, // GrFP::sampleMatrix()::isNoOp()
-    kConstUniform_SampleMatrix_Flag  = 0b01000, // GrFP::sampleMatrix()::isConstUniform()
-    kVariable_SampleMatrix_Flag      = 0b01100, // GrFP::sampleMatrix()::isVariable()
+    kNone_SampleMatrix_Flag      = 0b00100, // GrFP::sampleUsage()::hasMatrix() == false
+    kUniform_SampleMatrix_Flag   = 0b01000, // GrFP::sampleUsage()::hasUniformMatrix()
+    kVariable_SampleMatrix_Flag  = 0b01100, // GrFP::sampleUsage()::hasVariableMatrix()
 
     // Currently, sample(matrix) only specializes on no-perspective or general.
     // FIXME add new flags as more matrix types are supported.
-    kPersp_Matrix_Flag               = 0b10000, // GrFP::sampleMatrix()::fHasPerspective
+    kPersp_Matrix_Flag           = 0b10000, // GrFP::sampleUsage()::fHasPerspective
 };
 
 GrPrimitiveProcessor::GrPrimitiveProcessor(ClassID classID) : GrProcessor(classID) {}
@@ -47,18 +47,18 @@
         key |= kExplicitlySampled_Flag;
     }
 
-    switch(fp.sampleMatrix().fKind) {
-        case SkSL::SampleMatrix::Kind::kNone:
+    switch(fp.sampleUsage().fKind) {
+        case SkSL::SampleUsage::Kind::kNone:
             key |= kNone_SampleMatrix_Flag;
             break;
-        case SkSL::SampleMatrix::Kind::kConstantOrUniform:
-            key |= kConstUniform_SampleMatrix_Flag;
+        case SkSL::SampleUsage::Kind::kUniform:
+            key |= kUniform_SampleMatrix_Flag;
             break;
-        case SkSL::SampleMatrix::Kind::kVariable:
+        case SkSL::SampleUsage::Kind::kVariable:
             key |= kVariable_SampleMatrix_Flag;
             break;
     }
-    if (fp.sampleMatrix().fHasPerspective) {
+    if (fp.sampleUsage().fHasPerspective) {
         key |= kPersp_Matrix_Flag;
     }
 
diff --git a/src/gpu/ccpr/GrCCClipProcessor.cpp b/src/gpu/ccpr/GrCCClipProcessor.cpp
index 718985a..baa27e9 100644
--- a/src/gpu/ccpr/GrCCClipProcessor.cpp
+++ b/src/gpu/ccpr/GrCCClipProcessor.cpp
@@ -30,7 +30,7 @@
         , fMustCheckBounds(MustCheckBounds::kYes == mustCheckBounds) {
     auto view = make_view(caps, clipPath->atlasLazyProxy(), fIsCoverageCount);
     auto texEffect = GrTextureEffect::Make(std::move(view), kUnknown_SkAlphaType);
-    this->registerExplicitlySampledChild(std::move(texEffect));
+    this->registerChild(std::move(texEffect), SkSL::SampleUsage::Explicit());
 
     if (inputFP != nullptr) {
         this->registerChild(std::move(inputFP));
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index c607caf..e71faca 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -199,7 +199,7 @@
         , fDirection(direction)
         , fClamp(clamp) {
     this->setUsesSampleCoordsDirectly();
-    this->registerExplicitlySampledChild(std::move(fp));
+    this->registerChild(std::move(fp), SkSL::SampleUsage::Explicit());
 }
 
 GrBicubicEffect::GrBicubicEffect(const GrBicubicEffect& that)
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index a8ab80a..55fe393 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -169,7 +169,7 @@
                     ProcessorOptimizationFlags(child.get()))
         , fRadius(radius)
         , fDirection(direction) {
-    this->registerExplicitlySampledChild(std::move(child));
+    this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
     SkASSERT(radius <= kMaxKernelRadius);
     fill_in_1D_gaussian_kernel(fKernel, gaussianSigma, fRadius);
     this->setUsesSampleCoordsDirectly();
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index 12443af..6fb6b42 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -292,9 +292,9 @@
         , fGain(SkScalarToFloat(gain))
         , fBias(SkScalarToFloat(bias) / 255.0f)
         , fConvolveAlpha(convolveAlpha) {
-    this->registerExplicitlySampledChild(std::move(child));
+    this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
     if (kernelFP) {
-        this->registerExplicitlySampledChild(std::move(kernelFP));
+        this->registerChild(std::move(kernelFP), SkSL::SampleUsage::Explicit());
     }
     fKernelOffset = {static_cast<float>(kernelOffset.x()),
                      static_cast<float>(kernelOffset.y())};
diff --git a/src/gpu/effects/GrMatrixEffect.h b/src/gpu/effects/GrMatrixEffect.h
index ebc3833..0e6bba0 100644
--- a/src/gpu/effects/GrMatrixEffect.h
+++ b/src/gpu/effects/GrMatrixEffect.h
@@ -20,10 +20,7 @@
         if (matrix.isIdentity()) {
             return child;
         }
-        SkASSERT(!child->isSampledWithExplicitCoords());
-        SkASSERT(child->sampleMatrix().fKind == SkSL::SampleMatrix::Kind::kNone);
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrMatrixEffect(matrix, std::move(child)));
+        return std::unique_ptr<GrFragmentProcessor>(new GrMatrixEffect(matrix, std::move(child)));
     }
 
     std::unique_ptr<GrFragmentProcessor> clone() const override;
@@ -38,7 +35,7 @@
             , fMatrix(matrix) {
         SkASSERT(child);
         this->registerChild(std::move(child),
-                            SkSL::SampleMatrix::MakeConstUniform(
+                            SkSL::SampleUsage::UniformMatrix(
                                     "matrix", matrix.hasPerspective()));
     }
 
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index c706f2a..1f9f429 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -52,15 +52,15 @@
                     }
                     case SkSL::Compiler::FormatArg::Kind::kChildProcessorWithMatrix: {
                         const auto& fp(args.fFp.cast<GrSkSLFP>());
-                        const auto& sampleMatrices(fp.fEffect->fSampleMatrices);
+                        const auto& sampleUsages(fp.fEffect->fSampleUsages);
 
-                        SkASSERT((size_t)arg.fIndex < sampleMatrices.size());
-                        const SkSL::SampleMatrix& sampleMatrix(sampleMatrices[arg.fIndex]);
+                        SkASSERT((size_t)arg.fIndex < sampleUsages.size());
+                        const SkSL::SampleUsage& sampleUsage(sampleUsages[arg.fIndex]);
 
                         SkSL::String coords = this->expandFormatArgs(arg.fCoords, args, fmtArg);
                         result += this->invokeChildWithMatrix(
                                               arg.fIndex, args,
-                                              sampleMatrix.isConstUniform() ? "" : coords)
+                                              sampleUsage.hasUniformMatrix() ? "" : coords)
                                           .c_str();
                         break;
                     }
@@ -200,14 +200,8 @@
 
 void GrSkSLFP::addChild(std::unique_ptr<GrFragmentProcessor> child) {
     int childIndex = this->numChildProcessors();
-    SkASSERT((size_t)childIndex < fEffect->fSampleMatrices.size());
-    const auto& sampleMatrix(fEffect->fSampleMatrices[childIndex]);
-    // TODO: This doesn't account for calls to sample(child) with no matrix or explicit coords
-    if (sampleMatrix.isNoOp()) {
-        this->registerExplicitlySampledChild(std::move(child));
-    } else {
-        this->registerChild(std::move(child), sampleMatrix);
-    }
+    SkASSERT((size_t)childIndex < fEffect->fSampleUsages.size());
+    this->registerChild(std::move(child), fEffect->fSampleUsages[childIndex]);
 }
 
 GrGLSLFragmentProcessor* GrSkSLFP::onCreateGLSLInstance() const {
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index 236a9de..c453a3d 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -147,7 +147,7 @@
         // Need this so that we can access coords in SKSL to perform snapping.
         this->setUsesSampleCoordsDirectly();
         for (int i = 0; i < numPlanes; ++i) {
-            this->registerExplicitlySampledChild(std::move(planeFPs[i]));
+            this->registerChild(std::move(planeFPs[i]), SkSL::SampleUsage::Explicit());
         }
     } else {
         for (int i = 0; i < numPlanes; ++i) {
diff --git a/src/gpu/effects/generated/GrAARectEffect.h b/src/gpu/effects/generated/GrAARectEffect.h
index 332ec62..a3f0085 100644
--- a/src/gpu/effects/generated/GrAARectEffect.h
+++ b/src/gpu/effects/generated/GrAARectEffect.h
@@ -42,7 +42,8 @@
             , edgeType(edgeType)
             , rect(rect) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h
index a09909f..780914c 100644
--- a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h
@@ -46,10 +46,11 @@
             , innerThreshold(innerThreshold)
             , outerThreshold(outerThreshold) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
         SkASSERT(maskFP);
-        maskFP_index = this->registerChild(std::move(maskFP));
+        maskFP_index = this->registerChild(std::move(maskFP), SkSL::SampleUsage::PassThrough());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h
index 8af063e..2fdc7e7 100644
--- a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h
@@ -35,7 +35,8 @@
             : INHERITED(kGrBlurredEdgeFragmentProcessor_ClassID, kNone_OptimizationFlags)
             , mode(mode) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
index 3b17900..c306382 100644
--- a/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h
@@ -47,10 +47,12 @@
             , solidRadius(solidRadius)
             , textureRadius(textureRadius) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
         SkASSERT(blurProfile);
-        blurProfile_index = this->registerExplicitlySampledChild(std::move(blurProfile));
+        blurProfile_index =
+                this->registerChild(std::move(blurProfile), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrCircleEffect.h b/src/gpu/effects/generated/GrCircleEffect.h
index e77aa6f..0fc1de7 100644
--- a/src/gpu/effects/generated/GrCircleEffect.h
+++ b/src/gpu/effects/generated/GrCircleEffect.h
@@ -51,7 +51,8 @@
             , center(center)
             , radius(radius) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrClampFragmentProcessor.h b/src/gpu/effects/generated/GrClampFragmentProcessor.h
index b36c22a..e6e2d58 100644
--- a/src/gpu/effects/generated/GrClampFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrClampFragmentProcessor.h
@@ -47,7 +47,8 @@
                                  kPreservesOpaqueInput_OptimizationFlag))
             , clampToPremul(clampToPremul) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h b/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h
index 5a3c00f..15d0c88 100644
--- a/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h
@@ -85,7 +85,8 @@
             , clampRGBOutput(clampRGBOutput)
             , premulOutput(premulOutput) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrComposeLerpEffect.h b/src/gpu/effects/generated/GrComposeLerpEffect.h
index f43b484..1a7fdf6 100644
--- a/src/gpu/effects/generated/GrComposeLerpEffect.h
+++ b/src/gpu/effects/generated/GrComposeLerpEffect.h
@@ -37,10 +37,10 @@
                         float weight)
             : INHERITED(kGrComposeLerpEffect_ClassID, kNone_OptimizationFlags), weight(weight) {
         if (child1) {
-            child1_index = this->registerChild(std::move(child1));
+            child1_index = this->registerChild(std::move(child1), SkSL::SampleUsage::PassThrough());
         }
         if (child2) {
-            child2_index = this->registerChild(std::move(child2));
+            child2_index = this->registerChild(std::move(child2), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrConstColorProcessor.h b/src/gpu/effects/generated/GrConstColorProcessor.h
index 2394bf0..0e3d6ea 100644
--- a/src/gpu/effects/generated/GrConstColorProcessor.h
+++ b/src/gpu/effects/generated/GrConstColorProcessor.h
@@ -73,7 +73,8 @@
             , color(color)
             , mode(mode) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrDeviceSpaceEffect.h b/src/gpu/effects/generated/GrDeviceSpaceEffect.h
index dd4d4f7..bd40d38 100644
--- a/src/gpu/effects/generated/GrDeviceSpaceEffect.h
+++ b/src/gpu/effects/generated/GrDeviceSpaceEffect.h
@@ -35,7 +35,7 @@
             : INHERITED(kGrDeviceSpaceEffect_ClassID,
                         (OptimizationFlags)ProcessorOptimizationFlags(fp.get())) {
         SkASSERT(fp);
-        fp_index = this->registerExplicitlySampledChild(std::move(fp));
+        fp_index = this->registerChild(std::move(fp), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrEllipseEffect.h b/src/gpu/effects/generated/GrEllipseEffect.h
index 28df4ee..8726651 100644
--- a/src/gpu/effects/generated/GrEllipseEffect.h
+++ b/src/gpu/effects/generated/GrEllipseEffect.h
@@ -61,7 +61,8 @@
             , center(center)
             , radii(radii) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrHSLToRGBFilterEffect.h b/src/gpu/effects/generated/GrHSLToRGBFilterEffect.h
index 912ed1b..f346a4d 100644
--- a/src/gpu/effects/generated/GrHSLToRGBFilterEffect.h
+++ b/src/gpu/effects/generated/GrHSLToRGBFilterEffect.h
@@ -50,7 +50,8 @@
                                 (kConstantOutputForConstantInput_OptimizationFlag |
                                  kPreservesOpaqueInput_OptimizationFlag)) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrLumaColorFilterEffect.h b/src/gpu/effects/generated/GrLumaColorFilterEffect.h
index 624587b..301df9c 100644
--- a/src/gpu/effects/generated/GrLumaColorFilterEffect.h
+++ b/src/gpu/effects/generated/GrLumaColorFilterEffect.h
@@ -45,7 +45,8 @@
                                                     : kAll_OptimizationFlags) &
                                 kConstantOutputForConstantInput_OptimizationFlag) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrMagnifierEffect.h b/src/gpu/effects/generated/GrMagnifierEffect.h
index 8cee43e..86f6ecc 100644
--- a/src/gpu/effects/generated/GrMagnifierEffect.h
+++ b/src/gpu/effects/generated/GrMagnifierEffect.h
@@ -56,7 +56,7 @@
             , yInvInset(yInvInset) {
         this->setUsesSampleCoordsDirectly();
         SkASSERT(src);
-        src_index = this->registerExplicitlySampledChild(std::move(src));
+        src_index = this->registerChild(std::move(src), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrMixerEffect.h b/src/gpu/effects/generated/GrMixerEffect.h
index e44ab56..24c4d34 100644
--- a/src/gpu/effects/generated/GrMixerEffect.h
+++ b/src/gpu/effects/generated/GrMixerEffect.h
@@ -55,9 +55,9 @@
             : INHERITED(kGrMixerEffect_ClassID, (OptimizationFlags)OptFlags(fp0, fp1))
             , weight(weight) {
         SkASSERT(fp0);
-        fp0_index = this->registerChild(std::move(fp0));
+        fp0_index = this->registerChild(std::move(fp0), SkSL::SampleUsage::PassThrough());
         if (fp1) {
-            fp1_index = this->registerChild(std::move(fp1));
+            fp1_index = this->registerChild(std::move(fp1), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h b/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h
index 620bded..e5c1280 100644
--- a/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h
+++ b/src/gpu/effects/generated/GrOverrideInputFragmentProcessor.h
@@ -60,7 +60,7 @@
             , uniformColor(uniformColor)
             , literalColor(literalColor) {
         SkASSERT(fp);
-        fp_index = this->registerChild(std::move(fp));
+        fp_index = this->registerChild(std::move(fp), SkSL::SampleUsage::PassThrough());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrRGBToHSLFilterEffect.h b/src/gpu/effects/generated/GrRGBToHSLFilterEffect.h
index fdcfbb7..cafb605 100644
--- a/src/gpu/effects/generated/GrRGBToHSLFilterEffect.h
+++ b/src/gpu/effects/generated/GrRGBToHSLFilterEffect.h
@@ -52,7 +52,8 @@
                                 (kConstantOutputForConstantInput_OptimizationFlag |
                                  kPreservesOpaqueInput_OptimizationFlag)) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
diff --git a/src/gpu/effects/generated/GrRRectBlurEffect.h b/src/gpu/effects/generated/GrRRectBlurEffect.h
index 6b52daa..18b40b2 100644
--- a/src/gpu/effects/generated/GrRRectBlurEffect.h
+++ b/src/gpu/effects/generated/GrRRectBlurEffect.h
@@ -138,10 +138,12 @@
             , rect(rect)
             , cornerRadius(cornerRadius) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
         SkASSERT(ninePatchFP);
-        ninePatchFP_index = this->registerExplicitlySampledChild(std::move(ninePatchFP));
+        ninePatchFP_index =
+                this->registerChild(std::move(ninePatchFP), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/generated/GrRectBlurEffect.h b/src/gpu/effects/generated/GrRectBlurEffect.h
index 6506a09..03262b6 100644
--- a/src/gpu/effects/generated/GrRectBlurEffect.h
+++ b/src/gpu/effects/generated/GrRectBlurEffect.h
@@ -144,10 +144,11 @@
             , rect(rect)
             , isFast(isFast) {
         if (inputFP) {
-            inputFP_index = this->registerChild(std::move(inputFP));
+            inputFP_index =
+                    this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
         }
         SkASSERT(integral);
-        integral_index = this->registerExplicitlySampledChild(std::move(integral));
+        integral_index = this->registerChild(std::move(integral), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index 9b3b7b7..ad9b5ba 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -60,12 +60,12 @@
                                             inputColor ? inputColor : "half4(1)",
                                             skslCoords.c_str());
     } else {
-        // The child's function just takes a color; we should only get here for a call to
-        // sample(color) without explicit coordinates, so assert that the child has no sample matrix
-        // and skslCoords is _coords (a const/uniform sample call would go through
-        // invokeChildWithMatrix, and if a child was sampled with sample(matrix) and sample(), it
-        // should have been flagged as variable and hit the branch above).
-        SkASSERT(skslCoords == args.fSampleCoord && childProc.sampleMatrix().isNoOp());
+        // The child's function just takes a color. We should only get here for a call to sample
+        // without explicit coordinates. Assert that the child has no sample matrix and skslCoords
+        // is _coords (a uniform matrix sample call would go through invokeChildWithMatrix, and if
+        // a child was sampled with sample(matrix) and sample(), it should have been flagged as
+        // variable and hit the branch above).
+        SkASSERT(skslCoords == args.fSampleCoord && !childProc.sampleUsage().hasMatrix());
         return SkStringPrintf("%s(%s)", fFunctionNames[childIndex].c_str(),
                                         inputColor ? inputColor : "half4(1)");
     }
@@ -78,23 +78,23 @@
     this->emitChildFunction(childIndex, args);
 
     const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex);
+    SkASSERT(childProc.sampleUsage().hasMatrix());
 
-    // Since this is const/uniform, the provided sksl expression should exactly match the
-    // expression stored on the FP, or it should match the mangled uniform name.
+    // Since this is uniform, the provided sksl expression should exactly match the expression
+    // stored on the FP, or it should match the mangled uniform name.
     if (skslMatrix.empty()) {
-        // Empty matrix expression replaces with the sampleMatrix expression stored on the FP, but
-        // that is only valid for const/uniform sampled FPs
-        SkASSERT(childProc.sampleMatrix().isConstUniform());
-        skslMatrix.assign(childProc.sampleMatrix().fExpression);
+        // Empty matrix expression replaces with the sample matrix expression stored on the FP, but
+        // that is only valid for uniform sampled FPs
+        SkASSERT(childProc.sampleUsage().hasUniformMatrix());
+        skslMatrix.assign(childProc.sampleUsage().fExpression);
     }
 
-    if (childProc.sampleMatrix().isConstUniform()) {
-        // Attempt to resolve the uniform name from the raw name that was stored in the sample
-        // matrix. Since this is const/uniform, the provided expression better match what was given
-        // to the FP.
-        SkASSERT(childProc.sampleMatrix().fExpression == skslMatrix);
+    if (childProc.sampleUsage().hasUniformMatrix()) {
+        // Attempt to resolve the uniform name from the raw name stored in the sample usage.
+        // Since this is uniform, the provided expression better match what was given to the FP.
+        SkASSERT(childProc.sampleUsage().fExpression == skslMatrix);
         GrShaderVar uniform = args.fUniformHandler->getUniformMapping(
-                args.fFp, SkString(childProc.sampleMatrix().fExpression));
+                args.fFp, SkString(childProc.sampleUsage().fExpression));
         if (uniform.getType() != kVoid_GrSLType) {
             // Found the uniform, so replace the expression with the actual uniform name
             SkASSERT(uniform.getType() == kFloat3x3_GrSLType);
@@ -104,18 +104,17 @@
 
     // Produce a string containing the call to the helper function. sample(matrix) is special where
     // the provided skslMatrix expression means that the child FP should be invoked with coords
-    // equal to matrix * parent coords. However, if matrix is a constant/uniform AND the parent
-    // coords were produced by const/uniform transforms, then this expression is lifted to a vertex
+    // equal to matrix * parent coords. However, if matrix is a uniform expression AND the parent
+    // coords were produced by uniform transforms, then this expression is lifted to a vertex
     // shader and is stored in a varying. In that case, childProc will not have a variable sample
     // matrix and will not be sampled explicitly, so its function signature will not take in coords.
     //
     // In all other cases, we need to insert sksl to compute matrix * parent coords and then invoke
     // the function.
     if (childProc.isSampledWithExplicitCoords()) {
-        SkASSERT(!childProc.sampleMatrix().isNoOp());
         // Only check perspective for this specific matrix transform, not the aggregate FP property.
         // Any parent perspective will have already been applied when evaluated in the FS.
-        if (childProc.sampleMatrix().fHasPerspective) {
+        if (childProc.sampleUsage().fHasPerspective) {
             return SkStringPrintf("%s(%s, proj((%s) * %s.xy1))", fFunctionNames[childIndex].c_str(),
                                   inputColor ? inputColor : "half4(1)", skslMatrix.c_str(),
                                   args.fSampleCoord);
@@ -128,10 +127,10 @@
     } else {
         // A variable matrix expression should mark the child as explicitly sampled. A no-op
         // matrix should match sample(color), not sample(color, matrix).
-        SkASSERT(childProc.sampleMatrix().isConstUniform());
+        SkASSERT(childProc.sampleUsage().hasUniformMatrix());
 
-        // Since this is const/uniform and not explicitly sampled, it's transform has been
-        // promoted to the vertex shader and the signature doesn't take a float2 coord.
+        // Since this is uniform and not explicitly sampled, it's transform has been promoted to
+        // the vertex shader and the signature doesn't take a float2 coord.
         return SkStringPrintf("%s(%s)", fFunctionNames[childIndex].c_str(),
                                         inputColor ? inputColor : "half4(1)");
     }
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.h b/src/gpu/glsl/GrGLSLFragmentProcessor.h
index 7a83fb3..3408809 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.h
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.h
@@ -174,7 +174,7 @@
      * expression that evaluates to a float3x3 and is passed in as the 3rd argument.
      *
      * If skslMatrix is the empty string, then it is automatically replaced with the expression
-     * attached to the child's SampleMatrix object. This is only valid if the child is sampled with
+     * attached to the child's SampleUsage object. This is only valid if the child is sampled with
      * a const-uniform matrix. If the sample matrix is const-or-uniform, the expression will be
      * automatically resolved to the mangled uniform name.
      */
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index 48c9053..35de523 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -149,7 +149,7 @@
     this->nextStage();
 
     // An FP's function signature is theoretically always main(half4 color, float2 _coords).
-    // However, if it is only sampled by a chain of const/uniform matrices (or legacy coord
+    // However, if it is only sampled by a chain of uniform matrix expressions (or legacy coord
     // transforms), the value that would have been passed to _coords is lifted to the vertex shader
     // and stored in a unique varying. In that case it uses that variable and does not have a
     // second actual argument for _coords.
@@ -160,7 +160,7 @@
                              GrShaderVar(args.fSampleCoord, kFloat2_GrSLType) };
 
     if (!args.fFp.isSampledWithExplicitCoords()) {
-        // Sampled with a const/uniform matrix and/or a legacy coord transform. The actual
+        // Sampled with a uniform matrix expression and/or a legacy coord transform. The actual
         // transformation code is emitted in the vertex shader, so this only has to access it.
         // Add a float2 _coords variable that maps to the associated varying and replaces the
         // absent 2nd argument to the fp's function.
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
index 2433a70..0415b37 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
@@ -115,8 +115,8 @@
                     varyingVar = getBaseLocalCoord();
                 }
             } else {
-                // The FP's local coordinates are determined by the const/uniform transform
-                // hierarchy from this FP to the root, and can be computed in the vertex shader.
+                // The FP's local coordinates are determined by the uniform transform hierarchy
+                // from this FP to the root, and can be computed in the vertex shader.
                 // If this hierarchy would be the identity transform, then we should use the
                 // original local coordinate.
                 // NOTE: The actual transform logic is handled in emitTransformCode(), this just
@@ -127,10 +127,9 @@
                 const GrFragmentProcessor* node = &fp;
                 while(node) {
                     SkASSERT(!node->isSampledWithExplicitCoords() &&
-                             (node->sampleMatrix().isNoOp() ||
-                              node->sampleMatrix().isConstUniform()));
+                             !node->sampleUsage().hasVariableMatrix());
 
-                    if (node->sampleMatrix().isConstUniform()) {
+                    if (node->sampleUsage().hasUniformMatrix()) {
                         // We can stop once we hit an FP that adds transforms; this FP can reuse
                         // that FPs varying (possibly vivifying it if this was the first use).
                         transformedLocalCoord = localCoordsMap[node];
@@ -183,8 +182,8 @@
                                                 GrGLSLUniformHandler* uniformHandler) {
     std::unordered_map<const GrFragmentProcessor*, GrShaderVar> localCoordsMap;
     for (const auto& tr : fTransformInfos) {
-        // If we recorded a transform info, its sample matrix must be const/uniform
-        SkASSERT(tr.fFP->sampleMatrix().isConstUniform());
+        // If we recorded a transform info, its sample matrix must be uniform
+        SkASSERT(tr.fFP->sampleUsage().hasUniformMatrix());
 
         SkString localCoords;
         // Build a concatenated matrix expression that we apply to the root local coord.
@@ -203,11 +202,11 @@
                     localCoords = SkStringPrintf("%s.xy1", cachedBaseCoord.getName().c_str());
                 }
                 break;
-            } else if (base->sampleMatrix().isConstUniform()) {
+            } else if (base->sampleUsage().hasUniformMatrix()) {
                 // The FP knows the matrix expression it's sampled with, but its parent defined
                 // the uniform (when the expression is not a constant).
                 GrShaderVar uniform = uniformHandler->liftUniformToVertexShader(
-                        *base->parent(), SkString(base->sampleMatrix().fExpression));
+                        *base->parent(), SkString(base->sampleUsage().fExpression));
 
                 // Accumulate the base matrix expression as a preConcat
                 SkString matrix;
@@ -216,7 +215,7 @@
                     matrix = uniform.getName();
                 } else {
                     // No uniform found, so presumably this is a constant
-                    matrix = SkString(base->sampleMatrix().fExpression);
+                    matrix = SkString(base->sampleUsage().fExpression);
                 }
 
                 if (!transformExpression.isEmpty()) {
@@ -226,7 +225,7 @@
             } else {
                 // This intermediate FP is just a pass through and doesn't need to be built
                 // in to the expression, but must visit its parents in case they add transforms
-                SkASSERT(base->sampleMatrix().isNoOp());
+                SkASSERT(!base->sampleUsage().hasMatrix() && !base->sampleUsage().fExplicitCoords);
             }
 
             base = base->parent();
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.h b/src/gpu/glsl/GrGLSLGeometryProcessor.h
index f9c2d6d..71e51d8 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.h
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.h
@@ -24,7 +24,7 @@
 
     // 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.
+    // access to any uniforms the FPs registered for uniform sample matrix invocations.
     void emitTransformCode(GrGLSLVertexBuilder* vb,
                            GrGLSLUniformHandler* uniformHandler) override;
 
diff --git a/src/gpu/glsl/GrGLSLUniformHandler.cpp b/src/gpu/glsl/GrGLSLUniformHandler.cpp
index 0d7a299..0ceb883 100644
--- a/src/gpu/glsl/GrGLSLUniformHandler.cpp
+++ b/src/gpu/glsl/GrGLSLUniformHandler.cpp
@@ -31,7 +31,7 @@
         }
     }
     // Uniform not found; it's better to return a void variable than to assert because sample
-    // matrices that are const/uniform are treated the same for most of the code. When the sample
+    // matrices that are uniform are treated the same for most of the code. When the sample
     // matrix expression can't be found as a uniform, we can infer it's a constant.
     return GrShaderVar();
 }
diff --git a/src/gpu/gradients/generated/GrClampedGradientEffect.h b/src/gpu/gradients/generated/GrClampedGradientEffect.h
index 8d2c0bc..c2cc784 100644
--- a/src/gpu/gradients/generated/GrClampedGradientEffect.h
+++ b/src/gpu/gradients/generated/GrClampedGradientEffect.h
@@ -56,9 +56,11 @@
             , makePremul(makePremul)
             , colorsAreOpaque(colorsAreOpaque) {
         SkASSERT(colorizer);
-        colorizer_index = this->registerChild(std::move(colorizer));
+        colorizer_index =
+                this->registerChild(std::move(colorizer), SkSL::SampleUsage::PassThrough());
         SkASSERT(gradLayout);
-        gradLayout_index = this->registerChild(std::move(gradLayout));
+        gradLayout_index =
+                this->registerChild(std::move(gradLayout), SkSL::SampleUsage::PassThrough());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/gradients/generated/GrTextureGradientColorizer.h b/src/gpu/gradients/generated/GrTextureGradientColorizer.h
index b125b65..507e1a7 100644
--- a/src/gpu/gradients/generated/GrTextureGradientColorizer.h
+++ b/src/gpu/gradients/generated/GrTextureGradientColorizer.h
@@ -32,7 +32,7 @@
     GrTextureGradientColorizer(std::unique_ptr<GrFragmentProcessor> textureFP)
             : INHERITED(kGrTextureGradientColorizer_ClassID, kNone_OptimizationFlags) {
         SkASSERT(textureFP);
-        textureFP_index = this->registerExplicitlySampledChild(std::move(textureFP));
+        textureFP_index = this->registerChild(std::move(textureFP), SkSL::SampleUsage::Explicit());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/gradients/generated/GrTiledGradientEffect.h b/src/gpu/gradients/generated/GrTiledGradientEffect.h
index 5c004bb..561712d 100644
--- a/src/gpu/gradients/generated/GrTiledGradientEffect.h
+++ b/src/gpu/gradients/generated/GrTiledGradientEffect.h
@@ -51,9 +51,11 @@
             , makePremul(makePremul)
             , colorsAreOpaque(colorsAreOpaque) {
         SkASSERT(colorizer);
-        colorizer_index = this->registerChild(std::move(colorizer));
+        colorizer_index =
+                this->registerChild(std::move(colorizer), SkSL::SampleUsage::PassThrough());
         SkASSERT(gradLayout);
-        gradLayout_index = this->registerChild(std::move(gradLayout));
+        gradLayout_index =
+                this->registerChild(std::move(gradLayout), SkSL::SampleUsage::PassThrough());
     }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index 5ea3738..134a8f4 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -32,7 +32,7 @@
 }
 
 static bool has_matrix(const GrFragmentProcessor& fp) {
-    if (fp.sampleMatrix().fKind != SkSL::SampleMatrix::Kind::kNone) {
+    if (fp.sampleUsage().hasMatrix()) {
         return true;
     }
     for (int i = fp.numChildProcessors() - 1; i >= 0; --i) {
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index a38c417..2eb8125 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -783,8 +783,8 @@
             , fNumOctaves(numOctaves)
             , fStitchTiles(stitchTiles)
             , fPaintingData(std::move(paintingData)) {
-        this->registerExplicitlySampledChild(std::move(permutationsFP));
-        this->registerExplicitlySampledChild(std::move(noiseFP));
+        this->registerChild(std::move(permutationsFP), SkSL::SampleUsage::Explicit());
+        this->registerChild(std::move(noiseFP), SkSL::SampleUsage::Explicit());
         this->setUsesSampleCoordsDirectly();
     }
 
@@ -1144,8 +1144,8 @@
             , fOctaves(octaves)
             , fZ(z)
             , fPaintingData(std::move(paintingData)) {
-        this->registerExplicitlySampledChild(std::move(permutationsFP));
-        this->registerExplicitlySampledChild(std::move(gradientFP));
+        this->registerChild(std::move(permutationsFP), SkSL::SampleUsage::Explicit());
+        this->registerChild(std::move(gradientFP), SkSL::SampleUsage::Explicit());
         this->setUsesSampleCoordsDirectly();
     }
 
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index e77aa31..a221f11 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -7,7 +7,7 @@
 
 #include "src/sksl/SkSLAnalysis.h"
 
-#include "include/private/SkSLSampleMatrix.h"
+#include "include/private/SkSLSampleUsage.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLProgram.h"
 #include "src/sksl/ir/SkSLProgramElement.h"
@@ -69,49 +69,57 @@
             &((VariableReference&) *fc.fArguments[0]).fVariable == &fp;
 }
 
-// Visitor that determines the merged SampleMatrix for a given child 'fp' in the program.
-class MergeSampleMatrixVisitor : public ProgramVisitor {
+// Visitor that determines the merged SampleUsage for a given child 'fp' in the program.
+class MergeSampleUsageVisitor : public ProgramVisitor {
 public:
-    MergeSampleMatrixVisitor(const Variable& fp) : fFP(fp) {}
+    MergeSampleUsageVisitor(const Variable& fp) : fFP(fp) {}
 
-    SampleMatrix visit(const Program& program) {
-        fMatrix = SampleMatrix(); // reset to none
+    SampleUsage visit(const Program& program) {
+        fUsage = SampleUsage(); // reset to none
         this->INHERITED::visit(program);
-        return fMatrix;
+        return fUsage;
     }
 
 protected:
     const Variable& fFP;
-    SampleMatrix fMatrix;
+    SampleUsage fUsage;
 
     bool visitExpression(const Expression& e) override {
-        // Looking for sample(fp, inColor?, float3x3)
+        // Looking for sample(fp, inColor?, ...)
         if (e.fKind == Expression::kFunctionCall_Kind) {
             const FunctionCall& fc = (const FunctionCall&) e;
-            if (is_sample_call_to_fp(fc, fFP) && fc.fArguments.size() >= 2 &&
-                fc.fArguments.back()->fType == *this->program().fContext->fFloat3x3_Type) {
-                // Determine the type of matrix for this call site, then merge it with the
-                // previously accumulated matrix state.
-                if (fc.fArguments.back()->isConstantOrUniform()) {
-                    if (fc.fArguments.back()->fKind == Expression::Kind::kVariableReference_Kind ||
-                        fc.fArguments.back()->fKind == Expression::Kind::kConstructor_Kind) {
-                        // FIXME if this is a constant, we should parse the float3x3 constructor and
-                        // determine if the resulting matrix introduces perspective.
-                        fMatrix.merge(SampleMatrix::MakeConstUniform(
-                                fc.fArguments.back()->description()));
+            if (is_sample_call_to_fp(fc, fFP)) {
+                // Determine the type of call at this site, and merge it with the accumulated state
+                const Expression* lastArg = fc.fArguments.back().get();
+                const Context& context = *this->program().fContext;
+
+                if (lastArg->fType == *context.fFloat2_Type) {
+                    fUsage.merge(SampleUsage::Explicit());
+                } else if (lastArg->fType == *context.fFloat3x3_Type) {
+                    // Determine the type of matrix for this call site
+                    if (lastArg->isConstantOrUniform()) {
+                        if (lastArg->fKind == Expression::Kind::kVariableReference_Kind ||
+                            lastArg->fKind == Expression::Kind::kConstructor_Kind) {
+                            // FIXME if this is a constant, we should parse the float3x3 constructor
+                            // and determine if the resulting matrix introduces perspective.
+                            fUsage.merge(SampleUsage::UniformMatrix(lastArg->description()));
+                        } else {
+                            // FIXME this is really to workaround a restriction of the downstream
+                            // code that relies on the SampleUsage's fExpression to identify uniform
+                            // names. Once they are tracked separately, any uniform expression can
+                            // work, but right now this avoids issues from '0.5 * matrix' that is
+                            // both a constant AND a uniform.
+                            fUsage.merge(SampleUsage::VariableMatrix());
+                        }
                     } else {
-                        // FIXME this is really to workaround a restriction of the downstream code
-                        // that relies on the SampleMatrix's fExpression to identify uniform names.
-                        // Once they are tracked separately, any constant/uniform expression can
-                        // work, but right now this avoids issues from '0.5 * matrix' that is both
-                        // a constant AND a uniform.
-                        fMatrix.merge(SampleMatrix::MakeVariable());
+                        fUsage.merge(SampleUsage::VariableMatrix());
                     }
                 } else {
-                    fMatrix.merge(SampleMatrix::MakeVariable());
+                    // The only other signatures do pass-through sampling
+                    fUsage.merge(SampleUsage::PassThrough());
                 }
-                // NOTE: we don't return true here just because we found a sample matrix usage,
-                // we need to process the entire program and merge across all encountered calls.
+                // NOTE: we don't return true here just because we found a sample call. We need to
+                //  process the entire program and merge across all encountered calls.
             }
         }
 
@@ -121,30 +129,6 @@
     typedef ProgramVisitor INHERITED;
 };
 
-// Visitor that searches a program for sample() calls with the given 'fp' as the argument
-// and returns true if explicit float2 coords were passed to that call site.
-class ExplicitCoordsVisitor : public ProgramVisitor {
-public:
-    ExplicitCoordsVisitor(const Variable& fp) : fFP(fp) {}
-
-protected:
-    bool visitExpression(const Expression& e) override {
-        // Looking for sample(fp, inColor?, float2)
-        if (e.fKind == Expression::kFunctionCall_Kind) {
-            const FunctionCall& fc = (const FunctionCall&) e;
-            if (is_sample_call_to_fp(fc, fFP) && fc.fArguments.size() >= 2 &&
-                fc.fArguments.back()->fType == *this->program().fContext->fFloat2_Type) {
-                return true;
-            }
-        }
-        return this->INHERITED::visitExpression(e);
-    }
-
-    const Variable& fFP;
-
-    typedef ProgramVisitor INHERITED;
-};
-
 // Visitor that searches through a main function of the program for reference to the
 // sample coordinates provided by the parent FP or main program.
 class SampleCoordsVisitor : public ProgramVisitor {
@@ -181,13 +165,8 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Analysis
 
-SampleMatrix Analysis::GetSampleMatrix(const Program& program, const Variable& fp) {
-    MergeSampleMatrixVisitor visitor(fp);
-    return visitor.visit(program);
-}
-
-bool Analysis::IsExplicitlySampled(const Program& program, const Variable& fp) {
-    ExplicitCoordsVisitor visitor(fp);
+SampleUsage Analysis::GetSampleUsage(const Program& program, const Variable& fp) {
+    MergeSampleUsageVisitor visitor(fp);
     return visitor.visit(program);
 }
 
diff --git a/src/sksl/SkSLAnalysis.h b/src/sksl/SkSLAnalysis.h
index 164e026..f8b3460 100644
--- a/src/sksl/SkSLAnalysis.h
+++ b/src/sksl/SkSLAnalysis.h
@@ -8,7 +8,7 @@
 #ifndef SkSLAnalysis_DEFINED
 #define SkSLAnalysis_DEFINED
 
-#include "include/private/SkSLSampleMatrix.h"
+#include "include/private/SkSLSampleUsage.h"
 #include "src/sksl/SkSLDefines.h"
 
 namespace SkSL {
@@ -23,9 +23,7 @@
  * Provides utilities for analyzing SkSL statically before it's composed into a full program.
  */
 struct Analysis {
-    static SampleMatrix GetSampleMatrix(const Program& program, const Variable& fp);
-
-    static bool IsExplicitlySampled(const Program& program, const Variable& fp);
+    static SampleUsage GetSampleUsage(const Program& program, const Variable& fp);
 
     static bool ReferencesSampleCoords(const Program& program);
 };
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 0a88298..4a0cad9 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -7,7 +7,7 @@
 
 #include "src/sksl/SkSLCPPCodeGenerator.h"
 
-#include "include/private/SkSLSampleMatrix.h"
+#include "include/private/SkSLSampleUsage.h"
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLCPPUniformCTypes.h"
 #include "src/sksl/SkSLCompiler.h"
@@ -438,9 +438,9 @@
         } else if (c.fArguments.back()->fType.name() == "float3x3") {
             // Invoking child with a matrix, sampling relative to the input coords.
             invokeFunction = "invokeChildWithMatrix";
-            SampleMatrix matrix = Analysis::GetSampleMatrix(fProgram, child);
+            SampleUsage usage = Analysis::GetSampleUsage(fProgram, child);
 
-            if (!matrix.isConstUniform()) {
+            if (!usage.hasUniformMatrix()) {
                 inputCoord = "_matrix" + to_string(c.fOffset);
                 addExtraEmitCodeLine(convertSKSLExpressionToCPP(*c.fArguments.back(), inputCoord));
                 inputCoord.append(".c_str()");
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index f7c324b..3f5f2f3 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -7,7 +7,7 @@
 
 #include "src/sksl/SkSLHCodeGenerator.h"
 
-#include "include/private/SkSLSampleMatrix.h"
+#include "include/private/SkSLSampleUsage.h"
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLParser.h"
 #include "src/sksl/SkSLUtil.h"
@@ -276,53 +276,24 @@
                 this->writef("        SkASSERT(%s);", String(param->fName).c_str());
             }
 
-            bool explicitCoords = Analysis::IsExplicitlySampled(fProgram, *param);
-            SampleMatrix matrix = Analysis::GetSampleMatrix(fProgram, *param);
+            SampleUsage usage = Analysis::GetSampleUsage(fProgram, *param);
 
-            String registerFunc;
-            String matrixArg;
-            String explicitArg;
-
-            if (explicitCoords && matrix.fKind == SampleMatrix::Kind::kNone) {
-                registerFunc = "registerExplicitlySampledChild";
-            } else {
-                registerFunc = "registerChild";
-                if (explicitCoords) {
-                    explicitArg = ", true";
-                }
-                switch(matrix.fKind) {
-                    case SampleMatrix::Kind::kVariable:
-                        // FIXME As it stands, matrix.fHasPerspective will always be true. Ideally
-                        // we could build an expression from all const/uniform sample matrices used
-                        // in the sksl, e.g. m1.hasPerspective() || m2.hasPerspective(), where each
-                        // term was the type expression for the original const/uniform sample
-                        // matrices before they were merged during sksl analysis.
-                        matrixArg.appendf(", SkSL::SampleMatrix::MakeVariable(%s)",
-                                          matrix.fHasPerspective ? "true" : "false");
+            std::string perspExpression;
+            if (usage.hasUniformMatrix()) {
+                for (const Variable* p : fSectionAndParameterHelper.getParameters()) {
+                    if ((p->fModifiers.fFlags & Modifiers::kIn_Flag) &&
+                        usage.fExpression == String(p->fName)) {
+                        perspExpression = usage.fExpression + ".hasPerspective()";
                         break;
-                    case SampleMatrix::Kind::kConstantOrUniform: {
-                        std::string perspExpression = matrix.fHasPerspective ? "true" : "false";
-                        for (const Variable* p : fSectionAndParameterHelper.getParameters()) {
-                            if ((p->fModifiers.fFlags & Modifiers::kIn_Flag) &&
-                                matrix.fExpression == String(p->fName)) {
-                                perspExpression = matrix.fExpression + ".hasPerspective()";
-                                break;
-                            }
-                        }
-                        matrixArg.appendf(", SkSL::SampleMatrix::MakeConstUniform(\"%s\", %s)",
-                                          matrix.fExpression.c_str(), perspExpression.c_str());
-                        break; }
-                    case SampleMatrix::Kind::kNone:
-                        break;
+                    }
                 }
             }
+            std::string usageArg = usage.constructor(std::move(perspExpression));
 
-            this->writef("            %s_index = this->%s(std::move(%s)%s%s);",
+            this->writef("            %s_index = this->registerChild(std::move(%s), %s);",
                          FieldName(String(param->fName).c_str()).c_str(),
-                         registerFunc.c_str(),
                          String(param->fName).c_str(),
-                         matrixArg.c_str(),
-                         explicitArg.c_str());
+                         usageArg.c_str());
 
             if (param->fType.kind() == Type::kNullable_Kind) {
                 this->writef("       }");
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index 6c6925c..4b4bd76 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -37,7 +37,7 @@
 void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
     if (c.fFunction.fBuiltin && c.fFunction.fName == "sample" &&
         c.fArguments[0]->fType.kind() != Type::Kind::kSampler_Kind) {
-        SkASSERT(c.fArguments.size() == 2);
+        SkASSERT(c.fArguments.size() <= 2);
         SkASSERT("fragmentProcessor"  == c.fArguments[0]->fType.name() ||
                  "fragmentProcessor?" == c.fArguments[0]->fType.name());
         SkASSERT(Expression::kVariableReference_Kind == c.fArguments[0]->fKind);
@@ -68,12 +68,14 @@
                 matrixCall ? Compiler::FormatArg::Kind::kChildProcessorWithMatrix
                            : Compiler::FormatArg::Kind::kChildProcessor,
                 index));
-        OutputStream* oldOut = fOut;
-        StringStream buffer;
-        fOut = &buffer;
-        this->writeExpression(*c.fArguments[1], kSequence_Precedence);
-        fOut = oldOut;
-        fArgs->fFormatArgs[childCallIndex].fCoords = buffer.str();
+        if (c.fArguments.size() > 1) {
+            OutputStream* oldOut = fOut;
+            StringStream buffer;
+            fOut = &buffer;
+            this->writeExpression(*c.fArguments[1], kSequence_Precedence);
+            fOut = oldOut;
+            fArgs->fFormatArgs[childCallIndex].fCoords = buffer.str();
+        }
         return;
     }
     if (c.fFunction.fBuiltin) {
diff --git a/src/sksl/SkSLSampleMatrix.cpp b/src/sksl/SkSLSampleMatrix.cpp
deleted file mode 100644
index c961401..0000000
--- a/src/sksl/SkSLSampleMatrix.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2020 Google LLC
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/private/SkSLSampleMatrix.h"
-
-#include "src/sksl/ir/SkSLBinaryExpression.h"
-#include "src/sksl/ir/SkSLConstructor.h"
-#include "src/sksl/ir/SkSLDoStatement.h"
-#include "src/sksl/ir/SkSLExpression.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/SkSLProgram.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/SkSLVariable.h"
-#include "src/sksl/ir/SkSLWhileStatement.h"
-
-namespace SkSL {
-
-SampleMatrix SampleMatrix::merge(const SampleMatrix& other) {
-    if (fKind == Kind::kVariable || other.fKind == Kind::kVariable) {
-        *this = SampleMatrix::MakeVariable(this->fHasPerspective || other.fHasPerspective);
-        return *this;
-    }
-    if (other.fKind == Kind::kConstantOrUniform) {
-        if (fKind == other.fKind) {
-            if (fExpression == other.fExpression) {
-                return *this;
-            }
-            *this = SampleMatrix::MakeVariable(this->fHasPerspective || other.fHasPerspective);
-            return *this;
-        }
-        SkASSERT(fKind == Kind::kNone);
-        *this = other;
-        return *this;
-    }
-    return *this;
-}
-
-} // namespace
diff --git a/src/sksl/SkSLSampleUsage.cpp b/src/sksl/SkSLSampleUsage.cpp
new file mode 100644
index 0000000..6eac4b6
--- /dev/null
+++ b/src/sksl/SkSLSampleUsage.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/private/SkSLSampleUsage.h"
+
+#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLConstructor.h"
+#include "src/sksl/ir/SkSLDoStatement.h"
+#include "src/sksl/ir/SkSLExpression.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/SkSLProgram.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/SkSLVariable.h"
+#include "src/sksl/ir/SkSLWhileStatement.h"
+
+namespace SkSL {
+
+SampleUsage SampleUsage::merge(const SampleUsage& other) {
+    if (other.fExplicitCoords) { fExplicitCoords = true; }
+    if (other.fPassThrough)    { fPassThrough    = true; }
+    if (other.fHasPerspective) { fHasPerspective = true; }
+
+    if (other.fKind == Kind::kVariable) {
+        fKind = Kind::kVariable;
+        fExpression.clear();
+    } else if (other.fKind == Kind::kUniform) {
+        if (fKind == Kind::kUniform) {
+            if (fExpression != other.fExpression) {
+                fKind = Kind::kVariable;
+                fExpression.clear();
+            } else {
+                // Identical uniform expressions, so leave things as-is
+            }
+        } else if (fKind == Kind::kNone) {
+            fKind = Kind::kUniform;
+            fExpression = other.fExpression;
+        } else {
+            // We were already variable, so leave things as-is
+            SkASSERT(fKind == Kind::kVariable);
+        }
+    } else {
+        // other had no matrix information, so we're done
+    }
+
+    return *this;
+}
+
+std::string SampleUsage::constructor(std::string perspectiveExpression) const {
+    SkASSERT(this->hasMatrix() || perspectiveExpression.empty());
+    if (perspectiveExpression.empty()) {
+        perspectiveExpression = fHasPerspective ? "true" : "false";
+    }
+
+    // Check for special cases where we can use our factories:
+    if (!this->hasMatrix()) {
+        if (fExplicitCoords && !fPassThrough) {
+            return "SkSL::SampleUsage::Explicit()";
+        } else if (fPassThrough && !fExplicitCoords) {
+            return "SkSL::SampleUsage::PassThrough()";
+        }
+    }
+    if (!fExplicitCoords && !fPassThrough) {
+        if (fKind == Kind::kVariable) {
+            return "SkSL::SampleUsage::VariableMatrix(" + perspectiveExpression + ")";
+        } else if (fKind == Kind::kUniform) {
+            return "SkSL::SampleUsage::UniformMatrix(\"" + fExpression + "\", " +
+                   perspectiveExpression + ")";
+        }
+    }
+
+    // For more complex scenarios (mixed sampling), fall back to our universal constructor
+    std::string result = "SkSL::SampleUsage(SkSL::SampleUsage::Kind::";
+    switch (fKind) {
+        case Kind::kNone:     result += "kNone";     break;
+        case Kind::kUniform:  result += "kUniform";  break;
+        case Kind::kVariable: result += "kVariable"; break;
+    }
+    result += ", \"";
+    result += fExpression;
+    result += "\", ";
+    result += perspectiveExpression;
+    result += ", ";
+    result += fExplicitCoords ? "true, " : "false, ";
+    result += fPassThrough    ? "true)"  : "false)";
+
+    return result;
+}
+
+} // namespace
diff --git a/src/sksl/sksl_pipeline.inc b/src/sksl/sksl_pipeline.inc
index 2f31951..8c9d926 100644
--- a/src/sksl/sksl_pipeline.inc
+++ b/src/sksl/sksl_pipeline.inc
@@ -1,5 +1,6 @@
 STRINGIFY(
     layout(builtin=15) float4 sk_FragCoord;
+    half4 sample(fragmentProcessor fp);
     half4 sample(fragmentProcessor fp, float2 coords);
     half4 sample(fragmentProcessor fp, float3x3 transform);
 )