SkSL FPs now support child processors, converted ArithmeticFP to SkSL

Bug: skia:
Change-Id: I34ed3480073d05762a7d4692aeee4b87e454ce52
Reviewed-on: https://skia-review.googlesource.com/57961
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index b026e82..ef751db 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -310,6 +310,8 @@
   "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp",
   "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.h",
 
+  "$_src/gpu/effects/GrArithmeticFP.cpp",
+  "$_src/gpu/effects/GrArithmeticFP.h",
   "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp",
   "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.h",
   "$_src/gpu/effects/GrCircleEffect.cpp",
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 55e8bd2..1de85f6 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -27,6 +27,7 @@
 skia_gpu_processor_sources = [
   "$_src/effects/GrAlphaThresholdFragmentProcessor.fp",
   "$_src/effects/GrCircleBlurFragmentProcessor.fp",
+  "$_src/gpu/effects/GrArithmeticFP.fp",
   "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp",
   "$_src/gpu/effects/GrCircleEffect.fp",
   "$_src/gpu/effects/GrConfigConversionEffect.fp",
diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp
index 27bf28e..d2dd17c 100644
--- a/src/effects/SkArithmeticImageFilter.cpp
+++ b/src/effects/SkArithmeticImageFilter.cpp
@@ -20,6 +20,7 @@
 #include "GrRenderTargetContext.h"
 #include "GrTextureProxy.h"
 #include "SkGr.h"
+#include "effects/GrArithmeticFP.h"
 #include "effects/GrConstColorProcessor.h"
 #include "effects/GrTextureDomain.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
@@ -261,116 +262,8 @@
 
 #if SK_SUPPORT_GPU
 
-namespace {
-class ArithmeticFP : public GrFragmentProcessor {
-public:
-    static std::unique_ptr<GrFragmentProcessor> Make(float k1, float k2, float k3, float k4,
-                                                     bool enforcePMColor,
-                                                     std::unique_ptr<GrFragmentProcessor> dst) {
-        return std::unique_ptr<GrFragmentProcessor>(
-                new ArithmeticFP(k1, k2, k3, k4, enforcePMColor, std::move(dst)));
-    }
-
-    ~ArithmeticFP() override {}
-
-    const char* name() const override { return "Arithmetic"; }
-
-    SkString dumpInfo() const override {
-        SkString str;
-        str.appendf("K1: %.2f K2: %.2f K3: %.2f K4: %.2f", fK1, fK2, fK3, fK4);
-        return str;
-    }
-
-    std::unique_ptr<GrFragmentProcessor> clone() const override {
-        return Make(fK1, fK2, fK3, fK4, fEnforcePMColor, this->childProcessor(0).clone());
-    }
-
-    float k1() const { return fK1; }
-    float k2() const { return fK2; }
-    float k3() const { return fK3; }
-    float k4() const { return fK4; }
-    bool enforcePMColor() const { return fEnforcePMColor; }
-
-private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
-        class GLSLFP : public GrGLSLFragmentProcessor {
-        public:
-            void emitCode(EmitArgs& args) override {
-                const ArithmeticFP& arith = args.fFp.cast<ArithmeticFP>();
-
-                GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-                SkString dstColor("dstColor");
-                this->emitChild(0, &dstColor, args);
-
-                fKUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
-                                                         "k");
-                const char* kUni = args.fUniformHandler->getUniformCStr(fKUni);
-
-                // We don't try to optimize for this case at all
-                if (!args.fInputColor) {
-                    fragBuilder->codeAppend("const half4 src = half4(1);");
-                } else {
-                    fragBuilder->codeAppendf("half4 src = %s;", args.fInputColor);
-                }
-
-                fragBuilder->codeAppendf("half4 dst = %s;", dstColor.c_str());
-                fragBuilder->codeAppendf("%s = %s.x * src * dst + %s.y * src + %s.z * dst + %s.w;",
-                                         args.fOutputColor, kUni, kUni, kUni, kUni);
-                fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);\n", args.fOutputColor,
-                                         args.fOutputColor);
-                if (arith.fEnforcePMColor) {
-                    fragBuilder->codeAppendf("%s.rgb = min(%s.rgb, %s.a);", args.fOutputColor,
-                                             args.fOutputColor, args.fOutputColor);
-                }
-            }
-
-        protected:
-            void onSetData(const GrGLSLProgramDataManager& pdman,
-                           const GrFragmentProcessor& proc) override {
-                const ArithmeticFP& arith = proc.cast<ArithmeticFP>();
-                pdman.set4f(fKUni, arith.k1(), arith.k2(), arith.k3(), arith.k4());
-            }
-
-        private:
-            GrGLSLProgramDataManager::UniformHandle fKUni;
-        };
-        return new GLSLFP;
-    }
-
-    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
-        b->add32(fEnforcePMColor ? 1 : 0);
-    }
-
-    bool onIsEqual(const GrFragmentProcessor& fpBase) const override {
-        const ArithmeticFP& fp = fpBase.cast<ArithmeticFP>();
-        return fK1 == fp.fK1 && fK2 == fp.fK2 && fK3 == fp.fK3 && fK4 == fp.fK4 &&
-               fEnforcePMColor == fp.fEnforcePMColor;
-    }
-
-    // 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,
-                 std::unique_ptr<GrFragmentProcessor> dst)
-            : INHERITED(kArithmeticFP_ClassID, kNone_OptimizationFlags)
-            , fK1(k1)
-            , fK2(k2)
-            , fK3(k3)
-            , fK4(k4)
-            , fEnforcePMColor(enforcePMColor) {
-        SkASSERT(dst);
-        SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(dst));
-        SkASSERT(0 == dstIndex);
-    }
-
-    float fK1, fK2, fK3, fK4;
-    bool fEnforcePMColor;
-
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-    typedef GrFragmentProcessor INHERITED;
-};
-}
-
 #if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> ArithmeticFP::TestCreate(GrProcessorTestData* d) {
+std::unique_ptr<GrFragmentProcessor> GrArithmeticFP::TestCreate(GrProcessorTestData* d) {
     float k1 = d->fRandom->nextF();
     float k2 = d->fRandom->nextF();
     float k3 = d->fRandom->nextF();
@@ -378,11 +271,11 @@
     bool enforcePMColor = d->fRandom->nextBool();
 
     std::unique_ptr<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
-    return ArithmeticFP::Make(k1, k2, k3, k4, enforcePMColor, std::move(dst));
+    return GrArithmeticFP::Make(k1, k2, k3, k4, enforcePMColor, std::move(dst));
 }
 #endif
 
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(ArithmeticFP);
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrArithmeticFP);
 
 sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
         SkSpecialImage* source,
@@ -435,7 +328,7 @@
         paint.addColorFragmentProcessor(std::move(foregroundFP));
 
         std::unique_ptr<GrFragmentProcessor> xferFP =
-                ArithmeticFP::Make(fK[0], fK[1], fK[2], fK[3], fEnforcePMColor, std::move(bgFP));
+                GrArithmeticFP::Make(fK[0], fK[1], fK[2], fK[3], fEnforcePMColor, std::move(bgFP));
 
         // A null 'xferFP' here means kSrc_Mode was used in which case we can just proceed
         if (xferFP) {
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 7f51316..77a6808 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -66,7 +66,6 @@
 public:
     enum ClassID {
         kAARectEffect_ClassID,
-        kArithmeticFP_ClassID,
         kBigKeyProcessor_ClassID,
         kBlockInputFragmentProcessor_ClassID,
         kCircleGeometryProcessor_ClassID,
@@ -91,6 +90,7 @@
         kFocalOutside2PtConicalEffect_ClassID,
         kGP_ClassID,
         kGrAlphaThresholdFragmentProcessor_ClassID,
+        kGrArithmeticFP_ClassID,
         kGrBicubicEffect_ClassID,
         kGrBitmapTextGeoProc_ClassID,
         kGrBlurredEdgeFragmentProcessor_ClassID,
diff --git a/src/gpu/effects/GrArithmeticFP.cpp b/src/gpu/effects/GrArithmeticFP.cpp
new file mode 100644
index 0000000..bc73b45
--- /dev/null
+++ b/src/gpu/effects/GrArithmeticFP.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * This file was autogenerated from GrArithmeticFP.fp; do not modify.
+ */
+#include "GrArithmeticFP.h"
+#if SK_SUPPORT_GPU
+#include "glsl/GrGLSLColorSpaceXformHelper.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+class GrGLSLArithmeticFP : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLArithmeticFP() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrArithmeticFP& _outer = args.fFp.cast<GrArithmeticFP>();
+        (void)_outer;
+        fKVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
+                                                 kDefault_GrSLPrecision, "k");
+        SkString _child0("_child0");
+        this->emitChild(0, &_child0, args);
+        fragBuilder->codeAppendf(
+                "half4 dst = %s;\n%s = half4(clamp(float4(float4(((%s.x * float4(%s)) * dst + %s.y "
+                "* float4(%s)) + %s.z * float4(dst)) + %s.w), 0.0, 1.0));\nif (%s) {\n    %s.xyz = "
+                "half3(min(float3(%s.xyz), float(%s.w)));\n}\n",
+                _child0.c_str(), args.fOutputColor, args.fUniformHandler->getUniformCStr(fKVar),
+                args.fInputColor ? args.fInputColor : "half4(1)",
+                args.fUniformHandler->getUniformCStr(fKVar),
+                args.fInputColor ? args.fInputColor : "half4(1)",
+                args.fUniformHandler->getUniformCStr(fKVar),
+                args.fUniformHandler->getUniformCStr(fKVar),
+                (_outer.enforcePMColor() ? "true" : "false"), args.fOutputColor, args.fOutputColor,
+                args.fOutputColor);
+    }
+
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman,
+                   const GrFragmentProcessor& _proc) override {
+        const GrArithmeticFP& _outer = _proc.cast<GrArithmeticFP>();
+        auto k1 = _outer.k1();
+        (void)k1;
+        auto k2 = _outer.k2();
+        (void)k2;
+        auto k3 = _outer.k3();
+        (void)k3;
+        auto k4 = _outer.k4();
+        (void)k4;
+        auto enforcePMColor = _outer.enforcePMColor();
+        (void)enforcePMColor;
+        UniformHandle& k = fKVar;
+        (void)k;
+
+        pdman.set4f(k, k1, k2, k3, k4);
+    }
+    UniformHandle fKVar;
+};
+GrGLSLFragmentProcessor* GrArithmeticFP::onCreateGLSLInstance() const {
+    return new GrGLSLArithmeticFP();
+}
+void GrArithmeticFP::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                           GrProcessorKeyBuilder* b) const {
+    b->add32(fEnforcePMColor);
+}
+bool GrArithmeticFP::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrArithmeticFP& that = other.cast<GrArithmeticFP>();
+    (void)that;
+    if (fK1 != that.fK1) return false;
+    if (fK2 != that.fK2) return false;
+    if (fK3 != that.fK3) return false;
+    if (fK4 != that.fK4) return false;
+    if (fEnforcePMColor != that.fEnforcePMColor) return false;
+    return true;
+}
+GrArithmeticFP::GrArithmeticFP(const GrArithmeticFP& src)
+        : INHERITED(kGrArithmeticFP_ClassID, src.optimizationFlags())
+        , fK1(src.fK1)
+        , fK2(src.fK2)
+        , fK3(src.fK3)
+        , fK4(src.fK4)
+        , fEnforcePMColor(src.fEnforcePMColor) {
+    this->registerChildProcessor(src.childProcessor(0).clone());
+}
+std::unique_ptr<GrFragmentProcessor> GrArithmeticFP::clone() const {
+    return std::unique_ptr<GrFragmentProcessor>(new GrArithmeticFP(*this));
+}
+#endif
diff --git a/src/gpu/effects/GrArithmeticFP.fp b/src/gpu/effects/GrArithmeticFP.fp
new file mode 100644
index 0000000..546fa79
--- /dev/null
+++ b/src/gpu/effects/GrArithmeticFP.fp
@@ -0,0 +1,20 @@
+in float k1;
+in float k2;
+in float k3;
+in float k4;
+layout(key) in bool enforcePMColor;
+in fragmentProcessor child;
+
+uniform float4 k;
+
+void main() {
+    half4 dst = process(child);
+    sk_OutColor = clamp(k.x * sk_InColor * dst + k.y * sk_InColor + k.z * dst + k.w, 0, 1);
+    if (enforcePMColor) {
+        sk_OutColor.rgb = min(sk_OutColor.rgb, sk_OutColor.a);
+    }
+}
+
+@setData(pdman) {
+    pdman.set4f(k, k1, k2, k3, k4);
+}
diff --git a/src/gpu/effects/GrArithmeticFP.h b/src/gpu/effects/GrArithmeticFP.h
new file mode 100644
index 0000000..ee151a1
--- /dev/null
+++ b/src/gpu/effects/GrArithmeticFP.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * This file was autogenerated from GrArithmeticFP.fp; do not modify.
+ */
+#ifndef GrArithmeticFP_DEFINED
+#define GrArithmeticFP_DEFINED
+#include "SkTypes.h"
+#if SK_SUPPORT_GPU
+#include "GrFragmentProcessor.h"
+#include "GrCoordTransform.h"
+#include "GrColorSpaceXform.h"
+class GrArithmeticFP : public GrFragmentProcessor {
+public:
+    float k1() const { return fK1; }
+    float k2() const { return fK2; }
+    float k3() const { return fK3; }
+    float k4() const { return fK4; }
+    bool enforcePMColor() const { return fEnforcePMColor; }
+    static std::unique_ptr<GrFragmentProcessor> Make(float k1, float k2, float k3, float k4,
+                                                     bool enforcePMColor,
+                                                     std::unique_ptr<GrFragmentProcessor> child) {
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrArithmeticFP(k1, k2, k3, k4, enforcePMColor, child->clone()));
+    }
+    GrArithmeticFP(const GrArithmeticFP& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "ArithmeticFP"; }
+
+private:
+    GrArithmeticFP(float k1, float k2, float k3, float k4, bool enforcePMColor,
+                   std::unique_ptr<GrFragmentProcessor> child)
+            : INHERITED(kGrArithmeticFP_ClassID, kNone_OptimizationFlags)
+            , fK1(k1)
+            , fK2(k2)
+            , fK3(k3)
+            , fK4(k4)
+            , fEnforcePMColor(enforcePMColor) {
+        this->registerChildProcessor(std::move(child));
+    }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    float fK1;
+    float fK2;
+    float fK3;
+    float fK4;
+    bool fEnforcePMColor;
+    typedef GrFragmentProcessor INHERITED;
+};
+#endif
+#endif
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 3066130..705155e 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -246,6 +246,36 @@
 }
 
 void CPPCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+    if (c.fFunction.fBuiltin && c.fFunction.fName == "process") {
+        ASSERT(c.fArguments.size() == 1);
+        ASSERT(Expression::kVariableReference_Kind == c.fArguments[0]->fKind);
+        int index = 0;
+        bool found = false;
+        for (const auto& p : fProgram.fElements) {
+            if (ProgramElement::kVar_Kind == p->fKind) {
+                const VarDeclarations* decls = (const VarDeclarations*) p.get();
+                for (const auto& raw : decls->fVars) {
+                    VarDeclaration& decl = (VarDeclaration&) *raw;
+                    if (decl.fVar == &((VariableReference&) *c.fArguments[0]).fVariable) {
+                        found = true;
+                    } else if (decl.fVar->fType == *fContext.fFragmentProcessor_Type) {
+                        ++index;
+                    }
+                }
+            }
+            if (found) {
+                break;
+            }
+        }
+        ASSERT(found);
+        String childName = "_child" + to_string(index);
+        fExtraEmitCodeCode += "        SkString " + childName + "(\"" + childName + "\");\n" +
+                              "        this->emitChild(" + to_string(index) + ", &" + childName +
+                              ", args);\n";
+        this->write("%s");
+        fFormatArgs.push_back(childName + ".c_str()");
+        return;
+    }
     if (c.fFunction.fBuiltin && c.fFunction.fName == "COLORSPACE") {
         String tmpVar = "_tmpVar" + to_string(++fVarCount);
         fFunctionHeader += "half4 " + tmpVar + ";";
@@ -383,8 +413,13 @@
             for (const auto& raw : decls->fVars) {
                 VarDeclaration& decl = (VarDeclaration&) *raw;
                 if (is_private(*decl.fVar)) {
+                    if (decl.fVar->fType == *fContext.fFragmentProcessor_Type) {
+                        fErrors.error(decl.fOffset,
+                                      "fragmentProcessor variables must be declared 'in'");
+                        return;
+                    }
                     this->writef("%s %s;\n",
-                                 HCodeGenerator::FieldType(decl.fVar->fType).c_str(),
+                                 HCodeGenerator::FieldType(fContext, decl.fVar->fType).c_str(),
                                  String(decl.fVar->fName).c_str());
                 }
             }
@@ -477,6 +512,8 @@
                              "            fColorSpaceHelper.setData(%s, _outer.%s().get());\n"
                              "        }\n",
                              pdman, name);
+            } else if (u->fType == *fContext.fFragmentProcessor_Type) {
+                // do nothing
             } else {
                 this->writef("        %s.set1f(%sVar, _outer.%s());\n",
                              pdman, HCodeGenerator::FieldName(name).c_str(), name);
@@ -498,7 +535,8 @@
                         this->writef("        UniformHandle& %s = %sVar;\n"
                                      "        (void) %s;\n",
                                      name, HCodeGenerator::FieldName(name).c_str(), name);
-                    } else if (SectionAndParameterHelper::IsParameter(*decl.fVar)) {
+                    } else if (SectionAndParameterHelper::IsParameter(*decl.fVar) &&
+                               decl.fVar->fType != *fContext.fFragmentProcessor_Type) {
                         if (!wroteProcessor) {
                             this->writef("        const %s& _outer = _proc.cast<%s>();\n", fullName,
                                          fullName);
@@ -526,10 +564,13 @@
                      ": INHERITED(k%s_ClassID, src.optimizationFlags())", fFullName.c_str(),
                      fFullName.c_str(), fFullName.c_str(), fFullName.c_str());
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+            if (param->fType == *fContext.fFragmentProcessor_Type) {
+                continue;
+            }
             String fieldName = HCodeGenerator::FieldName(String(param->fName).c_str());
-            this->writef("\n, %s(%s)",
+            this->writef("\n, %s(src.%s)",
                          fieldName.c_str(),
-                         ("src." + fieldName).c_str());
+                         fieldName.c_str());
         }
         for (const Section* s : fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION)) {
             String fieldName = HCodeGenerator::FieldName(s->fArgument.c_str());
@@ -537,10 +578,14 @@
                          fieldName.c_str());
         }
         this->writef(" {\n");
+        int childCount = 0;
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
             if (param->fType.kind() == Type::kSampler_Kind) {
                 this->writef("    this->addTextureSampler(&%s);\n",
                              HCodeGenerator::FieldName(String(param->fName).c_str()).c_str());
+            } else if (param->fType == *fContext.fFragmentProcessor_Type) {
+                this->writef("    this->registerChildProcessor(src.childProcessor(%d).clone());"
+                             "\n", childCount++);
             }
         }
         for (const Section* s : fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION)) {
@@ -687,6 +732,9 @@
                  "    (void) that;\n",
                  fullName, fullName, fullName);
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+        if (param->fType == *fContext.fFragmentProcessor_Type) {
+            continue;
+        }
         String nameString(param->fName);
         const char* name = nameString.c_str();
         this->writef("    if (%s != that.%s) return false;\n",
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index 5373f9d..23c09ac 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -185,6 +185,7 @@
     ADD_TYPE(GSampler2DArrayShadow);
     ADD_TYPE(GSamplerCubeArrayShadow);
     ADD_TYPE(ColorSpaceXform);
+    ADD_TYPE(FragmentProcessor);
 
     StringFragment skCapsName("sk_Caps");
     Variable* skCaps = new Variable(-1, Modifiers(), skCapsName,
@@ -1246,7 +1247,7 @@
 
 bool Compiler::toH(const Program& program, String name, OutputStream& out) {
     fSource = program.fSource.get();
-    HCodeGenerator cg(&program, this, name, &out);
+    HCodeGenerator cg(&fContext, &program, this, name, &out);
     bool result = cg.generateCode();
     fSource = nullptr;
     this->writeErrorCount();
diff --git a/src/sksl/SkSLContext.h b/src/sksl/SkSLContext.h
index 5061b61..a8158be 100644
--- a/src/sksl/SkSLContext.h
+++ b/src/sksl/SkSLContext.h
@@ -185,6 +185,7 @@
     , fSkCaps_Type(new Type("$sk_Caps"))
     , fSkArgs_Type(new Type("$sk_Args"))
     , fColorSpaceXform_Type(new Type("colorSpaceXform", *fFloat_Type, 4, 4))
+    , fFragmentProcessor_Type(new Type("fragmentProcessor"))
     , fDefined_Expression(new Defined(*fInvalid_Type)) {}
 
     static std::vector<const Type*> static_type(const Type& t) {
@@ -333,6 +334,7 @@
     const std::unique_ptr<Type> fSkCaps_Type;
     const std::unique_ptr<Type> fSkArgs_Type;
     const std::unique_ptr<Type> fColorSpaceXform_Type;
+    const std::unique_ptr<Type> fFragmentProcessor_Type;
 
     // dummy expression used to mark that a variable has a value during dataflow analysis (when it
     // could have several different values, or the analyzer is otherwise unable to assign it a
diff --git a/src/sksl/SkSLHCodeGenerator.cpp b/src/sksl/SkSLHCodeGenerator.cpp
index eb82631..a4a5c9a 100644
--- a/src/sksl/SkSLHCodeGenerator.cpp
+++ b/src/sksl/SkSLHCodeGenerator.cpp
@@ -15,37 +15,45 @@
 
 namespace SkSL {
 
-HCodeGenerator::HCodeGenerator(const Program* program, ErrorReporter* errors, String name,
-                               OutputStream* out)
+HCodeGenerator::HCodeGenerator(const Context* context, const Program* program,
+                               ErrorReporter* errors, String name, OutputStream* out)
 : INHERITED(program, errors, out)
+, fContext(*context)
 , fName(std::move(name))
 , fFullName(String::printf("Gr%s", fName.c_str()))
 , fSectionAndParameterHelper(*program, *errors) {}
 
-String HCodeGenerator::ParameterType(const Type& type) {
-    if (type.name() == "float" || type.name() == "half") {
+String HCodeGenerator::ParameterType(const Context& context, const Type& type) {
+    if (type == *context.fFloat_Type || type == *context.fHalf_Type) {
         return "float";
-    } else if (type.name() == "float2" || type.name() == "half2") {
+    } else if (type == *context.fFloat2_Type || type == *context.fHalf2_Type) {
         return "SkPoint";
-    } else if (type.name() == "int4" || type.name() == "short4") {
+    } else if (type == *context.fInt4_Type || type == *context.fShort4_Type) {
         return "SkIRect";
-    } else if (type.name() == "float4" || type.name() == "half4") {
+    } else if (type == *context.fFloat4_Type || type == *context.fHalf4_Type) {
         return "SkRect";
-    } else if (type.name() == "float4x4" || type.name() == "half4x4") {
+    } else if (type == *context.fFloat4x4_Type || type == *context.fHalf4x4_Type) {
         return "SkMatrix44";
     } else if (type.kind() == Type::kSampler_Kind) {
         return "sk_sp<GrTextureProxy>";
-    } else if (type.name() == "colorSpaceXform") {
+    } else if (type == *context.fColorSpaceXform_Type) {
         return "sk_sp<GrColorSpaceXform>";
+    } else if (type == *context.fFragmentProcessor_Type) {
+        return "std::unique_ptr<GrFragmentProcessor>";
     }
     return type.name();
 }
 
-String HCodeGenerator::FieldType(const Type& type) {
+String HCodeGenerator::FieldType(const Context& context, const Type& type) {
     if (type.kind() == Type::kSampler_Kind) {
         return "TextureSampler";
+    } else if (type == *context.fFragmentProcessor_Type) {
+        // we don't store fragment processors in fields, they get registered via
+        // registerChildProcessor instead
+        ASSERT(false);
+        return "<error>";
     }
-    return ParameterType(type);
+    return ParameterType(context, type);
 }
 
 void HCodeGenerator::writef(const char* s, va_list va) {
@@ -128,7 +136,7 @@
         this->writef("    static std::unique_ptr<GrFragmentProcessor> Make(");
         separator = "";
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-            this->writef("%s%s %s", separator, ParameterType(param->fType).c_str(),
+            this->writef("%s%s %s", separator, ParameterType(fContext, param->fType).c_str(),
                          String(param->fName).c_str());
             separator = ", ";
         }
@@ -138,7 +146,11 @@
                      fFullName.c_str());
         separator = "";
         for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-            this->writef("%s%s", separator, String(param->fName).c_str());
+            if (param->fType == *fContext.fFragmentProcessor_Type) {
+                this->writef("%s%s->clone()", separator, String(param->fName).c_str());
+            } else {
+                this->writef("%s%s", separator, String(param->fName).c_str());
+            }
             separator = ", ";
         }
         this->writeExtraConstructorParams(separator);
@@ -166,7 +178,7 @@
     this->writef("    %s(", fFullName.c_str());
     const char* separator = "";
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        this->writef("%s%s %s", separator, ParameterType(param->fType).c_str(),
+        this->writef("%s%s %s", separator, ParameterType(fContext, param->fType).c_str(),
                      String(param->fName).c_str());
         separator = ", ";
     }
@@ -190,6 +202,8 @@
                 }
             }
             this->writef(")");
+        } else if (param->fType == *fContext.fFragmentProcessor_Type) {
+            // do nothing
         } else {
             this->writef("\n    , %s(%s)", FieldName(name).c_str(), name);
         }
@@ -205,6 +219,9 @@
         if (param->fType.kind() == Type::kSampler_Kind) {
             this->writef("        this->addTextureSampler(&%s);\n",
                          FieldName(String(param->fName).c_str()).c_str());
+        } else if (param->fType == *fContext.fFragmentProcessor_Type) {
+            this->writef("        this->registerChildProcessor(std::move(%s));",
+                         String(param->fName).c_str());
         }
     }
     for (const Section* s : fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION)) {
@@ -217,7 +234,10 @@
 void HCodeGenerator::writeFields() {
     this->writeSection(FIELDS_SECTION);
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        this->writef("    %s %s;\n", FieldType(param->fType).c_str(),
+        if (param->fType == *fContext.fFragmentProcessor_Type) {
+            continue;
+        }
+        this->writef("    %s %s;\n", FieldType(fContext, param->fType).c_str(),
                      FieldName(String(param->fName).c_str()).c_str());
     }
     for (const Section* s : fSectionAndParameterHelper.getSections(COORD_TRANSFORM_SECTION)) {
@@ -243,13 +263,14 @@
                  fFullName.c_str());
     this->writeSection(CLASS_SECTION);
     for (const auto& param : fSectionAndParameterHelper.getParameters()) {
-        if (param->fType.kind() == Type::kSampler_Kind) {
+        if (param->fType.kind() == Type::kSampler_Kind ||
+            param->fType.kind() == Type::kOther_Kind) {
             continue;
         }
         String nameString(param->fName);
         const char* name = nameString.c_str();
         this->writef("    %s %s() const { return %s; }\n",
-                     FieldType(param->fType).c_str(), name, FieldName(name).c_str());
+                     FieldType(fContext, param->fType).c_str(), name, FieldName(name).c_str());
     }
     this->writeMake();
     this->writef("    %s(const %s& src);\n"
diff --git a/src/sksl/SkSLHCodeGenerator.h b/src/sksl/SkSLHCodeGenerator.h
index 3e76555..5d3b0e7 100644
--- a/src/sksl/SkSLHCodeGenerator.h
+++ b/src/sksl/SkSLHCodeGenerator.h
@@ -32,13 +32,14 @@
 
 class HCodeGenerator : public CodeGenerator {
 public:
-    HCodeGenerator(const Program* program, ErrorReporter* errors, String name, OutputStream* out);
+    HCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
+                   String name, OutputStream* out);
 
     bool generateCode() override;
 
-    static String ParameterType(const Type& type);
+    static String ParameterType(const Context& context, const Type& type);
 
-    static String FieldType(const Type& type);
+    static String FieldType(const Context& context, const Type& type);
 
     static String FieldName(const char* varName) {
         return String::printf("f%c%s", toupper(varName[0]), varName + 1);
@@ -63,6 +64,7 @@
 
     void failOnSection(const char* section, const char* msg);
 
+    const Context& fContext;
     String fName;
     String fFullName;
     SectionAndParameterHelper fSectionAndParameterHelper;
diff --git a/src/sksl/sksl_fp.include b/src/sksl/sksl_fp.include
index 2cb46f1..eacc7e8 100644
--- a/src/sksl/sksl_fp.include
+++ b/src/sksl/sksl_fp.include
@@ -22,4 +22,6 @@
 layout(builtin=10006) sampler2D[] sk_TextureSamplers;
 
 half4 COLORSPACE(half4 color, colorSpaceXform colorSpace);
+
+half4 process(fragmentProcessor fp);
 )
diff --git a/tests/SkSLFPTest.cpp b/tests/SkSLFPTest.cpp
index 3e95d99..ce09b95 100644
--- a/tests/SkSLFPTest.cpp
+++ b/tests/SkSLFPTest.cpp
@@ -402,4 +402,26 @@
 
 }
 
+DEF_TEST(SkSLFPChildProcessors, r) {
+    test(r,
+         "in fragmentProcessor child1;"
+         "in fragmentProcessor child2;"
+         "void main() {"
+         "    sk_OutColor = process(child1) * process(child2);"
+         "}",
+         *SkSL::ShaderCapsFactory::Default(),
+         {
+            "this->registerChildProcessor(std::move(child1));",
+            "this->registerChildProcessor(std::move(child2));"
+         },
+         {
+            "SkString _child0(\"_child0\");",
+            "this->emitChild(0, &_child0, args);",
+            "SkString _child1(\"_child1\");",
+            "this->emitChild(1, &_child1, args);",
+            "this->registerChildProcessor(src.childProcessor(0).clone());",
+            "this->registerChildProcessor(src.childProcessor(1).clone());"
+         });
+}
+
 #endif