/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLStringStream.h"

#include "tests/Test.h"

static void test(skiatest::Reporter* r, const GrShaderCaps& caps, const char* src,
                 std::vector<const char*> expectedH, std::vector<const char*> expectedCPP) {
    SkSL::Program::Settings settings;
    settings.fCaps = &caps;
    settings.fRemoveDeadFunctions = false;
    SkSL::Compiler compiler;
    SkSL::StringStream output;
    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
                                                             SkSL::Program::kFragmentProcessor_Kind,
                                                             SkSL::String(src),
                                                             settings);
    if (!program) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
        return;
    }
    REPORTER_ASSERT(r, program);
    bool success = compiler.toH(*program, "Test", output);
    if (!success) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, success);
    if (success) {
        for (const char* expected : expectedH) {
            bool found = strstr(output.str().c_str(), expected);
            if (!found) {
                SkDebugf("HEADER MISMATCH:\nsource:\n%s\n\n"
                         "header expected:\n'%s'\n\n"
                         "header received:\n'%s'",
                         src, expected, output.str().c_str());
            }
            REPORTER_ASSERT(r, found);
        }
    }
    output.reset();
    success = compiler.toCPP(*program, "Test", output);
    if (!success) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, success);
    if (success) {
        for (const char* expected : expectedCPP) {
            bool found = strstr(output.str().c_str(), expected);
            if (!found) {
                SkDebugf("CPP MISMATCH:\nsource:\n%s\n\n"
                         "cpp expected:\n'%s'\n\n"
                         "cpp received:\n'%s'",
                         src, expected, output.str().c_str());
            }
            REPORTER_ASSERT(r, found);
        }
    }
}

static void test_failure(skiatest::Reporter* r, const char* src, const char* error) {
    SkSL::Compiler compiler;
    SkSL::Program::Settings settings;
    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
    settings.fCaps = caps.get();
    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
                                                             SkSL::Program::kFragmentProcessor_Kind,
                                                             SkSL::String(src),
                                                             settings);
    if (!compiler.errorCount()) {
        compiler.optimize(*program);
    }
    SkSL::String skError(error);
    if (compiler.errorText() != skError) {
        SkDebugf("SKSL ERROR:\n    source: %s\n    expected: %s    received: %s",
                 src, error, compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, compiler.errorText() == skError);
}

DEF_TEST(SkSLFPHelloWorld, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             /* HELLO WORLD */
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
R"__Header__(/* HELLO WORLD */

/**************************************************************************************************
 *** This file was autogenerated from GrTest.fp; do not modify.
 **************************************************************************************************/
#ifndef GrTest_DEFINED
#define GrTest_DEFINED

#include "include/core/SkM44.h"
#include "include/core/SkTypes.h"


#include "src/gpu/GrCoordTransform.h"
#include "src/gpu/GrFragmentProcessor.h"

class GrTest : public GrFragmentProcessor {
public:
    static std::unique_ptr<GrFragmentProcessor> Make() {
        return std::unique_ptr<GrFragmentProcessor>(new GrTest());
    }
    GrTest(const GrTest& src);
    std::unique_ptr<GrFragmentProcessor> clone() const override;
    const char* name() const override { return "Test"; }
private:
    GrTest()
    : INHERITED(kGrTest_ClassID, kNone_OptimizationFlags) {
    }
    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
    void onGetGLSLProcessorKey(const GrShaderCaps&,GrProcessorKeyBuilder*) const override;
    bool onIsEqual(const GrFragmentProcessor&) const override;
    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
    typedef GrFragmentProcessor INHERITED;
};
#endif
)__Header__"
         },
         /*expectedCPP=*/{
R"__Cpp__(/* HELLO WORLD */

/**************************************************************************************************
 *** This file was autogenerated from GrTest.fp; do not modify.
 **************************************************************************************************/
#include "GrTest.h"

#include "src/gpu/GrTexture.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/sksl/SkSLCPP.h"
#include "src/sksl/SkSLUtil.h"
class GrGLSLTest : public GrGLSLFragmentProcessor {
public:
    GrGLSLTest() {}
    void emitCode(EmitArgs& args) override {
        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
        const GrTest& _outer = args.fFp.cast<GrTest>();
        (void) _outer;
        fragBuilder->codeAppendf(
R"SkSL(%s = half4(1.0);
)SkSL"
, args.fOutputColor);
    }
private:
    void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& _proc) override {
    }
};
GrGLSLFragmentProcessor* GrTest::onCreateGLSLInstance() const {
    return new GrGLSLTest();
}
void GrTest::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const {
}
bool GrTest::onIsEqual(const GrFragmentProcessor& other) const {
    const GrTest& that = other.cast<GrTest>();
    (void) that;
    return true;
}
GrTest::GrTest(const GrTest& src)
: INHERITED(kGrTest_ClassID, src.optimizationFlags()) {
}
std::unique_ptr<GrFragmentProcessor> GrTest::clone() const {
    return std::unique_ptr<GrFragmentProcessor>(new GrTest(*this));
}
)__Cpp__"
         });
}

DEF_TEST(SkSLFPInput, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             layout(key) in half2 point;
             void main() {
                 sk_OutColor = half4(point, point);
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
             "return std::unique_ptr<GrFragmentProcessor>(new GrTest(point));",
             "GrTest(SkPoint point)",
             ", point(point)"
         },
         /*expectedCPP=*/{
             "fragBuilder->codeAppendf(\n"
             "R\"SkSL(%s = half4(half2(%f, %f), half2(%f, %f));\n"
             ")SkSL\"\n"
             ", args.fOutputColor, _outer.point.fX, _outer.point.fY, _outer.point.fX, _outer.point.fY);",
             "if (point != that.point) return false;"
         });
}

DEF_TEST(SkSLFPUniform, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             uniform half4 color;
             void main() {
                 sk_OutColor = color;
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make()"
         },
         /*expectedCPP=*/{
             "colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf4_GrSLType, \"color\");",
         });
}

// SkSLFPInUniform tests the simplest plumbing case, default type, no tracking
// with a setUniform template that supports inlining the value call with no
// local variable.
DEF_TEST(SkSLFPInUniform, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in uniform half4 color;
             void main() {
                 sk_OutColor = color;
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
         },
         /*expectedCPP=*/{
            "colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf4_GrSLType, \"color\");",
            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&(_outer.color)));"
         });
}

// As above, but tests in uniform's ability to override the default ctype.
DEF_TEST(SkSLFPInUniformCType, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             layout(ctype=SkPMColor4f) in uniform half4 color;
             void main() {
                 sk_OutColor = color;
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color) {",
         },
         /*expectedCPP=*/{
            "colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf4_GrSLType, \"color\");",
            "pdman.set4fv(colorVar, 1, (_outer.color).vec());"
         });
}

// Add state tracking to the default typed SkRect <-> half4 uniform. But since
// it now has to track state, the value inlining previously done for the
// setUniform call is removed in favor of a local variable.
DEF_TEST(SkSLFPTrackedInUniform, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             layout(tracked) in uniform half4 color;
             void main() {
                 sk_OutColor = color;
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
         },
         /*expectedCPP=*/{
            "SkRect colorPrev = SkRect::MakeEmpty();",
            "colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf4_GrSLType, \"color\");",
            "const SkRect& colorValue = _outer.color;",
            "if (colorPrev.isEmpty() || colorPrev != colorValue) {",
            "colorPrev = colorValue;",
            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&colorValue));"
         });
}

// Test the case where the template does not support variable inlining in
// setUniform (i.e. it references the value multiple times).
DEF_TEST(SkSLFPNonInlinedInUniform, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in uniform half2 point;
             void main() {
                 sk_OutColor = half4(point, point);
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
         },
         /*expectedCPP=*/{
            "pointVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf2_GrSLType, \"point\");",
            "const SkPoint& pointValue = _outer.point;",
            "pdman.set2f(pointVar, pointValue.fX, pointValue.fY);"
         });
}

// Test handling conditional uniforms (that use when= in layout), combined with
// state tracking and custom ctypes to really put the code generation through its paces.
DEF_TEST(SkSLFPConditionalInUniform, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             layout(key) in bool test;
             layout(ctype=SkPMColor4f, tracked, when=test) in uniform half4 color;
             void main() {
               if (test) {
                 sk_OutColor = color;
               } else {
                 sk_OutColor = half4(1);
               }
             }
         )__SkSL__",
         /*expectedH=*/{
             "static std::unique_ptr<GrFragmentProcessor> Make(bool test, SkPMColor4f color) {",
         },
         /*expectedCPP=*/{
            "SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}",
            "auto test = _outer.test;",
            "if (test) {",
            "colorVar = args.fUniformHandler->addUniform(&_outer, kFragment_GrShaderFlag, "
                                                        "kHalf4_GrSLType, \"color\");",
            "if (colorVar.isValid()) {",
            "const SkPMColor4f& colorValue = _outer.color;",
            "if (colorPrev != colorValue) {",
            "colorPrev = colorValue;",
            "pdman.set4fv(colorVar, 1, colorValue.vec());"
         });
}

DEF_TEST(SkSLFPSections, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @header { header section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
             "header section"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @class { class section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
             "class GrTest : public GrFragmentProcessor {\n"
             "public:\n"
             " class section"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @cpp { cpp section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "cpp section"
         });
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @constructorParams { int x, float y, std::vector<float> z }
             in float w;
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
             "Make(float w,  int x, float y, std::vector<float> z )",
             "return std::unique_ptr<GrFragmentProcessor>(new GrTest(w, x, y, z));",
             "GrTest(float w,  int x, float y, std::vector<float> z )",
             ", w(w) {"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @constructor { constructor section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
             "private:\n constructor section"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @initializers { initializers section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
             ": INHERITED(kGrTest_ClassID, kNone_OptimizationFlags)\n    ,  initializers section"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             half x = 10;
             @emitCode { fragBuilder->codeAppendf("half y = %d\n", x * 2); }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "x = 10.0;\n"
            " fragBuilder->codeAppendf(\"half y = %d\\n\", x * 2);"
         });
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @fields { fields section }
             @clone { }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
            "const char* name() const override { return \"Test\"; }\n"
            " fields section private:"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @make { make section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{
            "public:\n"
            " make section"
         },
         /*expectedCPP=*/{});
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             uniform half calculated;
             layout(key) in half provided;
             @setData(varName) { varName.set1f(calculated, provided * 2); }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
             "void onSetData(const GrGLSLProgramDataManager& varName, "
                            "const GrFragmentProcessor& _proc) override {\n",
             "UniformHandle& calculated = calculatedVar;",
             "auto provided = _outer.provided;",
             "varName.set1f(calculated, provided * 2);"
         });
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             @test(testDataName) { testDataName section }
             void main() {
                 sk_OutColor = half4(1);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
             "#if GR_TEST_UTILS\n"
             "std::unique_ptr<GrFragmentProcessor> GrTest::TestCreate(GrProcessorTestData* testDataName) {\n"
             " testDataName section }\n"
             "#endif"
         });
}

DEF_TEST(SkSLFPTransformedCoords, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             void main() {
                 sk_OutColor = half4(sk_TransformedCoords2D[0], sk_TransformedCoords2D[0]);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "SkString sk_TransformedCoords2D_0 = "
                           "fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint, "
                                                       "_outer.sampleMatrix());",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(%s = half4(%s, %s);\n"
            ")SkSL\"\n"
            ", args.fOutputColor, sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str());"
         });
}

DEF_TEST(SkSLFPLayoutWhen, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
            layout(when=someExpression(someOtherExpression())) uniform half sometimes;
            void main() {
            }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "if (someExpression(someOtherExpression())) {\n"
            "            sometimesVar = args.fUniformHandler->addUniform"
         });
}

DEF_TEST(SkSLFPChildProcessors, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child1;
             in fragmentProcessor child2;
             void main() {
                 sk_OutColor = sample(child1) * sample(child2);
             }
         )__SkSL__",
         /*expectedH=*/{
            "child1_index = this->registerChild(std::move(child1));",
            "child2_index = this->registerChild(std::move(child2));"
         },
         /*expectedCPP=*/{
            "SkString _sample149;\n",
            "_sample149 = this->invokeChild(_outer.child1_index, args);\n",
            "SkString _sample166;\n",
            "_sample166 = this->invokeChild(_outer.child2_index, args);\n",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(%s = %s * %s;\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample149.c_str(), _sample166.c_str());",
            "child1_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child1_index));",
            "child2_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child2_index));",
         });
}

DEF_TEST(SkSLFPChildProcessorsWithInput, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child1;
             in fragmentProcessor child2;
             void main() {
                 half4 childIn = sk_InColor;
                 half4 childOut1 = sample(child1, childIn);
                 half4 childOut2 = sample(child2, childOut1);
                 sk_OutColor = childOut2;
             }
         )__SkSL__",
         /*expectedH=*/{
            "child1_index = this->registerChild(std::move(child1));",
            "child2_index = this->registerChild(std::move(child2));"
         },
         /*expectedCPP=*/{
            "SkString _input198(\"childIn\");",
            "SkString _sample198;",
            "_sample198 = this->invokeChild(_outer.child1_index, _input198.c_str(), args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(\n"
            "half4 childOut1 = %s;)SkSL\"\n"
            ", _sample198.c_str());",
            "SkString _input258(\"childOut1\");",
            "SkString _sample258;",
            "_sample258 = this->invokeChild(_outer.child2_index, _input258.c_str(), args);",
            "child1_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child1_index));",
            "child2_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child2_index));",
         });
}

DEF_TEST(SkSLFPChildProcessorWithInputExpression, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child;
             void main() {
                 sk_OutColor = sample(child, sk_InColor * half4(0.5));
             }
         )__SkSL__",
         /*expectedH=*/{
            "child_index = this->registerChild(std::move(child));",
         },
         /*expectedCPP=*/{
            "SkString _input106 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
            "SkString _sample106;",
            "_sample106 = this->invokeChild(_outer.child_index, _input106.c_str(), args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(%s = %s;\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample106.c_str());",
            "child_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child_index));",
         });
}

DEF_TEST(SkSLFPNestedChildProcessors, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child1;
             in fragmentProcessor child2;
             void main() {
                 sk_OutColor = sample(child2, sk_InColor * sample(child1, sk_InColor * half4(0.5)));
             }
         )__SkSL__",
         /*expectedH=*/{
            "child1_index = this->registerChild(std::move(child1));",
            "child2_index = this->registerChild(std::move(child2));"
         },
         /*expectedCPP=*/{
            "SkString _input177 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
            "SkString _sample177;",
            "_sample177 = this->invokeChild(_outer.child1_index, _input177.c_str(), args);",
            "SkString _input149 = SkStringPrintf(\"%s * %s\", args.fInputColor, _sample177.c_str());",
            "SkString _sample149;",
            "_sample149 = this->invokeChild(_outer.child2_index, _input149.c_str(), args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(%s = %s;\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample149.c_str());",
            "child1_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child1_index));",
            "child2_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child2_index));",
            });
}

DEF_TEST(SkSLFPChildFPAndGlobal, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child;
             bool hasCap = sk_Caps.externalTextureSupport;
             void main() {
                 if (hasCap) {
                     sk_OutColor = sample(child, sk_InColor);
                 } else {
                     sk_OutColor = half4(1);
                 }
             }
         )__SkSL__",
         /*expectedH=*/{
            "child_index = this->registerChild(std::move(child));"
         },
         /*expectedCPP=*/{
            "hasCap = sk_Caps.externalTextureSupport;",

            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(bool hasCap = %s;\n"
            "if (hasCap) {)SkSL\"\n"
            ", (hasCap ? \"true\" : \"false\"));",
            "SkString _input200(args.fInputColor);",
            "SkString _sample200;",
            "_sample200 = this->invokeChild(_outer.child_index, _input200.c_str(), args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(\n"
            "    %s = %s;\n"
            "} else {\n"
            "    %s = half4(1.0);\n"
            "}\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample200.c_str(), args.fOutputColor);",
            "child_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child_index));",
         });
}

DEF_TEST(SkSLFPChildProcessorInlineFieldAccess, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child;
             void main() {
                 if (child.preservesOpaqueInput) {
                     sk_OutColor = sample(child, sk_InColor);
                 } else {
                     sk_OutColor = half4(1);
                 }
             }
         )__SkSL__",
         /*expectedH=*/{
            "child_index = this->registerChild(std::move(child));"
         },
         /*expectedCPP=*/{
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(if (%s) {)SkSL\"\n"
            ", (_outer.childProcessor(_outer.child_index).preservesOpaqueInput() ? \"true\" : \"false\"));",
            "SkString _input161(args.fInputColor);",
            "SkString _sample161;",
            "_sample161 = this->invokeChild(_outer.child_index, _input161.c_str(), args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(\n"
            "    %s = %s;\n"
            "} else {\n"
            "    %s = half4(1.0);\n"
            "}\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample161.c_str(), args.fOutputColor);",
            "child_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child_index));",
         });
}

DEF_TEST(SkSLFPChildProcessorFieldAccess, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child;
             bool opaque = child.preservesOpaqueInput;
             void main() {
                 if (opaque) {
                     sk_OutColor = sample(child);
                 } else {
                     sk_OutColor = half4(0.5);
                 }
         }
         )__SkSL__",
         /*expectedH=*/{
            "child_index = this->registerChild(std::move(child));"
         },
         /*expectedCPP=*/{
            "opaque = _outer.childProcessor(_outer.child_index).preservesOpaqueInput();",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(bool opaque = %s;\n"
            "if (opaque) {)SkSL\"\n"
            ", (opaque ? \"true\" : \"false\"));",
            "SkString _sample196;",
            "_sample196 = this->invokeChild(_outer.child_index, args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(\n"
            "    %s = %s;\n"
            "} else {\n"
            "    %s = half4(0.5);\n"
            "}\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample196.c_str(), args.fOutputColor);",
            "child_index = this->cloneAndRegisterChildProcessor(src.childProcessor(src.child_index));",
         });
}

DEF_TEST(SkSLFPNullableChildProcessor, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor? child;
             void main() {
                 if (child != null) {
                     sk_OutColor = sample(child);
                 } else {
                     sk_OutColor = half4(0.5);
                 }
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(if (%s) {)SkSL\"\n"
            ", _outer.child_index >= 0 ? \"true\" : \"false\");",
            "SkString _sample149;",
            "if (_outer.child_index >= 0) {",
            "_sample149 = this->invokeChild(_outer.child_index, args);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(\n"
            "    %s = %s;\n"
            "} else {\n"
            "    %s = half4(0.5);\n"
            "}\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample149.c_str(), args.fOutputColor);",
         });
}

DEF_TEST(SkSLFPBadIn, r) {
    test_failure(r,
         R"__SkSL__(
             in half4 c;
             void main() {
                 sk_OutColor = c;
             }
         )__SkSL__",
         "error: 4: 'in' variable must be either 'uniform' or 'layout(key)', or there must be a "
         "custom @setData function\n1 error\n");
}

DEF_TEST(SkSLFPSampleCoords, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor child;
             @coordTransform { SkMatrix() }
             void main() {
                 sk_OutColor = sample(child) + sample(child, sk_TransformedCoords2D[0] / 2);
             }
         )__SkSL__",
         /*expectedH=*/{
             "child_index = this->registerExplicitlySampledChild(std::move(child));"
         },
         /*expectedCPP=*/{
            "SkString _sample150;\n",
            "_sample150 = this->invokeChild(_outer.child_index, args);\n",
            "SkString _sample166;\n",
            "SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D("
                                                     "args.fTransformedCoords[0].fVaryingPoint, "
                                                     "_outer.sampleMatrix());\n",
            "SkString _coords166 = SkStringPrintf(\"%s / 2.0\", "
                "sk_TransformedCoords2D_0.c_str());\n",
            "_sample166 = this->invokeChild(_outer.child_index, args, _coords166.c_str());\n",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(%s = %s + %s;\n"
            ")SkSL\"\n"
            ", args.fOutputColor, _sample150.c_str(), _sample166.c_str());"
        });
}

DEF_TEST(SkSLFPFunction, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor? child;
             half4 flip(half4 c) { return c.abgr; }
             void main() {
                 sk_OutColor = flip(sk_InColor);
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{
            "SkString flip_name;",
            "const GrShaderVar flip_args[] = { GrShaderVar(\"c\", kHalf4_GrSLType)};",
            "fragBuilder->emitFunction(kHalf4_GrSLType, \"flip\", 1, flip_args,\n"
            "R\"SkSL(return c.wzyx;\n"
            ")SkSL\", &flip_name);",
            "fragBuilder->codeAppendf(\n"
            "R\"SkSL(half4 inlineResult0;\n"
            "half4 inlineArg1_0 = %s;\n"
            "{\n"
            "    inlineResult0 = inlineArg1_0.wzyx;\n"
            "}\n"
            "%s = inlineResult0;\n"
            "\n"
            ")SkSL\"\n"
            ", args.fInputColor, args.fOutputColor);"
         });
}

DEF_TEST(SkSLFPMatrixSample, r) {
    test(r,
         *SkSL::ShaderCapsFactory::Default(),
         R"__SkSL__(
             in fragmentProcessor? child;
             void main() {
                 sk_OutColor = sample(child, float3x3(2));
             }
         )__SkSL__",
         /*expectedH=*/{},
         /*expectedCPP=*/{});
}
