Add preserves premul and modulate optimization to compose fragment processors.
authorBrian Salomon <bsalomon@google.com>
Mon, 13 Feb 2017 17:41:44 +0000 (12:41 -0500)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Mon, 13 Feb 2017 18:14:12 +0000 (18:14 +0000)
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>
src/gpu/effects/GrDitherEffect.cpp
src/gpu/effects/GrMatrixConvolutionEffect.cpp
src/gpu/effects/GrXfermodeFragmentProcessor.cpp
tests/GLProgramsTest.cpp

index 036d42c..adae12c 100644 (file)
@@ -73,7 +73,7 @@ void GLDitherEffect::emitCode(EmitArgs& args) {
     fragBuilder->codeAppendf("\t\tfloat r = "
                              "fract(sin(dot(sk_FragCoord.xy, vec2(12.9898,78.233))) * "
                                                             "43758.5453);\n");
-    fragBuilder->codeAppendf("\t\t%s = (1.0/255.0) * vec4(r, r, r, r) + %s;\n",
+    fragBuilder->codeAppendf("\t\t%s = clamp((1.0/255.0) * vec4(r, r, r, r) + %s, 0, 1);\n",
                              args.fOutputColor, GrGLSLExpr4(args.fInputColor).c_str());
 }
 
index 1a40514..fc8add8 100644 (file)
@@ -98,6 +98,7 @@ void GrGLMatrixConvolutionEffect::emitCode(EmitArgs& args) {
     }
     if (mce.convolveAlpha()) {
         fragBuilder->codeAppendf("%s = sum * %s + %s;", args.fOutputColor, gain, bias);
+        fragBuilder->codeAppendf("%s.a = clamp(%s.a, 0, 1);", args.fOutputColor, args.fOutputColor);
         fragBuilder->codeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);",
                                  args.fOutputColor, args.fOutputColor, args.fOutputColor);
     } else {
@@ -109,7 +110,7 @@ void GrGLMatrixConvolutionEffect::emitCode(EmitArgs& args) {
                               coords2D,
                               args.fTexSamplers[0]);
         fragBuilder->codeAppendf("%s.a = c.a;", args.fOutputColor);
-        fragBuilder->codeAppendf("%s.rgb = sum.rgb * %s + %s;", args.fOutputColor, gain, bias);
+        fragBuilder->codeAppendf("%s.rgb = clamp(sum.rgb * %s + %s, 0, 1);", args.fOutputColor, gain, bias);
         fragBuilder->codeAppendf("%s.rgb *= %s.a;", args.fOutputColor, args.fOutputColor);
     }
 
@@ -157,9 +158,8 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture,
                                                      const SkIPoint& kernelOffset,
                                                      GrTextureDomain::Mode tileMode,
                                                      bool convolveAlpha)
-        // To advertise either the modulation or opaqueness optimizations we'd have to examine the
-        // parameters.
-        : INHERITED(texture, nullptr, SkMatrix::I(), kNone_OptimizationFlags)
+        // To advertise the preserves opaqueness optimization we'd have to examine the parameters.
+        : INHERITED(texture, nullptr, SkMatrix::I(), kModulatesInput_OptimizationFlag)
         , fKernelSize(kernelSize)
         , fGain(SkScalarToFloat(gain))
         , fBias(SkScalarToFloat(bias) / 255.0f)
index 612ebea..5433b67 100644 (file)
@@ -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 @@ public:
 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> ComposeTwoFragmentProcessor::TestCreate(GrProcessorTe
     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 @@ public:
         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 @@ public:
     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;
+        }
+        if (does_cpu_blend_impl_match_gpu(mode) && fp->hasConstantOutputForConstantInput()) {
+            flags |= kConstantOutputForConstantInput_OptimizationFlag;
         }
-        return kNone_OptimizationFlags;
+        return flags;
     }
 
     bool onIsEqual(const GrFragmentProcessor& that) const override {
@@ -280,11 +423,13 @@ sk_sp<GrFragmentProcessor> ComposeOneFragmentProcessor::TestCreate(GrProcessorTe
     // 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 @@ GrGLSLFragmentProcessor* ComposeOneFragmentProcessor::onCreateGLSLInstance() con
 
 //////////////////////////////////////////////////////////////////////////////
 
+// 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 @@ sk_sp<GrFragmentProcessor> GrXfermodeFragmentProcessor::MakeFromSrcProcessor(
     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));
     }
 }
index dc189d6..9c39abc 100644 (file)
@@ -189,14 +189,14 @@ static sk_sp<GrFragmentProcessor> create_random_proc_tree(GrProcessorTestData* d
     }
     // If we didn't terminate, choose either the left or right subtree to fulfill
     // the minLevels requirement of this tree; the other child can have as few levels as it wants.
-    // Also choose a random xfer mode that's supported by CreateFrom2Procs().
+    // Also choose a random xfer mode.
     if (minLevels > 1) {
         --minLevels;
     }
     sk_sp<GrFragmentProcessor> minLevelsChild(create_random_proc_tree(d, minLevels, maxLevels - 1));
     sk_sp<GrFragmentProcessor> otherChild(create_random_proc_tree(d, 1, maxLevels - 1));
     SkBlendMode mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0,
-                                                               (int)SkBlendMode::kLastCoeffMode));
+                                                               (int)SkBlendMode::kLastMode));
     sk_sp<GrFragmentProcessor> fp;
     if (d->fRandom->nextF() < 0.5f) {
         fp = GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(minLevelsChild),
@@ -216,8 +216,8 @@ static void set_random_color_coverage_stages(GrPaint* paint,
     // Randomly choose to either create a linear pipeline of procs or create one proc tree
     const float procTreeProbability = 0.5f;
     if (d->fRandom->nextF() < procTreeProbability) {
-        // A full tree with 5 levels (31 nodes) may exceed the max allowed length of the gl
-        // processor key; maxTreeLevels should be a number from 1 to 4 inclusive.
+        // A full tree with 5 levels (31 nodes) may cause a program that exceeds shader limits
+        // (e.g. uniform or varying limits); maxTreeLevels should be a number from 1 to 4 inclusive.
         const int maxTreeLevels = 4;
         sk_sp<GrFragmentProcessor> fp(create_random_proc_tree(d, 2, maxTreeLevels));
         paint->addColorFragmentProcessor(std::move(fp));