Add GrFragmentProcessor::Compose for composing two processors.

This is functionally equivalent to RunInSeries, but the implementation
can be simpler, because it doesn't support an arbitrary number of
processors.

In practice, Ganesh no longer calls RunInSeries with more than two
inputs at once, so all existing calls to RunInSeries have been replaced
with Compose.

Change-Id: I719d0a11ed747775af4e99fb9de33323d43e7874
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/305400
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index cea8a90..5d3fa6d 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -698,12 +698,8 @@
         }
 
         // Runtime effect scripts are written to take an input color, not a fragment processor.
-        // We need to evaluate the input and pass it to the runtime filter via RunInSeries.
-        std::unique_ptr<GrFragmentProcessor> fp[] = {
-            std::move(inputFP),
-            std::move(runtimeFP),
-        };
-        return GrFPSuccess(GrFragmentProcessor::RunInSeries(fp, SK_ARRAY_COUNT(fp)));
+        // We need to pass the input to the runtime filter using Compose.
+        return GrFPSuccess(GrFragmentProcessor::Compose(std::move(inputFP), std::move(runtimeFP)));
     }
 #endif
 
diff --git a/src/gpu/GrAppliedClip.h b/src/gpu/GrAppliedClip.h
index c02f815..a7347ea 100644
--- a/src/gpu/GrAppliedClip.h
+++ b/src/gpu/GrAppliedClip.h
@@ -121,12 +121,8 @@
         if (fCoverageFP == nullptr) {
             fCoverageFP = std::move(fp);
         } else {
-            // Run this coverage FP in series with the previously-added coverage.
-            std::unique_ptr<GrFragmentProcessor> series[] = {
-                std::move(fCoverageFP),
-                std::move(fp),
-            };
-            fCoverageFP = GrFragmentProcessor::RunInSeries(series, SK_ARRAY_COUNT(series));
+            // Compose this coverage FP with the previously-added coverage.
+            fCoverageFP = GrFragmentProcessor::Compose(std::move(fCoverageFP), std::move(fp));
         }
     }
 
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 0f3f3b2..79b579b 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -376,6 +376,8 @@
     return GrOverrideInputFragmentProcessor::Make(std::move(fp), color, useUniform);
 }
 
+//////////////////////////////////////////////////////////////////////////////
+
 std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::RunInSeries(
         std::unique_ptr<GrFragmentProcessor> series[], int cnt) {
     class SeriesFragmentProcessor : public GrFragmentProcessor {
@@ -479,6 +481,99 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
+std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::Compose(
+        std::unique_ptr<GrFragmentProcessor> f, std::unique_ptr<GrFragmentProcessor> g) {
+    class ComposeProcessor : public GrFragmentProcessor {
+    public:
+        static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> f,
+                                                         std::unique_ptr<GrFragmentProcessor> g) {
+            return std::unique_ptr<GrFragmentProcessor>(new ComposeProcessor(std::move(f),
+                                                                             std::move(g)));
+        }
+
+        const char* name() const override { return "Compose"; }
+
+        std::unique_ptr<GrFragmentProcessor> clone() const override {
+            return std::unique_ptr<GrFragmentProcessor>(new ComposeProcessor(*this));
+        }
+
+    private:
+        GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
+            class GLFP : public GrGLSLFragmentProcessor {
+            public:
+                void emitCode(EmitArgs& args) override {
+                    SkString result = this->invokeChild(0, args);
+                    result = this->invokeChild(1, result.c_str(), args);
+                    args.fFragBuilder->codeAppendf("%s = %s;", args.fOutputColor, result.c_str());
+                }
+            };
+            return new GLFP;
+        }
+
+        ComposeProcessor(std::unique_ptr<GrFragmentProcessor> f,
+                         std::unique_ptr<GrFragmentProcessor> g)
+                : INHERITED(kSeriesFragmentProcessor_ClassID,
+                            f->optimizationFlags() & g->optimizationFlags()) {
+            this->registerChild(std::move(f));
+            this->registerChild(std::move(g));
+        }
+
+        ComposeProcessor(const ComposeProcessor& that)
+                : INHERITED(kSeriesFragmentProcessor_ClassID, that.optimizationFlags()) {
+            this->cloneAndRegisterAllChildProcessors(that);
+        }
+
+        void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
+
+        bool onIsEqual(const GrFragmentProcessor&) const override { return true; }
+
+        SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& inColor) const override {
+            SkPMColor4f color = inColor;
+            color = ConstantOutputForConstantInput(this->childProcessor(0), color);
+            color = ConstantOutputForConstantInput(this->childProcessor(1), color);
+            return color;
+        }
+
+        typedef GrFragmentProcessor INHERITED;
+    };
+
+    // Allow either of the composed functions to be null.
+    if (f == nullptr) {
+        return g;
+    }
+    if (g == nullptr) {
+        return f;
+    }
+
+    // Run an optimization pass on this composition.
+    GrProcessorAnalysisColor inputColor;
+    inputColor.setToUnknown();
+
+    std::unique_ptr<GrFragmentProcessor> series[2] = {std::move(f), std::move(g)};
+    GrColorFragmentProcessorAnalysis info(inputColor, series, SK_ARRAY_COUNT(series));
+
+    SkPMColor4f knownColor;
+    int leadingFPsToEliminate = info.initialProcessorsToEliminate(&knownColor);
+    switch (leadingFPsToEliminate) {
+        default:
+            // We shouldn't eliminate more than we started with.
+            SkASSERT(leadingFPsToEliminate <= 2);
+            [[fallthrough]];
+        case 0:
+            // Compose the two processors as requested.
+            return ComposeProcessor::Make(std::move(series[0]), std::move(series[1]));
+        case 1:
+            // Replace the first processor with a constant color.
+            return ComposeProcessor::Make(GrConstColorProcessor::Make(knownColor),
+                                          std::move(series[1]));
+        case 2:
+            // Replace the entire composition with a constant color.
+            return GrConstColorProcessor::Make(knownColor);
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 GrFragmentProcessor::CIter::CIter(const GrPaint& paint) {
     if (paint.hasCoverageFragmentProcessor()) {
         fFPStack.push_back(paint.getCoverageFragmentProcessor());
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index ac7570e..69b7b23 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -111,6 +111,14 @@
                                                             int cnt);
 
     /**
+     * Returns a fragment processor that composes two fragment processors `f` and `g` into f(g(x)).
+     * This is equivalent to running them in series. This is not the same as transfer-mode
+     * composition; there is no blending step.
+     */
+    static std::unique_ptr<GrFragmentProcessor> Compose(std::unique_ptr<GrFragmentProcessor> f,
+                                                        std::unique_ptr<GrFragmentProcessor> g);
+
+    /**
      * Makes a copy of this fragment processor that draws equivalently to the original.
      * If the processor has child processors they are cloned as well.
      */
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 6b3518f..5049523 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -927,18 +927,7 @@
         }
     }
 
-    // Combine the clip and shader FPs using RunInSeries. (RunInSeries will automatically return the
-    // input as-is if we only have one.)
-    SkSTArray<2, std::unique_ptr<GrFragmentProcessor>> seriesFPs;
-    if (clipFP != nullptr) {
-        seriesFPs.push_back(std::move(clipFP));
-    }
-    if (shaderFP != nullptr) {
-        seriesFPs.push_back(std::move(shaderFP));
-    }
-
-    return seriesFPs.empty()
-               ? nullptr
-               : GrFragmentProcessor::RunInSeries(&seriesFPs.front(), seriesFPs.size());
+    // Compose the clip and shader FPs.
+    return GrFragmentProcessor::Compose(std::move(clipFP), std::move(shaderFP));
 }
 
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 004c160..9a3c687 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -415,8 +415,7 @@
             if (!shaderFP) {
                 return false;
             }
-            std::unique_ptr<GrFragmentProcessor> fpSeries[] = { std::move(shaderFP), std::move(fp) };
-            shaderFP = GrFragmentProcessor::RunInSeries(fpSeries, 2);
+            shaderFP = GrFragmentProcessor::Compose(std::move(shaderFP), std::move(fp));
         } else {
             shaderFP = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp));
         }