Fork CPPCodeGenerator into a DSL-based version.

The long term goal is for the DSLCPPCodeGenerator to replace the
CPPCodeGenerator entirely, but we will need both to coexist while DSL is
still under development.

Currently, the DSLCPPCodeGenerator is cloned from CPPCodeGenerator and
emits almost exactly the same code (it adds a comment at the top to
distinguish its output). Its output will change in followup CLs.

This CL also allows skslc to recognize the `_dsl.cpp` output suffix and
generate code using DSLCPPCodeGenerator instead of CPPCodeGenerator.
This allows test DSL FPs to be created for inspection.

Change-Id: If5136279c307ea53a9df3a292caa18344c1eb259
Bug: skia:11854
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/396096
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index bfec322..1116d02 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -790,9 +790,9 @@
 
         foreach(outExtension, invoker.outExtensions) {
           # SPIR-V uses separate extensions for .vert and .geom shaders.
-          if (outExtension == ".asm.frag" && ext == "vert") {
+          if (ext == "vert" && outExtension == ".asm.frag") {
             outExtension = ".asm.vert"
-          } else if (outExtension == ".asm.frag" && ext == "geom") {
+          } else if (ext == "geom" && outExtension == ".asm.frag") {
             outExtension = ".asm.geom"
           }
           outputs +=
@@ -811,6 +811,15 @@
     lang = "--fp"
     settings = "--settings"
   }
+  compile_sksl("dsl_fp_tests") {
+    sources = sksl_dsl_fp_tests_sources
+    outExtensions = [
+      "_dsl.cpp",
+      ".h",
+    ]
+    lang = "--dslfp"
+    settings = "--settings"
+  }
   compile_sksl("glsl_tests") {
     sources = sksl_glsl_tests_sources + sksl_glsl_settings_tests_sources
     outExtensions = [ ".glsl" ]
diff --git a/gn/compile_sksl_tests.py b/gn/compile_sksl_tests.py
index 4a05ef7..99203a8 100755
--- a/gn/compile_sksl_tests.py
+++ b/gn/compile_sksl_tests.py
@@ -80,6 +80,13 @@
         worklist.write(input + "\n")
         worklist.write(target + ".h\n")
         worklist.write(settings + "\n\n")
+    elif lang == "--dslfp":
+        worklist.write(input + "\n")
+        worklist.write(target + "_dsl.cpp\n")
+        worklist.write(settings + "\n\n")
+        worklist.write(input + "\n")
+        worklist.write(target + ".h\n")
+        worklist.write(settings + "\n\n")
     elif lang == "--glsl":
         worklist.write(input + "\n")
         worklist.write(target + ".glsl\n")
@@ -101,7 +108,8 @@
         worklist.write(target + ".stage\n")
         worklist.write(settings + "\n\n")
     else:
-        sys.exit("### Expected one of: --fp --glsl --metal --spirv --skvm --stage, got " + lang)
+        sys.exit("### Expected one of: --fp --dslfp --glsl --metal --spirv --skvm --stage, got " +
+                 lang)
 
     # Compile items one at a time.
     if not batchCompile:
@@ -118,15 +126,18 @@
 # A special case cleanup pass, just for CPP and H files: if either one of these files starts with
 # `### Compilation failed`, its sibling should be replaced by an empty file. This improves clarity
 # during code review; a failure on either file means that success on the sibling is irrelevant.
-if lang == "--fp":
+if lang == "--fp" or lang == "--dslfp":
+    cppExtension = ("_dsl.cpp" if lang == "--dslfp" else ".cpp")
+    hExtension = ".h"
+
     for target in targets:
-        cppFile = open(target + '.cpp', 'r')
-        hFile = open(target + '.h', 'r')
+        cppFile = open(target + cppExtension, 'r')
+        hFile = open(target + hExtension, 'r')
         if cppFile.readline().startswith("### Compilation failed"):
             # The CPP had a compilation failure. Clear the header file.
             hFile.close()
-            makeEmptyFile(target + '.h')
+            makeEmptyFile(target + hExtension)
         elif hFile.readline().startswith("### Compilation failed"):
             # The header had a compilation failure. Clear the CPP file.
             cppFile.close()
-            makeEmptyFile(target + '.cpp')
+            makeEmptyFile(target + cppExtension)
diff --git a/gn/sksl.gni b/gn/sksl.gni
index 4e77edc..13fbe33 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -182,6 +182,8 @@
   "$_src/sksl/codegen/SkSLCPPCodeGenerator.cpp",
   "$_src/sksl/codegen/SkSLCPPCodeGenerator.h",
   "$_src/sksl/codegen/SkSLCodeGenerator.h",
+  "$_src/sksl/codegen/SkSLDSLCPPCodeGenerator.cpp",
+  "$_src/sksl/codegen/SkSLDSLCPPCodeGenerator.h",
   "$_src/sksl/codegen/SkSLGLSLCodeGenerator.cpp",
   "$_src/sksl/codegen/SkSLGLSLCodeGenerator.h",
   "$_src/sksl/codegen/SkSLHCodeGenerator.cpp",
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index e23755c..d5cb0ca 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -15,6 +15,8 @@
   "/sksl/errors/GrRecursion.fp",
 ]
 
+sksl_dsl_fp_tests = [ "/sksl/fp/GrDSLHelloWorld.fp" ]
+
 sksl_fp_tests = [
   "/sksl/fp/GrChildProcessorAndGlobal.fp",
   "/sksl/fp/GrChildProcessorSampleCoords.fp",
@@ -589,6 +591,10 @@
 # a .cpp and a .h output file.
 sksl_fp_tests_sources = sksl_fp_error_tests + sksl_fp_tests
 
+# Tests in sksl_dsl_fp_tests_sources will be compiled with --settings on, and are expected to
+# generate a _dsl.cpp and a .h output file.
+sksl_dsl_fp_tests_sources = sksl_dsl_fp_tests
+
 # Tests in sksl_glsl_tests_sources will be compiled with --settings on, and are expected to generate
 # a .glsl output file.
 sksl_glsl_tests_sources =
diff --git a/resources/sksl/fp/GrDSLHelloWorld.fp b/resources/sksl/fp/GrDSLHelloWorld.fp
new file mode 100644
index 0000000..e72334c
--- /dev/null
+++ b/resources/sksl/fp/GrDSLHelloWorld.fp
@@ -0,0 +1,5 @@
+/* HELLO WORLD */
+
+half4 main() {
+    return half4(1);
+}
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index 268a1423..d1a562b 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -19,6 +19,7 @@
 #include "src/sksl/SkSLProgramSettings.h"
 #include "src/sksl/SkSLRehydrator.h"
 #include "src/sksl/codegen/SkSLCPPCodeGenerator.h"
+#include "src/sksl/codegen/SkSLDSLCPPCodeGenerator.h"
 #include "src/sksl/codegen/SkSLGLSLCodeGenerator.h"
 #include "src/sksl/codegen/SkSLHCodeGenerator.h"
 #include "src/sksl/codegen/SkSLMetalCodeGenerator.h"
@@ -834,6 +835,13 @@
     return result;
 }
 
+bool Compiler::toDSLCPP(Program& program, String name, OutputStream& out) {
+    AutoSource as(this, program.fSource.get());
+    DSLCPPCodeGenerator cg(fContext.get(), &program, this, name, &out);
+    bool result = cg.generateCode();
+    return result;
+}
+
 bool Compiler::toH(Program& program, String name, OutputStream& out) {
     AutoSource as(this, program.fSource.get());
     HCodeGenerator cg(fContext.get(), &program, this, name, &out);
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index 157cf56..c3dcbba 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -135,6 +135,8 @@
 #if defined(SKSL_STANDALONE) || GR_TEST_UTILS
     bool toCPP(Program& program, String name, OutputStream& out);
 
+    bool toDSLCPP(Program& program, String name, OutputStream& out);
+
     bool toH(Program& program, String name, OutputStream& out);
 #endif
 
diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp
index b47c7c4..b9670e2 100644
--- a/src/sksl/SkSLMain.cpp
+++ b/src/sksl/SkSLMain.cpp
@@ -379,6 +379,14 @@
                 [&](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
                     return compiler.toH(program, base_name(inputPath.c_str(), "Gr", ".fp"), out);
                 });
+    } else if (outputPath.endsWith("_dsl.cpp")) {
+        settings.fReplaceSettings = false;
+        settings.fPermitInvalidStaticTests = true;
+        return compileProgram(
+                [&](SkSL::Compiler& compiler, SkSL::Program& program, SkSL::OutputStream& out) {
+                    return compiler.toDSLCPP(program, base_name(inputPath.c_str(), "Gr", ".fp"),
+                                             out);
+                });
     } else if (outputPath.endsWith(".cpp")) {
         settings.fReplaceSettings = false;
         settings.fPermitInvalidStaticTests = true;
diff --git a/src/sksl/codegen/SkSLDSLCPPCodeGenerator.cpp b/src/sksl/codegen/SkSLDSLCPPCodeGenerator.cpp
new file mode 100644
index 0000000..e6d0cc6
--- /dev/null
+++ b/src/sksl/codegen/SkSLDSLCPPCodeGenerator.cpp
@@ -0,0 +1,1450 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/codegen/SkSLDSLCPPCodeGenerator.h"
+
+#include "include/private/SkSLSampleUsage.h"
+#include "src/sksl/SkSLAnalysis.h"
+#include "src/sksl/SkSLCPPUniformCTypes.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/codegen/SkSLHCodeGenerator.h"
+#include "src/sksl/ir/SkSLEnum.h"
+
+#include <algorithm>
+
+#if defined(SKSL_STANDALONE) || GR_TEST_UTILS
+
+namespace SkSL {
+
+static bool needs_uniform_var(const Variable& var) {
+    return (var.modifiers().fFlags & Modifiers::kUniform_Flag) &&
+            var.type().typeKind() != Type::TypeKind::kSampler;
+}
+
+DSLCPPCodeGenerator::DSLCPPCodeGenerator(const Context* context, const Program* program,
+                                         ErrorReporter* errors, String name, OutputStream* out)
+    : INHERITED(context, program, errors, out)
+    , fName(std::move(name))
+    , fFullName(String::printf("Gr%s", fName.c_str()))
+    , fSectionAndParameterHelper(program, *errors) {
+    fLineEnding = "\n";
+    fTextureFunctionOverride = "sample";
+}
+
+void DSLCPPCodeGenerator::writef(const char* s, va_list va) {
+    static constexpr int BUFFER_SIZE = 1024;
+    va_list copy;
+    va_copy(copy, va);
+    char buffer[BUFFER_SIZE];
+    int length = std::vsnprintf(buffer, BUFFER_SIZE, s, va);
+    if (length < BUFFER_SIZE) {
+        fOut->write(buffer, length);
+    } else {
+        std::unique_ptr<char[]> heap(new char[length + 1]);
+        vsprintf(heap.get(), s, copy);
+        fOut->write(heap.get(), length);
+    }
+    va_end(copy);
+}
+
+void DSLCPPCodeGenerator::writef(const char* s, ...) {
+    va_list va;
+    va_start(va, s);
+    this->writef(s, va);
+    va_end(va);
+}
+
+void DSLCPPCodeGenerator::writeHeader() {
+}
+
+bool DSLCPPCodeGenerator::usesPrecisionModifiers() const {
+    return false;
+}
+
+String DSLCPPCodeGenerator::getTypeName(const Type& type) {
+    return type.name();
+}
+
+void DSLCPPCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
+                                             Precedence parentPrecedence) {
+    const Expression& left = *b.left();
+    const Expression& right = *b.right();
+    Operator op = b.getOperator();
+    if (op.kind() == Token::Kind::TK_PERCENT) {
+        // need to use "%%" instead of "%" b/c the code will be inside of a printf
+        Precedence precedence = op.getBinaryPrecedence();
+        if (precedence >= parentPrecedence) {
+            this->write("(");
+        }
+        this->writeExpression(left, precedence);
+        this->write(" %% ");
+        this->writeExpression(right, precedence);
+        if (precedence >= parentPrecedence) {
+            this->write(")");
+        }
+    } else {
+        INHERITED::writeBinaryExpression(b, parentPrecedence);
+    }
+}
+
+static String default_value(const Type& type) {
+    if (type.isBoolean()) {
+        return "false";
+    }
+    switch (type.typeKind()) {
+        case Type::TypeKind::kScalar: return "0";
+        case Type::TypeKind::kVector: return type.name() + "(0)";
+        case Type::TypeKind::kMatrix: return type.name() + "(1)";
+        default: SK_ABORT("unsupported default_value type");
+    }
+}
+
+static String default_value(const Variable& var) {
+    if (var.modifiers().fLayout.fCType == SkSL::Layout::CType::kSkPMColor4f) {
+        return "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}";
+    }
+    return default_value(var.type());
+}
+
+static bool is_private(const Variable& var) {
+    const Modifiers& modifiers = var.modifiers();
+    return !(modifiers.fFlags & Modifiers::kUniform_Flag) &&
+           !(modifiers.fFlags & Modifiers::kIn_Flag) &&
+           var.storage() == Variable::Storage::kGlobal &&
+           modifiers.fLayout.fBuiltin == -1;
+}
+
+static bool is_uniform_in(const Variable& var) {
+    const Modifiers& modifiers = var.modifiers();
+    return (modifiers.fFlags & Modifiers::kUniform_Flag) &&
+           (modifiers.fFlags & Modifiers::kIn_Flag) &&
+           var.type().typeKind() != Type::TypeKind::kSampler;
+}
+
+String DSLCPPCodeGenerator::formatRuntimeValue(const Type& type,
+                                            const Layout& layout,
+                                            const String& cppCode,
+                                            std::vector<String>* formatArgs) {
+    if (type.isArray()) {
+        String result("[");
+        const char* separator = "";
+        for (int i = 0; i < type.columns(); i++) {
+            result += separator + this->formatRuntimeValue(type.componentType(), layout,
+                                                           "(" + cppCode + ")[" + to_string(i) +
+                                                           "]", formatArgs);
+            separator = ",";
+        }
+        result += "]";
+        return result;
+    }
+    if (type.isFloat()) {
+        formatArgs->push_back(cppCode);
+        return "%f";
+    }
+    if (type == *fContext.fTypes.fInt) {
+        formatArgs->push_back(cppCode);
+        return "%d";
+    }
+    if (type == *fContext.fTypes.fBool) {
+        formatArgs->push_back("(" + cppCode + " ? \"true\" : \"false\")");
+        return "%s";
+    }
+    if (type == *fContext.fTypes.fFloat2 || type == *fContext.fTypes.fHalf2) {
+        formatArgs->push_back(cppCode + ".fX");
+        formatArgs->push_back(cppCode + ".fY");
+        return type.name() + "(%f, %f)";
+    }
+    if (type == *fContext.fTypes.fFloat3 || type == *fContext.fTypes.fHalf3) {
+        formatArgs->push_back(cppCode + ".fX");
+        formatArgs->push_back(cppCode + ".fY");
+        formatArgs->push_back(cppCode + ".fZ");
+        return type.name() + "(%f, %f, %f)";
+    }
+    if (type == *fContext.fTypes.fFloat4 || type == *fContext.fTypes.fHalf4) {
+        switch (layout.fCType) {
+            case Layout::CType::kSkPMColor:
+                formatArgs->push_back("SkGetPackedR32(" + cppCode + ") / 255.0");
+                formatArgs->push_back("SkGetPackedG32(" + cppCode + ") / 255.0");
+                formatArgs->push_back("SkGetPackedB32(" + cppCode + ") / 255.0");
+                formatArgs->push_back("SkGetPackedA32(" + cppCode + ") / 255.0");
+                break;
+            case Layout::CType::kSkPMColor4f:
+                formatArgs->push_back(cppCode + ".fR");
+                formatArgs->push_back(cppCode + ".fG");
+                formatArgs->push_back(cppCode + ".fB");
+                formatArgs->push_back(cppCode + ".fA");
+                break;
+            case Layout::CType::kSkV4:
+                formatArgs->push_back(cppCode + ".x");
+                formatArgs->push_back(cppCode + ".y");
+                formatArgs->push_back(cppCode + ".z");
+                formatArgs->push_back(cppCode + ".w");
+                break;
+            case Layout::CType::kSkRect:
+            case Layout::CType::kDefault:
+                formatArgs->push_back(cppCode + ".left()");
+                formatArgs->push_back(cppCode + ".top()");
+                formatArgs->push_back(cppCode + ".right()");
+                formatArgs->push_back(cppCode + ".bottom()");
+                break;
+            default:
+                SkASSERT(false);
+        }
+        return type.name() + "(%f, %f, %f, %f)";
+    }
+    if (type.isMatrix()) {
+        SkASSERT(type.componentType() == *fContext.fTypes.fFloat ||
+                 type.componentType() == *fContext.fTypes.fHalf);
+
+        String format = type.name() + "(";
+        for (int c = 0; c < type.columns(); ++c) {
+            for (int r = 0; r < type.rows(); ++r) {
+                formatArgs->push_back(String::printf("%s.rc(%d, %d)", cppCode.c_str(), r, c));
+                format += "%f, ";
+            }
+        }
+
+        // Replace trailing ", " with ")".
+        format.pop_back();
+        format.back() = ')';
+        return format;
+    }
+    if (type.isEnum()) {
+        formatArgs->push_back("(int) " + cppCode);
+        return "%d";
+    }
+    if (type == *fContext.fTypes.fInt4 ||
+        type == *fContext.fTypes.fShort4 ||
+        type == *fContext.fTypes.fByte4) {
+        formatArgs->push_back(cppCode + ".left()");
+        formatArgs->push_back(cppCode + ".top()");
+        formatArgs->push_back(cppCode + ".right()");
+        formatArgs->push_back(cppCode + ".bottom()");
+        return type.name() + "(%d, %d, %d, %d)";
+    }
+
+    SkDEBUGFAILF("unsupported runtime value type '%s'\n", String(type.name()).c_str());
+    return "";
+}
+
+void DSLCPPCodeGenerator::writeRuntimeValue(const Type& type, const Layout& layout,
+                                         const String& cppCode) {
+    this->write(this->formatRuntimeValue(type, layout, cppCode, &fFormatArgs));
+}
+
+void DSLCPPCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) {
+    if (is_private(var)) {
+        this->writeRuntimeValue(var.type(), var.modifiers().fLayout, var.name());
+    } else {
+        this->writeExpression(value, Precedence::kTopLevel);
+    }
+}
+
+String DSLCPPCodeGenerator::getSamplerHandle(const Variable& var) {
+    int samplerCount = 0;
+    for (const auto param : fSectionAndParameterHelper.getParameters()) {
+        if (&var == param) {
+            return "args.fTexSamplers[" + to_string(samplerCount) + "]";
+        }
+        if (param->type().typeKind() == Type::TypeKind::kSampler) {
+            ++samplerCount;
+        }
+    }
+    SK_ABORT("should have found sampler in parameters\n");
+}
+
+void DSLCPPCodeGenerator::writeIntLiteral(const IntLiteral& i) {
+    this->write(to_string(i.value()));
+}
+
+void DSLCPPCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
+    if (fCPPMode) {
+        // no support for multiple swizzle components yet
+        SkASSERT(swizzle.components().size() == 1);
+        this->writeExpression(*swizzle.base(), Precedence::kPostfix);
+        switch (swizzle.components()[0]) {
+            case 0: this->write(".left()");   break;
+            case 1: this->write(".top()");    break;
+            case 2: this->write(".right()");  break;
+            case 3: this->write(".bottom()"); break;
+        }
+    } else {
+        INHERITED::writeSwizzle(swizzle);
+    }
+}
+
+void DSLCPPCodeGenerator::writeVariableReference(const VariableReference& ref) {
+    if (fCPPMode) {
+        this->write(ref.variable()->name());
+        return;
+    }
+    switch (ref.variable()->modifiers().fLayout.fBuiltin) {
+        case SK_MAIN_COORDS_BUILTIN:
+            this->write("%s");
+            fFormatArgs.push_back(String("args.fSampleCoord"));
+            fAccessSampleCoordsDirectly = true;
+            break;
+        default:
+            const Variable& var = *ref.variable();
+            if (var.type().typeKind() == Type::TypeKind::kSampler) {
+                this->write("%s");
+                fFormatArgs.push_back("fragBuilder->getProgramBuilder()->samplerVariable(" +
+                                      this->getSamplerHandle(*ref.variable()) + ")");
+                return;
+            }
+            if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+                this->write("%s");
+                String name = var.name();
+                String varCode = String::printf("args.fUniformHandler->getUniformCStr(%sVar)",
+                                                HCodeGenerator::FieldName(name.c_str()).c_str());
+                String code;
+                if (var.modifiers().fLayout.fWhen.fLength) {
+                    code = String::printf("%sVar.isValid() ? %s : \"%s\"",
+                                          HCodeGenerator::FieldName(name.c_str()).c_str(),
+                                          varCode.c_str(),
+                                          default_value(var.type()).c_str());
+                } else {
+                    code = varCode;
+                }
+                fFormatArgs.push_back(code);
+            } else if (SectionAndParameterHelper::IsParameter(var)) {
+                String name(var.name());
+                this->writeRuntimeValue(var.type(), var.modifiers().fLayout,
+                                        String::printf("_outer.%s", name.c_str()).c_str());
+            } else {
+                this->write(var.name());
+            }
+    }
+}
+
+void DSLCPPCodeGenerator::writeIfStatement(const IfStatement& s) {
+    if (s.isStatic()) {
+        this->write("@");
+    }
+    INHERITED::writeIfStatement(s);
+}
+
+void DSLCPPCodeGenerator::writeReturnStatement(const ReturnStatement& s) {
+    INHERITED::writeReturnStatement(s);
+}
+
+void DSLCPPCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
+    if (s.isStatic()) {
+        this->write("@");
+    }
+    INHERITED::writeSwitchStatement(s);
+}
+
+int DSLCPPCodeGenerator::getChildFPIndex(const Variable& var) const {
+    int index = 0;
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const VarDeclaration& decl =
+                                  p->as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>();
+            if (&decl.var() == &var) {
+                return index;
+            } else if (decl.var().type().isFragmentProcessor()) {
+                ++index;
+            }
+        }
+    }
+    SkDEBUGFAIL("child fragment processor not found");
+    return 0;
+}
+
+String DSLCPPCodeGenerator::getSampleVarName(const char* prefix, int sampleCounter) {
+    return String::printf("%s%d", prefix, sampleCounter);
+}
+
+void DSLCPPCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+    const FunctionDeclaration& function = c.function();
+    const ExpressionArray& arguments = c.arguments();
+    if (function.isBuiltin() && function.name() == "sample" &&
+        arguments[0]->type().typeKind() != Type::TypeKind::kSampler) {
+        int sampleCounter = fSampleCounter++;
+
+        // Validity checks that are detected by function definition in sksl_fp.inc
+        SkASSERT(arguments.size() >= 1 && arguments.size() <= 3);
+        SkASSERT(arguments[0]->type().isFragmentProcessor());
+
+        // Actually fail during compilation if arguments with valid types are
+        // provided that are not variable references, since sample() is a
+        // special function that impacts code emission.
+        if (!arguments[0]->is<VariableReference>()) {
+            fErrors.error(arguments[0]->fOffset,
+                    "sample()'s fragmentProcessor argument must be a variable reference\n");
+            return;
+        }
+        const Variable& child = *arguments[0]->as<VariableReference>().variable();
+
+        // Start a new extra emit code section so that the emitted child processor can depend on
+        // sksl variables defined in earlier sksl code.
+        this->newExtraEmitCodeBlock();
+
+        // inputColor is an optional argument that always appears last
+        String inputColor;
+        if (arguments.back()->type().name() == "half4") {
+            // Use the invokeChild() variant that accepts an input color, so convert the 2nd
+            // argument's expression into C++ code that produces sksl stored in an SkString.
+            String inputColorName = this->getSampleVarName("_input", sampleCounter);
+            addExtraEmitCodeLine(convertSKSLExpressionToCPP(*arguments.back(), inputColorName));
+
+            // invokeChild() needs a char* and a pre-pended comma
+            inputColor = ", " + inputColorName + ".c_str()";
+        }
+
+        // coords can be float2, float3x3, or not there at all. They appear right after the fp.
+        String inputCoord;
+        String invokeFunction = "invokeChild";
+        if (arguments.size() > 1) {
+            if (arguments[1]->type().name() == "float2") {
+                // Invoking child with explicit coordinates at this call site
+                inputCoord = this->getSampleVarName("_coords", sampleCounter);
+                addExtraEmitCodeLine(convertSKSLExpressionToCPP(*arguments[1], inputCoord));
+                inputCoord.append(".c_str()");
+            } else if (arguments[1]->type().name() == "float3x3") {
+                // Invoking child with a matrix, sampling relative to the input coords.
+                invokeFunction = "invokeChildWithMatrix";
+                SampleUsage usage = Analysis::GetSampleUsage(fProgram, child);
+
+                if (!usage.hasUniformMatrix()) {
+                    inputCoord = this->getSampleVarName("_matrix", sampleCounter);
+                    addExtraEmitCodeLine(convertSKSLExpressionToCPP(*arguments[1], inputCoord));
+                    inputCoord.append(".c_str()");
+                }
+                // else pass in the empty string to rely on invokeChildWithMatrix's automatic
+                // uniform resolution
+            }
+        }
+        if (!inputCoord.empty()) {
+            inputCoord = ", " + inputCoord;
+        }
+
+        // Write the output handling after the possible input handling
+        String childName = this->getSampleVarName("_sample", sampleCounter);
+        String childIndexStr = to_string(this->getChildFPIndex(child));
+        addExtraEmitCodeLine("SkString " + childName + " = this->" + invokeFunction + "(" +
+                             childIndexStr + inputColor + ", args" + inputCoord + ");");
+
+        this->write("%s");
+        fFormatArgs.push_back(childName + ".c_str()");
+        return;
+    }
+    if (function.isBuiltin()) {
+        INHERITED::writeFunctionCall(c);
+    } else {
+        this->write("%s");
+        fFormatArgs.push_back((String(function.name()) + "_name.c_str()").c_str());
+        this->write("(");
+        const char* separator = "";
+        for (const auto& arg : arguments) {
+            this->write(separator);
+            separator = ", ";
+            this->writeExpression(*arg, Precedence::kSequence);
+        }
+        this->write(")");
+    }
+    if (function.isBuiltin() && function.name() == "sample") {
+        this->write(".%s");
+        SkASSERT(arguments.size() >= 1);
+        SkASSERT(arguments[0]->is<VariableReference>());
+        String sampler =
+                this->getSamplerHandle(*arguments[0]->as<VariableReference>().variable());
+        fFormatArgs.push_back("fragBuilder->getProgramBuilder()->samplerSwizzle(" + sampler +
+                              ").asString().c_str()");
+    }
+}
+
+static const char* glsltype_string(const Context& context, const Type& type) {
+    // If a new GrSL type is added, this function will need to be updated.
+    static_assert(kGrSLTypeCount == 49);
+
+    if (type == *context.fTypes.fVoid    ) { return "kVoid_GrSLType";     }
+    if (type == *context.fTypes.fBool    ) { return "kBool_GrSLType";     }
+    if (type == *context.fTypes.fBool2   ) { return "kBool2_GrSLType";    }
+    if (type == *context.fTypes.fBool3   ) { return "kBool3_GrSLType";    }
+    if (type == *context.fTypes.fBool4   ) { return "kBool4_GrSLType";    }
+    if (type == *context.fTypes.fByte    ) { return "kByte_GrSLType";     }
+    if (type == *context.fTypes.fByte2   ) { return "kByte2_GrSLType";    }
+    if (type == *context.fTypes.fByte3   ) { return "kByte3_GrSLType";    }
+    if (type == *context.fTypes.fByte4   ) { return "kByte4_GrSLType";    }
+    if (type == *context.fTypes.fUByte   ) { return "kUByte_GrSLType";    }
+    if (type == *context.fTypes.fUByte2  ) { return "kUByte2_GrSLType";   }
+    if (type == *context.fTypes.fUByte3  ) { return "kUByte3_GrSLType";   }
+    if (type == *context.fTypes.fUByte4  ) { return "kUByte4_GrSLType";   }
+    if (type == *context.fTypes.fShort   ) { return "kShort_GrSLType";    }
+    if (type == *context.fTypes.fShort2  ) { return "kShort2_GrSLType";   }
+    if (type == *context.fTypes.fShort3  ) { return "kShort3_GrSLType";   }
+    if (type == *context.fTypes.fShort4  ) { return "kShort4_GrSLType";   }
+    if (type == *context.fTypes.fUShort  ) { return "kUShort_GrSLType";   }
+    if (type == *context.fTypes.fUShort2 ) { return "kUShort2_GrSLType";  }
+    if (type == *context.fTypes.fUShort3 ) { return "kUShort3_GrSLType";  }
+    if (type == *context.fTypes.fUShort4 ) { return "kUShort4_GrSLType";  }
+    if (type == *context.fTypes.fFloat   ) { return "kFloat_GrSLType";    }
+    if (type == *context.fTypes.fFloat2  ) { return "kFloat2_GrSLType";   }
+    if (type == *context.fTypes.fFloat3  ) { return "kFloat3_GrSLType";   }
+    if (type == *context.fTypes.fFloat4  ) { return "kFloat4_GrSLType";   }
+    if (type == *context.fTypes.fFloat2x2) { return "kFloat2x2_GrSLType"; }
+    if (type == *context.fTypes.fFloat3x3) { return "kFloat3x3_GrSLType"; }
+    if (type == *context.fTypes.fFloat4x4) { return "kFloat4x4_GrSLType"; }
+    if (type == *context.fTypes.fHalf    ) { return "kHalf_GrSLType";     }
+    if (type == *context.fTypes.fHalf2   ) { return "kHalf2_GrSLType";    }
+    if (type == *context.fTypes.fHalf3   ) { return "kHalf3_GrSLType";    }
+    if (type == *context.fTypes.fHalf4   ) { return "kHalf4_GrSLType";    }
+    if (type == *context.fTypes.fHalf2x2 ) { return "kHalf2x2_GrSLType";  }
+    if (type == *context.fTypes.fHalf3x3 ) { return "kHalf3x3_GrSLType";  }
+    if (type == *context.fTypes.fHalf4x4 ) { return "kHalf4x4_GrSLType";  }
+    if (type == *context.fTypes.fInt     ) { return "kInt_GrSLType";      }
+    if (type == *context.fTypes.fInt2    ) { return "kInt2_GrSLType";     }
+    if (type == *context.fTypes.fInt3    ) { return "kInt3_GrSLType";     }
+    if (type == *context.fTypes.fInt4    ) { return "kInt4_GrSLType";     }
+    if (type == *context.fTypes.fUInt    ) { return "kUint_GrSLType";     }
+    if (type == *context.fTypes.fUInt2   ) { return "kUint2_GrSLType";    }
+    if (type == *context.fTypes.fUInt3   ) { return "kUint3_GrSLType";    }
+    if (type == *context.fTypes.fUInt4   ) { return "kUint4_GrSLType";    }
+    if (type.isEnum())                     { return "kInt_GrSLType";      }
+
+    SkDEBUGFAILF("unsupported type: %s", type.description().c_str());
+    return nullptr;
+}
+
+void DSLCPPCodeGenerator::prepareHelperFunction(const FunctionDeclaration& decl) {
+    if (decl.isBuiltin() || decl.isMain()) {
+        return;
+    }
+
+    String funcName = decl.name();
+    this->addExtraEmitCodeLine(
+            String::printf("SkString %s_name = fragBuilder->getMangledFunctionName(\"%s\");",
+                           funcName.c_str(),
+                           funcName.c_str()));
+
+    String args = String::printf("const GrShaderVar %s_args[] = { ", funcName.c_str());
+    const char* separator = "";
+    for (const Variable* param : decl.parameters()) {
+        String paramName = param->name();
+        args.appendf("%sGrShaderVar(\"%s\", %s)", separator, paramName.c_str(),
+                                                  glsltype_string(fContext, param->type()));
+        separator = ", ";
+    }
+    args += " };";
+
+    this->addExtraEmitCodeLine(args.c_str());
+}
+
+void DSLCPPCodeGenerator::prototypeHelperFunction(const FunctionDeclaration& decl) {
+    String funcName = decl.name();
+    this->addExtraEmitCodeLine(String::printf(
+            "fragBuilder->emitFunctionPrototype(%s, %s_name.c_str(), {%s_args, %zu});",
+            glsltype_string(fContext, decl.returnType()),
+            funcName.c_str(),
+            funcName.c_str(),
+            decl.parameters().size()));
+}
+
+void DSLCPPCodeGenerator::writeFunction(const FunctionDefinition& f) {
+    const FunctionDeclaration& decl = f.declaration();
+    if (decl.isBuiltin()) {
+        return;
+    }
+    fFunctionHeader = "";
+    OutputStream* oldOut = fOut;
+    StringStream buffer;
+    fOut = &buffer;
+    if (decl.isMain()) {
+        fInMain = true;
+        for (const std::unique_ptr<Statement>& s : f.body()->as<Block>().children()) {
+            this->writeStatement(*s);
+            this->writeLine();
+        }
+        fInMain = false;
+
+        fOut = oldOut;
+        this->write(fFunctionHeader);
+        this->write(buffer.str());
+    } else {
+        for (const std::unique_ptr<Statement>& s : f.body()->as<Block>().children()) {
+            this->writeStatement(*s);
+            this->writeLine();
+        }
+
+        fOut = oldOut;
+        String funcName = decl.name();
+
+        String funcImpl;
+        if (!fFormatArgs.empty()) {
+            this->addExtraEmitCodeLine("const String " + funcName + "_impl = String::printf(" +
+                                       assembleCodeAndFormatArgPrintf(buffer.str()).c_str() + ");");
+            funcImpl = String::printf(" %s_impl.c_str()", funcName.c_str());
+        } else {
+            funcImpl = "\nR\"SkSL(" + buffer.str() + ")SkSL\"";
+        }
+
+        this->addExtraEmitCodeLine(String::printf(
+                "fragBuilder->emitFunction(%s, %s_name.c_str(), {%s_args, %zu},%s);",
+                glsltype_string(fContext, decl.returnType()),
+                funcName.c_str(),
+                funcName.c_str(),
+                decl.parameters().size(),
+                funcImpl.c_str()));
+    }
+}
+
+void DSLCPPCodeGenerator::writeSetting(const Setting& s) {
+    this->writef("sk_Caps.%s", s.name().c_str());
+}
+
+bool DSLCPPCodeGenerator::writeSection(const char* name, const char* prefix) {
+    const Section* s = fSectionAndParameterHelper.getSection(name);
+    if (s) {
+        this->writef("%s%s", prefix, s->text().c_str());
+        return true;
+    }
+    return false;
+}
+
+void DSLCPPCodeGenerator::writeProgramElement(const ProgramElement& p) {
+    switch (p.kind()) {
+        case ProgramElement::Kind::kSection:
+            return;
+        case ProgramElement::Kind::kGlobalVar: {
+            const GlobalVarDeclaration& decl = p.as<GlobalVarDeclaration>();
+            const Variable& var = decl.declaration()->as<VarDeclaration>().var();
+            if (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) ||
+                -1 != var.modifiers().fLayout.fBuiltin) {
+                return;
+            }
+            break;
+        }
+        case ProgramElement::Kind::kFunctionPrototype: {
+            // Function prototypes are handled at the C++ level (in writeEmitCode).
+            // We don't want prototypes to be emitted inside the FP's main() function.
+            return;
+        }
+        default:
+            break;
+    }
+    INHERITED::writeProgramElement(p);
+}
+
+void DSLCPPCodeGenerator::addUniform(const Variable& var) {
+    if (!needs_uniform_var(var)) {
+        return;
+    }
+    if (var.modifiers().fLayout.fWhen.fLength) {
+        this->writef("        if (%s) {\n    ", String(var.modifiers().fLayout.fWhen).c_str());
+    }
+    String name(var.name());
+    if (!var.type().isArray()) {
+        this->writef("        %sVar = args.fUniformHandler->addUniform(&_outer, "
+                     "kFragment_GrShaderFlag, %s, \"%s\");\n",
+                     HCodeGenerator::FieldName(name.c_str()).c_str(),
+                     glsltype_string(fContext, var.type()),
+                     name.c_str());
+    } else {
+        this->writef("        %sVar = args.fUniformHandler->addUniformArray(&_outer, "
+                     "kFragment_GrShaderFlag, %s, \"%s\", %d);\n",
+                     HCodeGenerator::FieldName(name.c_str()).c_str(),
+                     glsltype_string(fContext, var.type().componentType()),
+                     name.c_str(),
+                     var.type().columns());
+    }
+    if (var.modifiers().fLayout.fWhen.fLength) {
+        this->write("        }\n");
+    }
+}
+
+void DSLCPPCodeGenerator::writeInputVars() {
+}
+
+void DSLCPPCodeGenerator::writePrivateVars() {
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+            const Variable& var = global.declaration()->as<VarDeclaration>().var();
+            if (is_private(var)) {
+                if (var.type().isFragmentProcessor()) {
+                    fErrors.error(global.fOffset,
+                                  "fragmentProcessor variables must be declared 'in'");
+                    return;
+                }
+                this->writef("%s %s = %s;\n",
+                             HCodeGenerator::FieldType(fContext, var.type(),
+                                                       var.modifiers().fLayout).c_str(),
+                             String(var.name()).c_str(),
+                             default_value(var).c_str());
+            } else if (var.modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
+                // An auto-tracked uniform in variable, so add a field to hold onto the prior
+                // state. Note that tracked variables must be uniform in's and that is validated
+                // before writePrivateVars() is called.
+                const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, var);
+                SkASSERT(mapper && mapper->supportsTracking());
+
+                String name = HCodeGenerator::FieldName(String(var.name()).c_str());
+                // The member statement is different if the mapper reports a default value
+                if (mapper->defaultValue().size() > 0) {
+                    this->writef("%s %sPrev = %s;\n",
+                                    Layout::CTypeToStr(mapper->ctype()), name.c_str(),
+                                    mapper->defaultValue().c_str());
+                } else {
+                    this->writef("%s %sPrev;\n",
+                                    Layout::CTypeToStr(mapper->ctype()), name.c_str());
+                }
+            }
+        }
+    }
+}
+
+void DSLCPPCodeGenerator::writePrivateVarValues() {
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+            const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+            if (is_private(decl.var()) && decl.value()) {
+                this->writef("%s = ", String(decl.var().name()).c_str());
+                fCPPMode = true;
+                this->writeExpression(*decl.value(), Precedence::kAssignment);
+                fCPPMode = false;
+                this->write(";\n");
+            }
+        }
+    }
+}
+
+static bool is_accessible(const Variable& var) {
+    const Type& type = var.type();
+    return !type.isFragmentProcessor() &&
+           Type::TypeKind::kSampler != type.typeKind() &&
+           Type::TypeKind::kOther != type.typeKind();
+}
+
+void DSLCPPCodeGenerator::newExtraEmitCodeBlock() {
+    // This should only be called when emitting SKSL for emitCode(), which can be detected if the
+    // cpp buffer is not null, and the cpp buffer is not the current output.
+    SkASSERT(fCPPBuffer && fCPPBuffer != fOut);
+
+    // Start a new block as an empty string
+    fExtraEmitCodeBlocks.push_back("");
+    // Mark its location in the output buffer, uses ${\d} for the token since ${} will not occur in
+    // valid sksl and makes detection trivial.
+    this->writef("${%zu}", fExtraEmitCodeBlocks.size() - 1);
+}
+
+void DSLCPPCodeGenerator::addExtraEmitCodeLine(const String& toAppend) {
+    SkASSERT(fExtraEmitCodeBlocks.size() > 0);
+    String& currentBlock = fExtraEmitCodeBlocks[fExtraEmitCodeBlocks.size() - 1];
+    // Automatically add indentation and newline
+    currentBlock += "        " + toAppend + "\n";
+}
+
+void DSLCPPCodeGenerator::flushEmittedCode() {
+    if (fCPPBuffer == nullptr) {
+        // Not actually within writeEmitCode() so nothing to flush
+        return;
+    }
+
+    StringStream* skslBuffer = static_cast<StringStream*>(fOut);
+
+    String sksl = skslBuffer->str();
+    // Empty the accumulation buffer since its current contents are consumed.
+    skslBuffer->reset();
+
+    // Switch to the cpp buffer
+    fOut = fCPPBuffer;
+
+    // Iterate through the sksl, keeping track of where the last statement ended (e.g. the latest
+    // encountered ';', '{', or '}'). If an extra emit code block token is encountered then the
+    // code from 0 to last statement end is sent to writeCodeAppend, the extra code block is
+    // appended to the cpp buffer, and then the sksl string is trimmed to start where the last
+    // statement left off (minus the encountered token).
+    size_t i = 0;
+    int flushPoint = -1;
+    int tokenStart = -1;
+    while (i < sksl.size()) {
+        if (tokenStart >= 0) {
+            // Looking for the end of the token
+            if (sksl[i] == '}') {
+                // Must append the sksl from 0 to flushPoint (inclusive) then the extra code
+                // accumulated in the block with index parsed from chars [tokenStart+2, i-1]
+                String toFlush = String(sksl.c_str(), flushPoint + 1);
+                // writeCodeAppend automatically removes the format args that it consumed, so
+                // fFormatArgs will be in a valid state for any future sksl
+                this->writeCodeAppend(toFlush);
+
+                SKSL_INT codeBlock;
+                SkAssertResult(
+                        stoi(StringFragment(sksl.c_str() + tokenStart + 2, i - tokenStart - 2),
+                             &codeBlock));
+                SkASSERT((size_t)codeBlock < fExtraEmitCodeBlocks.size());
+                if (fExtraEmitCodeBlocks[codeBlock].size() > 0) {
+                    this->write(fExtraEmitCodeBlocks[codeBlock].c_str());
+                }
+
+                // Now reset the sksl buffer to start after the flush point, but remove the token.
+                String compacted = String(sksl.c_str() + flushPoint + 1,
+                                          tokenStart - flushPoint - 1);
+                if (i < sksl.size() - 1) {
+                    compacted += String(sksl.c_str() + i + 1, sksl.size() - i - 1);
+                }
+                sksl = compacted;
+
+                // And reset iteration
+                i = -1;
+                flushPoint = -1;
+                tokenStart = -1;
+            }
+        } else {
+            // Looking for the start of extra emit block tokens, and tracking when statements end
+            if (sksl[i] == ';' || sksl[i] == '{' || sksl[i] == '}') {
+                flushPoint = i;
+            } else if (i < sksl.size() - 1 && sksl[i] == '$' && sksl[i + 1] == '{') {
+                // found an extra emit code block token
+                tokenStart = i++;
+            }
+        }
+        i++;
+    }
+
+    // Once we've gone through the sksl string to this point, there are no remaining extra emit
+    // code blocks to interleave, so append the remainder as usual.
+    this->writeCodeAppend(sksl);
+
+    // After appending, switch back to the emptied sksl buffer and reset the extra code blocks
+    fOut = skslBuffer;
+    fExtraEmitCodeBlocks.clear();
+}
+
+String DSLCPPCodeGenerator::assembleCodeAndFormatArgPrintf(const String& code) {
+    // Count % format specifiers.
+    size_t argCount = 0;
+    for (size_t index = 0; index < code.size(); ++index) {
+        if ('%' == code[index]) {
+            if (index == code.size() - 1) {
+                SkDEBUGFAIL("found a dangling format specifier at the end of a string");
+                break;
+            }
+            if (code[index + 1] == '%') {
+                // %% indicates a literal % sign, not a format argument. Skip over the next
+                // character to avoid mistakenly counting that one as an argument.
+                ++index;
+            } else {
+                // Count the format argument that we found.
+                ++argCount;
+            }
+        }
+    }
+
+    // Assemble the printf arguments.
+    String result = String::printf("R\"SkSL(%s)SkSL\"\n", code.c_str());
+    for (size_t i = 0; i < argCount; ++i) {
+        result += ", ";
+        result += fFormatArgs[i].c_str();
+    }
+
+    // argCount is equal to the number of fFormatArgs that were consumed, so they should be
+    // removed from the list.
+    if (argCount > 0) {
+        fFormatArgs.erase(fFormatArgs.begin(), fFormatArgs.begin() + argCount);
+    }
+
+    return result;
+}
+
+void DSLCPPCodeGenerator::writeCodeAppend(const String& code) {
+    if (!code.empty()) {
+        this->write("        fragBuilder->codeAppendf(\n");
+        this->write(assembleCodeAndFormatArgPrintf(code));
+        this->write(");\n");
+    }
+}
+
+String DSLCPPCodeGenerator::convertSKSLExpressionToCPP(const Expression& e,
+                                                    const String& cppVar) {
+    // To do this conversion, we temporarily switch the sksl output stream
+    // to an empty stringstream and reset the format args to empty.
+    OutputStream* oldSKSL = fOut;
+    StringStream exprBuffer;
+    fOut = &exprBuffer;
+
+    std::vector<String> oldArgs(fFormatArgs);
+    fFormatArgs.clear();
+
+    // Convert the argument expression into a format string and args
+    this->writeExpression(e, Precedence::kTopLevel);
+    std::vector<String> newArgs(fFormatArgs);
+    String expr = exprBuffer.str();
+
+    // After generating, restore the original output stream and format args
+    fFormatArgs = oldArgs;
+    fOut = oldSKSL;
+
+    // The sksl written to exprBuffer is not processed by flushEmittedCode(), so any extra emit code
+    // block tokens won't get handled. So we need to strip them from the expression and stick them
+    // to the end of the original sksl stream.
+    String exprFormat = "";
+    int tokenStart = -1;
+    for (size_t i = 0; i < expr.size(); i++) {
+        if (tokenStart >= 0) {
+            if (expr[i] == '}') {
+                // End of the token, so append the token to fOut
+                fOut->write(expr.c_str() + tokenStart, i - tokenStart + 1);
+                tokenStart = -1;
+            }
+        } else {
+            if (i < expr.size() - 1 && expr[i] == '$' && expr[i + 1] == '{') {
+                tokenStart = i++;
+            } else {
+                exprFormat += expr[i];
+            }
+        }
+    }
+
+    // Now build the final C++ code snippet from the format string and args
+    String cppExpr;
+    if (newArgs.empty()) {
+        // This was a static expression, so we can simplify the input
+        // color declaration in the emitted code to just a static string
+        cppExpr = "SkString " + cppVar + "(\"" + exprFormat + "\");";
+    } else if (newArgs.size() == 1 && exprFormat == "%s") {
+        // If the format expression is simply "%s", we can avoid an expensive call to printf.
+        // This happens fairly often in codegen so it is worth simplifying.
+        cppExpr = "SkString " + cppVar + "(" + newArgs[0] + ");";
+    } else {
+        // String formatting must occur dynamically, so have the C++ declaration
+        // use SkStringPrintf with the format args that were accumulated
+        // when the expression was written.
+        cppExpr = "SkString " + cppVar + " = SkStringPrintf(\"" + exprFormat + "\"";
+        for (size_t i = 0; i < newArgs.size(); i++) {
+            cppExpr += ", " + newArgs[i];
+        }
+        cppExpr += ");";
+    }
+    return cppExpr;
+}
+
+bool DSLCPPCodeGenerator::writeEmitCode(std::vector<const Variable*>& uniforms) {
+    this->write("    void emitCode(EmitArgs& args) override {\n"
+                "        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;\n");
+    this->writef("        const %s& _outer = args.fFp.cast<%s>();\n"
+                 "        (void) _outer;\n",
+                 fFullName.c_str(), fFullName.c_str());
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+            const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+            String nameString(decl.var().name());
+            const char* name = nameString.c_str();
+            if (SectionAndParameterHelper::IsParameter(decl.var()) &&
+                is_accessible(decl.var())) {
+                this->writef("        auto %s = _outer.%s;\n"
+                             "        (void) %s;\n",
+                             name, name, name);
+            }
+        }
+    }
+    this->writePrivateVarValues();
+    for (const auto u : uniforms) {
+        this->addUniform(*u);
+    }
+    this->writeSection(kEmitCodeSection);
+
+    // Save original buffer as the CPP buffer for flushEmittedCode()
+    fCPPBuffer = fOut;
+    StringStream skslBuffer;
+    fOut = &skslBuffer;
+
+    this->newExtraEmitCodeBlock();
+
+    // Generate mangled names and argument lists for helper functions.
+    std::unordered_set<const FunctionDeclaration*> definedHelpers;
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<FunctionDefinition>()) {
+            const FunctionDeclaration* decl = &p->as<FunctionDefinition>().declaration();
+            definedHelpers.insert(decl);
+            this->prepareHelperFunction(*decl);
+        }
+    }
+
+    // Emit prototypes for defined helper functions that originally had prototypes in the FP file.
+    // (If a function was prototyped but never defined, we skip it, since it wasn't prepared above.)
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<FunctionPrototype>()) {
+            const FunctionDeclaration* decl = &p->as<FunctionPrototype>().declaration();
+            if (definedHelpers.find(decl) != definedHelpers.end()) {
+                this->prototypeHelperFunction(*decl);
+            }
+        }
+    }
+
+    bool result = INHERITED::generateCode();
+    this->flushEmittedCode();
+
+    // Then restore the original CPP buffer and close the function
+    fOut = fCPPBuffer;
+    fCPPBuffer = nullptr;
+    this->write("    }\n");
+    return result;
+}
+
+void DSLCPPCodeGenerator::writeSetData(std::vector<const Variable*>& uniforms) {
+    const char* fullName = fFullName.c_str();
+    const Section* section = fSectionAndParameterHelper.getSection(kSetDataSection);
+    const char* pdman = section ? section->argument().c_str() : "pdman";
+    this->writef("    void onSetData(const GrGLSLProgramDataManager& %s, "
+                                    "const GrFragmentProcessor& _proc) override {\n",
+                 pdman);
+    bool wroteProcessor = false;
+    for (const Variable* u : uniforms) {
+        if (is_uniform_in(*u)) {
+            if (!wroteProcessor) {
+                this->writef("        const %s& _outer = _proc.cast<%s>();\n", fullName, fullName);
+                wroteProcessor = true;
+                this->writef("        {\n");
+            }
+
+            const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *u);
+            SkASSERT(mapper);
+
+            String nameString(u->name());
+            const char* name = nameString.c_str();
+
+            // Switches for setData behavior in the generated code
+            bool conditionalUniform = u->modifiers().fLayout.fWhen != "";
+            bool isTracked = u->modifiers().fLayout.fFlags & Layout::kTracked_Flag;
+            bool needsValueDeclaration = isTracked || !mapper->canInlineUniformValue();
+
+            String uniformName = HCodeGenerator::FieldName(name) + "Var";
+
+            String indent = "        "; // 8 by default, 12 when nested for conditional uniforms
+            if (conditionalUniform) {
+                // Add a pre-check to make sure the uniform was emitted
+                // before trying to send any data to the GPU
+                this->writef("        if (%s.isValid()) {\n", uniformName.c_str());
+                indent += "    ";
+            }
+
+            String valueVar = "";
+            if (needsValueDeclaration) {
+                valueVar.appendf("%sValue", name);
+                // Use AccessType since that will match the return type of _outer's public API.
+                String valueType = HCodeGenerator::AccessType(fContext, u->type(),
+                                                              u->modifiers().fLayout);
+                this->writef("%s%s %s = _outer.%s;\n",
+                             indent.c_str(), valueType.c_str(), valueVar.c_str(), name);
+            } else {
+                // Not tracked and the mapper only needs to use the value once
+                // so send it a safe expression instead of the variable name
+                valueVar.appendf("(_outer.%s)", name);
+            }
+
+            if (isTracked) {
+                SkASSERT(mapper->supportsTracking());
+
+                String prevVar = HCodeGenerator::FieldName(name) + "Prev";
+                this->writef("%sif (%s) {\n"
+                             "%s    %s;\n"
+                             "%s    %s;\n"
+                             "%s}\n", indent.c_str(),
+                        mapper->dirtyExpression(valueVar, prevVar).c_str(), indent.c_str(),
+                        mapper->saveState(valueVar, prevVar).c_str(), indent.c_str(),
+                        mapper->setUniform(pdman, uniformName, valueVar).c_str(), indent.c_str());
+            } else {
+                this->writef("%s%s;\n", indent.c_str(),
+                        mapper->setUniform(pdman, uniformName, valueVar).c_str());
+            }
+
+            if (conditionalUniform) {
+                // Close the earlier precheck block
+                this->writef("        }\n");
+            }
+        }
+    }
+    if (wroteProcessor) {
+        this->writef("        }\n");
+    }
+    if (section) {
+        int samplerIndex = 0;
+        for (const ProgramElement* p : fProgram.elements()) {
+            if (p->is<GlobalVarDeclaration>()) {
+                const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+                const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+                const Variable& variable = decl.var();
+                String nameString(variable.name());
+                const char* name = nameString.c_str();
+                if (variable.type().typeKind() == Type::TypeKind::kSampler) {
+                    this->writef("        const GrSurfaceProxyView& %sView = "
+                                 "_outer.textureSampler(%d).view();\n",
+                                 name, samplerIndex);
+                    this->writef("        GrTexture& %s = *%sView.proxy()->peekTexture();\n",
+                                 name, name);
+                    this->writef("        (void) %s;\n", name);
+                    ++samplerIndex;
+                } else if (needs_uniform_var(variable)) {
+                    this->writef("        UniformHandle& %s = %sVar;\n"
+                                    "        (void) %s;\n",
+                                    name, HCodeGenerator::FieldName(name).c_str(), name);
+                } else if (SectionAndParameterHelper::IsParameter(variable) &&
+                           !variable.type().isFragmentProcessor()) {
+                    if (!wroteProcessor) {
+                        this->writef("        const %s& _outer = _proc.cast<%s>();\n", fullName,
+                                     fullName);
+                        wroteProcessor = true;
+                    }
+
+                    if (!variable.type().isFragmentProcessor()) {
+                        this->writef("        auto %s = _outer.%s;\n"
+                                        "        (void) %s;\n",
+                                        name, name, name);
+                    }
+                }
+            }
+        }
+        this->writeSection(kSetDataSection);
+    }
+    this->write("    }\n");
+}
+
+void DSLCPPCodeGenerator::writeOnTextureSampler() {
+    bool foundSampler = false;
+    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+        if (param->type().typeKind() == Type::TypeKind::kSampler) {
+            if (!foundSampler) {
+                this->writef(
+                        "const GrFragmentProcessor::TextureSampler& %s::onTextureSampler(int "
+                        "index) const {\n",
+                        fFullName.c_str());
+                this->writef("    return IthTextureSampler(index, %s",
+                             HCodeGenerator::FieldName(String(param->name()).c_str()).c_str());
+                foundSampler = true;
+            } else {
+                this->writef(", %s",
+                             HCodeGenerator::FieldName(String(param->name()).c_str()).c_str());
+            }
+        }
+    }
+    if (foundSampler) {
+        this->write(");\n}\n");
+    }
+}
+
+void DSLCPPCodeGenerator::writeClone() {
+    if (!this->writeSection(kCloneSection)) {
+        if (fSectionAndParameterHelper.getSection(kFieldsSection)) {
+            fErrors.error(/*offset=*/0, "fragment processors with custom @fields must also have a "
+                                        "custom @clone");
+        }
+        this->writef("%s::%s(const %s& src)\n"
+                     ": INHERITED(k%s_ClassID, src.optimizationFlags())", fFullName.c_str(),
+                     fFullName.c_str(), fFullName.c_str(), fFullName.c_str());
+        for (const Variable* param : fSectionAndParameterHelper.getParameters()) {
+            String fieldName = HCodeGenerator::FieldName(String(param->name()).c_str());
+            if (!param->type().isFragmentProcessor()) {
+                this->writef("\n, %s(src.%s)",
+                             fieldName.c_str(),
+                             fieldName.c_str());
+            }
+        }
+        this->writef(" {\n");
+        this->writef("        this->cloneAndRegisterAllChildProcessors(src);\n");
+        int samplerCount = 0;
+        for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+            if (param->type().typeKind() == Type::TypeKind::kSampler) {
+                ++samplerCount;
+            }
+        }
+        if (samplerCount) {
+            this->writef("     this->setTextureSamplerCnt(%d);", samplerCount);
+        }
+        if (fAccessSampleCoordsDirectly) {
+            this->writef("    this->setUsesSampleCoordsDirectly();\n");
+        }
+        this->write("}\n");
+        this->writef("std::unique_ptr<GrFragmentProcessor> %s::clone() const {\n",
+                     fFullName.c_str());
+        this->writef("    return std::make_unique<%s>(*this);\n",
+                     fFullName.c_str());
+        this->write("}\n");
+    }
+}
+
+void DSLCPPCodeGenerator::writeDumpInfo() {
+    this->writef("#if GR_TEST_UTILS\n"
+                 "SkString %s::onDumpInfo() const {\n", fFullName.c_str());
+
+    if (!this->writeSection(kDumpInfoSection)) {
+        if (fSectionAndParameterHelper.getSection(kFieldsSection)) {
+            fErrors.error(/*offset=*/0, "fragment processors with custom @fields must also have a "
+                                        "custom @dumpInfo");
+        }
+
+        String formatString;
+        std::vector<String> argumentList;
+
+        for (const Variable* param : fSectionAndParameterHelper.getParameters()) {
+            // dumpInfo() doesn't need to log child FPs.
+            if (param->type().isFragmentProcessor()) {
+                continue;
+            }
+
+            // Add this field onto the format string and argument list.
+            String fieldName = HCodeGenerator::FieldName(String(param->name()).c_str());
+            String runtimeValue = this->formatRuntimeValue(param->type(),
+                                                           param->modifiers().fLayout,
+                                                           param->name(),
+                                                           &argumentList);
+            formatString.appendf("%s%s=%s",
+                                 formatString.empty() ? "" : ", ",
+                                 fieldName.c_str(),
+                                 runtimeValue.c_str());
+        }
+
+        if (!formatString.empty()) {
+            // Emit the finished format string and associated arguments.
+            this->writef("    return SkStringPrintf(\"(%s)\"", formatString.c_str());
+
+            for (const String& argument : argumentList) {
+                this->writef(", %s", argument.c_str());
+            }
+
+            this->write(");");
+        } else {
+            // No fields to dump at all; just return an empty string.
+            this->write("    return SkString();");
+        }
+    }
+
+    this->write("\n"
+                "}\n"
+                "#endif\n");
+}
+
+void DSLCPPCodeGenerator::writeTest() {
+    const Section* test = fSectionAndParameterHelper.getSection(kTestCodeSection);
+    if (test) {
+        this->writef(
+                "GR_DEFINE_FRAGMENT_PROCESSOR_TEST(%s);\n"
+                "#if GR_TEST_UTILS\n"
+                "std::unique_ptr<GrFragmentProcessor> %s::TestCreate(GrProcessorTestData* %s) {\n",
+                fFullName.c_str(),
+                fFullName.c_str(),
+                test->argument().c_str());
+        this->writeSection(kTestCodeSection);
+        this->write("}\n"
+                    "#endif\n");
+    }
+}
+
+static int bits_needed(uint32_t v) {
+    int bits = 1;
+    while (v >= (1u << bits)) {
+        bits++;
+    }
+    return bits;
+}
+
+void DSLCPPCodeGenerator::writeGetKey() {
+    auto bitsForEnum = [&](const Type& type) {
+        for (const ProgramElement* e : fProgram.elements()) {
+            if (!e->is<Enum>() || type.name() != e->as<Enum>().typeName()) {
+                continue;
+            }
+            SKSL_INT minVal = 0, maxVal = 0;
+            auto gatherEnumRange = [&](StringFragment, SKSL_INT value) {
+                minVal = std::min(minVal, value);
+                maxVal = std::max(maxVal, value);
+            };
+            e->as<Enum>().foreach(gatherEnumRange);
+            if (minVal < 0) {
+                // Found a negative value in the enum, just use 32 bits
+                return 32;
+            }
+            SkASSERT(SkTFitsIn<uint32_t>(maxVal));
+            return bits_needed(maxVal);
+        }
+        SK_ABORT("Didn't find declaring element for enum type!");
+        return 32;
+    };
+
+    this->writef("void %s::onGetGLSLProcessorKey(const GrShaderCaps& caps, "
+                                                "GrProcessorKeyBuilder* b) const {\n",
+                 fFullName.c_str());
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+            const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+            const Variable& var = decl.var();
+            const Type& varType = var.type();
+            String nameString(var.name());
+            const char* name = nameString.c_str();
+            if (var.modifiers().fLayout.fFlags & Layout::kKey_Flag) {
+                if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
+                    fErrors.error(var.fOffset, "layout(key) may not be specified on uniforms");
+                }
+                if (is_private(var)) {
+                    this->writef(
+                            "%s %s =",
+                            HCodeGenerator::FieldType(fContext, varType, var.modifiers().fLayout)
+                                    .c_str(),
+                            String(var.name()).c_str());
+                    if (decl.value()) {
+                        fCPPMode = true;
+                        this->writeExpression(*decl.value(), Precedence::kAssignment);
+                        fCPPMode = false;
+                    } else {
+                        this->writef("%s", default_value(var).c_str());
+                    }
+                    this->write(";\n");
+                }
+                if (var.modifiers().fLayout.fWhen.fLength) {
+                    this->writef("if (%s) {", String(var.modifiers().fLayout.fWhen).c_str());
+                }
+                if (varType == *fContext.fTypes.fHalf4) {
+                    this->writef("    uint16_t red = SkFloatToHalf(%s.fR);\n",
+                                 HCodeGenerator::FieldName(name).c_str());
+                    this->writef("    uint16_t green = SkFloatToHalf(%s.fG);\n",
+                                 HCodeGenerator::FieldName(name).c_str());
+                    this->writef("    uint16_t blue = SkFloatToHalf(%s.fB);\n",
+                                 HCodeGenerator::FieldName(name).c_str());
+                    this->writef("    uint16_t alpha = SkFloatToHalf(%s.fA);\n",
+                                 HCodeGenerator::FieldName(name).c_str());
+                    this->writef("    b->add32(((uint32_t)red << 16) | green, \"%s.rg\");\n", name);
+                    this->writef("    b->add32(((uint32_t)blue << 16) | alpha, \"%s.ba\");\n",
+                                 name);
+                } else if (varType == *fContext.fTypes.fHalf ||
+                           varType == *fContext.fTypes.fFloat) {
+                    this->writef("    b->add32(sk_bit_cast<uint32_t>(%s), \"%s\");\n",
+                                 HCodeGenerator::FieldName(name).c_str(), name);
+                } else if (varType.isBoolean()) {
+                    this->writef("    b->addBool(%s, \"%s\");\n",
+                                 HCodeGenerator::FieldName(name).c_str(), name);
+                } else if (varType.isEnum()) {
+                    this->writef("    b->addBits(%d, (uint32_t) %s, \"%s\");\n",
+                                 bitsForEnum(varType), HCodeGenerator::FieldName(name).c_str(),
+                                 name);
+                } else if (varType.isInteger()) {
+                    this->writef("    b->add32((uint32_t) %s, \"%s\");\n",
+                                 HCodeGenerator::FieldName(name).c_str(), name);
+                } else {
+                    SK_ABORT("NOT YET IMPLEMENTED: automatic key handling for %s\n",
+                             varType.displayName().c_str());
+                }
+                if (var.modifiers().fLayout.fWhen.fLength) {
+                    this->write("}");
+                }
+            }
+        }
+    }
+    this->write("}\n");
+}
+
+bool DSLCPPCodeGenerator::generateCode() {
+    std::vector<const Variable*> uniforms;
+    for (const ProgramElement* p : fProgram.elements()) {
+        if (p->is<GlobalVarDeclaration>()) {
+            const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+            const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+            if ((decl.var().modifiers().fFlags & Modifiers::kUniform_Flag) &&
+                        decl.var().type().typeKind() != Type::TypeKind::kSampler) {
+                uniforms.push_back(&decl.var());
+            }
+
+            if (is_uniform_in(decl.var())) {
+                // Validate the "uniform in" declarations to make sure they are fully supported,
+                // instead of generating surprising C++
+                const UniformCTypeMapper* mapper =
+                        UniformCTypeMapper::Get(fContext, decl.var());
+                if (mapper == nullptr) {
+                    fErrors.error(decl.fOffset, String(decl.var().name())
+                            + "'s type is not supported for use as a 'uniform in'");
+                    return false;
+                }
+                if (decl.var().modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
+                    if (!mapper->supportsTracking()) {
+                        fErrors.error(decl.fOffset, String(decl.var().name())
+                                + "'s type does not support state tracking");
+                        return false;
+                    }
+                }
+
+            } else {
+                // If it's not a uniform_in, it's an error to be tracked
+                if (decl.var().modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
+                    fErrors.error(decl.fOffset, "Non-'in uniforms' cannot be tracked");
+                    return false;
+                }
+            }
+        }
+    }
+    const char* baseName = fName.c_str();
+    const char* fullName = fFullName.c_str();
+    this->writef("%s\n", HCodeGenerator::GetHeader(fProgram, fErrors).c_str());
+    this->writef(kFragmentProcessorHeader, fullName);
+    this->write("/* TODO(skia:11854): DSLCPPCodeGenerator is currently a work in progress. */\n");
+    this->writef("#include \"%s.h\"\n\n", fullName);
+    this->writeSection(kCppSection);
+    this->writef("#include \"src/core/SkUtils.h\"\n"
+                 "#include \"src/gpu/GrTexture.h\"\n"
+                 "#include \"src/gpu/glsl/GrGLSLFragmentProcessor.h\"\n"
+                 "#include \"src/gpu/glsl/GrGLSLFragmentShaderBuilder.h\"\n"
+                 "#include \"src/gpu/glsl/GrGLSLProgramBuilder.h\"\n"
+                 "#include \"src/sksl/SkSLCPP.h\"\n"
+                 "#include \"src/sksl/SkSLUtil.h\"\n"
+                 "class GrGLSL%s : public GrGLSLFragmentProcessor {\n"
+                 "public:\n"
+                 "    GrGLSL%s() {}\n",
+                 baseName, baseName);
+    bool result = this->writeEmitCode(uniforms);
+    this->write("private:\n");
+    this->writeSetData(uniforms);
+    this->writePrivateVars();
+    for (const auto& u : uniforms) {
+        if (needs_uniform_var(*u) && !(u->modifiers().fFlags & Modifiers::kIn_Flag)) {
+            this->writef("    UniformHandle %sVar;\n",
+                         HCodeGenerator::FieldName(String(u->name()).c_str()).c_str());
+        }
+    }
+    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+        if (needs_uniform_var(*param)) {
+            this->writef("    UniformHandle %sVar;\n",
+                         HCodeGenerator::FieldName(String(param->name()).c_str()).c_str());
+        }
+    }
+    this->writef("};\n"
+                 "std::unique_ptr<GrGLSLFragmentProcessor> %s::onMakeProgramImpl() const {\n"
+                 "    return std::make_unique<GrGLSL%s>();\n"
+                 "}\n",
+                 fullName, baseName);
+    this->writeGetKey();
+    this->writef("bool %s::onIsEqual(const GrFragmentProcessor& other) const {\n"
+                 "    const %s& that = other.cast<%s>();\n"
+                 "    (void) that;\n",
+                 fullName, fullName, fullName);
+    for (const auto& param : fSectionAndParameterHelper.getParameters()) {
+        if (param->type().isFragmentProcessor()) {
+            continue;
+        }
+        String nameString(param->name());
+        const char* name = nameString.c_str();
+        this->writef("    if (%s != that.%s) return false;\n",
+                     HCodeGenerator::FieldName(name).c_str(),
+                     HCodeGenerator::FieldName(name).c_str());
+    }
+    this->write("    return true;\n"
+                "}\n");
+    this->writeClone();
+    this->writeDumpInfo();
+    this->writeOnTextureSampler();
+    this->writeTest();
+    this->writeSection(kCppEndSection);
+
+    result &= 0 == fErrors.errorCount();
+    return result;
+}
+
+}  // namespace SkSL
+
+#endif // defined(SKSL_STANDALONE) || GR_TEST_UTILS
diff --git a/src/sksl/codegen/SkSLDSLCPPCodeGenerator.h b/src/sksl/codegen/SkSLDSLCPPCodeGenerator.h
new file mode 100644
index 0000000..f35be11
--- /dev/null
+++ b/src/sksl/codegen/SkSLDSLCPPCodeGenerator.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_DSLCPPCODEGENERATOR
+#define SKSL_DSLCPPCODEGENERATOR
+
+#include "src/sksl/SkSLSectionAndParameterHelper.h"
+#include "src/sksl/codegen/SkSLGLSLCodeGenerator.h"
+
+#include <set>
+
+#if defined(SKSL_STANDALONE) || GR_TEST_UTILS
+
+namespace SkSL {
+
+class DSLCPPCodeGenerator : public GLSLCodeGenerator {
+public:
+    DSLCPPCodeGenerator(const Context* context, const Program* program, ErrorReporter* errors,
+                        String name, OutputStream* out);
+
+    bool generateCode() override;
+
+private:
+    using Precedence = Operator::Precedence;
+
+    void writef(const char* s, va_list va) SK_PRINTF_LIKE(2, 0);
+
+    void writef(const char* s, ...) SK_PRINTF_LIKE(2, 3);
+
+    bool writeSection(const char* name, const char* prefix = "");
+
+    void writeHeader() override;
+
+    bool usesPrecisionModifiers() const override;
+
+    String getTypeName(const Type& type) override;
+
+    void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence) override;
+
+    void writeIntLiteral(const IntLiteral& i) override;
+
+    void writeSwizzle(const Swizzle& swizzle) override;
+
+    void writeVariableReference(const VariableReference& ref) override;
+
+    String getSamplerHandle(const Variable& var);
+
+    void writeIfStatement(const IfStatement& s) override;
+
+    void writeReturnStatement(const ReturnStatement& s) override;
+
+    void writeSwitchStatement(const SwitchStatement& s) override;
+
+    String getSampleVarName(const char* prefix, int sampleCounter);
+
+    void writeFunctionCall(const FunctionCall& c) override;
+
+    void writeFunction(const FunctionDefinition& f) override;
+
+    void prepareHelperFunction(const FunctionDeclaration& decl);
+
+    void prototypeHelperFunction(const FunctionDeclaration& decl);
+
+    void writeSetting(const Setting& s) override;
+
+    void writeProgramElement(const ProgramElement& p) override;
+
+    void addUniform(const Variable& var);
+
+    // writes a printf escape that will be filled in at runtime by the given C++ expression string
+    void writeRuntimeValue(const Type& type, const Layout& layout, const String& cppCode);
+    String formatRuntimeValue(const Type& type, const Layout& layout, const String& cppCode,
+                              std::vector<String>* formatArgs);
+
+    void writeVarInitializer(const Variable& var, const Expression& value) override;
+
+    void writeInputVars() override;
+
+    void writePrivateVars();
+
+    void writePrivateVarValues();
+
+    void writeCodeAppend(const String& code);
+
+    String assembleCodeAndFormatArgPrintf(const String& code);
+
+    bool writeEmitCode(std::vector<const Variable*>& uniforms);
+
+    void writeSetData(std::vector<const Variable*>& uniforms);
+
+    void writeGetKey();
+
+    void writeOnTextureSampler();
+
+    void writeClone();
+
+    void writeDumpInfo();
+
+    void writeTest();
+
+    // If the returned C++ is included in the generated code, then the variable name stored in
+    // cppVar will refer to a valid SkString that matches the Expression. Successful returns leave
+    // the output buffer (and related state) unmodified.
+    //
+    // In the simplest cases, this will return "SkString {cppVar}(\"{e}\");", while more advanced
+    // cases will properly insert format arguments.
+    String convertSKSLExpressionToCPP(const Expression& e, const String& cppVar);
+
+    // Process accumulated sksl to split it into appended code sections, properly interleaved with
+    // the extra emit code blocks, based on statement/block locations and the inserted tokens
+    // from newExtraEmitCodeBlock(). It is necessary to split the sksl after the program has been
+    // fully walked since many elements redirect fOut to simultaneously build header sections and
+    // bodies that are then concatenated; due to this it is not possible to split the sksl emission
+    // on the fly.
+    void flushEmittedCode();
+
+    // Start a new extra emit code block for accumulating C++ code. This will insert a token into
+    // the sksl stream to mark the fence between previous complete sksl statements and where the
+    // C++ code added to the new block will be added to emitCode(). These tokens are removed by
+    // flushEmittedCode() as it consumes them before passing pure sksl to writeCodeAppend().
+    void newExtraEmitCodeBlock();
+
+    // Append CPP code to the current extra emit code block.
+    void addExtraEmitCodeLine(const String& toAppend);
+
+    int getChildFPIndex(const Variable& var) const;
+
+    String fName;
+    String fFullName;
+    SectionAndParameterHelper fSectionAndParameterHelper;
+    std::vector<String> fExtraEmitCodeBlocks;
+
+    std::vector<String> fFormatArgs;
+    // true if the sksl declared its main() function with a float2 parameter AND referenced that
+    // parameter in its body.
+    bool fAccessSampleCoordsDirectly = false;
+
+    // If true, we are writing a C++ expression instead of a GLSL expression
+    bool fCPPMode = false;
+
+    // True while compiling the main() function of the FP.
+    bool fInMain = false;
+
+    // Gives unique but predictable names to invocations of sample().
+    int fSampleCounter = 0;
+
+    // if not null, we are accumulating SkSL for emitCode into fOut, which
+    // replaced the original buffer with a StringStream. The original buffer is
+    // stored here for restoration.
+    OutputStream* fCPPBuffer = nullptr;
+
+    using INHERITED = GLSLCodeGenerator;
+};
+
+}  // namespace SkSL
+
+#endif // defined(SKSL_STANDALONE) || GR_TEST_UTILS
+
+#endif // SKSL_DSLCPPCODEGENERATOR
diff --git a/tests/sksl/fp/GrDSLHelloWorld.h b/tests/sksl/fp/GrDSLHelloWorld.h
new file mode 100644
index 0000000..f2b019b
--- /dev/null
+++ b/tests/sksl/fp/GrDSLHelloWorld.h
@@ -0,0 +1,36 @@
+/* HELLO WORLD */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrDSLHelloWorld.fp; do not modify.
+ **************************************************************************************************/
+#ifndef GrDSLHelloWorld_DEFINED
+#define GrDSLHelloWorld_DEFINED
+
+#include "include/core/SkM44.h"
+#include "include/core/SkTypes.h"
+
+
+#include "src/gpu/GrFragmentProcessor.h"
+
+class GrDSLHelloWorld : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make() {
+        return std::unique_ptr<GrFragmentProcessor>(new GrDSLHelloWorld());
+    }
+    GrDSLHelloWorld(const GrDSLHelloWorld& src);
+    std::unique_ptr<GrFragmentProcessor> clone() const override;
+    const char* name() const override { return "DSLHelloWorld"; }
+private:
+    GrDSLHelloWorld()
+    : INHERITED(kGrDSLHelloWorld_ClassID, kNone_OptimizationFlags) {
+    }
+    std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
+    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
+    bool onIsEqual(const GrFragmentProcessor&) const override;
+#if GR_TEST_UTILS
+    SkString onDumpInfo() const override;
+#endif
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+    using INHERITED = GrFragmentProcessor;
+};
+#endif
diff --git a/tests/sksl/fp/GrDSLHelloWorld_dsl.cpp b/tests/sksl/fp/GrDSLHelloWorld_dsl.cpp
new file mode 100644
index 0000000..d0cb7bf
--- /dev/null
+++ b/tests/sksl/fp/GrDSLHelloWorld_dsl.cpp
@@ -0,0 +1,53 @@
+/* HELLO WORLD */
+
+/**************************************************************************************************
+ *** This file was autogenerated from GrDSLHelloWorld.fp; do not modify.
+ **************************************************************************************************/
+/* TODO(skia:11854): DSLCPPCodeGenerator is currently a work in progress. */
+#include "GrDSLHelloWorld.h"
+
+#include "src/core/SkUtils.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 GrGLSLDSLHelloWorld : public GrGLSLFragmentProcessor {
+public:
+    GrGLSLDSLHelloWorld() {}
+    void emitCode(EmitArgs& args) override {
+        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+        const GrDSLHelloWorld& _outer = args.fFp.cast<GrDSLHelloWorld>();
+        (void) _outer;
+        fragBuilder->codeAppendf(
+R"SkSL(return half4(1.0);
+)SkSL"
+);
+    }
+private:
+    void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& _proc) override {
+    }
+};
+std::unique_ptr<GrGLSLFragmentProcessor> GrDSLHelloWorld::onMakeProgramImpl() const {
+    return std::make_unique<GrGLSLDSLHelloWorld>();
+}
+void GrDSLHelloWorld::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const {
+}
+bool GrDSLHelloWorld::onIsEqual(const GrFragmentProcessor& other) const {
+    const GrDSLHelloWorld& that = other.cast<GrDSLHelloWorld>();
+    (void) that;
+    return true;
+}
+GrDSLHelloWorld::GrDSLHelloWorld(const GrDSLHelloWorld& src)
+: INHERITED(kGrDSLHelloWorld_ClassID, src.optimizationFlags()) {
+        this->cloneAndRegisterAllChildProcessors(src);
+}
+std::unique_ptr<GrFragmentProcessor> GrDSLHelloWorld::clone() const {
+    return std::make_unique<GrDSLHelloWorld>(*this);
+}
+#if GR_TEST_UTILS
+SkString GrDSLHelloWorld::onDumpInfo() const {
+    return SkString();
+}
+#endif