Refactor 'in uniform' CPP code generation

Easily supports mapping ctypes to sksl types with templates that
specify how to send data to the GPU and how to track state changes.
The template logic and type mappings are defined in
SkSLCPPUniformCTypes.* while SkSLCPPCodeGenerator is updated to
utilize it.

It also updates the supported ctypes to properly generate code for
SkPoint, SkIPoint, SkIRect, and GrColor4f. The code generated for
'in uniforms' now also correctly supports conditional uniforms.


Bug: skia:
Change-Id: Ib7c0a873bdd68a966b6a00871f33102dfa2c432d
Reviewed-on: https://skia-review.googlesource.com/150129
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
diff --git a/gn/sksl.gni b/gn/sksl.gni
index aae7e26..07b8804 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -10,6 +10,7 @@
   "$_src/sksl/SkSLCFGGenerator.cpp",
   "$_src/sksl/SkSLCompiler.cpp",
   "$_src/sksl/SkSLCPPCodeGenerator.cpp",
+  "$_src/sksl/SkSLCPPUniformCTypes.cpp",
   "$_src/sksl/SkSLGLSLCodeGenerator.cpp",
   "$_src/sksl/SkSLHCodeGenerator.cpp",
   "$_src/sksl/SkSLInterpreter.cpp",
diff --git a/src/gpu/effects/GrAARectEffect.h b/src/gpu/effects/GrAARectEffect.h
index efb7ecb..94974e2 100644
--- a/src/gpu/effects/GrAARectEffect.h
+++ b/src/gpu/effects/GrAARectEffect.h
@@ -15,8 +15,8 @@
 #include "GrCoordTransform.h"
 class GrAARectEffect : public GrFragmentProcessor {
 public:
-    GrClipEdgeType edgeType() const { return fEdgeType; }
-    SkRect rect() const { return fRect; }
+    const GrClipEdgeType& edgeType() const { return fEdgeType; }
+    const SkRect& rect() const { return fRect; }
     static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkRect rect) {
         return std::unique_ptr<GrFragmentProcessor>(new GrAARectEffect(edgeType, rect));
     }
diff --git a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp b/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp
index 45852ed..3a13123 100644
--- a/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp
+++ b/src/gpu/effects/GrAlphaThresholdFragmentProcessor.cpp
@@ -66,8 +66,8 @@
         const GrAlphaThresholdFragmentProcessor& _outer =
                 _proc.cast<GrAlphaThresholdFragmentProcessor>();
         {
-            pdman.set1f(fInnerThresholdVar, _outer.innerThreshold());
-            pdman.set1f(fOuterThresholdVar, _outer.outerThreshold());
+            pdman.set1f(fInnerThresholdVar, (_outer.innerThreshold()));
+            pdman.set1f(fOuterThresholdVar, (_outer.outerThreshold()));
         }
     }
     UniformHandle fInnerThresholdVar;
diff --git a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h b/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h
index 7bbdd32..71376d7 100644
--- a/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h
+++ b/src/gpu/effects/GrBlurredEdgeFragmentProcessor.h
@@ -16,7 +16,7 @@
 class GrBlurredEdgeFragmentProcessor : public GrFragmentProcessor {
 public:
     enum class Mode { kGaussian = 0, kSmoothStep = 1 };
-    Mode mode() const { return fMode; }
+    const Mode& mode() const { return fMode; }
     static std::unique_ptr<GrFragmentProcessor> Make(Mode mode) {
         return std::unique_ptr<GrFragmentProcessor>(new GrBlurredEdgeFragmentProcessor(mode));
     }
diff --git a/src/gpu/effects/GrCircleBlurFragmentProcessor.h b/src/gpu/effects/GrCircleBlurFragmentProcessor.h
index 373ea42..7a53b01 100644
--- a/src/gpu/effects/GrCircleBlurFragmentProcessor.h
+++ b/src/gpu/effects/GrCircleBlurFragmentProcessor.h
@@ -15,7 +15,7 @@
 #include "GrCoordTransform.h"
 class GrCircleBlurFragmentProcessor : public GrFragmentProcessor {
 public:
-    SkRect circleRect() const { return fCircleRect; }
+    const SkRect& circleRect() const { return fCircleRect; }
     float textureRadius() const { return fTextureRadius; }
     float solidRadius() const { return fSolidRadius; }
 
diff --git a/src/gpu/effects/GrCircleEffect.h b/src/gpu/effects/GrCircleEffect.h
index cb28f60..686591a 100644
--- a/src/gpu/effects/GrCircleEffect.h
+++ b/src/gpu/effects/GrCircleEffect.h
@@ -15,8 +15,8 @@
 #include "GrCoordTransform.h"
 class GrCircleEffect : public GrFragmentProcessor {
 public:
-    GrClipEdgeType edgeType() const { return fEdgeType; }
-    SkPoint center() const { return fCenter; }
+    const GrClipEdgeType& edgeType() const { return fEdgeType; }
+    const SkPoint& center() const { return fCenter; }
     float radius() const { return fRadius; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
diff --git a/src/gpu/effects/GrConfigConversionEffect.h b/src/gpu/effects/GrConfigConversionEffect.h
index ed362ae..4da5b0f 100644
--- a/src/gpu/effects/GrConfigConversionEffect.h
+++ b/src/gpu/effects/GrConfigConversionEffect.h
@@ -129,7 +129,7 @@
 
         return true;
     }
-    PMConversion pmConversion() const { return fPmConversion; }
+    const PMConversion& pmConversion() const { return fPmConversion; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> fp,
                                                      PMConversion pmConversion) {
diff --git a/src/gpu/effects/GrConstColorProcessor.cpp b/src/gpu/effects/GrConstColorProcessor.cpp
index 43e9ffc..5b07eca 100644
--- a/src/gpu/effects/GrConstColorProcessor.cpp
+++ b/src/gpu/effects/GrConstColorProcessor.cpp
@@ -26,39 +26,33 @@
         (void)color;
         auto mode = _outer.mode();
         (void)mode;
-        fColorUniformVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                            kDefault_GrSLPrecision, "colorUniform");
+        fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
+                                                     kDefault_GrSLPrecision, "color");
         fragBuilder->codeAppendf(
-                "half4 prevColor;\n@switch (%d) {\n    case 0:\n        %s = %s;\n        break;\n "
-                "   case 1:\n        %s = %s * %s;\n        break;\n    case 2:\n        %s = %s.w "
-                "* %s;\n        break;\n}\n",
+                "@switch (%d) {\n    case 0:\n        %s = %s;\n        break;\n    case 1:\n      "
+                "  %s = %s * %s;\n        break;\n    case 2:\n        %s = %s.w * %s;\n        "
+                "break;\n}\n",
                 (int)_outer.mode(), args.fOutputColor,
-                args.fUniformHandler->getUniformCStr(fColorUniformVar), args.fOutputColor,
-                args.fInputColor, args.fUniformHandler->getUniformCStr(fColorUniformVar),
+                args.fUniformHandler->getUniformCStr(fColorVar), args.fOutputColor,
+                args.fInputColor, args.fUniformHandler->getUniformCStr(fColorVar),
                 args.fOutputColor, args.fInputColor,
-                args.fUniformHandler->getUniformCStr(fColorUniformVar));
+                args.fUniformHandler->getUniformCStr(fColorVar));
     }
 
 private:
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
         const GrConstColorProcessor& _outer = _proc.cast<GrConstColorProcessor>();
-        auto color = _outer.color();
-        (void)color;
-        UniformHandle& colorUniform = fColorUniformVar;
-        (void)colorUniform;
-        auto mode = _outer.mode();
-        (void)mode;
-
-        // We use the "illegal" color value as an uninit sentinel. With GrColor4f, the "illegal"
-        // color is *really* illegal (not just unpremultiplied), so this check is simple.
-        if (prevColor != color) {
-            pdman.set4fv(colorUniform, 1, color.fRGBA);
-            prevColor = color;
+        {
+            const GrColor4f& colorValue = _outer.color();
+            if (fColorPrev != colorValue) {
+                fColorPrev = colorValue;
+                pdman.set4fv(fColorVar, 1, colorValue.fRGBA);
+            }
         }
     }
-    GrColor4f prevColor = GrColor4f::kIllegalConstructor;
-    UniformHandle fColorUniformVar;
+    GrColor4f fColorPrev = GrColor4f::kIllegalConstructor;
+    UniformHandle fColorVar;
 };
 GrGLSLFragmentProcessor* GrConstColorProcessor::onCreateGLSLInstance() const {
     return new GrGLSLConstColorProcessor();
diff --git a/src/gpu/effects/GrConstColorProcessor.fp b/src/gpu/effects/GrConstColorProcessor.fp
index dbad279..80e855b 100644
--- a/src/gpu/effects/GrConstColorProcessor.fp
+++ b/src/gpu/effects/GrConstColorProcessor.fp
@@ -13,9 +13,7 @@
     kLast = kModulateA
 };
 
-layout(ctype=GrColor4f) in half4 color;
-uniform half4 colorUniform;
-layout(ctype=GrColor4f) half4 prevColor;
+layout(ctype=GrColor4f, tracked) in uniform half4 color;
 layout(key) in InputMode mode;
 
 @optimizationFlags {
@@ -25,26 +23,17 @@
 void main() {
     @switch (mode) {
         case InputMode::kIgnore:
-            sk_OutColor = colorUniform;
+            sk_OutColor = color;
             break;
         case InputMode::kModulateRGBA:
-            sk_OutColor = sk_InColor * colorUniform;
+            sk_OutColor = sk_InColor * color;
             break;
         case InputMode::kModulateA:
-            sk_OutColor = sk_InColor.a * colorUniform;
+            sk_OutColor = sk_InColor.a * color;
             break;
     }
 }
 
-@setData(pdman) {
-    // We use the "illegal" color value as an uninit sentinel. With GrColor4f, the "illegal"
-    // color is *really* illegal (not just unpremultiplied), so this check is simple.
-    if (prevColor != color) {
-        pdman.set4fv(colorUniform, 1, color.fRGBA);
-        prevColor = color;
-    }
-}
-
 @class {
     static const int kInputModeCnt = (int) InputMode::kLast + 1;
 
@@ -95,4 +84,4 @@
     }
     InputMode mode = static_cast<InputMode>(d->fRandom->nextULessThan(kInputModeCnt));
     return GrConstColorProcessor::Make(color, mode);
-}
\ No newline at end of file
+}
diff --git a/src/gpu/effects/GrConstColorProcessor.h b/src/gpu/effects/GrConstColorProcessor.h
index e36a38f..8e94900 100644
--- a/src/gpu/effects/GrConstColorProcessor.h
+++ b/src/gpu/effects/GrConstColorProcessor.h
@@ -42,8 +42,8 @@
         SK_ABORT("Unexpected mode");
         return GrColor4f::TransparentBlack();
     }
-    GrColor4f color() const { return fColor; }
-    InputMode mode() const { return fMode; }
+    const GrColor4f& color() const { return fColor; }
+    const InputMode& mode() const { return fMode; }
     static std::unique_ptr<GrFragmentProcessor> Make(GrColor4f color, InputMode mode) {
         return std::unique_ptr<GrFragmentProcessor>(new GrConstColorProcessor(color, mode));
     }
diff --git a/src/gpu/effects/GrEllipseEffect.h b/src/gpu/effects/GrEllipseEffect.h
index 62625fc..ca1574d 100644
--- a/src/gpu/effects/GrEllipseEffect.h
+++ b/src/gpu/effects/GrEllipseEffect.h
@@ -17,9 +17,9 @@
 #include "GrCoordTransform.h"
 class GrEllipseEffect : public GrFragmentProcessor {
 public:
-    GrClipEdgeType edgeType() const { return fEdgeType; }
-    SkPoint center() const { return fCenter; }
-    SkPoint radii() const { return fRadii; }
+    const GrClipEdgeType& edgeType() const { return fEdgeType; }
+    const SkPoint& center() const { return fCenter; }
+    const SkPoint& radii() const { return fRadii; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(GrClipEdgeType edgeType, SkPoint center,
                                                      SkPoint radii, const GrShaderCaps& caps) {
diff --git a/src/gpu/effects/GrMagnifierEffect.cpp b/src/gpu/effects/GrMagnifierEffect.cpp
index ae53b33..b709c48 100644
--- a/src/gpu/effects/GrMagnifierEffect.cpp
+++ b/src/gpu/effects/GrMagnifierEffect.cpp
@@ -77,10 +77,10 @@
                    const GrFragmentProcessor& _proc) override {
         const GrMagnifierEffect& _outer = _proc.cast<GrMagnifierEffect>();
         {
-            pdman.set1f(fXInvZoomVar, _outer.xInvZoom());
-            pdman.set1f(fYInvZoomVar, _outer.yInvZoom());
-            pdman.set1f(fXInvInsetVar, _outer.xInvInset());
-            pdman.set1f(fYInvInsetVar, _outer.yInvInset());
+            pdman.set1f(fXInvZoomVar, (_outer.xInvZoom()));
+            pdman.set1f(fYInvZoomVar, (_outer.yInvZoom()));
+            pdman.set1f(fXInvInsetVar, (_outer.xInvInset()));
+            pdman.set1f(fYInvInsetVar, (_outer.yInvInset()));
         }
         GrSurfaceProxy& srcProxy = *_outer.textureSampler(0).proxy();
         GrTexture& src = *srcProxy.peekTexture();
diff --git a/src/gpu/effects/GrMagnifierEffect.h b/src/gpu/effects/GrMagnifierEffect.h
index 582e0ef..c0c44d7 100644
--- a/src/gpu/effects/GrMagnifierEffect.h
+++ b/src/gpu/effects/GrMagnifierEffect.h
@@ -15,8 +15,8 @@
 #include "GrCoordTransform.h"
 class GrMagnifierEffect : public GrFragmentProcessor {
 public:
-    SkIRect bounds() const { return fBounds; }
-    SkRect srcRect() const { return fSrcRect; }
+    const SkIRect& bounds() const { return fBounds; }
+    const SkRect& srcRect() const { return fSrcRect; }
     float xInvZoom() const { return fXInvZoom; }
     float yInvZoom() const { return fYInvZoom; }
     float xInvInset() const { return fXInvInset; }
diff --git a/src/gpu/effects/GrRRectBlurEffect.cpp b/src/gpu/effects/GrRRectBlurEffect.cpp
index ac17b17..fa85ea5 100644
--- a/src/gpu/effects/GrRRectBlurEffect.cpp
+++ b/src/gpu/effects/GrRRectBlurEffect.cpp
@@ -100,7 +100,7 @@
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
         const GrRRectBlurEffect& _outer = _proc.cast<GrRRectBlurEffect>();
-        { pdman.set1f(fCornerRadiusVar, _outer.cornerRadius()); }
+        { pdman.set1f(fCornerRadiusVar, (_outer.cornerRadius())); }
         auto sigma = _outer.sigma();
         (void)sigma;
         auto rect = _outer.rect();
diff --git a/src/gpu/effects/GrRRectBlurEffect.h b/src/gpu/effects/GrRRectBlurEffect.h
index 3e386a6..3093b31 100644
--- a/src/gpu/effects/GrRRectBlurEffect.h
+++ b/src/gpu/effects/GrRRectBlurEffect.h
@@ -96,7 +96,7 @@
         return mask;
     }
     float sigma() const { return fSigma; }
-    SkRect rect() const { return fRect; }
+    const SkRect& rect() const { return fRect; }
     float cornerRadius() const { return fCornerRadius; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(GrContext* context, float sigma,
diff --git a/src/gpu/effects/GrRectBlurEffect.cpp b/src/gpu/effects/GrRectBlurEffect.cpp
index 4c4ca86..aa74f61 100644
--- a/src/gpu/effects/GrRectBlurEffect.cpp
+++ b/src/gpu/effects/GrRectBlurEffect.cpp
@@ -104,10 +104,7 @@
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
         const GrRectBlurEffect& _outer = _proc.cast<GrRectBlurEffect>();
-        {
-            const SkRect rectValue = _outer.rect();
-            pdman.set4fv(fRectVar, 1, (float*)&rectValue);
-        }
+        { pdman.set4fv(fRectVar, 1, reinterpret_cast<const float*>(&(_outer.rect()))); }
         UniformHandle& rect = fRectVar;
         (void)rect;
         auto sigma = _outer.sigma();
diff --git a/src/gpu/effects/GrRectBlurEffect.h b/src/gpu/effects/GrRectBlurEffect.h
index 0d443c3..bad6be1 100644
--- a/src/gpu/effects/GrRectBlurEffect.h
+++ b/src/gpu/effects/GrRectBlurEffect.h
@@ -60,7 +60,7 @@
 
         return blurProfile;
     }
-    SkRect rect() const { return fRect; }
+    const SkRect& rect() const { return fRect; }
     float sigma() const { return fSigma; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(GrProxyProvider* proxyProvider,
diff --git a/src/gpu/effects/GrSimpleTextureEffect.h b/src/gpu/effects/GrSimpleTextureEffect.h
index 8a8924b..fb4e373 100644
--- a/src/gpu/effects/GrSimpleTextureEffect.h
+++ b/src/gpu/effects/GrSimpleTextureEffect.h
@@ -15,7 +15,7 @@
 #include "GrCoordTransform.h"
 class GrSimpleTextureEffect : public GrFragmentProcessor {
 public:
-    SkMatrix44 matrix() const { return fMatrix; }
+    const SkMatrix44& matrix() const { return fMatrix; }
 
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
                                                      const SkMatrix& matrix) {
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index 33dff41..c1b77b4 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -130,11 +130,7 @@
     void onSetData(const GrGLSLProgramDataManager& pdman,
                    const GrFragmentProcessor& _proc) override {
         const GrYUVtoRGBEffect& _outer = _proc.cast<GrYUVtoRGBEffect>();
-        {
-            float colorSpaceMatrixValue[16];
-            _outer.colorSpaceMatrix().asColMajorf(colorSpaceMatrixValue);
-            pdman.setMatrix4f(fColorSpaceMatrixVar, colorSpaceMatrixValue);
-        }
+        { pdman.setSkMatrix44(fColorSpaceMatrixVar, (_outer.colorSpaceMatrix())); }
     }
     UniformHandle fColorSpaceMatrixVar;
 };
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index 0b3393c..efc1944 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -20,11 +20,11 @@
                                                      sk_sp<GrTextureProxy> vProxy,
                                                      SkYUVColorSpace colorSpace, bool nv12);
     SkString dumpInfo() const override;
-    SkMatrix44 ySamplerTransform() const { return fYSamplerTransform; }
-    SkMatrix44 uSamplerTransform() const { return fUSamplerTransform; }
-    SkMatrix44 vSamplerTransform() const { return fVSamplerTransform; }
-    SkMatrix44 colorSpaceMatrix() const { return fColorSpaceMatrix; }
-    bool nv12() const { return fNv12; }
+    const SkMatrix44& ySamplerTransform() const { return fYSamplerTransform; }
+    const SkMatrix44& uSamplerTransform() const { return fUSamplerTransform; }
+    const SkMatrix44& vSamplerTransform() const { return fVSamplerTransform; }
+    const SkMatrix44& colorSpaceMatrix() const { return fColorSpaceMatrix; }
+    const bool& nv12() const { return fNv12; }
     static std::unique_ptr<GrFragmentProcessor> Make(
             sk_sp<GrTextureProxy> ySampler, SkMatrix44 ySamplerTransform,
             sk_sp<GrTextureProxy> uSampler, SkMatrix44 uSamplerTransform,
diff --git a/src/sksl/README b/src/sksl/README
index efe9be6..72e2839 100644
--- a/src/sksl/README
+++ b/src/sksl/README
@@ -110,7 +110,8 @@
 * global 'in' variables represent data passed to the fragment processor at
   construction time. These variables become constructor parameters and are
   stored in fragment processor fields. By default float2/half2 maps to SkPoints,
-  and float4/half4 maps to SkRects (in x, y, width, height) order. Use ctype
+  and float4/half4 maps to SkRects (in x, y, width, height) order. Similarly,
+  int2/short2 maps to SkIPoint and int4/half4 maps to SkIRect. Use ctype
   (below) to override this default mapping.
 * global variables support an additional 'ctype' layout key, providing the type
   they should be represented as from within the C++ code. For instance, you can
@@ -124,6 +125,9 @@
   fragment processor constructor parameters. The fragment processor will accept
   a parameter representing the uniform's value, and automatically plumb it
   through to the uniform's value in its generated setData() function.
+* 'in uniform' variables support a 'tracked' flag in the layout that will
+  have the generated code automatically implement state tracking on the uniform
+  value to minimize GPU calls.
 * the 'sk_TransformedCoords2D' array provides access to 2D transformed
   coordinates. sk_TransformedCoords2D[0] is equivalent to calling
   fragBuilder->ensureCoords2D(args.fTransformedCoords[0]) (and the result is
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 548089f..f9c6ee3 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -8,6 +8,7 @@
 #include "SkSLCPPCodeGenerator.h"
 
 #include "SkSLCompiler.h"
+#include "SkSLCPPUniformCTypes.h"
 #include "SkSLHCodeGenerator.h"
 
 #include <algorithm>
@@ -144,6 +145,12 @@
            var.fModifiers.fLayout.fBuiltin == -1;
 }
 
+static bool is_uniform_in(const Variable& var) {
+    return (var.fModifiers.fFlags & Modifiers::kUniform_Flag) &&
+           (var.fModifiers.fFlags & Modifiers::kIn_Flag) &&
+           var.fType.kind() != Type::kSampler_Kind;
+}
+
 void CPPCodeGenerator::writeRuntimeValue(const Type& type, const Layout& layout,
                                          const String& cppCode) {
     if (type.isFloat()) {
@@ -515,6 +522,23 @@
                                                            decl.fVar->fModifiers.fLayout).c_str(),
                                  String(decl.fVar->fName).c_str(),
                                  default_value(*decl.fVar).c_str());
+                } else if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+                    // An auto-tracked uniform in variable, so add a field to hold onto the prior
+                    // state. Note that tracked variables must be uniform in's and that is validated
+                    // before writePrivateVars() is called.
+                    const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *decl.fVar);
+                    SkASSERT(mapper && mapper->supportsTracking());
+
+                    String name = HCodeGenerator::FieldName(String(decl.fVar->fName).c_str());
+                    // The member statement is different if the mapper reports a default value
+                    if (mapper->defaultValue().size() > 0) {
+                        this->writef("%s %sPrev = %s;\n",
+                                     mapper->ctype().c_str(), name.c_str(),
+                                     mapper->defaultValue().c_str());
+                    } else {
+                        this->writef("%s %sPrev;\n",
+                                     mapper->ctype().c_str(), name.c_str());
+                    }
                 }
             }
         }
@@ -726,30 +750,67 @@
                  pdman);
     bool wroteProcessor = false;
     for (const auto u : uniforms) {
-        if (u->fModifiers.fFlags & Modifiers::kIn_Flag) {
+        if (is_uniform_in(*u)) {
             if (!wroteProcessor) {
                 this->writef("        const %s& _outer = _proc.cast<%s>();\n", fullName, fullName);
                 wroteProcessor = true;
                 this->writef("        {\n");
             }
+
+            const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *u);
+            SkASSERT(mapper);
+
             String nameString(u->fName);
             const char* name = nameString.c_str();
-            if (u->fType == *fContext.fFloat4_Type || u->fType == *fContext.fHalf4_Type) {
-                this->writef("        const SkRect %sValue = _outer.%s();\n"
-                             "        %s.set4fv(%sVar, 1, (float*) &%sValue);\n",
-                             name, name, pdman, HCodeGenerator::FieldName(name).c_str(), name);
-            } else if (u->fType == *fContext.fFloat4x4_Type ||
-                       u->fType == *fContext.fHalf4x4_Type) {
-                this->writef("        float %sValue[16];\n"
-                             "        _outer.%s().asColMajorf(%sValue);\n"
-                             "        %s.setMatrix4f(%sVar, %sValue);\n",
-                             name, name, name, pdman, HCodeGenerator::FieldName(name).c_str(),
-                             name);
-            } else if (u->fType == *fContext.fFragmentProcessor_Type) {
-                // do nothing
+
+            // Switches for setData behavior in the generated code
+            bool conditionalUniform = u->fModifiers.fLayout.fWhen != "";
+            bool isTracked = u->fModifiers.fLayout.fFlags & Layout::kTracked_Flag;
+            bool needsValueDeclaration = isTracked || !mapper->canInlineUniformValue();
+
+            String uniformName = HCodeGenerator::FieldName(name) + "Var";
+
+            String indent = "        "; // 8 by default, 12 when nested for conditional uniforms
+            if (conditionalUniform) {
+                // Add a pre-check to make sure the uniform was emitted
+                // before trying to send any data to the GPU
+                this->writef("        if (%s.isValid()) {\n", uniformName.c_str());
+                indent += "    ";
+            }
+
+            String valueVar = "";
+            if (needsValueDeclaration) {
+                valueVar.appendf("%sValue", name);
+                // Use AccessType since that will match the return type of _outer's public API.
+                String valueType = HCodeGenerator::AccessType(fContext, u->fType,
+                                                              u->fModifiers.fLayout);
+                this->writef("%s%s %s = _outer.%s();\n",
+                             indent.c_str(), valueType.c_str(), valueVar.c_str(), name);
             } else {
-                this->writef("        %s.set1f(%sVar, _outer.%s());\n",
-                             pdman, HCodeGenerator::FieldName(name).c_str(), name);
+                // Not tracked and the mapper only needs to use the value once
+                // so send it a safe expression instead of the variable name
+                valueVar.appendf("(_outer.%s())", name);
+            }
+
+            if (isTracked) {
+                SkASSERT(mapper->supportsTracking());
+
+                String prevVar = HCodeGenerator::FieldName(name) + "Prev";
+                this->writef("%sif (%s) {\n"
+                             "%s    %s;\n"
+                             "%s    %s;\n"
+                             "%s}\n", indent.c_str(),
+                        mapper->dirtyExpression(valueVar, prevVar).c_str(), indent.c_str(),
+                        mapper->saveState(valueVar, prevVar).c_str(), indent.c_str(),
+                        mapper->setUniform(pdman, uniformName, valueVar).c_str(), indent.c_str());
+            } else {
+                this->writef("%s%s;\n", indent.c_str(),
+                        mapper->setUniform(pdman, uniformName, valueVar).c_str());
+            }
+
+            if (conditionalUniform) {
+                // Close the earlier precheck block
+                this->writef("        }\n");
             }
         }
     }
@@ -948,6 +1009,32 @@
                            decl.fVar->fType.kind() != Type::kSampler_Kind) {
                     uniforms.push_back(decl.fVar);
                 }
+
+                if (is_uniform_in(*decl.fVar)) {
+                    // Validate the "uniform in" declarations to make sure they are fully supported,
+                    // instead of generating surprising C++
+                    const UniformCTypeMapper* mapper =
+                            UniformCTypeMapper::Get(fContext, *decl.fVar);
+                    if (mapper == nullptr) {
+                        fErrors.error(decl.fOffset, String(decl.fVar->fName)
+                                + "'s type is not supported for use as a 'uniform in'");
+                        return false;
+                    }
+                    if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+                        if (!mapper->supportsTracking()) {
+                            fErrors.error(decl.fOffset, String(decl.fVar->fName)
+                                    + "'s type does not support state tracking");
+                            return false;
+                        }
+                    }
+
+                } else {
+                    // If it's not a uniform_in, it's an error to be tracked
+                    if (decl.fVar->fModifiers.fLayout.fFlags & Layout::kTracked_Flag) {
+                        fErrors.error(decl.fOffset, "Non-'in uniforms' cannot be tracked");
+                        return false;
+                    }
+                }
             }
         }
     }
diff --git a/src/sksl/SkSLCPPUniformCTypes.cpp b/src/sksl/SkSLCPPUniformCTypes.cpp
new file mode 100644
index 0000000..69e0498
--- /dev/null
+++ b/src/sksl/SkSLCPPUniformCTypes.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkSLCPPUniformCTypes.h"
+#include "SkSLHCodeGenerator.h"
+#include "SkSLStringStream.h"
+
+#include <vector>
+
+namespace SkSL {
+
+/////////////////////////
+// Template evaluation //
+/////////////////////////
+
+static String eval_template(const String& format, const std::vector<String>& tokens,
+                            const std::vector<const String*>& values) {
+    StringStream stream;
+
+    int tokenNameStart = -1;
+    for (size_t i = 0; i < format.size(); i++) {
+        if (tokenNameStart >= 0) {
+            // Within a token name so check if it is the end
+            if (format[i] == '}') {
+                // Skip 2 extra characters at the beginning for the $ and {, which must exist since
+                // otherwise tokenNameStart < 0
+                String token(format.c_str() + tokenNameStart + 2, i - tokenNameStart - 2);
+                // Search for the token in supported list
+                bool found = false;
+                for (size_t j = 0; j < tokens.size(); j++) {
+                    if (token == tokens[j]) {
+                        // Found a match so append the value corresponding to j to the output
+                        stream.writeText(values[j]->c_str());
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    // Write out original characters as if we didn't consider it to be a token name
+                    stream.writeText("${");
+                    stream.writeText(token.c_str());
+                    stream.writeText("}");
+                }
+
+                // And end the token name state
+                tokenNameStart = -1;
+            }
+        } else {
+            // Outside of a token name, so check if this character starts a name:
+            // i == $ and i+1 == {
+            if (i < format.size() - 1 && format[i] == '$' && format[i + 1] == '{') {
+                // Begin parsing the token
+                tokenNameStart = i;
+            } else {
+                // Just a character so append it
+                stream.write8(format[i]);
+            }
+        }
+    }
+
+    return stream.str();
+}
+
+static bool determine_inline_from_template(const String& uniformTemplate) {
+    // True if there is at most one instance of the ${var} template matcher in fUniformTemplate.
+    int firstMatch = uniformTemplate.find("${var}");
+
+    if (firstMatch < 0) {
+        // Template doesn't use the value variable at all, so it can "inlined"
+        return true;
+    }
+
+    // Check for another occurrence of ${var}, after firstMatch + 6
+    int secondMatch = uniformTemplate.find("${var}", firstMatch + strlen("${var}"));
+    // If there's no second match, then the value can be inlined in the c++ code
+    return secondMatch < 0;
+}
+
+///////////////////////////////////////
+// UniformCTypeMapper implementation //
+///////////////////////////////////////
+
+String UniformCTypeMapper::dirtyExpression(const String& newVar, const String& oldVar) const {
+    if (fSupportsTracking) {
+        std::vector<String> tokens = { "newVar", "oldVar" };
+        std::vector<const String*> values = { &newVar, &oldVar };
+        return eval_template(fDirtyExpressionTemplate, tokens, values);
+    } else {
+        return "";
+    }
+}
+
+String UniformCTypeMapper::saveState(const String& newVar, const String& oldVar) const {
+    if (fSupportsTracking) {
+        std::vector<String> tokens = { "newVar", "oldVar" };
+        std::vector<const String*> values = { &newVar, &oldVar };
+        return eval_template(fSaveStateTemplate, tokens, values);
+    } else {
+        return "";
+    }
+}
+
+String UniformCTypeMapper::setUniform(const String& pdman, const String& uniform,
+                                      const String& var) const {
+    std::vector<String> tokens = { "pdman", "uniform", "var" };
+    std::vector<const String*> values = { &pdman, &uniform, &var };
+    return eval_template(fUniformTemplate, tokens, values);
+}
+
+UniformCTypeMapper::UniformCTypeMapper(
+        const String& ctype, const std::vector<String>& skslTypes,
+        const String& setUniformFormat, bool enableTracking, const String& defaultValue,
+        const String& dirtyExpressionFormat, const String& saveStateFormat)
+    : fCType(ctype)
+    , fSKSLTypes(skslTypes)
+    , fUniformTemplate(setUniformFormat)
+    , fInlineValue(determine_inline_from_template(setUniformFormat))
+    , fSupportsTracking(enableTracking)
+    , fDefaultValue(defaultValue)
+    , fDirtyExpressionTemplate(dirtyExpressionFormat)
+    , fSaveStateTemplate(saveStateFormat) { }
+
+// NOTE: These would be macros, but C++ initialization lists for the sksl type names do not play
+// well with macro parsing.
+
+static UniformCTypeMapper REGISTER(const char* ctype, const std::vector<String>& skslTypes,
+                                   const char* uniformFormat, const char* defaultValue,
+                                   const char* dirtyExpression) {
+    return UniformCTypeMapper(ctype, skslTypes, uniformFormat, defaultValue, dirtyExpression,
+                              "${oldVar} = ${newVar}");
+}
+
+static UniformCTypeMapper REGISTER(const char* ctype, const std::vector<String>& skslTypes,
+                                   const char* uniformFormat, const char* defaultValue) {
+    return REGISTER(ctype, skslTypes, uniformFormat, defaultValue,
+                    "${oldVar} != ${newVar}");
+}
+
+//////////////////////////////
+// Currently defined ctypes //
+//////////////////////////////
+
+static const std::vector<UniformCTypeMapper>& get_mappers() {
+    static const std::vector<UniformCTypeMapper> registeredMappers = {
+    REGISTER("SkRect", { "half4", "float4", "double4" },
+        "${pdman}.set4fv(${uniform}, 1, reinterpret_cast<const float*>(&${var}))", // to gpu
+        "SkRect::MakeEmpty()",                                                     // default value
+        "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"),                          // dirty check
+
+    REGISTER("SkIRect", { "int4", "short4", "byte4" },
+        "${pdman}.set4iv(${uniform}, 1, reinterpret_cast<const int*>(&${var}))",   // to gpu
+        "SkIRect::MakeEmpty()",                                                    // default value
+        "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"),                          // dirty check
+
+    REGISTER("GrColor4f", { "half4", "float4", "double4" },
+        "${pdman}.set4fv(${uniform}, 1, ${var}.fRGBA)",                            // to gpu
+        "GrColor4f::kIllegalConstructor"),                                         // default value
+
+    REGISTER("SkPoint", { "half2", "float2", "double2" } ,
+        "${pdman}.set2f(${uniform}, ${var}.fX, ${var}.fY)",                        // to gpu
+        "SkPoint::Make(NaN, NaN)"),                                                // default value
+
+    REGISTER("SkIPoint", { "int2", "short2", "byte2" },
+        "${pdman}.set2i(${uniform}, ${var}.fX, ${var}.fY)",                        // to gpu
+        "SkIPoint::Make(~0, ~0)"),                                                 // default value
+
+    REGISTER("SkMatrix", { "half3x3", "float3x3", "double3x3" },
+        "${pdman}.setSkMatrix(${uniform}, ${var})",                                // to gpu
+        "SkMatrix::MakeScale(NaN)",                                                // default value
+        "!${oldVar}.cheapEqualTo(${newVar})"),                                     // dirty check
+
+    REGISTER("SkMatrix44",  { "half4x4", "float4x4", "double4x4" },
+        "${pdman}.setSkMatrix44(${uniform}, ${var})",                              // to gpu
+        "SkMatrix::MakeScale(NaN)",                                                // default value
+        "!${oldVar}.cheapEqualTo(${newVar})"),                                     // dirty check
+
+    REGISTER("float",  { "half", "float", "double" },
+        "${pdman}.set1f(${uniform}, ${var})",                                      // to gpu
+        "NaN"),                                                                    // default value
+
+    REGISTER("int32_t", { "int", "short", "byte" },
+        "${pdman}.set1i(${uniform}, ${var})",                                      // to gpu
+        "SK_NaN32"),                                                               // default value
+    };
+
+    return registeredMappers;
+}
+
+/////
+
+// Greedy search through registered handlers for one that has a matching
+// ctype and supports the sksl type of the variable.
+const UniformCTypeMapper* UniformCTypeMapper::Get(const Context& context, const Type& type,
+                                                  const Layout& layout) {
+    const std::vector<UniformCTypeMapper>& registeredMappers = get_mappers();
+
+    String ctype = layout.fCType;
+    // If there's no custom ctype declared in the layout, use the default type mapping
+    if (ctype == "") {
+        ctype = HCodeGenerator::ParameterType(context, type, layout);
+    }
+
+    const String& skslType = type.name();
+
+    for (size_t i = 0; i < registeredMappers.size(); i++) {
+        if (registeredMappers[i].ctype() == ctype) {
+            // Check for sksl support, since some c types (e.g. SkMatrix) can be used in multiple
+            // uniform types and send data to the gpu differently in those conditions
+            const std::vector<String> supportedSKSL = registeredMappers[i].supportedTypeNames();
+            for (size_t j = 0; j < supportedSKSL.size(); j++) {
+                if (supportedSKSL[j] == skslType) {
+                    // Found a match, so return it or an explicitly untracked version if tracking is
+                    // disabled in the layout
+                    return &registeredMappers[i];
+                }
+            }
+        }
+    }
+
+    // Didn't find a match
+    return nullptr;
+}
+
+} // namespace
diff --git a/src/sksl/SkSLCPPUniformCTypes.h b/src/sksl/SkSLCPPUniformCTypes.h
new file mode 100644
index 0000000..d42fe0a
--- /dev/null
+++ b/src/sksl/SkSLCPPUniformCTypes.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSLUniformCTypes_DEFINED
+#define SkSLUniformCTypes_DEFINED
+
+#include "SkSLString.h"
+#include "SkSLContext.h"
+#include "ir/SkSLType.h"
+#include "ir/SkSLVariable.h"
+
+namespace SkSL {
+
+// This uses templates to define dirtyExpression(), saveState() and setUniform(). Each template can
+// reference token names formatted ${name} that are replaced with the actual values passed into the
+// functions.
+//
+// dirtyExpression() and saveState() support the following tokens:
+//  - ${newVar} replaced with value of newValueVarName (1st argument)
+//  - ${oldVar} replaced with value of oldValueVarName (2nd argument)
+//
+// setUniform() supports these tokens:
+//  - ${pdman} replaced with value of pdmanName (1st argument)
+//  - ${uniform} replaced with value of uniformHandleName (2nd argument)
+//  - ${var} replaced with value of valueVarName (3rd argument)
+//
+// All templates and C++ snippets should produce valid expressions, but do not need to include
+// semicolons or newlines, which will be handled by the code generation itself.
+class UniformCTypeMapper {
+public:
+    // Create a templated mapper that does not support state tracking
+    UniformCTypeMapper(const String& ctype, const std::vector<String>& skslTypes,
+            const char* setUniformFormat)
+        : UniformCTypeMapper(ctype, skslTypes, setUniformFormat, false, "", "", "") { }
+
+    // Create a templated mapper that provides extra patterns for the state
+    // tracking expressions.
+    UniformCTypeMapper(const String& ctype, const std::vector<String>& skslTypes,
+            const String& setUniformFormat, const String& defaultValue,
+            const String& dirtyExpressionFormat, const String& saveStateFormat)
+        : UniformCTypeMapper(ctype, skslTypes, setUniformFormat,
+                true, defaultValue, dirtyExpressionFormat, saveStateFormat) { }
+
+    // Returns nullptr if the type and layout are not supported; the returned pointer's ownership
+    // is not transfered to the caller.
+    //
+    // The returned mapper can support tracking even if tracking is disabled based on the flags in
+    // the layout.
+    static const UniformCTypeMapper* Get(const Context& context, const Type& type,
+                                         const Layout& layout);
+
+    static const UniformCTypeMapper* Get(const Context& context, const Variable& variable) {
+        return Get(context, variable.fType, variable.fModifiers.fLayout);
+    }
+
+    // The C++ type name that this mapper applies to
+    const String& ctype() const {
+        return fCType;
+    }
+
+    // The sksl type names that the mapper's ctype can be mapped to
+    const std::vector<String>& supportedTypeNames() const {
+        return fSKSLTypes;
+    }
+
+    // Whether or not this handler knows how to write state tracking code
+    // for the uniform variables
+    bool supportsTracking() const {
+        return fSupportsTracking;
+    }
+
+    // What the C++ class fields are initialized to in the GLSLFragmentProcessor The empty string
+    // implies the no-arg constructor is suitable. This is not used if supportsTracking() returns
+    // false.
+    //
+    // The returned snippet will be a valid as the lhs of an assignment.
+    const String& defaultValue() const {
+        return fDefaultValue;
+    }
+
+    // Return a boolean expression that returns true if the variables specified by newValueVarName
+    // and oldValueVarName have different values. This is ignored if supportsTracking() returns
+    // false.
+    //
+    // The returned snippet will be a valid expression to be inserted into the condition of an 'if'
+    // statement.
+    String dirtyExpression(const String& newValueVarName, const String& oldValueVarName) const;
+
+    // Return a statement that stores the value of newValueVarName into the variable specified by
+    // oldValueVarName. This is ignored if supportsTracking() returns false.
+    //
+    // The returned snippet will be a valid expression.
+    String saveState(const String& newValueVarName, const String& oldValueVarName) const;
+
+    // Return a statement that invokes the appropriate setX method on the GrGLSLProgramDataManager
+    // specified by pdmanName, where the uniform is provided by the expression stored in
+    // uniformHandleName, and valueVarName is the variable name pointing to the ctype instance
+    // holding the new value.
+    //
+    // The returned snippet will be a valid expression.
+    String setUniform(const String& pdmanName, const String& uniformHandleName,
+                      const String& valueVarName) const;
+
+    // True if the setUniform() template only uses the value variable once in its expression. The
+    // variable does not necessarily get inlined if this returns true, since a local variable may be
+    // needed if state tracking is employed for a particular uniform.
+    bool canInlineUniformValue() const {
+        return fInlineValue;
+    }
+
+private:
+    UniformCTypeMapper(const String& ctype, const std::vector<String>& skslTypes,
+            const String& setUniformFormat, bool enableTracking, const String& defaultValue,
+            const String& dirtyExpressionFormat, const String& saveStateFormat);
+
+    String fCType;
+    std::vector<String> fSKSLTypes;
+    String fUniformTemplate;
+    bool fInlineValue; // Cached value calculated from fUniformTemplate
+
+    bool fSupportsTracking;
+    String fDefaultValue;
+    String fDirtyExpressionTemplate;
+    String fSaveStateTemplate;
+};
+
+} // namespace
+
+#endif // SkSLUniformCTypes_DEFINED
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index 532ebef..90d96f7 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -15,6 +15,8 @@
 #include "ir/SkSLSection.h"
 #include "ir/SkSLVarDeclarations.h"
 
+#include <set>
+
 namespace SkSL {
 
 HCodeGenerator::HCodeGenerator(const Context* context, const Program* program,
@@ -31,14 +33,24 @@
         return layout.fCType;
     } else if (type == *context.fFloat_Type || type == *context.fHalf_Type) {
         return "float";
+    } else if (type == *context.fInt_Type ||
+               type == *context.fShort_Type ||
+               type == *context.fByte_Type) {
+        return "int32_t";
     } else if (type == *context.fFloat2_Type || type == *context.fHalf2_Type) {
         return "SkPoint";
+    } else if (type == *context.fInt2_Type ||
+               type == *context.fShort2_Type ||
+               type == *context.fByte2_Type) {
+        return "SkIPoint";
     } else if (type == *context.fInt4_Type ||
                type == *context.fShort4_Type ||
                type == *context.fByte4_Type) {
         return "SkIRect";
     } else if (type == *context.fFloat4_Type || type == *context.fHalf4_Type) {
         return "SkRect";
+    } else if (type == *context.fFloat3x3_Type || type == *context.fHalf3x3_Type) {
+        return "SkMatrix";
     } else if (type == *context.fFloat4x4_Type || type == *context.fHalf4x4_Type) {
         return "SkMatrix44";
     } else if (type.kind() == Type::kSampler_Kind) {
@@ -62,6 +74,19 @@
     return ParameterType(context, type, layout);
 }
 
+String HCodeGenerator::AccessType(const Context& context, const Type& type,
+                                  const Layout& layout) {
+    static const std::set<String> primitiveTypes = { "int32_t", "float", "SkPMColor" };
+
+    String fieldType = FieldType(context, type, layout);
+    bool isPrimitive = primitiveTypes.find(fieldType) != primitiveTypes.end();
+    if (isPrimitive) {
+        return fieldType;
+    } else {
+        return String::printf("const %s&", fieldType.c_str());
+    }
+}
+
 void HCodeGenerator::writef(const char* s, va_list va) {
     static constexpr int BUFFER_SIZE = 1024;
     va_list copy;
@@ -312,7 +337,7 @@
         String nameString(param->fName);
         const char* name = nameString.c_str();
         this->writef("    %s %s() const { return %s; }\n",
-                     FieldType(fContext, param->fType, param->fModifiers.fLayout).c_str(), name,
+                     AccessType(fContext, param->fType, param->fModifiers.fLayout).c_str(), name,
                      FieldName(name).c_str());
     }
     this->writeMake();
diff --git a/src/sksl/SkSLHCodeGenerator.h b/src/sksl/SkSLHCodeGenerator.h
index 8a6c420..8c5c232 100644
--- a/src/sksl/SkSLHCodeGenerator.h
+++ b/src/sksl/SkSLHCodeGenerator.h
@@ -35,6 +35,9 @@
 
     static String FieldType(const Context& context, const Type& type, const Layout& layout);
 
+    // Either the field type, or a const reference of the field type if the field type is complex.
+    static String AccessType(const Context& context, const Type& type, const Layout& layout);
+
     static String FieldName(const char* varName) {
         return String::printf("f%c%s", toupper(varName[0]), varName + 1);
     }
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index b8ceb5a..6bd0b81 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -120,6 +120,7 @@
     TOKEN(WHEN,                         "when");
     TOKEN(KEY,                          "key");
     TOKEN(CTYPE,                        "ctype");
+    TOKEN(TRACKED,                      "tracked");
     #undef TOKEN
 }
 
@@ -823,6 +824,9 @@
                     case LayoutToken::PUSH_CONSTANT:
                         flags |= Layout::kPushConstant_Flag;
                         break;
+                    case LayoutToken::TRACKED:
+                        flags |= Layout::kTracked_Flag;
+                        break;
                     case LayoutToken::POINTS:
                         primitive = Layout::kPoints_Primitive;
                         break;
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
index d821372..8d8702b 100644
--- a/src/sksl/SkSLParser.h
+++ b/src/sksl/SkSLParser.h
@@ -89,7 +89,8 @@
         INVOCATIONS,
         WHEN,
         KEY,
-        CTYPE
+        CTYPE,
+        TRACKED
     };
 
     Parser(const char* text, size_t length, SymbolTable& types, ErrorReporter& errors);
diff --git a/src/sksl/SkSLString.cpp b/src/sksl/SkSLString.cpp
index 00307a6..effb3db 100644
--- a/src/sksl/SkSLString.cpp
+++ b/src/sksl/SkSLString.cpp
@@ -79,6 +79,34 @@
     return !strncmp(c_str() + size() - len, s, len);
 }
 
+int String::find(const String& substring, int fromPos) const {
+    return find(substring.c_str(), fromPos);
+}
+
+int String::find(const char* substring, int fromPos) const {
+    SkASSERT(fromPos >= 0);
+#ifdef SKSL_USE_STD_STRING
+    // use std::string find() and check it against npos for not found, and find() natively supports
+    // searching from a position
+    size_t found = INHERITED::find(substring, (size_t) fromPos);
+    return found == std::string::npos ? -1 : found;
+#else
+    // use SkStrFind on the underlying c string, and pointer arithmetic to support the searching
+    // position
+    if (substring == nullptr) {
+        // Treat null as empty, and an empty string shows up immediately
+        return 0;
+    }
+
+    size_t sublen = strlen(substring);
+    if (fromPos >= size() - sublen) {
+        // Can't find it if there aren't enough characters left
+        return -1;
+    }
+    return SkStrFind(c_str() + fromPos, substring);
+#endif
+}
+
 String String::operator+(const char* s) const {
     String result(*this);
     result.append(s);
diff --git a/src/sksl/SkSLString.h b/src/sksl/SkSLString.h
index 26641f8..b3e411e 100644
--- a/src/sksl/SkSLString.h
+++ b/src/sksl/SkSLString.h
@@ -92,6 +92,9 @@
     bool startsWith(const char* s) const;
     bool endsWith(const char* s) const;
 
+    int find(const char* substring, int fromPos = 0) const;
+    int find(const String& substring, int fromPos = 0) const;
+
     String operator+(const char* s) const;
     String operator+(const String& s) const;
     String operator+(StringFragment s) const;
diff --git a/src/sksl/ir/SkSLLayout.h b/src/sksl/ir/SkSLLayout.h
index 50c88b4..324c00d 100644
--- a/src/sksl/ir/SkSLLayout.h
+++ b/src/sksl/ir/SkSLLayout.h
@@ -38,7 +38,8 @@
         kBlendSupportHSLHue_Flag         = 1 << 15,
         kBlendSupportHSLSaturation_Flag  = 1 << 16,
         kBlendSupportHSLColor_Flag       = 1 << 17,
-        kBlendSupportHSLLuminosity_Flag  = 1 << 18
+        kBlendSupportHSLLuminosity_Flag  = 1 << 18,
+        kTracked_Flag                    = 1 << 19
     };
 
     enum Primitive {
@@ -264,6 +265,10 @@
             result += separator + "push_constant";
             separator = ", ";
         }
+        if (fFlags & kTracked_Flag) {
+            result += separator + "tracked";
+            separator = ", ";
+        }
         switch (fPrimitive) {
             case kPoints_Primitive:
                 result += separator + "points";
diff --git a/tests/SkSLFPTest.cpp b/tests/SkSLFPTest.cpp
index 20a17bb..510b51f 100644
--- a/tests/SkSLFPTest.cpp
+++ b/tests/SkSLFPTest.cpp
@@ -151,7 +151,7 @@
          "}",
          *SkSL::ShaderCapsFactory::Default(),
          {
-             "SkPoint point() const { return fPoint; }",
+             "const SkPoint& point() const { return fPoint; }",
              "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
              "return std::unique_ptr<GrFragmentProcessor>(new GrTest(point));",
              "GrTest(SkPoint point)",
@@ -181,6 +181,9 @@
          });
 }
 
+// SkSLFPInUniform tests the simplest plumbing case, default type, no tracking
+// with a setUniform template that supports inlining the value call with no
+// local variable.
 DEF_TEST(SkSLFPInUniform, r) {
     test(r,
          "in uniform half4 color;"
@@ -194,8 +197,100 @@
          {
             "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                          "kDefault_GrSLPrecision, \"color\");",
-            "const SkRect colorValue = _outer.color();",
-            "pdman.set4fv(fColorVar, 1, (float*) &colorValue);"
+            "pdman.set4fv(fColorVar, 1, reinterpret_cast<const float*>(&(_outer.color())));"
+         });
+}
+
+// As above, but tests in uniform's ability to override the default ctype.
+DEF_TEST(SkSLFPInUniformCType, r) {
+    test(r,
+         "layout(ctype=GrColor4f) in uniform half4 color;"
+         "void main() {"
+         "sk_OutColor = color;"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {
+             "static std::unique_ptr<GrFragmentProcessor> Make(GrColor4f color) {",
+         },
+         {
+            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                         "kDefault_GrSLPrecision, \"color\");",
+            "pdman.set4fv(fColorVar, 1, (_outer.color()).fRGBA);"
+         });
+}
+
+// Add state tracking to the default typed SkRect <-> half4 uniform. But since
+// it now has to track state, the value inlining previously done for the
+// setUniform call is removed in favor of a local variable.
+DEF_TEST(SkSLFPTrackedInUniform, r) {
+    test(r,
+         "layout(tracked) in uniform half4 color;"
+         "void main() {"
+         "sk_OutColor = color;"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {
+             "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
+         },
+         {
+            "SkRect fColorPrev = SkRect::MakeEmpty();",
+            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                         "kDefault_GrSLPrecision, \"color\");",
+            "const SkRect& colorValue = _outer.color();",
+            "if (fColorPrev.isEmpty() || fColorPrev != colorValue) {",
+            "fColorPrev = colorValue;",
+            "pdman.set4fv(fColorVar, 1, reinterpret_cast<const float*>(&colorValue));"
+         });
+}
+
+// Test the case where the template does not support variable inlining in
+// setUniform (i.e. it references the value multiple times).
+DEF_TEST(SkSLFPNonInlinedInUniform, r) {
+    test(r,
+         "in uniform half2 point;"
+         "void main() {"
+         "sk_OutColor = half4(point, point);"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {
+             "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
+         },
+         {
+            "fPointVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "
+                                                         "kDefault_GrSLPrecision, \"point\");",
+            "const SkPoint& pointValue = _outer.point();",
+            "pdman.set2f(fPointVar, pointValue.fX, pointValue.fY);"
+         });
+}
+
+// Test handling conditional uniforms (that use when= in layout), combined with
+// state tracking and custom ctypes to really put the code generation through its paces.
+DEF_TEST(SkSLFPConditionalInUniform, r) {
+    test(r,
+         "in bool test;"
+         "layout(ctype=GrColor4f, tracked, when=test) in uniform half4 color;"
+         "void main() {"
+         "  if (test) {"
+         "    sk_OutColor = color;"
+         "  } else {"
+         "    sk_OutColor = half4(1);"
+         "  }"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {
+             "static std::unique_ptr<GrFragmentProcessor> Make(bool test, GrColor4f color) {",
+         },
+         {
+            "GrColor4f fColorPrev = GrColor4f::kIllegalConstructor",
+            "auto test = _outer.test();",
+            "if (test) {",
+            "fColorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
+                                                         "kDefault_GrSLPrecision, \"color\");",
+            "if (fColorVar.isValid()) {",
+            "const GrColor4f& colorValue = _outer.color();",
+            "if (fColorPrev != colorValue) {",
+            "fColorPrev = colorValue;",
+            "pdman.set4fv(fColorVar, 1, colorValue.fRGBA);"
          });
 }