Add preserves premul and modulate optimization to compose fragment processors.

Fixes out of range colors produced by matrix convolution and dither effects. Adds modulate optimization to matrix convolution.

Change-Id: I8424250a52e864f4b5feaf4474293695c26039d8
Reviewed-on: https://skia-review.googlesource.com/8351
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
index 612ebea..5433b67 100644
--- a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
+++ b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp
@@ -1,9 +1,9 @@
 /*
-* Copyright 2015 Google Inc.
-*
-* Use of this source code is governed by a BSD-style license that can be
-* found in the LICENSE file.
-*/
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
 
 #include "effects/GrXfermodeFragmentProcessor.h"
 
@@ -49,11 +49,73 @@
 private:
     static OptimizationFlags OptFlags(const GrFragmentProcessor* src,
                                       const GrFragmentProcessor* dst, SkBlendMode mode) {
+        OptimizationFlags flags;
+        switch (mode) {
+            case SkBlendMode::kClear:
+            case SkBlendMode::kSrc:
+            case SkBlendMode::kDst:
+                SkFAIL("Should never create clear, src, or dst compose two FP.");
+                flags = kNone_OptimizationFlags;
+                break;
+
+            // Produces opaque if both src and dst are opaque.
+            case SkBlendMode::kSrcIn:
+            case SkBlendMode::kDstIn:
+            case SkBlendMode::kModulate:
+                flags = src->preservesOpaqueInput() && dst->preservesOpaqueInput()
+                                ? kPreservesOpaqueInput_OptimizationFlag
+                                : kNone_OptimizationFlags;
+                break;
+
+            // Produces zero when both are opaque, indeterminate if one is opaque.
+            case SkBlendMode::kSrcOut:
+            case SkBlendMode::kDstOut:
+            case SkBlendMode::kXor:
+                flags = kNone_OptimizationFlags;
+                break;
+
+            // Is opaque if the dst is opaque.
+            case SkBlendMode::kSrcATop:
+                flags = dst->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                    : kNone_OptimizationFlags;
+                break;
+
+            // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque.
+            case SkBlendMode::kDstATop:
+            case SkBlendMode::kScreen:
+                flags = src->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                    : kNone_OptimizationFlags;
+                break;
+
+            // These modes are all opaque if either src or dst is opaque. All the advanced modes
+            // compute alpha as src-over.
+            case SkBlendMode::kSrcOver:
+            case SkBlendMode::kDstOver:
+            case SkBlendMode::kPlus:
+            case SkBlendMode::kOverlay:
+            case SkBlendMode::kDarken:
+            case SkBlendMode::kLighten:
+            case SkBlendMode::kColorDodge:
+            case SkBlendMode::kColorBurn:
+            case SkBlendMode::kHardLight:
+            case SkBlendMode::kSoftLight:
+            case SkBlendMode::kDifference:
+            case SkBlendMode::kExclusion:
+            case SkBlendMode::kMultiply:
+            case SkBlendMode::kHue:
+            case SkBlendMode::kSaturation:
+            case SkBlendMode::kColor:
+            case SkBlendMode::kLuminosity:
+                flags = src->preservesOpaqueInput() || dst->preservesOpaqueInput()
+                                ? kPreservesOpaqueInput_OptimizationFlag
+                                : kNone_OptimizationFlags;
+                break;
+        }
         if (does_cpu_blend_impl_match_gpu(mode) && src->hasConstantOutputForConstantInput() &&
             dst->hasConstantOutputForConstantInput()) {
-            return kConstantOutputForConstantInput_OptimizationFlag;
+            flags |= kConstantOutputForConstantInput_OptimizationFlag;
         }
-        return kNone_OptimizationFlags;
+        return flags;
     }
 
     bool onIsEqual(const GrFragmentProcessor& other) const override {
@@ -101,8 +163,10 @@
     sk_sp<GrFragmentProcessor> fpA(GrProcessorUnitTest::MakeChildFP(d));
     sk_sp<GrFragmentProcessor> fpB(GrProcessorUnitTest::MakeChildFP(d));
 
-    SkBlendMode mode = static_cast<SkBlendMode>(
-        d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
+    SkBlendMode mode;
+    do {
+        mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
+    } while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode);
     return sk_sp<GrFragmentProcessor>(
         new ComposeTwoFragmentProcessor(std::move(fpA), std::move(fpB), mode));
 }
@@ -172,10 +236,10 @@
         kSrc_Child,
     };
 
-    ComposeOneFragmentProcessor(sk_sp<GrFragmentProcessor> dst, SkBlendMode mode, Child child)
-            : INHERITED(OptFlags(dst.get(), mode)), fMode(mode), fChild(child) {
+    ComposeOneFragmentProcessor(sk_sp<GrFragmentProcessor> fp, SkBlendMode mode, Child child)
+            : INHERITED(OptFlags(fp.get(), mode, child)), fMode(mode), fChild(child) {
         this->initClassID<ComposeOneFragmentProcessor>();
-        SkDEBUGCODE(int dstIndex = )this->registerChildProcessor(std::move(dst));
+        SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(fp));
         SkASSERT(0 == dstIndex);
     }
 
@@ -200,11 +264,90 @@
     Child child() const { return fChild; }
 
 private:
-    OptimizationFlags OptFlags(const GrFragmentProcessor* child, SkBlendMode mode) {
-        if (does_cpu_blend_impl_match_gpu(mode) && child->hasConstantOutputForConstantInput()) {
-            return kConstantOutputForConstantInput_OptimizationFlag;
+    OptimizationFlags OptFlags(const GrFragmentProcessor* fp, SkBlendMode mode, Child child) {
+        OptimizationFlags flags;
+        switch (mode) {
+            case SkBlendMode::kClear:
+                SkFAIL("Should never create clear compose one FP.");
+                flags = kNone_OptimizationFlags;
+                break;
+
+            case SkBlendMode::kSrc:
+                SkASSERT(child == kSrc_Child);
+                flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                   : kNone_OptimizationFlags;
+                break;
+
+            case SkBlendMode::kDst:
+                SkASSERT(child == kDst_Child);
+                flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                   : kNone_OptimizationFlags;
+                break;
+
+            // Produces opaque if both src and dst are opaque. These also will modulate the child's
+            // output by either the input color or alpha.
+            case SkBlendMode::kSrcIn:
+            case SkBlendMode::kDstIn:
+            case SkBlendMode::kModulate:
+                flags = fp->preservesOpaqueInput()
+                        ? kPreservesOpaqueInput_OptimizationFlag | kModulatesInput_OptimizationFlag
+                        : kModulatesInput_OptimizationFlag;
+                break;
+
+            // Produces zero when both are opaque, indeterminate if one is opaque.
+            case SkBlendMode::kSrcOut:
+            case SkBlendMode::kDstOut:
+            case SkBlendMode::kXor:
+                flags = kNone_OptimizationFlags;
+                break;
+
+            // Is opaque if the dst is opaque.
+            case SkBlendMode::kSrcATop:
+                if (child == kDst_Child) {
+                    flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                       : kNone_OptimizationFlags;
+                } else {
+                    flags = kPreservesOpaqueInput_OptimizationFlag;
+                }
+                break;
+
+            // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque.
+            case SkBlendMode::kDstATop:
+            case SkBlendMode::kScreen:
+                if (child == kSrc_Child) {
+                    flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag
+                                                       : kNone_OptimizationFlags;
+                } else {
+                    flags = kPreservesOpaqueInput_OptimizationFlag;
+                }
+                break;
+
+            // These modes are all opaque if either src or dst is opaque. All the advanced modes
+            // compute alpha as src-over.
+            case SkBlendMode::kSrcOver:
+            case SkBlendMode::kDstOver:
+            case SkBlendMode::kPlus:
+            case SkBlendMode::kOverlay:
+            case SkBlendMode::kDarken:
+            case SkBlendMode::kLighten:
+            case SkBlendMode::kColorDodge:
+            case SkBlendMode::kColorBurn:
+            case SkBlendMode::kHardLight:
+            case SkBlendMode::kSoftLight:
+            case SkBlendMode::kDifference:
+            case SkBlendMode::kExclusion:
+            case SkBlendMode::kMultiply:
+            case SkBlendMode::kHue:
+            case SkBlendMode::kSaturation:
+            case SkBlendMode::kColor:
+            case SkBlendMode::kLuminosity:
+                flags = kPreservesOpaqueInput_OptimizationFlag;
+                break;
         }
-        return kNone_OptimizationFlags;
+        if (does_cpu_blend_impl_match_gpu(mode) && fp->hasConstantOutputForConstantInput()) {
+            flags |= kConstantOutputForConstantInput_OptimizationFlag;
+        }
+        return flags;
     }
 
     bool onIsEqual(const GrFragmentProcessor& that) const override {
@@ -280,11 +423,13 @@
     // For now, we'll prevent either children from being a shader with children to prevent the
     // possibility of an arbitrarily large tree of procs.
     sk_sp<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
-    SkBlendMode mode = static_cast<SkBlendMode>(
-        d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
-    ComposeOneFragmentProcessor::Child child = d->fRandom->nextBool() ?
-        ComposeOneFragmentProcessor::kDst_Child :
-        ComposeOneFragmentProcessor::kSrc_Child;
+    SkBlendMode mode;
+    ComposeOneFragmentProcessor::Child child;
+    do {
+        mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
+        child = d->fRandom->nextBool() ? kDst_Child : kSrc_Child;
+    } while (SkBlendMode::kClear == mode || (SkBlendMode::kDst == mode && child == kSrc_Child) ||
+             (SkBlendMode::kSrc == mode && child == kDst_Child));
     return sk_sp<GrFragmentProcessor>(new ComposeOneFragmentProcessor(std::move(dst), mode, child));
 }
 #endif
@@ -295,12 +440,18 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
+// It may seems as though when the input FP is the dst and the mode is kDst (or same for src/kSrc)
+// that these factories could simply return the input FP. However, that doesn't have quite
+// the same effect as the returned compose FP will replace the FP's input with solid white and
+// ignore the original input. This could be implemented as:
+// RunInSeries(ConstColor(GrColor_WHITE, kIgnoreInput), inputFP).
+
 sk_sp<GrFragmentProcessor> GrXfermodeFragmentProcessor::MakeFromDstProcessor(
     sk_sp<GrFragmentProcessor> dst, SkBlendMode mode) {
     switch (mode) {
         case SkBlendMode::kClear:
             return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
-                                                 GrConstColorProcessor::kIgnore_InputMode);
+                                               GrConstColorProcessor::kIgnore_InputMode);
         case SkBlendMode::kSrc:
             return nullptr;
         default:
@@ -315,12 +466,12 @@
     switch (mode) {
         case SkBlendMode::kClear:
             return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
-                                                 GrConstColorProcessor::kIgnore_InputMode);
+                                               GrConstColorProcessor::kIgnore_InputMode);
         case SkBlendMode::kDst:
             return nullptr;
         default:
             return sk_sp<GrFragmentProcessor>(
-                new ComposeOneFragmentProcessor(src, mode,
+                new ComposeOneFragmentProcessor(std::move(src), mode,
                                                 ComposeOneFragmentProcessor::kSrc_Child));
     }
 }