Revert "Revert "Start of rewrite of GrFragmentProcessor optimizations.""

This reverts commit 052fd5158f7f85e478a9f87c45fecaacf7d0f5f3.

Disables the test (of unused code) until platform-specific issues are addressed.

Change-Id: I7aa23a07954fccf382aa07d28afcbffb0bebcd6d
Reviewed-on: https://skia-review.googlesource.com/7656
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/experimental/SkPerlinNoiseShader2/SkPerlinNoiseShader2.cpp b/experimental/SkPerlinNoiseShader2/SkPerlinNoiseShader2.cpp
index de5cfab..c9fca4b 100644
--- a/experimental/SkPerlinNoiseShader2/SkPerlinNoiseShader2.cpp
+++ b/experimental/SkPerlinNoiseShader2/SkPerlinNoiseShader2.cpp
@@ -675,17 +675,17 @@
         inout->setToUnknown();
     }
 
-    GrPerlinNoise2Effect(SkPerlinNoiseShader2::Type type,
-                        int numOctaves, bool stitchTiles,
-                        SkPerlinNoiseShader2::PaintingData* paintingData,
-                        GrTexture* permutationsTexture, GrTexture* noiseTexture,
-                        const SkMatrix& matrix)
-      : fType(type)
-      , fNumOctaves(numOctaves)
-      , fStitchTiles(stitchTiles)
-      , fPermutationsSampler(permutationsTexture)
-      , fNoiseSampler(noiseTexture)
-      , fPaintingData(paintingData) {
+    GrPerlinNoise2Effect(SkPerlinNoiseShader2::Type type, int numOctaves, bool stitchTiles,
+                         SkPerlinNoiseShader2::PaintingData* paintingData,
+                         GrTexture* permutationsTexture, GrTexture* noiseTexture,
+                         const SkMatrix& matrix)
+            : INHERITED(kNone_OptimizationFlags)
+            , fType(type)
+            , fNumOctaves(numOctaves)
+            , fStitchTiles(stitchTiles)
+            , fPermutationsSampler(permutationsTexture)
+            , fNoiseSampler(noiseTexture)
+            , fPaintingData(paintingData) {
         this->initClassID<GrPerlinNoise2Effect>();
         this->addTextureSampler(&fPermutationsSampler);
         this->addTextureSampler(&fNoiseSampler);
@@ -703,7 +703,6 @@
     TextureSampler                      fNoiseSampler;
     SkPerlinNoiseShader2::PaintingData* fPaintingData;
 
-private:
     typedef GrFragmentProcessor INHERITED;
 };
 
@@ -1087,15 +1086,16 @@
         inout->setToUnknown();
     }
 
-    GrImprovedPerlinNoiseEffect(int octaves, SkScalar z, 
+    GrImprovedPerlinNoiseEffect(int octaves, SkScalar z,
                                 SkPerlinNoiseShader2::PaintingData* paintingData,
                                 GrTexture* permutationsTexture, GrTexture* gradientTexture,
                                 const SkMatrix& matrix)
-      : fOctaves(octaves)
-      , fZ(z)
-      , fPermutationsSampler(permutationsTexture)
-      , fGradientSampler(gradientTexture)
-      , fPaintingData(paintingData) {
+            : INHERITED(kNone_OptimizationFlags)
+            , fOctaves(octaves)
+            , fZ(z)
+            , fPermutationsSampler(permutationsTexture)
+            , fGradientSampler(gradientTexture)
+            , fPaintingData(paintingData) {
         this->initClassID<GrImprovedPerlinNoiseEffect>();
         this->addTextureSampler(&fPermutationsSampler);
         this->addTextureSampler(&fGradientSampler);
@@ -1112,7 +1112,6 @@
     TextureSampler                      fGradientSampler;
     SkPerlinNoiseShader2::PaintingData* fPaintingData;
 
-private:
     typedef GrFragmentProcessor INHERITED;
 };
 
diff --git a/include/gpu/GrColor.h b/include/gpu/GrColor.h
index cebceea..64368b9 100644
--- a/include/gpu/GrColor.h
+++ b/include/gpu/GrColor.h
@@ -213,6 +213,17 @@
         return GrColor4f(color.fR, color.fG, color.fB, color.fA);
     }
 
+    GrColor4f modulate(const GrColor4f& x) const {
+        return GrColor4f(fRGBA[0] * x.fRGBA[0],
+                         fRGBA[1] * x.fRGBA[1],
+                         fRGBA[2] * x.fRGBA[2],
+                         fRGBA[3] * x.fRGBA[3]);
+    }
+
+    GrColor4f mulByScalar(float x) const {
+        return GrColor4f(fRGBA[0] * x, fRGBA[1] * x, fRGBA[2] * x, fRGBA[3] * x);
+    }
+
     bool operator==(const GrColor4f& other) const {
         return
             fRGBA[0] == other.fRGBA[0] &&
@@ -240,6 +251,10 @@
         return GrColor4f(fRGBA[0], fRGBA[1], fRGBA[2], 1.0f);
     }
 
+    bool isOpaque() const {
+        return fRGBA[3] >= 1.f;  // just in case precision causes a superopaque value.
+    }
+
     GrColor4f premul() const {
         float a = fRGBA[3];
         return GrColor4f(fRGBA[0] * a, fRGBA[1] * a, fRGBA[2] * a, a);
diff --git a/include/gpu/GrFragmentProcessor.h b/include/gpu/GrFragmentProcessor.h
index 49cccea..0d41a26 100644
--- a/include/gpu/GrFragmentProcessor.h
+++ b/include/gpu/GrFragmentProcessor.h
@@ -64,11 +64,6 @@
      */
     static sk_sp<GrFragmentProcessor> RunInSeries(sk_sp<GrFragmentProcessor>*, int cnt);
 
-    GrFragmentProcessor()
-        : INHERITED()
-        , fUsesDistanceVectorField(false)
-        , fUsesLocalCoords(false) {}
-
     ~GrFragmentProcessor() override;
 
     GrGLSLFragmentProcessor* createGLSLInstance() const;
@@ -95,10 +90,42 @@
     const GrFragmentProcessor& childProcessor(int index) const { return *fChildProcessors[index]; }
 
     /** Do any of the coordtransforms for this processor require local coords? */
-    bool usesLocalCoords() const { return fUsesLocalCoords; }
+    bool usesLocalCoords() const { return SkToBool(fFlags & kUsesLocalCoords_Flag); }
 
     /** Does this FP need a vector to the nearest edge? */
-    bool usesDistanceVectorField() const { return fUsesDistanceVectorField; }
+    bool usesDistanceVectorField() const {
+        return SkToBool(fFlags & kUsesDistanceVectorField_Flag);
+    }
+
+    /**
+     * True if the processor's output is a modulation of its input color or alpha with a computed
+     * color or alpha in the 0..1 range. If true and the blend mode allows it we may fold coverage
+     * into the first color fragment processor's input.
+     */
+    bool modulatesInput() const { return SkToBool(fFlags & kModulatesInput_OptimizationFlag); }
+
+    /**
+     * If this is true then all opaque input colors to the processor produce opaque output colors.
+     */
+    bool preservesOpaqueInput() const {
+        return SkToBool(fFlags & kPreservesOpaqueInput_OptimizationFlag);
+    }
+
+    /**
+     * Tests whether given a constant input color the processor produces a constant output color
+     * (for all fragments). If true outputColor will contain the constant color produces for
+     * inputColor.
+     */
+    bool hasConstantOutputForConstantInput(GrColor4f inputColor, GrColor4f* outputColor) const {
+        if (fFlags & kConstantOutputForConstantInput_OptimizationFlag) {
+            *outputColor = this->constantOutputForConstantInput(inputColor);
+            return true;
+        }
+        return false;
+    }
+    bool hasConstantOutputForConstantInput() const {
+        return SkToBool(fFlags & kConstantOutputForConstantInput_OptimizationFlag);
+    }
 
     /** Returns true if this and other processor conservatively draw identically. It can only return
         true when the two processor are of the same subclass (i.e. they return the same object from
@@ -189,6 +216,36 @@
                                          &GrProcessor::textureSampler>;
 
 protected:
+    enum OptimizationFlags : uint32_t {
+        kNone_OptimizationFlags,
+        kModulatesInput_OptimizationFlag = 0x1,
+        kPreservesOpaqueInput_OptimizationFlag = 0x2,
+        kConstantOutputForConstantInput_OptimizationFlag = 0x4,
+        kAll_OptimizationFlags = kModulatesInput_OptimizationFlag |
+                                 kPreservesOpaqueInput_OptimizationFlag |
+                                 kConstantOutputForConstantInput_OptimizationFlag
+    };
+    GR_DECL_BITFIELD_OPS_FRIENDS(OptimizationFlags)
+
+    GrFragmentProcessor(OptimizationFlags optimizationFlags) : fFlags(optimizationFlags) {
+        SkASSERT((fFlags & ~kAll_OptimizationFlags) == 0);
+    }
+
+    OptimizationFlags optimizationFlags() const {
+        return static_cast<OptimizationFlags>(kAll_OptimizationFlags & fFlags);
+    }
+
+    /**
+     * This allows one subclass to access another subclass's implementation of
+     * constantOutputForConstantInput. It must only be called when
+     * hasConstantOutputForConstantInput() is known to be true.
+     */
+    static GrColor4f ConstantOutputForConstantInput(const GrFragmentProcessor& fp,
+                                                    GrColor4f input) {
+        SkASSERT(fp.hasConstantOutputForConstantInput());
+        return fp.constantOutputForConstantInput(input);
+    }
+
     /**
      * Fragment Processor subclasses call this from their constructor to register coordinate
      * transformations. Coord transforms provide a mechanism for a processor to receive coordinates
@@ -227,14 +284,20 @@
      */
     virtual void onComputeInvariantOutput(GrInvariantOutput* inout) const = 0;
 
-    /* Sub-classes should set this to true in their constructors if they need access to a distance
+    /**
+     * Sub-classes should call this in their constructors if they need access to a distance
      * vector field to the nearest edge
      */
-    bool fUsesDistanceVectorField;
+    void setWillUseDistanceVectorField() { fFlags |= kUsesDistanceVectorField_Flag; }
 
 private:
     void notifyRefCntIsZero() const final;
 
+    virtual GrColor4f constantOutputForConstantInput(GrColor4f /* inputColor */) const {
+        SkFAIL("Subclass must override this if advertising this optimization.");
+        return GrColor4f::TransparentBlack();
+    }
+
     /** Returns a new instance of the appropriate *GL* implementation class
         for the given GrFragmentProcessor; caller is responsible for deleting
         the object. */
@@ -253,7 +316,13 @@
 
     bool hasSameTransforms(const GrFragmentProcessor&) const;
 
-    bool                                       fUsesLocalCoords;
+    enum PrivateFlags {
+        kFirstPrivateFlag = kAll_OptimizationFlags + 1,
+        kUsesLocalCoords_Flag = kFirstPrivateFlag,
+        kUsesDistanceVectorField_Flag = kFirstPrivateFlag << 1,
+    };
+
+    mutable uint32_t fFlags = 0;
 
     SkSTArray<4, const GrCoordTransform*, true> fCoordTransforms;
 
@@ -261,9 +330,11 @@
      * This is not SkSTArray<1, sk_sp<GrFragmentProcessor>> because this class holds strong
      * references until notifyRefCntIsZero and then it holds pending executions.
      */
-    SkSTArray<1, GrFragmentProcessor*, true>    fChildProcessors;
+    SkSTArray<1, GrFragmentProcessor*, true> fChildProcessors;
 
     typedef GrProcessor INHERITED;
 };
 
+GR_MAKE_BITFIELD_OPS(GrFragmentProcessor::OptimizationFlags)
+
 #endif
diff --git a/include/gpu/GrProcessorUnitTest.h b/include/gpu/GrProcessorUnitTest.h
index 7cec307..a56db81 100644
--- a/include/gpu/GrProcessorUnitTest.h
+++ b/include/gpu/GrProcessorUnitTest.h
@@ -46,10 +46,8 @@
     GrProcessorTestData(SkRandom* random,
                         GrContext* context,
                         const GrRenderTargetContext* renderTargetContext,
-                        GrTexture* textures[2])
-        : fRandom(random)
-        , fContext(context)
-        , fRenderTargetContext(renderTargetContext) {
+                        GrTexture* const textures[2])
+            : fRandom(random), fContext(context), fRenderTargetContext(renderTargetContext) {
         fTextures[0] = textures[0];
         fTextures[1] = textures[1];
 
diff --git a/include/gpu/GrTypes.h b/include/gpu/GrTypes.h
index b3f1c19..8da7f71 100644
--- a/include/gpu/GrTypes.h
+++ b/include/gpu/GrTypes.h
@@ -19,56 +19,59 @@
  * bitfield.
  */
 #define GR_MAKE_BITFIELD_OPS(X) \
-    inline X operator | (X a, X b) { \
+    inline X operator |(X a, X b) { \
         return (X) (+a | +b); \
     } \
-    inline X& operator |= (X& a, X b) { \
+    inline X& operator |=(X& a, X b) { \
         return (a = a | b); \
     } \
-    \
-    inline X operator & (X a, X b) { \
+    inline X operator &(X a, X b) { \
+        return (X) (+a & +b); \
+    } \
+    inline X& operator &=(X& a, X b) { \
+        return (a = a & b); \
+    } \
+    template <typename T> \
+    inline X operator &(T a, X b) { \
         return (X) (+a & +b); \
     } \
     template <typename T> \
-    inline X operator & (T a, X b) { \
-        return (X) (+a & +b); \
-    } \
-    template <typename T> \
-    inline X operator & (X a, T b) { \
+    inline X operator &(X a, T b) { \
         return (X) (+a & +b); \
     } \
 
 #define GR_DECL_BITFIELD_OPS_FRIENDS(X) \
-    friend X operator | (X a, X b); \
-    friend X& operator |= (X& a, X b); \
+    friend X operator |(X a, X b); \
+    friend X& operator |=(X& a, X b); \
     \
-    friend X operator & (X a, X b); \
+    friend X operator &(X a, X b); \
+    friend X& operator &=(X& a, X b); \
     \
     template <typename T> \
-    friend X operator & (T a, X b); \
+    friend X operator &(T a, X b); \
     \
     template <typename T> \
-    friend X operator & (X a, T b); \
+    friend X operator &(X a, T b); \
 
 /**
  * Defines bitwise operators that make it possible to use an enum class as a
  * very basic bitfield.
  */
 #define GR_MAKE_BITFIELD_CLASS_OPS(X) \
-    inline X operator | (X a, X b) { \
+    inline X operator |(X a, X b) { \
         return (X) ((int)a | (int)b); \
     } \
-    inline X& operator |= (X& a, X b) { \
+    inline X& operator |=(X& a, X b) { \
         return (a = a | b); \
     } \
-    inline bool operator & (X a, X b) { \
+    inline bool operator &(X a, X b) { \
         return SkToBool((int)a & (int)b); \
     }
 
 #define GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(X) \
-    friend X operator | (X a, X b); \
-    friend X& operator |= (X& a, X b); \
-    friend bool operator & (X a, X b);
+    friend X operator |(X a, X b); \
+    friend X& operator |=(X& a, X b); \
+    friend bool operator &(X a, X b);
 
 ////////////////////////////////////////////////////////////////////////////////
 
diff --git a/include/gpu/effects/GrConstColorProcessor.h b/include/gpu/effects/GrConstColorProcessor.h
index 73ea9eb..b32421b 100644
--- a/include/gpu/effects/GrConstColorProcessor.h
+++ b/include/gpu/effects/GrConstColorProcessor.h
@@ -43,7 +43,19 @@
     InputMode inputMode() const { return fMode; }
 
 private:
-    GrConstColorProcessor(GrColor4f color, InputMode mode) : fColor(color), fMode(mode) {
+    static OptimizationFlags OptFlags(GrColor4f color, InputMode mode) {
+        OptimizationFlags flags = kConstantOutputForConstantInput_OptimizationFlag;
+        if (mode != kIgnore_InputMode) {
+            flags |= kModulatesInput_OptimizationFlag;
+        }
+        if (color.isOpaque()) {
+            flags |= kPreservesOpaqueInput_OptimizationFlag;
+        }
+        return flags;
+    }
+
+    GrConstColorProcessor(GrColor4f color, InputMode mode)
+            : INHERITED(OptFlags(color, mode)), fColor(color), fMode(mode) {
         this->initClassID<GrConstColorProcessor>();
     }
 
@@ -54,6 +66,7 @@
     bool onIsEqual(const GrFragmentProcessor&) const override;
 
     void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
+    GrColor4f constantOutputForConstantInput(GrColor4f input) const override;
 
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
 
diff --git a/src/core/SkColorMatrixFilterRowMajor255.cpp b/src/core/SkColorMatrixFilterRowMajor255.cpp
index 2bcc79e..167fd21 100644
--- a/src/core/SkColorMatrixFilterRowMajor255.cpp
+++ b/src/core/SkColorMatrixFilterRowMajor255.cpp
@@ -345,9 +345,10 @@
 
         typedef GrGLSLFragmentProcessor INHERITED;
     };
-
 private:
-    ColorMatrixEffect(const SkScalar matrix[20]) {
+    // We could implement the constant input->constant output optimization but haven't. Other
+    // optimizations would be matrix-dependent.
+    ColorMatrixEffect(const SkScalar matrix[20]) : INHERITED(kNone_OptimizationFlags) {
         memcpy(fMatrix, matrix, sizeof(SkScalar) * 20);
         this->initClassID<ColorMatrixEffect>();
     }
diff --git a/src/core/SkLightingShader.cpp b/src/core/SkLightingShader.cpp
index bb2828a..4ed0057 100644
--- a/src/core/SkLightingShader.cpp
+++ b/src/core/SkLightingShader.cpp
@@ -117,8 +117,8 @@
 // premul'd.
 class LightingFP : public GrFragmentProcessor {
 public:
-    LightingFP(sk_sp<GrFragmentProcessor> normalFP, sk_sp<SkLights> lights) {
-
+    LightingFP(sk_sp<GrFragmentProcessor> normalFP, sk_sp<SkLights> lights)
+            : INHERITED(kPreservesOpaqueInput_OptimizationFlag) {
         // fuse all ambient lights into a single one
         fAmbientColor = lights->ambientLightColor();
         for (int i = 0; i < lights->numLights(); ++i) {
@@ -263,6 +263,8 @@
 
     SkTArray<SkLights::Light> fDirectionalLights;
     SkColor3f                 fAmbientColor;
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 ////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkNormalBevelSource.cpp b/src/core/SkNormalBevelSource.cpp
index bcdb999..5d49253 100644
--- a/src/core/SkNormalBevelSource.cpp
+++ b/src/core/SkNormalBevelSource.cpp
@@ -30,12 +30,13 @@
 class NormalBevelFP : public GrFragmentProcessor {
 public:
     NormalBevelFP(SkNormalSource::BevelType bevelType, SkScalar bevelWidth, SkScalar bevelHeight)
-        : fBevelType(bevelType)
-        , fBevelWidth(bevelWidth)
-        , fBevelHeight(bevelHeight) {
+            : INHERITED(kNone_OptimizationFlags)
+            , fBevelType(bevelType)
+            , fBevelWidth(bevelWidth)
+            , fBevelHeight(bevelHeight) {
         this->initClassID<NormalBevelFP>();
 
-        fUsesDistanceVectorField = true;
+        this->setWillUseDistanceVectorField();
     }
 
     class GLSLNormalBevelFP : public GLSLNormalFP {
@@ -239,6 +240,8 @@
     SkNormalSource::BevelType fBevelType;
     SkScalar fBevelWidth;
     SkScalar fBevelHeight;
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 sk_sp<GrFragmentProcessor> SkNormalBevelSourceImpl::asFragmentProcessor(
diff --git a/src/core/SkNormalFlatSource.cpp b/src/core/SkNormalFlatSource.cpp
index 4fca57c..b4ed977 100644
--- a/src/core/SkNormalFlatSource.cpp
+++ b/src/core/SkNormalFlatSource.cpp
@@ -20,7 +20,7 @@
 
 class NormalFlatFP : public GrFragmentProcessor {
 public:
-    NormalFlatFP() {
+    NormalFlatFP() : INHERITED(kConstantOutputForConstantInput_OptimizationFlag) {
         this->initClassID<NormalFlatFP>();
     }
 
@@ -43,20 +43,23 @@
                            const GrProcessor& proc) override {}
     };
 
+    const char* name() const override { return "NormalFlatFP"; }
+
+private:
     void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
         GLSLNormalFlatFP::GenKey(*this, caps, b);
     }
-
-    const char* name() const override { return "NormalFlatFP"; }
-
     void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
         inout->setToUnknown();
     }
-
-private:
+    GrColor4f constantOutputForConstantInput(GrColor4f) const override {
+        return GrColor4f(0, 0, 1, 0);
+    }
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalFlatFP; }
 
     bool onIsEqual(const GrFragmentProcessor&) const override { return true; }
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 sk_sp<GrFragmentProcessor> SkNormalFlatSourceImpl::asFragmentProcessor(
diff --git a/src/core/SkNormalMapSource.cpp b/src/core/SkNormalMapSource.cpp
index 71e5565..3a9f9cf 100644
--- a/src/core/SkNormalMapSource.cpp
+++ b/src/core/SkNormalMapSource.cpp
@@ -26,7 +26,7 @@
 class NormalMapFP : public GrFragmentProcessor {
 public:
     NormalMapFP(sk_sp<GrFragmentProcessor> mapFP, const SkMatrix& invCTM)
-        : fInvCTM(invCTM) {
+            : INHERITED(kNone_OptimizationFlags), fInvCTM(invCTM) {
         this->registerChildProcessor(mapFP);
 
         this->initClassID<NormalMapFP>();
@@ -117,6 +117,8 @@
     }
 
     SkMatrix fInvCTM;
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 sk_sp<GrFragmentProcessor> SkNormalMapSourceImpl::asFragmentProcessor(
diff --git a/src/effects/GrAlphaThresholdFragmentProcessor.cpp b/src/effects/GrAlphaThresholdFragmentProcessor.cpp
index a259607..8f9e45d 100644
--- a/src/effects/GrAlphaThresholdFragmentProcessor.cpp
+++ b/src/effects/GrAlphaThresholdFragmentProcessor.cpp
@@ -32,6 +32,14 @@
                                                                 bounds));
 }
 
+inline GrFragmentProcessor::OptimizationFlags GrAlphaThresholdFragmentProcessor::OptFlags(float outerThreshold) {
+    if (outerThreshold >= 1.f) {
+        return kPreservesOpaqueInput_OptimizationFlag | kModulatesInput_OptimizationFlag;
+    } else {
+        return kModulatesInput_OptimizationFlag;
+    }
+}
+
 GrAlphaThresholdFragmentProcessor::GrAlphaThresholdFragmentProcessor(
                                                            GrTexture* texture,
                                                            sk_sp<GrColorSpaceXform> colorSpaceXform,
@@ -39,16 +47,17 @@
                                                            float innerThreshold,
                                                            float outerThreshold,
                                                            const SkIRect& bounds)
-    : fInnerThreshold(innerThreshold)
-    , fOuterThreshold(outerThreshold)
-    , fImageCoordTransform(SkMatrix::I(), texture, GrSamplerParams::kNone_FilterMode)
-    , fImageTextureSampler(texture)
-    , fColorSpaceXform(std::move(colorSpaceXform))
-    , fMaskCoordTransform(SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()),
-                                              SkIntToScalar(-bounds.y())),
-                          maskTexture,
-                          GrSamplerParams::kNone_FilterMode)
-    , fMaskTextureSampler(maskTexture) {
+        : INHERITED(OptFlags(outerThreshold))
+        , fInnerThreshold(innerThreshold)
+        , fOuterThreshold(outerThreshold)
+        , fImageCoordTransform(SkMatrix::I(), texture, GrSamplerParams::kNone_FilterMode)
+        , fImageTextureSampler(texture)
+        , fColorSpaceXform(std::move(colorSpaceXform))
+        , fMaskCoordTransform(
+                  SkMatrix::MakeTrans(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())),
+                  maskTexture,
+                  GrSamplerParams::kNone_FilterMode)
+        , fMaskTextureSampler(maskTexture) {
     this->initClassID<GrAlphaThresholdFragmentProcessor>();
     this->addCoordTransform(&fImageCoordTransform);
     this->addTextureSampler(&fImageTextureSampler);
diff --git a/src/effects/GrAlphaThresholdFragmentProcessor.h b/src/effects/GrAlphaThresholdFragmentProcessor.h
index f7a2491..f2c14c6 100644
--- a/src/effects/GrAlphaThresholdFragmentProcessor.h
+++ b/src/effects/GrAlphaThresholdFragmentProcessor.h
@@ -35,6 +35,8 @@
     GrColorSpaceXform* colorSpaceXform() const { return fColorSpaceXform.get(); }
 
 private:
+    static OptimizationFlags OptFlags(float outerThreshold);
+
     GrAlphaThresholdFragmentProcessor(GrTexture* texture,
                                       sk_sp<GrColorSpaceXform> colorSpaceXform,
                                       GrTexture* maskTexture,
diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp
index d3eb325..91d17d6 100644
--- a/src/effects/GrCircleBlurFragmentProcessor.cpp
+++ b/src/effects/GrCircleBlurFragmentProcessor.cpp
@@ -88,10 +88,11 @@
                                                              float textureRadius,
                                                              float solidRadius,
                                                              GrTexture* blurProfile)
-    : fCircle(circle)
-    , fSolidRadius(solidRadius)
-    , fTextureRadius(textureRadius)
-    , fBlurProfileSampler(blurProfile, GrSamplerParams::kBilerp_FilterMode) {
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fCircle(circle)
+        , fSolidRadius(solidRadius)
+        , fTextureRadius(textureRadius)
+        , fBlurProfileSampler(blurProfile, GrSamplerParams::kBilerp_FilterMode) {
     this->initClassID<GrCircleBlurFragmentProcessor>();
     this->addTextureSampler(&fBlurProfileSampler);
 }
diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp
index 8d80984..a9a2325 100644
--- a/src/effects/SkArithmeticImageFilter.cpp
+++ b/src/effects/SkArithmeticImageFilter.cpp
@@ -290,9 +290,15 @@
         inout->setToUnknown();
     }
 
+    // This could implement the const input -> const output optimization but it's unlikely to help.
     ArithmeticFP(float k1, float k2, float k3, float k4, bool enforcePMColor,
                  sk_sp<GrFragmentProcessor> dst)
-            : fK1(k1), fK2(k2), fK3(k3), fK4(k4), fEnforcePMColor(enforcePMColor) {
+            : INHERITED(kNone_OptimizationFlags)
+            , fK1(k1)
+            , fK2(k2)
+            , fK3(k3)
+            , fK4(k4)
+            , fEnforcePMColor(enforcePMColor) {
         this->initClassID<ArithmeticFP>();
         SkASSERT(dst);
         SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(dst));
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index b53fc41..bf54407 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -974,12 +974,13 @@
     return blurProfile;
 }
 
-GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile,
+GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture* blurProfile,
                                    GrSLPrecision precision)
-    : fRect(rect)
-    , fSigma(sigma)
-    , fBlurProfileSampler(blurProfile)
-    , fPrecision(precision) {
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fRect(rect)
+        , fSigma(sigma)
+        , fBlurProfileSampler(blurProfile)
+        , fPrecision(precision) {
     this->initClassID<GrRectBlurEffect>();
     this->addTextureSampler(&fBlurProfileSampler);
 }
@@ -1206,10 +1207,11 @@
     inout->mulByUnknownSingleComponent();
 }
 
-GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture *ninePatchTexture)
-    : fRRect(rrect),
-      fSigma(sigma),
-      fNinePatchSampler(ninePatchTexture) {
+GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture* ninePatchTexture)
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fRRect(rrect)
+        , fSigma(sigma)
+        , fNinePatchSampler(ninePatchTexture) {
     this->initClassID<GrRRectBlurEffect>();
     this->addTextureSampler(&fNinePatchSampler);
 }
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index d838e1e..cee47f4 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -487,24 +487,26 @@
 }
 
 GrDisplacementMapEffect::GrDisplacementMapEffect(
-                             SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
-                             SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
-                             const SkVector& scale,
-                             GrTexture* displacement,
-                             const SkMatrix& offsetMatrix,
-                             GrTexture* color,
-                             sk_sp<GrColorSpaceXform> colorSpaceXform,
-                             const SkISize& colorDimensions)
-    : fDisplacementTransform(offsetMatrix, displacement, GrSamplerParams::kNone_FilterMode)
-    , fDisplacementSampler(displacement)
-    , fColorTransform(color, GrSamplerParams::kNone_FilterMode)
-    , fDomain(color, GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions)),
-              GrTextureDomain::kDecal_Mode)
-    , fColorSampler(color)
-    , fColorSpaceXform(std::move(colorSpaceXform))
-    , fXChannelSelector(xChannelSelector)
-    , fYChannelSelector(yChannelSelector)
-    , fScale(scale) {
+        SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
+        SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
+        const SkVector& scale,
+        GrTexture* displacement,
+        const SkMatrix& offsetMatrix,
+        GrTexture* color,
+        sk_sp<GrColorSpaceXform> colorSpaceXform,
+        const SkISize& colorDimensions)
+        : INHERITED(GrPixelConfigIsOpaque(color->config()) ? kPreservesOpaqueInput_OptimizationFlag
+                                                           : kNone_OptimizationFlags)
+        , fDisplacementTransform(offsetMatrix, displacement, GrSamplerParams::kNone_FilterMode)
+        , fDisplacementSampler(displacement)
+        , fColorTransform(color, GrSamplerParams::kNone_FilterMode)
+        , fDomain(color, GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions)),
+                  GrTextureDomain::kDecal_Mode)
+        , fColorSampler(color)
+        , fColorSpaceXform(std::move(colorSpaceXform))
+        , fXChannelSelector(xChannelSelector)
+        , fYChannelSelector(yChannelSelector)
+        , fScale(scale) {
     this->initClassID<GrDisplacementMapEffect>();
     this->addCoordTransform(&fDisplacementTransform);
     this->addTextureSampler(&fDisplacementSampler);
diff --git a/src/effects/SkGaussianEdgeShader.cpp b/src/effects/SkGaussianEdgeShader.cpp
index 7405fd1..5bd7dca 100644
--- a/src/effects/SkGaussianEdgeShader.cpp
+++ b/src/effects/SkGaussianEdgeShader.cpp
@@ -63,11 +63,11 @@
 
 class GaussianEdgeFP : public GrFragmentProcessor {
 public:
-    GaussianEdgeFP() {
+    GaussianEdgeFP() : INHERITED(kNone_OptimizationFlags) {
         this->initClassID<GaussianEdgeFP>();
 
         // enable output of distance information for shape
-        fUsesDistanceVectorField = true;
+        this->setWillUseDistanceVectorField();
     }
 
     class GLSLGaussianEdgeFP : public GrGLSLFragmentProcessor {
@@ -123,6 +123,8 @@
     }
 
     bool onIsEqual(const GrFragmentProcessor& proc) const override { return true; }
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 ////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index da4dcb8..3a03a17 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -1625,7 +1625,7 @@
 
 }
 
-class GrGLLightingEffect  : public GrGLSLFragmentProcessor {
+class GrGLLightingEffect : public GrGLSLFragmentProcessor {
 public:
     GrGLLightingEffect() : fLight(nullptr) { }
     virtual ~GrGLLightingEffect() { delete fLight; }
@@ -1702,12 +1702,13 @@
                                    const SkMatrix& matrix,
                                    BoundaryMode boundaryMode,
                                    const SkIRect* srcBounds)
-    : INHERITED(texture, nullptr, SkMatrix::I())
-    , fLight(light)
-    , fSurfaceScale(surfaceScale)
-    , fFilterMatrix(matrix)
-    , fBoundaryMode(boundaryMode)
-    , fDomain(create_domain(texture, srcBounds, GrTextureDomain::kDecal_Mode)) {
+        // Perhaps this could advertise the opaque or modulating optimizations?
+        : INHERITED(texture, nullptr, SkMatrix::I(), kNone_OptimizationFlags)
+        , fLight(light)
+        , fSurfaceScale(surfaceScale)
+        , fFilterMatrix(matrix)
+        , fBoundaryMode(boundaryMode)
+        , fDomain(create_domain(texture, srcBounds, GrTextureDomain::kDecal_Mode)) {
     fLight->ref();
 }
 
diff --git a/src/effects/SkLumaColorFilter.cpp b/src/effects/SkLumaColorFilter.cpp
index a9516af..1792437 100644
--- a/src/effects/SkLumaColorFilter.cpp
+++ b/src/effects/SkLumaColorFilter.cpp
@@ -98,7 +98,7 @@
     };
 
 private:
-    LumaColorFilterEffect() {
+    LumaColorFilterEffect() : INHERITED(kConstantOutputForConstantInput_OptimizationFlag) {
         this->initClassID<LumaColorFilterEffect>();
     }
 
@@ -117,6 +117,14 @@
         // The output is always black. The alpha value for the color passed in is arbitrary.
         inout->setToOther(kRGB_GrColorComponentFlags, GrColorPackRGBA(0, 0, 0, 0));
     }
+    GrColor4f constantOutputForConstantInput(GrColor4f input) const override {
+        float luma = SK_ITU_BT709_LUM_COEFF_R * input.fRGBA[0] +
+                     SK_ITU_BT709_LUM_COEFF_G * input.fRGBA[1] +
+                     SK_ITU_BT709_LUM_COEFF_B * input.fRGBA[2];
+        return GrColor4f(0, 0, 0, luma);
+    }
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 sk_sp<GrFragmentProcessor> SkLumaColorFilter::asFragmentProcessor(GrContext*, SkColorSpace*) const {
diff --git a/src/effects/SkMagnifierImageFilter.cpp b/src/effects/SkMagnifierImageFilter.cpp
index c8b32ae..49f4fdd 100644
--- a/src/effects/SkMagnifierImageFilter.cpp
+++ b/src/effects/SkMagnifierImageFilter.cpp
@@ -27,7 +27,6 @@
 #include "../private/GrGLSL.h"
 
 class GrMagnifierEffect : public GrSingleTextureEffect {
-
 public:
     static sk_sp<GrFragmentProcessor> Make(GrTexture* texture,
                                            sk_sp<GrColorSpaceXform> colorSpaceXform,
@@ -71,14 +70,15 @@
                       float yInvZoom,
                       float xInvInset,
                       float yInvInset)
-        : INHERITED(texture, std::move(colorSpaceXform), SkMatrix::I())
-        , fBounds(bounds)
-        , fXOffset(xOffset)
-        , fYOffset(yOffset)
-        , fXInvZoom(xInvZoom)
-        , fYInvZoom(yInvZoom)
-        , fXInvInset(xInvInset)
-        , fYInvInset(yInvInset) {
+            : INHERITED(texture, std::move(colorSpaceXform), SkMatrix::I(),
+                        ModulationFlags(texture->config()))
+            , fBounds(bounds)
+            , fXOffset(xOffset)
+            , fYOffset(yOffset)
+            , fXInvZoom(xInvZoom)
+            , fYInvZoom(yInvZoom)
+            , fXInvInset(xInvInset)
+            , fYInvInset(yInvInset) {
         this->initClassID<GrMagnifierEffect>();
     }
 
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index 6e2a805..febde20 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -140,9 +140,7 @@
  * color.
  */
 class GrMorphologyEffect : public Gr1DKernelEffect {
-
 public:
-
     enum MorphologyType {
         kErode_MorphologyType,
         kDilate_MorphologyType,
@@ -327,9 +325,9 @@
                                        Direction direction,
                                        int radius,
                                        MorphologyType type)
-    : INHERITED(texture, direction, radius)
-    , fType(type)
-    , fUseRange(false) {
+        : INHERITED(texture, direction, radius, ModulationFlags(texture->config()))
+        , fType(type)
+        , fUseRange(false) {
     this->initClassID<GrMorphologyEffect>();
 }
 
@@ -338,9 +336,9 @@
                                        int radius,
                                        MorphologyType type,
                                        const float range[2])
-    : INHERITED(texture, direction, radius)
-    , fType(type)
-    , fUseRange(true) {
+        : INHERITED(texture, direction, radius, ModulationFlags(texture->config()))
+        , fType(type)
+        , fUseRange(true) {
     this->initClassID<GrMorphologyEffect>();
     fRange[0] = range[0];
     fRange[1] = range[1];
diff --git a/src/effects/SkOverdrawColorFilter.cpp b/src/effects/SkOverdrawColorFilter.cpp
index dea4f8b..243ef77 100644
--- a/src/effects/SkOverdrawColorFilter.cpp
+++ b/src/effects/SkOverdrawColorFilter.cpp
@@ -59,7 +59,6 @@
     static sk_sp<GrFragmentProcessor> Make(const SkPMColor* colors);
 
     const char* name() const override { return "Overdraw"; }
-
 private:
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
@@ -105,7 +104,10 @@
     return sk_sp<OverdrawFragmentProcessor>(new OverdrawFragmentProcessor(grColors));
 }
 
-OverdrawFragmentProcessor::OverdrawFragmentProcessor(const GrColor4f* colors) {
+// This could implement the constant input -> constant output optimization, but we don't really
+// care given how this is used.
+OverdrawFragmentProcessor::OverdrawFragmentProcessor(const GrColor4f* colors)
+        : INHERITED(kNone_OptimizationFlags) {
     this->initClassID<OverdrawFragmentProcessor>();
     memcpy(fColors, colors, SkOverdrawColorFilter::kNumColors * sizeof(GrColor4f));
 }
diff --git a/src/effects/SkPerlinNoiseShader.cpp b/src/effects/SkPerlinNoiseShader.cpp
index 3798a40..450fcaa 100644
--- a/src/effects/SkPerlinNoiseShader.cpp
+++ b/src/effects/SkPerlinNoiseShader.cpp
@@ -538,18 +538,18 @@
         inout->setToUnknown();
     }
 
-    GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type,
-                        int numOctaves, bool stitchTiles,
+    GrPerlinNoiseEffect(SkPerlinNoiseShader::Type type, int numOctaves, bool stitchTiles,
                         SkPerlinNoiseShader::PaintingData* paintingData,
                         GrTexture* permutationsTexture, GrTexture* noiseTexture,
                         const SkMatrix& matrix)
-      : fType(type)
-      , fCoordTransform(matrix)
-      , fNumOctaves(numOctaves)
-      , fStitchTiles(stitchTiles)
-      , fPermutationsSampler(permutationsTexture)
-      , fNoiseSampler(noiseTexture)
-      , fPaintingData(paintingData) {
+            : INHERITED(kNone_OptimizationFlags)
+            , fType(type)
+            , fCoordTransform(matrix)
+            , fNumOctaves(numOctaves)
+            , fStitchTiles(stitchTiles)
+            , fPermutationsSampler(permutationsTexture)
+            , fNoiseSampler(noiseTexture)
+            , fPaintingData(paintingData) {
         this->initClassID<GrPerlinNoiseEffect>();
         this->addTextureSampler(&fPermutationsSampler);
         this->addTextureSampler(&fNoiseSampler);
diff --git a/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
index 5da3ed1..223887b 100644
--- a/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
+++ b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
@@ -207,9 +207,7 @@
     };
 
     RRectsGaussianEdgeFP(const SkRRect& first, const SkRRect& second, SkScalar radius)
-        : fFirst(first)
-        , fSecond(second)
-        , fRadius(radius) {
+            : INHERITED(kNone_OptimizationFlags), fFirst(first), fSecond(second), fRadius(radius) {
         this->initClassID<RRectsGaussianEdgeFP>();
 
         fFirstMode = ComputeMode(fFirst);
diff --git a/src/effects/SkTableColorFilter.cpp b/src/effects/SkTableColorFilter.cpp
index 1fd4c90..9198460 100644
--- a/src/effects/SkTableColorFilter.cpp
+++ b/src/effects/SkTableColorFilter.cpp
@@ -512,10 +512,11 @@
 
 ColorTableEffect::ColorTableEffect(GrTexture* texture, GrTextureStripAtlas* atlas, int row,
                                    unsigned flags)
-    : fTextureSampler(texture)
-    , fFlags(flags)
-    , fAtlas(atlas)
-    , fRow(row) {
+        : INHERITED(kNone_OptimizationFlags)  // Not bothering with table-specific optimizations.
+        , fTextureSampler(texture)
+        , fFlags(flags)
+        , fAtlas(atlas)
+        , fRow(row) {
     this->initClassID<ColorTableEffect>();
     this->addTextureSampler(&fTextureSampler);
 }
diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp
index b1d3e13..9a3438e 100644
--- a/src/effects/gradients/SkGradientShader.cpp
+++ b/src/effects/gradients/SkGradientShader.cpp
@@ -1572,7 +1572,13 @@
 
 /////////////////////////////////////////////////////////////////////
 
-GrGradientEffect::GrGradientEffect(const CreateArgs& args) {
+inline GrFragmentProcessor::OptimizationFlags GrGradientEffect::OptFlags(bool isOpaque) {
+    return isOpaque ? kPreservesOpaqueInput_OptimizationFlag | kModulatesInput_OptimizationFlag
+                    : kModulatesInput_OptimizationFlag;
+}
+
+GrGradientEffect::GrGradientEffect(const CreateArgs& args, bool isOpaque)
+        : INHERITED(OptFlags(isOpaque)) {
     const SkGradientShaderBase& shader(*args.fShader);
 
     fIsOpaque = shader.isOpaque();
diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h
index 48ccbd5..6f46698 100644
--- a/src/effects/gradients/SkGradientShaderPriv.h
+++ b/src/effects/gradients/SkGradientShaderPriv.h
@@ -352,8 +352,6 @@
 
     class GLSLProcessor;
 
-    GrGradientEffect(const CreateArgs&);
-
     virtual ~GrGradientEffect();
 
     bool useAtlas() const { return SkToBool(-1 != fRow); }
@@ -402,6 +400,8 @@
     }
 
 protected:
+    GrGradientEffect(const CreateArgs&, bool isOpaque);
+
     /** Helper struct that stores (and populates) parameters to construct a random gradient.
         If fUseColors4f is true, then the SkColor4f factory should be called, with fColors4f and
         fColorSpace. Otherwise, the SkColor factory should be called, with fColors. fColorCount
@@ -430,6 +430,8 @@
     const GrCoordTransform& getCoordTransform() const { return fCoordTransform; }
 
 private:
+    static OptimizationFlags OptFlags(bool isOpaque);
+
     // If we're in legacy mode, then fColors will be populated. If we're gamma-correct, then
     // fColors4f and fColorSpaceXform will be populated.
     SkTDArray<SkColor>       fColors;
diff --git a/src/effects/gradients/SkLinearGradient.cpp b/src/effects/gradients/SkLinearGradient.cpp
index aab1ac7..07418e8 100644
--- a/src/effects/gradients/SkLinearGradient.cpp
+++ b/src/effects/gradients/SkLinearGradient.cpp
@@ -426,8 +426,7 @@
     const char* name() const override { return "Linear Gradient"; }
 
 private:
-    GrLinearGradient(const CreateArgs& args)
-        : INHERITED(args) {
+    GrLinearGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) {
         this->initClassID<GrLinearGradient>();
     }
 
diff --git a/src/effects/gradients/SkRadialGradient.cpp b/src/effects/gradients/SkRadialGradient.cpp
index 887410a..4eb3e6d 100644
--- a/src/effects/gradients/SkRadialGradient.cpp
+++ b/src/effects/gradients/SkRadialGradient.cpp
@@ -256,8 +256,7 @@
     const char* name() const override { return "Radial Gradient"; }
 
 private:
-    GrRadialGradient(const CreateArgs& args)
-        : INHERITED(args) {
+    GrRadialGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) {
         this->initClassID<GrRadialGradient>();
     }
 
diff --git a/src/effects/gradients/SkSweepGradient.cpp b/src/effects/gradients/SkSweepGradient.cpp
index 64778c3..7e93a21 100644
--- a/src/effects/gradients/SkSweepGradient.cpp
+++ b/src/effects/gradients/SkSweepGradient.cpp
@@ -138,8 +138,7 @@
     const char* name() const override { return "Sweep Gradient"; }
 
 private:
-    GrSweepGradient(const CreateArgs& args)
-    : INHERITED(args) {
+    GrSweepGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) {
         this->initClassID<GrSweepGradient>();
     }
 
diff --git a/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp b/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp
index 5f26dcd..75b1cf7 100644
--- a/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp
+++ b/src/effects/gradients/SkTwoPointConicalGradient_gpu.cpp
@@ -92,7 +92,7 @@
     }
 
     Edge2PtConicalEffect(const CreateArgs& args)
-        : INHERITED(args) {
+            : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) {
         const SkTwoPointConicalGradient& shader =
             *static_cast<const SkTwoPointConicalGradient*>(args.fShader);
         fCenterX1 = shader.getCenterX1();
@@ -398,10 +398,15 @@
                 this->fIsFlipped == s.fIsFlipped);
     }
 
+    static bool IsFlipped(const CreateArgs& args) {
+        // eww.
+        return static_cast<const SkTwoPointConicalGradient*>(args.fShader)->isFlippedGrad();
+    }
+
     FocalOutside2PtConicalEffect(const CreateArgs& args, SkScalar focalX)
-    : INHERITED(args)
-    , fFocalX(focalX)
-    , fIsFlipped(static_cast<const SkTwoPointConicalGradient*>(args.fShader)->isFlippedGrad()) {
+            : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */)
+            , fFocalX(focalX)
+            , fIsFlipped(IsFlipped(args)) {
         this->initClassID<FocalOutside2PtConicalEffect>();
     }
 
@@ -413,7 +418,7 @@
     typedef GrGradientEffect INHERITED;
 };
 
-class FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor 
+class FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor
     : public GrGradientEffect::GLSLProcessor {
 public:
     GLSLFocalOutside2PtConicalProcessor(const GrProcessor&);
@@ -606,7 +611,7 @@
     }
 
     FocalInside2PtConicalEffect(const CreateArgs& args, SkScalar focalX)
-        : INHERITED(args), fFocalX(focalX) {
+            : INHERITED(args, args.fShader->colorsAreOpaque()), fFocalX(focalX) {
         this->initClassID<FocalInside2PtConicalEffect>();
     }
 
@@ -847,7 +852,7 @@
     }
 
     CircleInside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info)
-        : INHERITED(args), fInfo(info) {
+            : INHERITED(args, args.fShader->colorsAreOpaque()), fInfo(info) {
         this->initClassID<CircleInside2PtConicalEffect>();
     }
 
@@ -1064,7 +1069,8 @@
     }
 
     CircleOutside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info)
-        : INHERITED(args), fInfo(info) {
+            : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */)
+            , fInfo(info) {
         this->initClassID<CircleOutside2PtConicalEffect>();
         const SkTwoPointConicalGradient& shader =
             *static_cast<const SkTwoPointConicalGradient*>(args.fShader);
diff --git a/src/effects/shadows/SkAmbientShadowMaskFilter.cpp b/src/effects/shadows/SkAmbientShadowMaskFilter.cpp
index d92a7b5..e7d8d31 100755
--- a/src/effects/shadows/SkAmbientShadowMaskFilter.cpp
+++ b/src/effects/shadows/SkAmbientShadowMaskFilter.cpp
@@ -140,9 +140,7 @@
 //
 class ShadowEdgeFP : public GrFragmentProcessor {
 public:
-    ShadowEdgeFP() {
-        this->initClassID<ShadowEdgeFP>();
-    }
+    ShadowEdgeFP() : INHERITED(kNone_OptimizationFlags) { this->initClassID<ShadowEdgeFP>(); }
 
     class GLSLShadowEdgeFP : public GrGLSLFragmentProcessor {
     public:
@@ -179,6 +177,8 @@
     }
 
     bool onIsEqual(const GrFragmentProcessor& proc) const override { return true; }
+
+    typedef GrFragmentProcessor INHERITED;
 };
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 0ad144a..f294ac6 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -58,7 +58,7 @@
 
 void GrFragmentProcessor::addCoordTransform(const GrCoordTransform* transform) {
     fCoordTransforms.push_back(transform);
-    fUsesLocalCoords = true;
+    fFlags |= kUsesLocalCoords_Flag;
     SkDEBUGCODE(transform->setInProcessor();)
 }
 
@@ -66,10 +66,10 @@
     this->combineRequiredFeatures(*child);
 
     if (child->usesLocalCoords()) {
-        fUsesLocalCoords = true;
+        fFlags |= kUsesLocalCoords_Flag;
     }
     if (child->usesDistanceVectorField()) {
-        fUsesDistanceVectorField = true;
+        fFlags |= kUsesDistanceVectorField_Flag;
     }
 
     int index = fChildProcessors.count();
@@ -111,12 +111,13 @@
 
     class PremulInputFragmentProcessor : public GrFragmentProcessor {
     public:
-        PremulInputFragmentProcessor() {
+        PremulInputFragmentProcessor()
+                : INHERITED(kPreservesOpaqueInput_OptimizationFlag |
+                            kConstantOutputForConstantInput_OptimizationFlag) {
             this->initClassID<PremulInputFragmentProcessor>();
         }
 
         const char* name() const override { return "PremultiplyInput"; }
-
     private:
         GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
             class GLFP : public GrGLSLFragmentProcessor {
@@ -139,6 +140,11 @@
         void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
             inout->premulFourChannelColor();
         }
+        GrColor4f constantOutputForConstantInput(GrColor4f input) const override {
+            return input.premul();
+        }
+
+        typedef GrFragmentProcessor INHERITED;
     };
     if (!fp) {
         return nullptr;
@@ -152,7 +158,8 @@
 
     class PremulFragmentProcessor : public GrFragmentProcessor {
     public:
-        PremulFragmentProcessor(sk_sp<GrFragmentProcessor> processor) {
+        PremulFragmentProcessor(sk_sp<GrFragmentProcessor> processor)
+                : INHERITED(OptFlags(processor.get())) {
             this->initClassID<PremulFragmentProcessor>();
             this->registerChildProcessor(processor);
         }
@@ -178,6 +185,16 @@
 
         bool onIsEqual(const GrFragmentProcessor&) const override { return true; }
 
+        static OptimizationFlags OptFlags(const GrFragmentProcessor* inner) {
+            OptimizationFlags flags = kNone_OptimizationFlags;
+            if (inner->preservesOpaqueInput()) {
+                flags |= kPreservesOpaqueInput_OptimizationFlag;
+            }
+            if (inner->hasConstantOutputForConstantInput()) {
+                flags |= kConstantOutputForConstantInput_OptimizationFlag;
+            }
+            return flags;
+        }
         void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
             // TODO: Add a helper to GrInvariantOutput that handles multiplying by color with flags?
             if (!(inout->validFlags() & kA_GrColorComponentFlag)) {
@@ -210,6 +227,16 @@
             }
             inout->setToOther(commonFlags, color);
         }
+        GrColor4f constantOutputForConstantInput(GrColor4f input) const override {
+            GrColor4f childColor = ConstantOutputForConstantInput(this->childProcessor(0),
+                                                                  GrColor4f::OpaqueWhite());
+            return GrColor4f(input.fRGBA[3] * input.fRGBA[0] * childColor.fRGBA[0],
+                             input.fRGBA[3] * input.fRGBA[1] * childColor.fRGBA[1],
+                             input.fRGBA[3] * input.fRGBA[2] * childColor.fRGBA[2],
+                             input.fRGBA[3] * childColor.fRGBA[3]);
+        }
+
+        typedef GrFragmentProcessor INHERITED;
     };
     if (!fp) {
         return nullptr;
@@ -224,7 +251,7 @@
     class ReplaceInputFragmentProcessor : public GrFragmentProcessor {
     public:
         ReplaceInputFragmentProcessor(sk_sp<GrFragmentProcessor> child, GrColor4f color)
-            : fColor(color) {
+                : INHERITED(OptFlags(child.get(), color)), fColor(color) {
             this->initClassID<ReplaceInputFragmentProcessor>();
             this->registerChildProcessor(std::move(child));
         }
@@ -264,6 +291,18 @@
         }
 
     private:
+        static OptimizationFlags OptFlags(const GrFragmentProcessor* child, GrColor4f color) {
+            OptimizationFlags childFlags = child->optimizationFlags();
+            OptimizationFlags flags = kNone_OptimizationFlags;
+            if (childFlags & kConstantOutputForConstantInput_OptimizationFlag) {
+                flags |= kConstantOutputForConstantInput_OptimizationFlag;
+            }
+            if ((childFlags & kPreservesOpaqueInput_OptimizationFlag) && color.isOpaque()) {
+                flags |= kPreservesOpaqueInput_OptimizationFlag;
+            }
+            return flags;
+        }
+
         void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override
         {}
 
@@ -276,7 +315,13 @@
             this->childProcessor(0).computeInvariantOutput(inout);
         }
 
+        GrColor4f constantOutputForConstantInput(GrColor4f) const override {
+            return ConstantOutputForConstantInput(this->childProcessor(0), fColor);
+        }
+
         GrColor4f fColor;
+
+        typedef GrFragmentProcessor INHERITED;
     };
 
     GrInvariantOutput childOut(0x0, kNone_GrColorComponentFlags);
@@ -288,7 +333,8 @@
                                                             int cnt) {
     class SeriesFragmentProcessor : public GrFragmentProcessor {
     public:
-        SeriesFragmentProcessor(sk_sp<GrFragmentProcessor>* children, int cnt){
+        SeriesFragmentProcessor(sk_sp<GrFragmentProcessor>* children, int cnt)
+                : INHERITED(OptFlags(children, cnt)) {
             SkASSERT(cnt > 1);
             this->initClassID<SeriesFragmentProcessor>();
             for (int i = 0; i < cnt; ++i) {
@@ -317,8 +363,14 @@
             };
             return new GLFP;
         }
-
     private:
+        static OptimizationFlags OptFlags(sk_sp<GrFragmentProcessor>* children, int cnt) {
+            OptimizationFlags flags = kAll_OptimizationFlags;
+            for (int i = 0; i < cnt && flags != kNone_OptimizationFlags; ++i) {
+                flags &= children[i]->optimizationFlags();
+            }
+            return flags;
+        }
         void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
 
         bool onIsEqual(const GrFragmentProcessor&) const override { return true; }
@@ -328,6 +380,15 @@
                 this->childProcessor(i).computeInvariantOutput(inout);
             }
         }
+        GrColor4f constantOutputForConstantInput(GrColor4f color) const override {
+            int childCnt = this->numChildProcessors();
+            for (int i = 0; i < childCnt; ++i) {
+                color = ConstantOutputForConstantInput(this->childProcessor(i), color);
+            }
+            return color;
+        }
+
+        typedef GrFragmentProcessor INHERITED;
     };
 
     if (!cnt) {
diff --git a/src/gpu/SkGrPriv.h b/src/gpu/SkGrPriv.h
index a4e3976..9e2aad0 100644
--- a/src/gpu/SkGrPriv.h
+++ b/src/gpu/SkGrPriv.h
@@ -8,10 +8,11 @@
 #ifndef SkGrPriv_DEFINED
 #define SkGrPriv_DEFINED
 
-#include "GrTypes.h"
 #include "GrBlend.h"
+#include "GrTypes.h"
 #include "SkImageInfo.h"
 #include "SkMatrix.h"
+#include "SkPM4f.h"
 #include "SkXfermodePriv.h"
 
 class GrCaps;
@@ -102,6 +103,21 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
+static inline SkPM4f GrColor4fToSkPM4f(const GrColor4f& c) {
+    SkPM4f pm4f;
+    pm4f.fVec[SkPM4f::R] = c.fRGBA[0];
+    pm4f.fVec[SkPM4f::G] = c.fRGBA[1];
+    pm4f.fVec[SkPM4f::B] = c.fRGBA[2];
+    pm4f.fVec[SkPM4f::A] = c.fRGBA[3];
+    return pm4f;
+}
+
+static inline GrColor4f SkPM4fToGrColor4f(const SkPM4f& c) {
+    return GrColor4f{c.r(), c.g(), c.b(), c.a()};
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 GrSurfaceDesc GrImageInfoToSurfaceDesc(const SkImageInfo&, const GrCaps&);
 
 bool GrPixelConfigToColorType(GrPixelConfig, SkColorType*);
diff --git a/src/gpu/effects/Gr1DKernelEffect.h b/src/gpu/effects/Gr1DKernelEffect.h
index 29e0e5d..67cc771 100644
--- a/src/gpu/effects/Gr1DKernelEffect.h
+++ b/src/gpu/effects/Gr1DKernelEffect.h
@@ -28,12 +28,11 @@
         kY_Direction,
     };
 
-    Gr1DKernelEffect(GrTexture* texture,
-                     Direction direction,
-                     int radius)
-        : INHERITED(texture, nullptr, SkMatrix::I())
-        , fDirection(direction)
-        , fRadius(radius) {}
+    Gr1DKernelEffect(GrTexture* texture, Direction direction, int radius,
+                     OptimizationFlags optFlags)
+            : INHERITED(texture, nullptr, SkMatrix::I(), optFlags)
+            , fDirection(direction)
+            , fRadius(radius) {}
 
     virtual ~Gr1DKernelEffect() {}
 
@@ -51,7 +50,6 @@
     }
 
 private:
-
     Direction       fDirection;
     int             fRadius;
 
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index 07d1c53..cf3d2b5 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -132,21 +132,23 @@
 
 GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
                                  sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                 const SkMatrix &matrix,
+                                 const SkMatrix& matrix,
                                  const SkShader::TileMode tileModes[2])
-  : INHERITED(texture, std::move(colorSpaceXform), matrix,
-              GrSamplerParams(tileModes, GrSamplerParams::kNone_FilterMode))
-  , fDomain(GrTextureDomain::IgnoredDomain()) {
+        : INHERITED(texture, std::move(colorSpaceXform), matrix,
+                    GrSamplerParams(tileModes, GrSamplerParams::kNone_FilterMode),
+                    ModulationFlags(texture->config()))
+        , fDomain(GrTextureDomain::IgnoredDomain()) {
     this->initClassID<GrBicubicEffect>();
 }
 
 GrBicubicEffect::GrBicubicEffect(GrTexture* texture,
                                  sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                 const SkMatrix &matrix,
+                                 const SkMatrix& matrix,
                                  const SkRect& domain)
-  : INHERITED(texture, std::move(colorSpaceXform), matrix,
-              GrSamplerParams(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode))
-  , fDomain(texture, domain, GrTextureDomain::kClamp_Mode) {
+        : INHERITED(texture, std::move(colorSpaceXform), matrix,
+                    GrSamplerParams(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode),
+                    ModulationFlags(texture->config()))
+        , fDomain(texture, domain, GrTextureDomain::kClamp_Mode) {
     this->initClassID<GrBicubicEffect>();
 }
 
diff --git a/src/gpu/effects/GrConfigConversionEffect.cpp b/src/gpu/effects/GrConfigConversionEffect.cpp
index 2380ad3..74cf2ca 100644
--- a/src/gpu/effects/GrConfigConversionEffect.cpp
+++ b/src/gpu/effects/GrConfigConversionEffect.cpp
@@ -98,9 +98,9 @@
                                                    const GrSwizzle& swizzle,
                                                    PMConversion pmConversion,
                                                    const SkMatrix& matrix)
-    : INHERITED(texture, nullptr, matrix)
-    , fSwizzle(swizzle)
-    , fPMConversion(pmConversion) {
+        : INHERITED(texture, nullptr, matrix, ModulationFlags(texture->config()))
+        , fSwizzle(swizzle)
+        , fPMConversion(pmConversion) {
     this->initClassID<GrConfigConversionEffect>();
     // We expect to get here with non-BGRA/RGBA only if we're doing not doing a premul/unpremul
     // conversion.
@@ -116,7 +116,7 @@
                                                    const GrSwizzle& swizzle,
                                                    PMConversion pmConversion,
                                                    const SkMatrix& matrix)
-    : INHERITED(context, proxy, nullptr, matrix)
+    : INHERITED(context, ModulationFlags(proxy->config()), proxy, nullptr, matrix)
     , fSwizzle(swizzle)
     , fPMConversion(pmConversion) {
     this->initClassID<GrConfigConversionEffect>();
diff --git a/src/gpu/effects/GrConfigConversionEffect.h b/src/gpu/effects/GrConfigConversionEffect.h
index 46180bb..151365b 100644
--- a/src/gpu/effects/GrConfigConversionEffect.h
+++ b/src/gpu/effects/GrConfigConversionEffect.h
@@ -52,7 +52,6 @@
     static void TestForPreservingPMConversions(GrContext* context,
                                                PMConversion* PMToUPMRule,
                                                PMConversion* UPMToPMRule);
-
 private:
     GrConfigConversionEffect(GrTexture*, const GrSwizzle&, PMConversion, const SkMatrix& matrix);
 
diff --git a/src/gpu/effects/GrConstColorProcessor.cpp b/src/gpu/effects/GrConstColorProcessor.cpp
index e656879..3875ffc 100644
--- a/src/gpu/effects/GrConstColorProcessor.cpp
+++ b/src/gpu/effects/GrConstColorProcessor.cpp
@@ -85,6 +85,19 @@
     }
 }
 
+GrColor4f GrConstColorProcessor::constantOutputForConstantInput(GrColor4f input) const {
+    switch (fMode) {
+        case kIgnore_InputMode:
+            return fColor;
+        case kModulateA_InputMode:
+            return fColor.mulByScalar(input.fRGBA[3]);
+        case kModulateRGBA_InputMode:
+            return fColor.modulate(input);
+    }
+    SkFAIL("Unexpected mode");
+    return GrColor4f::TransparentBlack();
+}
+
 void GrConstColorProcessor::onGetGLSLProcessorKey(const GrShaderCaps&,
                                                   GrProcessorKeyBuilder* b) const {
     b->add32(fMode);
diff --git a/src/gpu/effects/GrConvexPolyEffect.cpp b/src/gpu/effects/GrConvexPolyEffect.cpp
index 65ed87b..4100a0f 100644
--- a/src/gpu/effects/GrConvexPolyEffect.cpp
+++ b/src/gpu/effects/GrConvexPolyEffect.cpp
@@ -32,7 +32,7 @@
 
 private:
     AARectEffect(GrPrimitiveEdgeType edgeType, const SkRect& rect)
-        : fRect(rect), fEdgeType(edgeType) {
+            : INHERITED(kModulatesInput_OptimizationFlag), fRect(rect), fEdgeType(edgeType) {
         this->initClassID<AARectEffect>();
     }
 
@@ -339,8 +339,7 @@
 }
 
 GrConvexPolyEffect::GrConvexPolyEffect(GrPrimitiveEdgeType edgeType, int n, const SkScalar edges[])
-    : fEdgeType(edgeType)
-    , fEdgeCount(n) {
+        : INHERITED(kModulatesInput_OptimizationFlag), fEdgeType(edgeType), fEdgeCount(n) {
     this->initClassID<GrConvexPolyEffect>();
     // Factory function should have already ensured this.
     SkASSERT(n <= kMaxEdges);
diff --git a/src/gpu/effects/GrDitherEffect.cpp b/src/gpu/effects/GrDitherEffect.cpp
index 2ba47d2..24e3ba6 100644
--- a/src/gpu/effects/GrDitherEffect.cpp
+++ b/src/gpu/effects/GrDitherEffect.cpp
@@ -26,9 +26,7 @@
     const char* name() const override { return "Dither"; }
 
 private:
-    DitherEffect() {
-        this->initClassID<DitherEffect>();
-    }
+    DitherEffect() : INHERITED(kNone_OptimizationFlags) { this->initClassID<DitherEffect>(); }
 
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index 91301ff..be6d4d8 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -150,7 +150,8 @@
                                                                                float gaussianSigma,
                                                                                bool useBounds,
                                                                                float bounds[2])
-        : INHERITED(texture, direction, radius), fUseBounds(useBounds) {
+        : INHERITED(texture, direction, radius, ModulationFlags(texture->config()))
+        , fUseBounds(useBounds) {
     this->initClassID<GrGaussianConvolutionFragmentProcessor>();
     SkASSERT(radius <= kMaxKernelRadius);
     int width = this->width();
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index 01fc6ce..9b670e1 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -156,12 +156,14 @@
                                                      const SkIPoint& kernelOffset,
                                                      GrTextureDomain::Mode tileMode,
                                                      bool convolveAlpha)
-  : INHERITED(texture, nullptr, SkMatrix::I()),
-    fKernelSize(kernelSize),
-    fGain(SkScalarToFloat(gain)),
-    fBias(SkScalarToFloat(bias) / 255.0f),
-    fConvolveAlpha(convolveAlpha),
-    fDomain(texture, GrTextureDomain::MakeTexelDomainForMode(bounds, tileMode), tileMode) {
+        // To advertise either the modulation or opaqueness optimizations we'd have to examine the
+        // parameters.
+        : INHERITED(texture, nullptr, SkMatrix::I(), kNone_OptimizationFlags)
+        , fKernelSize(kernelSize)
+        , fGain(SkScalarToFloat(gain))
+        , fBias(SkScalarToFloat(bias) / 255.0f)
+        , fConvolveAlpha(convolveAlpha)
+        , fDomain(texture, GrTextureDomain::MakeTexelDomainForMode(bounds, tileMode), tileMode) {
     this->initClassID<GrMatrixConvolutionEffect>();
     for (int i = 0; i < kernelSize.width() * kernelSize.height(); i++) {
         fKernel[i] = SkScalarToFloat(kernel[i]);
diff --git a/src/gpu/effects/GrOvalEffect.cpp b/src/gpu/effects/GrOvalEffect.cpp
index 21a4ba0..3b0a887 100644
--- a/src/gpu/effects/GrOvalEffect.cpp
+++ b/src/gpu/effects/GrOvalEffect.cpp
@@ -64,9 +64,7 @@
 }
 
 CircleEffect::CircleEffect(GrPrimitiveEdgeType edgeType, const SkPoint& c, SkScalar r)
-    : fCenter(c)
-    , fRadius(r)
-    , fEdgeType(edgeType) {
+        : INHERITED(kModulatesInput_OptimizationFlag), fCenter(c), fRadius(r), fEdgeType(edgeType) {
     this->initClassID<CircleEffect>();
 }
 
@@ -230,10 +228,12 @@
     inout->mulByUnknownSingleComponent();
 }
 
-EllipseEffect::EllipseEffect(GrPrimitiveEdgeType edgeType, const SkPoint& c, SkScalar rx, SkScalar ry)
-    : fCenter(c)
-    , fRadii(SkVector::Make(rx, ry))
-    , fEdgeType(edgeType) {
+EllipseEffect::EllipseEffect(GrPrimitiveEdgeType edgeType, const SkPoint& c, SkScalar rx,
+                             SkScalar ry)
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fCenter(c)
+        , fRadii(SkVector::Make(rx, ry))
+        , fEdgeType(edgeType) {
     this->initClassID<EllipseEffect>();
 }
 
diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp
index adebc24..4397fcf 100644
--- a/src/gpu/effects/GrRRectEffect.cpp
+++ b/src/gpu/effects/GrRRectEffect.cpp
@@ -95,9 +95,10 @@
 
 CircularRRectEffect::CircularRRectEffect(GrPrimitiveEdgeType edgeType, uint32_t circularCornerFlags,
                                          const SkRRect& rrect)
-    : fRRect(rrect)
-    , fEdgeType(edgeType)
-    , fCircularCornerFlags(circularCornerFlags) {
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fRRect(rrect)
+        , fEdgeType(edgeType)
+        , fCircularCornerFlags(circularCornerFlags) {
     this->initClassID<CircularRRectEffect>();
 }
 
@@ -410,8 +411,8 @@
 
     void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
 
-    SkRRect             fRRect;
-    GrPrimitiveEdgeType    fEdgeType;
+    SkRRect fRRect;
+    GrPrimitiveEdgeType fEdgeType;
 
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
 
@@ -431,8 +432,7 @@
 }
 
 EllipticalRRectEffect::EllipticalRRectEffect(GrPrimitiveEdgeType edgeType, const SkRRect& rrect)
-    : fRRect(rrect)
-    , fEdgeType(edgeType) {
+        : INHERITED(kModulatesInput_OptimizationFlag), fRRect(rrect), fEdgeType(edgeType) {
     this->initClassID<EllipticalRRectEffect>();
 }
 
diff --git a/src/gpu/effects/GrSRGBEffect.cpp b/src/gpu/effects/GrSRGBEffect.cpp
index 67600ef..9279586 100644
--- a/src/gpu/effects/GrSRGBEffect.cpp
+++ b/src/gpu/effects/GrSRGBEffect.cpp
@@ -71,7 +71,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 GrSRGBEffect::GrSRGBEffect(Mode mode)
-    : fMode(mode) {
+        : INHERITED(kPreservesOpaqueInput_OptimizationFlag |
+                    kConstantOutputForConstantInput_OptimizationFlag)
+        , fMode(mode) {
     this->initClassID<GrSRGBEffect>();
 }
 
@@ -84,6 +86,26 @@
     inout->setToUnknown();
 }
 
+static inline float srgb_to_linear(float srgb) {
+    return (srgb <= 0.04045f) ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f);
+}
+static inline float linear_to_srgb(float linear) {
+    return (linear <= 0.0031308) ? linear * 12.92f : 1.055f * powf(linear, 1.f / 2.4f) - 0.055f;
+}
+
+GrColor4f GrSRGBEffect::constantOutputForConstantInput(GrColor4f input) const {
+    switch (fMode) {
+        case Mode::kLinearToSRGB:
+            return GrColor4f(linear_to_srgb(input.fRGBA[0]), linear_to_srgb(input.fRGBA[1]),
+                             linear_to_srgb(input.fRGBA[2]), input.fRGBA[3]);
+        case Mode::kSRGBToLinear:
+            return GrColor4f(srgb_to_linear(input.fRGBA[0]), srgb_to_linear(input.fRGBA[1]),
+                             srgb_to_linear(input.fRGBA[2]), input.fRGBA[3]);
+    }
+    SkFAIL("Unexpected mode");
+    return GrColor4f::TransparentBlack();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSRGBEffect);
diff --git a/src/gpu/effects/GrSRGBEffect.h b/src/gpu/effects/GrSRGBEffect.h
index 2952689..3d05dd6 100644
--- a/src/gpu/effects/GrSRGBEffect.h
+++ b/src/gpu/effects/GrSRGBEffect.h
@@ -33,6 +33,7 @@
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
     bool onIsEqual(const GrFragmentProcessor&) const override;
     void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
+    GrColor4f constantOutputForConstantInput(GrColor4f input) const override;
 
     Mode fMode;
 
diff --git a/src/gpu/effects/GrSimpleTextureEffect.h b/src/gpu/effects/GrSimpleTextureEffect.h
index bf013e9..c44ce44 100644
--- a/src/gpu/effects/GrSimpleTextureEffect.h
+++ b/src/gpu/effects/GrSimpleTextureEffect.h
@@ -13,6 +13,29 @@
 
 class GrInvariantOutput;
 
+// In a few places below we rely on braced initialization order being defined by the C++ spec (left
+// to right). We use operator-> on a sk_sp and then in a later argument std::move() the sk_sp. GCC
+// 4.9.0 and earlier has a bug where the left to right order evaluation isn't implemented correctly.
+#if defined(__GNUC__) && !defined(__clang__)
+#   define GCC_VERSION (__GNUC__ * 10000  + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#   if (GCC_VERSION > 40900)
+#       define GCC_EVAL_ORDER_BUG 0
+#   else
+#       define GCC_EVAL_ORDER_BUG 1
+#   endif
+#   undef GCC_VERSION
+#else
+#   define GCC_EVAL_ORDER_BUG 0
+#endif
+
+#if GCC_EVAL_ORDER_BUG
+#   define PROXY_MOVE(X) (X)
+#else
+#   define PROXY_MOVE(X) (std::move(X))
+#endif
+
+#undef GCC_EVAL_ORDER_BUG
+
 /**
  * The output color of this effect is a modulation of the input color and a sample from a texture.
  * It allows explicit specification of the filtering and wrap modes (GrSamplerParams) and accepts
@@ -81,32 +104,41 @@
                           sk_sp<GrColorSpaceXform> colorSpaceXform,
                           const SkMatrix& matrix,
                           GrSamplerParams::FilterMode filterMode)
-        : GrSingleTextureEffect(texture, std::move(colorSpaceXform), matrix, filterMode) {
+            : INHERITED(texture, std::move(colorSpaceXform), matrix, filterMode,
+                        ModulationFlags(texture->config())) {
         this->initClassID<GrSimpleTextureEffect>();
     }
 
     GrSimpleTextureEffect(GrContext* ctx, sk_sp<GrTextureProxy> proxy,
-                          sk_sp<GrColorSpaceXform> colorSpaceXform,
-                          const SkMatrix& matrix,
+                          sk_sp<GrColorSpaceXform> colorSpaceXform, const SkMatrix& matrix,
                           GrSamplerParams::FilterMode filterMode)
-        : GrSingleTextureEffect(ctx, std::move(proxy), std::move(colorSpaceXform),
-                                matrix, filterMode) {
+            : INHERITED{ctx,
+                        ModulationFlags(proxy->config()),
+                        PROXY_MOVE(proxy),
+                        std::move(colorSpaceXform),
+                        matrix,
+                        filterMode} {
         this->initClassID<GrSimpleTextureEffect>();
     }
 
     GrSimpleTextureEffect(GrTexture* texture,
-                          sk_sp<GrColorSpaceXform> colorSpaceXform,
+                          sk_sp<GrColorSpaceXform>colorSpaceXform,
                           const SkMatrix& matrix,
                           const GrSamplerParams& params)
-        : GrSingleTextureEffect(texture, std::move(colorSpaceXform), matrix, params) {
+            : INHERITED(texture, std::move(colorSpaceXform), matrix, params,
+                        ModulationFlags(texture->config())) {
         this->initClassID<GrSimpleTextureEffect>();
     }
 
     GrSimpleTextureEffect(GrContext* ctx, sk_sp<GrTextureProxy> proxy,
-                          sk_sp<GrColorSpaceXform> colorSpaceXform,
-                          const SkMatrix& matrix,
+                          sk_sp<GrColorSpaceXform> colorSpaceXform, const SkMatrix& matrix,
                           const GrSamplerParams& params)
-        : GrSingleTextureEffect(ctx, std::move(proxy), std::move(colorSpaceXform), matrix, params) {
+            : INHERITED{ctx,
+                        ModulationFlags(proxy->config()),
+                        PROXY_MOVE(proxy),
+                        std::move(colorSpaceXform),
+                        matrix,
+                        params} {
         this->initClassID<GrSimpleTextureEffect>();
     }
 
@@ -123,4 +155,6 @@
     typedef GrSingleTextureEffect INHERITED;
 };
 
+#undef PROXY_MOVE
+
 #endif
diff --git a/src/gpu/effects/GrSingleTextureEffect.cpp b/src/gpu/effects/GrSingleTextureEffect.cpp
index c493920..acfc0d1 100644
--- a/src/gpu/effects/GrSingleTextureEffect.cpp
+++ b/src/gpu/effects/GrSingleTextureEffect.cpp
@@ -12,10 +12,11 @@
 
 GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                             const SkMatrix& m)
-    : fCoordTransform(m, texture, GrSamplerParams::kNone_FilterMode)
-    , fTextureSampler(texture)
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+                                             const SkMatrix& m, OptimizationFlags optFlags)
+        : INHERITED(optFlags)
+        , fCoordTransform(m, texture, GrSamplerParams::kNone_FilterMode)
+        , fTextureSampler(texture)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
@@ -23,56 +24,61 @@
 GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
                                              const SkMatrix& m,
-                                             GrSamplerParams::FilterMode filterMode)
-    : fCoordTransform(m, texture, filterMode)
-    , fTextureSampler(texture, filterMode)
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+                                             GrSamplerParams::FilterMode filterMode,
+                                             OptimizationFlags optFlags)
+        : INHERITED(optFlags)
+        , fCoordTransform(m, texture, filterMode)
+        , fTextureSampler(texture, filterMode)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
 
 GrSingleTextureEffect::GrSingleTextureEffect(GrTexture* texture,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                             const SkMatrix& m,
-                                             const GrSamplerParams& params)
-    : fCoordTransform(m, texture, params.filterMode())
-    , fTextureSampler(texture, params)
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+                                             const SkMatrix& m, const GrSamplerParams& params,
+                                             OptimizationFlags optFlags)
+        : INHERITED(optFlags)
+        , fCoordTransform(m, texture, params.filterMode())
+        , fTextureSampler(texture, params)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
 
-GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, sk_sp<GrTextureProxy> proxy,
+GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, OptimizationFlags optFlags,
+                                             sk_sp<GrTextureProxy> proxy,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
                                              const SkMatrix& m)
-    : fCoordTransform(ctx, m, proxy.get(), GrSamplerParams::kNone_FilterMode)
-    , fTextureSampler(ctx->textureProvider(), std::move(proxy))
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+        : INHERITED(optFlags)
+        , fCoordTransform(ctx, m, proxy.get(), GrSamplerParams::kNone_FilterMode)
+        , fTextureSampler(ctx->textureProvider(), std::move(proxy))
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
 
-GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, sk_sp<GrTextureProxy> proxy,
+GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, OptimizationFlags optFlags,
+                                             sk_sp<GrTextureProxy> proxy,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
                                              const SkMatrix& m,
                                              GrSamplerParams::FilterMode filterMode)
-    : fCoordTransform(ctx, m, proxy.get(), filterMode)
-    , fTextureSampler(ctx->textureProvider(), std::move(proxy), filterMode)
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+        : INHERITED(optFlags)
+        , fCoordTransform(ctx, m, proxy.get(), filterMode)
+        , fTextureSampler(ctx->textureProvider(), std::move(proxy), filterMode)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
 
-GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, sk_sp<GrTextureProxy> proxy,
+GrSingleTextureEffect::GrSingleTextureEffect(GrContext* ctx, OptimizationFlags optFlags,
+                                             sk_sp<GrTextureProxy> proxy,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
-                                             const SkMatrix& m,
-                                             const GrSamplerParams& params)
-    : fCoordTransform(ctx, m, proxy.get(), params.filterMode())
-    , fTextureSampler(ctx->textureProvider(), std::move(proxy), params)
-    , fColorSpaceXform(std::move(colorSpaceXform)) {
+                                             const SkMatrix& m, const GrSamplerParams& params)
+        : INHERITED(optFlags)
+        , fCoordTransform(ctx, m, proxy.get(), params.filterMode())
+        , fTextureSampler(ctx->textureProvider(), std::move(proxy), params)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     this->addCoordTransform(&fCoordTransform);
     this->addTextureSampler(&fTextureSampler);
 }
-
-GrSingleTextureEffect::~GrSingleTextureEffect() {
-}
diff --git a/src/gpu/effects/GrSingleTextureEffect.h b/src/gpu/effects/GrSingleTextureEffect.h
index 1d0f27a..29105b5 100644
--- a/src/gpu/effects/GrSingleTextureEffect.h
+++ b/src/gpu/effects/GrSingleTextureEffect.h
@@ -23,8 +23,6 @@
  */
 class GrSingleTextureEffect : public GrFragmentProcessor {
 public:
-    ~GrSingleTextureEffect() override;
-
     SkString dumpInfo() const override {
         SkString str;
         str.appendf("Texture: %d", fTextureSampler.texture()->uniqueID().asUInt());
@@ -35,25 +33,26 @@
 
 protected:
     /** unfiltered, clamp mode */
-    GrSingleTextureEffect(GrTexture*, sk_sp<GrColorSpaceXform>, const SkMatrix&);
+    GrSingleTextureEffect(GrTexture*, sk_sp<GrColorSpaceXform>, const SkMatrix&,
+                          OptimizationFlags optFlags);
     /** clamp mode */
     GrSingleTextureEffect(GrTexture*, sk_sp<GrColorSpaceXform>, const SkMatrix&,
-                          GrSamplerParams::FilterMode filterMode);
+                          GrSamplerParams::FilterMode filterMode, OptimizationFlags optFlags);
     GrSingleTextureEffect(GrTexture*,
                           sk_sp<GrColorSpaceXform>,
                           const SkMatrix&,
-                          const GrSamplerParams&);
+                          const GrSamplerParams&,
+                          OptimizationFlags optFlags);
 
     /** unfiltered, clamp mode */
-    GrSingleTextureEffect(GrContext*,
-                          sk_sp<GrTextureProxy>, sk_sp<GrColorSpaceXform>, const SkMatrix&);
+    GrSingleTextureEffect(GrContext*, OptimizationFlags, sk_sp<GrTextureProxy>,
+                          sk_sp<GrColorSpaceXform>, const SkMatrix&);
     /** clamp mode */
-    GrSingleTextureEffect(GrContext*,
-                          sk_sp<GrTextureProxy>, sk_sp<GrColorSpaceXform>, const SkMatrix&,
+    GrSingleTextureEffect(GrContext*, OptimizationFlags, sk_sp<GrTextureProxy>,
+                          sk_sp<GrColorSpaceXform>, const SkMatrix&,
                           GrSamplerParams::FilterMode filterMode);
-    GrSingleTextureEffect(GrContext*,
-                          sk_sp<GrTextureProxy>, sk_sp<GrColorSpaceXform>, const SkMatrix&,
-                          const GrSamplerParams&);
+    GrSingleTextureEffect(GrContext*, OptimizationFlags, sk_sp<GrTextureProxy>,
+                          sk_sp<GrColorSpaceXform>, const SkMatrix&, const GrSamplerParams&);
 
     /**
      * Can be used as a helper to implement subclass onComputeInvariantOutput(). It assumes that
@@ -71,6 +70,19 @@
         }
     }
 
+    /**
+     * Can be used as a helper to implement subclass onOptimizationFlags(). It assumes that
+     * the subclass output color will be a modulation of the input color with a value read from the
+     * texture.
+     */
+    static OptimizationFlags ModulationFlags(GrPixelConfig config) {
+        if (GrPixelConfigIsOpaque(config)) {
+            return kModulatesInput_OptimizationFlag | kPreservesOpaqueInput_OptimizationFlag;
+        } else {
+            return kModulatesInput_OptimizationFlag;
+        }
+    }
+
 private:
     GrCoordTransform fCoordTransform;
     TextureSampler fTextureSampler;
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index 095f100..7ab598f 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -208,14 +208,25 @@
     }
 }
 
+inline GrFragmentProcessor::OptimizationFlags GrTextureDomainEffect::OptFlags(
+        GrTexture* texture, GrTextureDomain::Mode mode) {
+    if (mode == GrTextureDomain::kDecal_Mode || !GrPixelConfigIsOpaque(texture->config())) {
+        return GrFragmentProcessor::kModulatesInput_OptimizationFlag;
+    } else {
+        return GrFragmentProcessor::kModulatesInput_OptimizationFlag |
+               GrFragmentProcessor::kPreservesOpaqueInput_OptimizationFlag;
+    }
+}
+
 GrTextureDomainEffect::GrTextureDomainEffect(GrTexture* texture,
                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
                                              const SkMatrix& matrix,
                                              const SkRect& domain,
                                              GrTextureDomain::Mode mode,
                                              GrSamplerParams::FilterMode filterMode)
-    : GrSingleTextureEffect(texture, std::move(colorSpaceXform), matrix, filterMode)
-    , fTextureDomain(texture, domain, mode) {
+        : GrSingleTextureEffect(texture, std::move(colorSpaceXform), matrix, filterMode,
+                                OptFlags(texture, mode))
+        , fTextureDomain(texture, domain, mode) {
     SkASSERT(mode != GrTextureDomain::kRepeat_Mode ||
             filterMode == GrSamplerParams::kNone_FilterMode);
     this->initClassID<GrTextureDomainEffect>();
@@ -323,7 +334,8 @@
 
 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor(
         GrTexture* texture, const SkIRect& subset, const SkIPoint& deviceSpaceOffset)
-        : fTextureSampler(texture, GrSamplerParams::ClampNoFilter())
+        : INHERITED(kModulatesInput_OptimizationFlag)
+        , fTextureSampler(texture, GrSamplerParams::ClampNoFilter())
         , fTextureDomain(texture, GrTextureDomain::MakeTexelDomain(subset),
                          GrTextureDomain::kDecal_Mode) {
     this->addTextureSampler(&fTextureSampler);
diff --git a/src/gpu/effects/GrTextureDomain.h b/src/gpu/effects/GrTextureDomain.h
index 66bd220..6758d90 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -178,6 +178,8 @@
                           GrTextureDomain::Mode,
                           GrSamplerParams::FilterMode);
 
+    static OptimizationFlags OptFlags(GrTexture* texture, GrTextureDomain::Mode mode);
+
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
index d984787..af32ade 100644
--- a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
+++ b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
@@ -19,7 +19,7 @@
 public:
     ComposeTwoFragmentProcessor(sk_sp<GrFragmentProcessor> src, sk_sp<GrFragmentProcessor> dst,
                                 SkBlendMode mode)
-        : fMode(mode) {
+            : INHERITED(OptFlags(src.get(), dst.get(), mode)), fMode(mode) {
         this->initClassID<ComposeTwoFragmentProcessor>();
         SkDEBUGCODE(int shaderAChildIndex = )this->registerChildProcessor(std::move(src));
         SkDEBUGCODE(int shaderBChildIndex = )this->registerChildProcessor(std::move(dst));
@@ -35,7 +35,18 @@
 
     SkBlendMode getMode() const { return fMode; }
 
-protected:
+private:
+    static OptimizationFlags OptFlags(const GrFragmentProcessor* src,
+                                      const GrFragmentProcessor* dst, SkBlendMode mode) {
+        // We only attempt the constant output optimization.
+        // The CPU and GPU implementations differ significantly for the advanced modes.
+        if (mode <= SkBlendMode::kLastSeparableMode && src->hasConstantOutputForConstantInput() &&
+            dst->hasConstantOutputForConstantInput()) {
+            return kConstantOutputForConstantInput_OptimizationFlag;
+        }
+        return kNone_OptimizationFlags;
+    }
+
     bool onIsEqual(const GrFragmentProcessor& other) const override {
         const ComposeTwoFragmentProcessor& cs = other.cast<ComposeTwoFragmentProcessor>();
         return fMode == cs.fMode;
@@ -45,7 +56,17 @@
         inout->setToUnknown();
     }
 
-private:
+    GrColor4f constantOutputForConstantInput(GrColor4f input) const override {
+        float alpha = input.fRGBA[3];
+        input = input.opaque();
+        GrColor4f srcColor = ConstantOutputForConstantInput(this->childProcessor(0), input);
+        GrColor4f dstColor = ConstantOutputForConstantInput(this->childProcessor(1), input);
+        SkPM4f src = GrColor4fToSkPM4f(srcColor);
+        SkPM4f dst = GrColor4fToSkPM4f(dstColor);
+        auto proc = SkXfermode::GetProc4f(fMode);
+        return SkPM4fToGrColor4f(proc(src, dst)).mulByScalar(alpha);
+    }
+
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
     SkBlendMode fMode;
@@ -145,8 +166,7 @@
     };
 
     ComposeOneFragmentProcessor(sk_sp<GrFragmentProcessor> dst, SkBlendMode mode, Child child)
-        : fMode(mode)
-        , fChild(child) {
+            : INHERITED(OptFlags(dst.get(), mode)), fMode(mode), fChild(child) {
         this->initClassID<ComposeOneFragmentProcessor>();
         SkDEBUGCODE(int dstIndex = )this->registerChildProcessor(std::move(dst));
         SkASSERT(0 == dstIndex);
@@ -172,7 +192,16 @@
 
     Child child() const { return fChild; }
 
-protected:
+private:
+    OptimizationFlags OptFlags(const GrFragmentProcessor* child, SkBlendMode mode) {
+        // We only attempt the constant output optimization.
+        // The CPU and GPU implementations differ significantly for the advanced modes.
+        if (mode <= SkBlendMode::kLastSeparableMode && child->hasConstantOutputForConstantInput()) {
+            return kConstantOutputForConstantInput_OptimizationFlag;
+        }
+        return kNone_OptimizationFlags;
+    }
+
     bool onIsEqual(const GrFragmentProcessor& that) const override {
         return fMode == that.cast<ComposeOneFragmentProcessor>().fMode;
     }
@@ -203,6 +232,21 @@
         }
     }
 
+    GrColor4f constantOutputForConstantInput(GrColor4f inputColor) const override {
+        GrColor4f childColor =
+                ConstantOutputForConstantInput(this->childProcessor(0), GrColor4f::OpaqueWhite());
+        SkPM4f src, dst;
+        if (kSrc_Child == fChild) {
+            src = GrColor4fToSkPM4f(childColor);
+            dst = GrColor4fToSkPM4f(inputColor);
+        } else {
+            src = GrColor4fToSkPM4f(inputColor);
+            dst = GrColor4fToSkPM4f(childColor);
+        }
+        auto proc = SkXfermode::GetProc4f(fMode);
+        return SkPM4fToGrColor4f(proc(src, dst));
+    }
+
 private:
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
diff --git a/src/gpu/effects/GrYUVEffect.cpp b/src/gpu/effects/GrYUVEffect.cpp
index 254cdff..6d917f3 100644
--- a/src/gpu/effects/GrYUVEffect.cpp
+++ b/src/gpu/effects/GrYUVEffect.cpp
@@ -155,19 +155,17 @@
     };
 
 private:
-    YUVtoRGBEffect(GrContext* ctx,
-                   sk_sp<GrTextureProxy> yProxy,
-                   sk_sp<GrTextureProxy> uProxy,
-                   sk_sp<GrTextureProxy> vProxy,
-                   const SkMatrix yuvMatrix[3], GrSamplerParams::FilterMode uvFilterMode,
-                   SkYUVColorSpace colorSpace, bool nv12)
-        : fYTransform(ctx, yuvMatrix[0], yProxy.get(), GrSamplerParams::kNone_FilterMode)
-        , fYSampler(ctx->textureProvider(), std::move(yProxy))
-        , fUTransform(ctx, yuvMatrix[1], uProxy.get(), uvFilterMode)
-        , fUSampler(ctx->textureProvider(), std::move(uProxy), uvFilterMode)
-        , fVSampler(ctx->textureProvider(), vProxy, uvFilterMode)
-        , fColorSpace(colorSpace)
-        , fNV12(nv12) {
+    YUVtoRGBEffect(GrContext* ctx, sk_sp<GrTextureProxy> yProxy, sk_sp<GrTextureProxy> uProxy,
+                   sk_sp<GrTextureProxy> vProxy, const SkMatrix yuvMatrix[3],
+                   GrSamplerParams::FilterMode uvFilterMode, SkYUVColorSpace colorSpace, bool nv12)
+            : INHERITED(kPreservesOpaqueInput_OptimizationFlag)
+            , fYTransform(ctx, yuvMatrix[0], yProxy.get(), GrSamplerParams::kNone_FilterMode)
+            , fYSampler(ctx->textureProvider(), std::move(yProxy))
+            , fUTransform(ctx, yuvMatrix[1], uProxy.get(), uvFilterMode)
+            , fUSampler(ctx->textureProvider(), std::move(uProxy), uvFilterMode)
+            , fVSampler(ctx->textureProvider(), vProxy, uvFilterMode)
+            , fColorSpace(colorSpace)
+            , fNV12(nv12) {
         this->initClassID<YUVtoRGBEffect>();
         this->addCoordTransform(&fYTransform);
         this->addTextureSampler(&fYSampler);
@@ -228,8 +226,10 @@
 
     RGBToYUVEffect(sk_sp<GrFragmentProcessor> rgbFP, SkYUVColorSpace colorSpace,
                    OutputChannels output)
-        : fColorSpace(colorSpace)
-        , fOutputChannels(output) {
+            // This could advertise kConstantOutputForConstantInput, but doesn't seem useful.
+            : INHERITED(kPreservesOpaqueInput_OptimizationFlag)
+            , fColorSpace(colorSpace)
+            , fOutputChannels(output) {
         this->initClassID<RGBToYUVEffect>();
         this->registerChildProcessor(std::move(rgbFP));
     }
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index b9f31e6..e6d738d 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -78,9 +78,7 @@
     }
 
 private:
-    BigKeyProcessor() {
-        this->initClassID<BigKeyProcessor>();
-    }
+    BigKeyProcessor() : INHERITED(kNone_OptimizationFlags) { this->initClassID<BigKeyProcessor>(); }
     virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps,
                                        GrProcessorKeyBuilder* b) const override {
         GLBigKeyProcessor::GenKey(*this, caps, b);
@@ -122,7 +120,8 @@
         typedef GrGLSLFragmentProcessor INHERITED;
     };
 
-    BlockInputFragmentProcessor(sk_sp<GrFragmentProcessor> child) {
+    BlockInputFragmentProcessor(sk_sp<GrFragmentProcessor> child)
+            : INHERITED(kNone_OptimizationFlags) {
         this->initClassID<BlockInputFragmentProcessor>();
         this->registerChildProcessor(std::move(child));
     }
diff --git a/tests/ImageStorageTest.cpp b/tests/ImageStorageTest.cpp
index 59d134d..aa34831 100644
--- a/tests/ImageStorageTest.cpp
+++ b/tests/ImageStorageTest.cpp
@@ -28,7 +28,8 @@
 
     private:
         TestFP(sk_sp<GrTexture> texture, GrSLMemoryModel mm, GrSLRestrict restrict)
-                : fImageStorageAccess(std::move(texture), kRead_GrIOType, mm, restrict) {
+                : INHERITED(kNone_OptimizationFlags)
+                , fImageStorageAccess(std::move(texture), kRead_GrIOType, mm, restrict) {
             this->initClassID<TestFP>();
             this->addImageStorageAccess(&fImageStorageAccess);
         }
@@ -73,6 +74,7 @@
         }
 
         ImageStorageAccess fImageStorageAccess;
+        typedef GrFragmentProcessor INHERITED;
     };
 
     static constexpr int kS = 256;
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
index 398e702..ff31e49 100644
--- a/tests/ProcessorTest.cpp
+++ b/tests/ProcessorTest.cpp
@@ -70,7 +70,7 @@
 private:
     TestFP(const SkTArray<sk_sp<GrTexture>>& textures, const SkTArray<sk_sp<GrBuffer>>& buffers,
            const SkTArray<Image>& images)
-            : fSamplers(4), fBuffers(4), fImages(4) {
+            : INHERITED(kNone_OptimizationFlags), fSamplers(4), fBuffers(4), fImages(4) {
         for (const auto& texture : textures) {
             this->addTextureSampler(&fSamplers.emplace_back(texture.get()));
         }
@@ -83,7 +83,8 @@
         }
     }
 
-    TestFP(sk_sp<GrFragmentProcessor> child) : fSamplers(4), fBuffers(4), fImages(4) {
+    TestFP(sk_sp<GrFragmentProcessor> child)
+            : INHERITED(kNone_OptimizationFlags), fSamplers(4), fBuffers(4), fImages(4) {
         this->registerChildProcessor(std::move(child));
     }
 
@@ -106,6 +107,7 @@
     GrTAllocator<TextureSampler> fSamplers;
     GrTAllocator<BufferAccess> fBuffers;
     GrTAllocator<ImageStorageAccess> fImages;
+    typedef GrFragmentProcessor INHERITED;
 };
 }
 
@@ -230,4 +232,144 @@
         }
     }
 }
+
+// This test uses the random GrFragmentProcessor test factory, which relies on static initializers.
+#if SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+
+static GrColor texel_color(int i, int j) {
+    SkASSERT((unsigned)i < 256 && (unsigned)j < 256);
+    GrColor color = GrColorPackRGBA(j, (uint8_t)(i + j), (uint8_t)(2 * j - i), i);
+    return GrPremulColor(color);
+}
+
+static GrColor4f texel_color4f(int i, int j) { return GrColor4f::FromGrColor(texel_color(i, j)); }
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, reporter, ctxInfo) {
+    // This tests code under development but not used in skia lib. Leaving this disabled until
+    // some platform-specific issues are addressed.
+    if (1) {
+        return;
+    }
+    GrContext* context = ctxInfo.grContext();
+    using FPFactory = GrProcessorTestFactory<GrFragmentProcessor>;
+    SkRandom random;
+    sk_sp<GrRenderTargetContext> rtc = context->makeRenderTargetContext(
+            SkBackingFit::kExact, 256, 256, kRGBA_8888_GrPixelConfig, nullptr);
+    GrSurfaceDesc desc;
+    desc.fWidth = 256;
+    desc.fHeight = 256;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    sk_sp<GrTexture> tex0(context->textureProvider()->createTexture(desc, SkBudgeted::kYes));
+    desc.fConfig = kAlpha_8_GrPixelConfig;
+    sk_sp<GrTexture> tex1(context->textureProvider()->createTexture(desc, SkBudgeted::kYes));
+    GrTexture* textures[] = {tex0.get(), tex1.get()};
+    GrProcessorTestData testData(&random, context, rtc.get(), textures);
+
+    std::unique_ptr<GrColor> data(new GrColor[256 * 256]);
+    for (int y = 0; y < 256; ++y) {
+        for (int x = 0; x < 256; ++x) {
+            data.get()[256 * y + x] = texel_color(x, y);
+        }
+    }
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    sk_sp<GrTexture> dataTexture(context->textureProvider()->createTexture(
+            desc, SkBudgeted::kYes, data.get(), 256 * sizeof(GrColor)));
+
+    // Because processors factories configure themselves in random ways, this is not exhaustive.
+    for (int i = 0; i < FPFactory::Count(); ++i) {
+        int timesToInvokeFactory = 5;
+        // Increase the number of attempts if the FP has child FPs since optimizations likely depend
+        // on child optimizations being present.
+        sk_sp<GrFragmentProcessor> fp = FPFactory::MakeIdx(i, &testData);
+        for (int j = 0; j < fp->numChildProcessors(); ++j) {
+            // This value made a reasonable trade off between time and coverage when this test was
+            // written.
+            timesToInvokeFactory *= FPFactory::Count() / 2;
+        }
+        for (int j = 0; j < timesToInvokeFactory; ++j) {
+            fp = FPFactory::MakeIdx(i, &testData);
+            if (!fp->hasConstantOutputForConstantInput() && !fp->preservesOpaqueInput() &&
+                !fp->modulatesInput()) {
+                continue;
+            }
+            GrPaint paint;
+            paint.addColorTextureProcessor(dataTexture.get(), nullptr, SkMatrix::I());
+            paint.addColorFragmentProcessor(fp);
+            paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+            rtc->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(),
+                          SkRect::MakeWH(256.f, 256.f));
+            memset(data.get(), 0x0, sizeof(GrColor) * 256 * 256);
+            rtc->readPixels(
+                    SkImageInfo::Make(256, 256, kRGBA_8888_SkColorType, kPremul_SkAlphaType),
+                    data.get(), 0, 0, 0);
+            bool passing = true;
+            if (0) {  // Useful to see what FPs are being tested.
+                SkString children;
+                for (int c = 0; c < fp->numChildProcessors(); ++c) {
+                    if (!c) {
+                        children.append("(");
+                    }
+                    children.append(fp->childProcessor(c).name());
+                    children.append(c == fp->numChildProcessors() - 1 ? ")" : ", ");
+                }
+                SkDebugf("%s %s\n", fp->name(), children.c_str());
+            }
+            for (int y = 0; y < 256 && passing; ++y) {
+                for (int x = 0; x < 256 && passing; ++x) {
+                    GrColor input = texel_color(x, y);
+                    GrColor output = data.get()[y * 256 + x];
+                    if (fp->modulatesInput()) {
+                        // A modulating processor is allowed to modulate either the input color or
+                        // just the input alpha.
+                        bool legalColorModulation =
+                                GrColorUnpackA(output) <= GrColorUnpackA(input) &&
+                                GrColorUnpackR(output) <= GrColorUnpackR(input) &&
+                                GrColorUnpackG(output) <= GrColorUnpackG(input) &&
+                                GrColorUnpackB(output) <= GrColorUnpackB(input);
+                        bool legalAlphaModulation =
+                                GrColorUnpackA(output) <= GrColorUnpackA(input) &&
+                                GrColorUnpackR(output) <= GrColorUnpackA(input) &&
+                                GrColorUnpackG(output) <= GrColorUnpackA(input) &&
+                                GrColorUnpackB(output) <= GrColorUnpackA(input);
+                        if (!legalColorModulation && !legalAlphaModulation) {
+                            ERRORF(reporter,
+                                   "\"Modulating\" processor %s made color/alpha value larger. "
+                                   "Input: 0x%0x8, Output: 0x%08x.",
+                                   fp->name(), input, output);
+                            passing = false;
+                        }
+                    }
+                    GrColor4f input4f = texel_color4f(x, y);
+                    GrColor4f output4f = GrColor4f::FromGrColor(output);
+                    GrColor4f expected4f;
+                    if (fp->hasConstantOutputForConstantInput(input4f, &expected4f)) {
+                        float rDiff = fabsf(output4f.fRGBA[0] - expected4f.fRGBA[0]);
+                        float gDiff = fabsf(output4f.fRGBA[1] - expected4f.fRGBA[1]);
+                        float bDiff = fabsf(output4f.fRGBA[2] - expected4f.fRGBA[2]);
+                        float aDiff = fabsf(output4f.fRGBA[3] - expected4f.fRGBA[3]);
+                        static constexpr float kTol = 3 / 255.f;
+                        if (rDiff > kTol || gDiff > kTol || bDiff > kTol || aDiff > kTol) {
+                            ERRORF(reporter,
+                                   "Processor %s claimed output for const input doesn't match "
+                                   "actual output.",
+                                   fp->name());
+                            passing = false;
+                        }
+                    }
+                    if (GrColorIsOpaque(input) && fp->preservesOpaqueInput() &&
+                        !GrColorIsOpaque(output)) {
+                        ERRORF(reporter,
+                               "Processor %s claimed opaqueness is preserved but it is not. Input: "
+                               "0x%0x8, Output: 0x%08x.",
+                               fp->name(), input, output);
+                        passing = false;
+                    }
+                }
+            }
+        }
+    }
+}
+#endif
+
 #endif