Add runtime color filter and shader modes to the SkSL compiler

These enforce stricter rules about the signature of main, and each one
uses a separate pre-include module. That prevents color filters from
being able to reference sk_FragCoord (or coords passed to main) at all.
It also limits the versions of sample() that are exposed.

In the new world, an effect created for a specific stage of the Skia
pipeline can only be used to create instances of that stage (SkShader or
SkColorFilter). For now, SkRuntimeEffect::Make uses kRuntimeEffect,
which continues to be more lenient and allow creation of either shaders
or color filters from a single effect. After we migrate all clients, we
can deprecate and then delete that mode.

Bug: skia:11813
Change-Id: I0afd79a72beeec84da42c86146e8fcd8d0e4c09f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/395716
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index f0bcae4..50dc7ae 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -133,15 +133,16 @@
     return false;
 }
 
-SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& options) {
+SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl,
+                                              const Options& options,
+                                              SkSL::ProgramKind kind) {
     SkSL::SharedCompiler compiler;
     SkSL::Program::Settings settings;
     settings.fInlineThreshold = 0;
     settings.fForceNoInline = options.forceNoInline;
     settings.fAllowNarrowingConversions = true;
-    auto program = compiler->convertProgram(SkSL::ProgramKind::kRuntimeEffect,
-                                            SkSL::String(sksl.c_str(), sksl.size()),
-                                            settings);
+    auto program =
+            compiler->convertProgram(kind, SkSL::String(sksl.c_str(), sksl.size()), settings);
     // TODO: Many errors aren't caught until we process the generated Program here. Catching those
     // in the IR generator would provide better errors messages (with locations).
     #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
@@ -151,8 +152,17 @@
     }
 
     const SkSL::FunctionDefinition* main = nullptr;
-    const bool usesSampleCoords = SkSL::Analysis::ReferencesSampleCoords(*program);
-    const bool usesFragCoords   = SkSL::Analysis::ReferencesFragCoords(*program);
+    uint32_t flags = 0;
+    switch (kind) {
+        case SkSL::ProgramKind::kRuntimeColorFilter: flags |= kAllowColorFilter_Flag; break;
+        case SkSL::ProgramKind::kRuntimeShader:      flags |= kAllowShader_Flag;      break;
+        case SkSL::ProgramKind::kRuntimeEffect:      flags |= (kAllowColorFilter_Flag |
+                                                               kAllowShader_Flag);    break;
+        default: SkUNREACHABLE;
+    }
+    if (SkSL::Analysis::ReferencesSampleCoords(*program)) {
+        flags |= kUsesSampleCoords_Flag;
+    }
 
     // Color filters are not allowed to depend on position (local or device) in any way, but they
     // can sample children with matrices or explicit coords. Because the children are color filters,
@@ -160,7 +170,13 @@
     //
     // Further down, we also ensure that color filters can't use varyings or layout(marker), which
     // would allow them to change behavior based on the CTM.
-    bool allowColorFilter = !usesSampleCoords && !usesFragCoords;
+    // TODO(skbug.com/11813): When ProgramKind is always kRuntimeColorFilter or kRuntimeShader,
+    // this can be simpler. There is no way for color filters to refer to sk_FragCoord or sample
+    // coords in that mode.
+    if ((flags & kAllowColorFilter_Flag) &&
+        (SkSL::Analysis::ReferencesFragCoords(*program) || (flags & kUsesSampleCoords_Flag))) {
+        flags &= ~kAllowColorFilter_Flag;
+    }
 
     size_t offset = 0;
     std::vector<Uniform> uniforms;
@@ -181,7 +197,7 @@
 
             // Varyings (only used in conjunction with drawVertices)
             if (var.modifiers().fFlags & SkSL::Modifiers::kVarying_Flag) {
-                allowColorFilter = false;
+                flags &= ~kAllowColorFilter_Flag;
                 varyings.push_back({var.name(),
                                     varType.typeKind() == SkSL::Type::TypeKind::kVector
                                             ? varType.columns()
@@ -213,7 +229,7 @@
                 const SkSL::StringFragment& marker(var.modifiers().fLayout.fMarker);
                 if (marker.fLength) {
                     uni.flags |= Uniform::kMarker_Flag;
-                    allowColorFilter = false;
+                    flags &= ~kAllowColorFilter_Flag;
                     if (!parse_marker(marker, &uni.marker, &uni.flags)) {
                         RETURN_FAILURE("Invalid 'marker' string: '%.*s'", (int)marker.fLength,
                                         marker.fChars);
@@ -255,11 +271,26 @@
                                                       std::move(children),
                                                       std::move(sampleUsages),
                                                       std::move(varyings),
-                                                      usesSampleCoords,
-                                                      allowColorFilter));
+                                                      flags));
     return Result{std::move(effect), SkString()};
 }
 
+SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& options) {
+    return Make(std::move(sksl), options, SkSL::ProgramKind::kRuntimeEffect);
+}
+
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForColorFilter(SkString sksl, const Options& options) {
+    auto result = Make(std::move(sksl), options, SkSL::ProgramKind::kRuntimeColorFilter);
+    SkASSERT(!result.effect || result.effect->allowColorFilter());
+    return result;
+}
+
+SkRuntimeEffect::Result SkRuntimeEffect::MakeForShader(SkString sksl, const Options& options) {
+    auto result = Make(std::move(sksl), options, SkSL::ProgramKind::kRuntimeShader);
+    SkASSERT(!result.effect || result.effect->allowShader());
+    return result;
+}
+
 sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect(SkString sksl) {
     SK_BEGIN_REQUIRE_DENSE
     struct Key {
@@ -332,8 +363,7 @@
                                  std::vector<SkString>&& children,
                                  std::vector<SkSL::SampleUsage>&& sampleUsages,
                                  std::vector<Varying>&& varyings,
-                                 bool usesSampleCoords,
-                                 bool allowColorFilter)
+                                 uint32_t flags)
         : fHash(SkGoodHash()(sksl))
         , fSkSL(std::move(sksl))
         , fBaseProgram(std::move(baseProgram))
@@ -342,8 +372,7 @@
         , fChildren(std::move(children))
         , fSampleUsages(std::move(sampleUsages))
         , fVaryings(std::move(varyings))
-        , fUsesSampleCoords(usesSampleCoords)
-        , fAllowColorFilter(allowColorFilter) {
+        , fFlags(flags) {
     SkASSERT(fBaseProgram);
     SkASSERT(fChildren.size() == fSampleUsages.size());
 
@@ -376,7 +405,7 @@
 }
 
 SkRuntimeEffect::FilterColorInfo SkRuntimeEffect::getFilterColorInfo() {
-    SkASSERT(fAllowColorFilter);
+    SkASSERT(this->allowColorFilter());
 
     fColorFilterProgramOnce([&] {
         // Runtime effects are often long lived & cached. So: build and save a program that can
@@ -853,6 +882,9 @@
                                             size_t childCount,
                                             const SkMatrix* localMatrix,
                                             bool isOpaque) const {
+    if (!this->allowShader()) {
+        return nullptr;
+    }
     if (!uniforms) {
         uniforms = SkData::MakeEmpty();
     }
@@ -948,7 +980,7 @@
 sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<SkData> uniforms,
                                                       sk_sp<SkColorFilter> children[],
                                                       size_t childCount) const {
-    if (!fAllowColorFilter) {
+    if (!this->allowColorFilter()) {
         return nullptr;
     }
     if (!uniforms) {
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index f03f931..a660536 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -174,8 +174,8 @@
                    const char* name,
                    sk_sp<SkData> uniforms)
         : INHERITED(kGrSkSLFP_ClassID,
-                    effect->fAllowColorFilter ? kConstantOutputForConstantInput_OptimizationFlag
-                                              : kNone_OptimizationFlags)
+                    effect->allowColorFilter() ? kConstantOutputForConstantInput_OptimizationFlag
+                                               : kNone_OptimizationFlags)
         , fEffect(std::move(effect))
         , fName(name)
         , fUniforms(std::move(uniforms)) {
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index d1a562b..7f64d19 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -63,6 +63,8 @@
 #include "src/sksl/generated/sksl_geom.dehydrated.sksl"
 #include "src/sksl/generated/sksl_gpu.dehydrated.sksl"
 #include "src/sksl/generated/sksl_public.dehydrated.sksl"
+#include "src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl"
+#include "src/sksl/generated/sksl_rt_shader.dehydrated.sksl"
 #include "src/sksl/generated/sksl_runtime.dehydrated.sksl"
 #include "src/sksl/generated/sksl_vert.dehydrated.sksl"
 
@@ -242,39 +244,63 @@
     return fPublicModule;
 }
 
+static void add_glsl_type_aliases(SkSL::SymbolTable* symbols, const SkSL::BuiltinTypes& types) {
+    // Add some aliases to the runtime effect modules so that it's friendlier, and more like GLSL
+    symbols->addAlias("vec2", types.fFloat2.get());
+    symbols->addAlias("vec3", types.fFloat3.get());
+    symbols->addAlias("vec4", types.fFloat4.get());
+
+    symbols->addAlias("ivec2", types.fInt2.get());
+    symbols->addAlias("ivec3", types.fInt3.get());
+    symbols->addAlias("ivec4", types.fInt4.get());
+
+    symbols->addAlias("bvec2", types.fBool2.get());
+    symbols->addAlias("bvec3", types.fBool3.get());
+    symbols->addAlias("bvec4", types.fBool4.get());
+
+    symbols->addAlias("mat2", types.fFloat2x2.get());
+    symbols->addAlias("mat3", types.fFloat3x3.get());
+    symbols->addAlias("mat4", types.fFloat4x4.get());
+}
+
 const ParsedModule& Compiler::loadRuntimeEffectModule() {
     if (!fRuntimeEffectModule.fSymbols) {
-        fRuntimeEffectModule = this->parseModule(ProgramKind::kRuntimeEffect, MODULE_DATA(runtime),
-                                                 this->loadPublicModule());
-
-        // Add some aliases to the runtime effect module so that it's friendlier, and more like GLSL
-        fRuntimeEffectModule.fSymbols->addAlias("vec2", fContext->fTypes.fFloat2.get());
-        fRuntimeEffectModule.fSymbols->addAlias("vec3", fContext->fTypes.fFloat3.get());
-        fRuntimeEffectModule.fSymbols->addAlias("vec4", fContext->fTypes.fFloat4.get());
-
-        fRuntimeEffectModule.fSymbols->addAlias("ivec2", fContext->fTypes.fInt2.get());
-        fRuntimeEffectModule.fSymbols->addAlias("ivec3", fContext->fTypes.fInt3.get());
-        fRuntimeEffectModule.fSymbols->addAlias("ivec4", fContext->fTypes.fInt4.get());
-
-        fRuntimeEffectModule.fSymbols->addAlias("bvec2", fContext->fTypes.fBool2.get());
-        fRuntimeEffectModule.fSymbols->addAlias("bvec3", fContext->fTypes.fBool3.get());
-        fRuntimeEffectModule.fSymbols->addAlias("bvec4", fContext->fTypes.fBool4.get());
-
-        fRuntimeEffectModule.fSymbols->addAlias("mat2", fContext->fTypes.fFloat2x2.get());
-        fRuntimeEffectModule.fSymbols->addAlias("mat3", fContext->fTypes.fFloat3x3.get());
-        fRuntimeEffectModule.fSymbols->addAlias("mat4", fContext->fTypes.fFloat4x4.get());
+        fRuntimeEffectModule = this->parseModule(
+                ProgramKind::kRuntimeEffect, MODULE_DATA(runtime), this->loadPublicModule());
+        add_glsl_type_aliases(fRuntimeEffectModule.fSymbols.get(), fContext->fTypes);
     }
     return fRuntimeEffectModule;
 }
 
+const ParsedModule& Compiler::loadRuntimeColorFilterModule() {
+    if (!fRuntimeColorFilterModule.fSymbols) {
+        fRuntimeColorFilterModule = this->parseModule(ProgramKind::kRuntimeColorFilter,
+                                                      MODULE_DATA(rt_colorfilter),
+                                                      this->loadPublicModule());
+        add_glsl_type_aliases(fRuntimeColorFilterModule.fSymbols.get(), fContext->fTypes);
+    }
+    return fRuntimeColorFilterModule;
+}
+
+const ParsedModule& Compiler::loadRuntimeShaderModule() {
+    if (!fRuntimeShaderModule.fSymbols) {
+        fRuntimeShaderModule = this->parseModule(
+                ProgramKind::kRuntimeShader, MODULE_DATA(rt_shader), this->loadPublicModule());
+        add_glsl_type_aliases(fRuntimeShaderModule.fSymbols.get(), fContext->fTypes);
+    }
+    return fRuntimeShaderModule;
+}
+
 const ParsedModule& Compiler::moduleForProgramKind(ProgramKind kind) {
     switch (kind) {
-        case ProgramKind::kVertex:            return this->loadVertexModule();        break;
-        case ProgramKind::kFragment:          return this->loadFragmentModule();      break;
-        case ProgramKind::kGeometry:          return this->loadGeometryModule();      break;
-        case ProgramKind::kFragmentProcessor: return this->loadFPModule();            break;
-        case ProgramKind::kRuntimeEffect:     return this->loadRuntimeEffectModule(); break;
-        case ProgramKind::kGeneric:           return this->loadPublicModule();        break;
+        case ProgramKind::kVertex:             return this->loadVertexModule();             break;
+        case ProgramKind::kFragment:           return this->loadFragmentModule();           break;
+        case ProgramKind::kGeometry:           return this->loadGeometryModule();           break;
+        case ProgramKind::kFragmentProcessor:  return this->loadFPModule();                 break;
+        case ProgramKind::kRuntimeEffect:      return this->loadRuntimeEffectModule();      break;
+        case ProgramKind::kRuntimeColorFilter: return this->loadRuntimeColorFilterModule(); break;
+        case ProgramKind::kRuntimeShader:      return this->loadRuntimeShaderModule();      break;
+        case ProgramKind::kGeneric:            return this->loadPublicModule();             break;
     }
     SkUNREACHABLE;
 }
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index c3dcbba..93f20d5 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -190,6 +190,8 @@
     const ParsedModule& loadGeometryModule();
     const ParsedModule& loadPublicModule();
     const ParsedModule& loadRuntimeEffectModule();
+    const ParsedModule& loadRuntimeColorFilterModule();
+    const ParsedModule& loadRuntimeShaderModule();
 
     /** Verifies that @if and @switch statements were actually optimized away. */
     void verifyStaticTests(const Program& program);
@@ -214,17 +216,19 @@
     std::shared_ptr<SymbolTable> fRootSymbolTable;
     std::shared_ptr<SymbolTable> fPrivateSymbolTable;
 
-    ParsedModule fRootModule;           // Core types
+    ParsedModule fRootModule;                // Core types
 
-    ParsedModule fPrivateModule;        // [Root] + Internal types
-    ParsedModule fGPUModule;            // [Private] + GPU intrinsics, helper functions
-    ParsedModule fVertexModule;         // [GPU] + Vertex stage decls
-    ParsedModule fFragmentModule;       // [GPU] + Fragment stage decls
-    ParsedModule fGeometryModule;       // [GPU] + Geometry stage decls
-    ParsedModule fFPModule;             // [GPU] + FP features
+    ParsedModule fPrivateModule;             // [Root] + Internal types
+    ParsedModule fGPUModule;                 // [Private] + GPU intrinsics, helper functions
+    ParsedModule fVertexModule;              // [GPU] + Vertex stage decls
+    ParsedModule fFragmentModule;            // [GPU] + Fragment stage decls
+    ParsedModule fGeometryModule;            // [GPU] + Geometry stage decls
+    ParsedModule fFPModule;                  // [GPU] + FP features
 
-    ParsedModule fPublicModule;         // [Root] + Public features
-    ParsedModule fRuntimeEffectModule;  // [Public] + Runtime effect decls
+    ParsedModule fPublicModule;              // [Root] + Public features
+    ParsedModule fRuntimeEffectModule;       // [Public] + Runtime effect decls
+    ParsedModule fRuntimeColorFilterModule;  // [Public] + Runtime shader decls
+    ParsedModule fRuntimeShaderModule;       // [Public] + Runtime color filter decls
 
     // holds ModifiersPools belonging to the core includes for lifetime purposes
     std::vector<std::unique_ptr<ModifiersPool>> fModifiers;
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 149341c..645c594 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -293,7 +293,9 @@
                                         "'key' is only permitted within fragment processors");
         }
     }
-    if (this->programKind() == ProgramKind::kRuntimeEffect) {
+    if (this->programKind() == ProgramKind::kRuntimeEffect ||
+        this->programKind() == ProgramKind::kRuntimeColorFilter ||
+        this->programKind() == ProgramKind::kRuntimeShader) {
         if (modifiers.fFlags & Modifiers::kIn_Flag) {
             this->errorReporter().error(offset, "'in' variables not permitted in runtime effects");
         }
@@ -307,9 +309,9 @@
         this->errorReporter().error(offset, "'key' is not permitted on 'uniform' variables");
     }
     if (modifiers.fLayout.fMarker.fLength) {
-        if (this->programKind() != ProgramKind::kRuntimeEffect) {
-            this->errorReporter().error(offset,
-                                        "'marker' is only permitted in runtime effects");
+        if (this->programKind() != ProgramKind::kRuntimeEffect &&
+            this->programKind() != ProgramKind::kRuntimeShader) {
+            this->errorReporter().error(offset, "'marker' is only permitted in runtime shaders");
         }
         if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
             this->errorReporter().error(offset,
@@ -321,7 +323,9 @@
         }
     }
     if (modifiers.fLayout.fFlags & Layout::kSRGBUnpremul_Flag) {
-        if (this->programKind() != ProgramKind::kRuntimeEffect) {
+        if (this->programKind() != ProgramKind::kRuntimeEffect &&
+            this->programKind() != ProgramKind::kRuntimeColorFilter &&
+            this->programKind() != ProgramKind::kRuntimeShader) {
             this->errorReporter().error(offset,
                                         "'srgb_unpremul' is only permitted in runtime effects");
         }
@@ -341,8 +345,9 @@
         }
     }
     if (modifiers.fFlags & Modifiers::kVarying_Flag) {
-        if (this->programKind() != ProgramKind::kRuntimeEffect) {
-            this->errorReporter().error(offset, "'varying' is only permitted in runtime effects");
+        if (this->programKind() != ProgramKind::kRuntimeEffect &&
+            this->programKind() != ProgramKind::kRuntimeShader) {
+            this->errorReporter().error(offset, "'varying' is only permitted in runtime shaders");
         }
         if (!baseType->isFloat() &&
             !(baseType->isVector() && baseType->componentType().isFloat())) {
@@ -1061,6 +1066,8 @@
 
         Modifiers m = pd.fModifiers;
         if (isMain && (this->programKind() == ProgramKind::kRuntimeEffect ||
+                       this->programKind() == ProgramKind::kRuntimeColorFilter ||
+                       this->programKind() == ProgramKind::kRuntimeShader ||
                        this->programKind() == ProgramKind::kFragmentProcessor)) {
             // We verify that the signature is fully correct later. For now, if this is an .fp or
             // runtime effect of any flavor, a float2 param is supposed to be the coords, and
@@ -1095,11 +1102,6 @@
         switch (this->programKind()) {
             case ProgramKind::kRuntimeEffect: {
                 // Legacy/generic runtime effects take a wide variety of main() signatures.
-                // TODO(skbug.com/11813): When we have dedicated program kinds for runtime shader
-                // vs color filter, those will only accept the suitable versions. Also, be even
-                // more restrictive: shaders must take coords, and color filters must take input
-                // color, even if unused.
-
                 // (half4|float4) main(float2?, (half4|float4)?)
                 if (!typeIsValidForColor(*returnType)) {
                     this->errorReporter().error(f.fOffset,
@@ -1119,6 +1121,37 @@
                 }
                 break;
             }
+            case ProgramKind::kRuntimeColorFilter: {
+                // (half4|float4) main(half4|float4)
+                if (!typeIsValidForColor(*returnType)) {
+                    this->errorReporter().error(f.fOffset,
+                                                "'main' must return: 'vec4', 'float4', or 'half4'");
+                    return;
+                }
+                bool validParams = (parameters.size() == 1 && paramIsInputColor(0));
+                if (!validParams) {
+                    this->errorReporter().error(
+                            f.fOffset, "'main' parameter must be 'vec4', 'float4', or 'half4'");
+                    return;
+                }
+                break;
+            }
+            case ProgramKind::kRuntimeShader: {
+                // (half4|float4) main(float2)  -or-  (half4|float4) main(float2, half4|float4)
+                if (!typeIsValidForColor(*returnType)) {
+                    this->errorReporter().error(f.fOffset,
+                                                "'main' must return: 'vec4', 'float4', or 'half4'");
+                    return;
+                }
+                bool validParams =
+                        (parameters.size() == 1 && paramIsCoords(0)) ||
+                        (parameters.size() == 2 && paramIsCoords(0) && paramIsInputColor(1));
+                if (!validParams) {
+                    this->errorReporter().error(
+                            f.fOffset, "'main' parameters must be (float2, (vec4|float4|half4)?)");
+                }
+                break;
+            }
             case ProgramKind::kFragmentProcessor: {
                 if (*returnType != *fContext.fTypes.fHalf4) {
                     this->errorReporter().error(f.fOffset, ".fp 'main' must return 'half4'");
diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp
index d49b5b5..419a8ae 100644
--- a/src/sksl/SkSLMain.cpp
+++ b/src/sksl/SkSLMain.cpp
@@ -284,8 +284,13 @@
         kind = SkSL::ProgramKind::kFragmentProcessor;
     } else if (inputPath.endsWith(".rte")) {
         kind = SkSL::ProgramKind::kRuntimeEffect;
+    } else if (inputPath.endsWith(".rtcf")) {
+        kind = SkSL::ProgramKind::kRuntimeColorFilter;
+    } else if (inputPath.endsWith(".rts")) {
+        kind = SkSL::ProgramKind::kRuntimeShader;
     } else {
-        printf("input filename must end in '.vert', '.frag', '.geom', '.fp', '.rte', or '.sksl'\n");
+        printf("input filename must end in '.vert', '.frag', '.geom', '.fp', '.rte', '.rtcf', "
+               "'.rts', or '.sksl'\n");
         return ResultCode::kInputError;
     }
 
diff --git a/src/sksl/SkSLProgramSettings.h b/src/sksl/SkSLProgramSettings.h
index 66260a1..bc4aad5 100644
--- a/src/sksl/SkSLProgramSettings.h
+++ b/src/sksl/SkSLProgramSettings.h
@@ -74,7 +74,10 @@
     ProgramSettings fSettings;
 
     bool strictES2Mode() const {
-        return fKind == ProgramKind::kRuntimeEffect || fKind == ProgramKind::kGeneric;
+        return fKind == ProgramKind::kRuntimeEffect ||
+               fKind == ProgramKind::kRuntimeColorFilter ||
+               fKind == ProgramKind::kRuntimeShader ||
+               fKind == ProgramKind::kGeneric;
     }
 };
 
diff --git a/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
new file mode 100644
index 0000000..5459f25
--- /dev/null
+++ b/src/sksl/generated/sksl_rt_colorfilter.dehydrated.sksl
@@ -0,0 +1,21 @@
+static uint8_t SKSL_INCLUDE_sksl_rt_colorfilter[] = {36,0,
+1,115,
+6,115,104,97,100,101,114,
+6,99,111,111,114,100,115,
+6,102,108,111,97,116,50,
+6,115,97,109,112,108,101,
+5,104,97,108,102,52,
+49,3,0,
+53,1,0,
+16,2,0,
+50,2,0,4,0,3,
+53,3,0,
+16,11,0,
+50,4,0,18,0,3,
+30,5,0,
+16,25,0,2,1,0,3,0,
+50,6,0,32,0,1,0,
+2,0,
+19,
+20,};
+static constexpr size_t SKSL_INCLUDE_sksl_rt_colorfilter_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_colorfilter);
diff --git a/src/sksl/generated/sksl_rt_shader.dehydrated.sksl b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
new file mode 100644
index 0000000..18a60ff
--- /dev/null
+++ b/src/sksl/generated/sksl_rt_shader.dehydrated.sksl
@@ -0,0 +1,57 @@
+static uint8_t SKSL_INCLUDE_sksl_rt_shader[] = {76,0,
+0,
+12,115,107,95,70,114,97,103,67,111,111,114,100,
+6,102,108,111,97,116,52,
+1,115,
+6,115,104,97,100,101,114,
+6,115,97,109,112,108,101,
+5,104,97,108,102,52,
+9,116,114,97,110,115,102,111,114,109,
+8,102,108,111,97,116,51,120,51,
+6,99,111,111,114,100,115,
+6,102,108,111,97,116,50,
+49,11,0,
+53,1,0,
+37,
+36,0,32,0,0,255,255,255,255,255,15,0,255,255,255,255,2,0,2,0,0,0,3,0,
+50,2,0,16,0,0,
+53,3,0,
+16,23,0,
+50,4,0,25,0,3,
+30,5,0,
+16,32,0,1,3,0,
+50,6,0,39,0,
+53,7,0,
+16,23,0,
+47,4,0,3,
+53,8,0,
+16,45,0,
+50,9,0,55,0,3,
+52,10,0,2,
+47,5,0,
+30,11,0,
+16,32,0,2,7,0,8,0,
+47,6,0,
+47,11,0,
+53,12,0,
+16,23,0,
+47,4,0,3,
+53,13,0,
+16,64,0,
+50,14,0,71,0,3,
+52,15,0,3,
+47,5,0,
+47,11,0,
+30,16,0,
+16,32,0,2,12,0,13,0,
+47,6,0,
+47,16,0,2,0,
+9,0,
+0,0,
+19,
+55,
+54,1,0,
+47,2,0,0,
+57,
+20,};
+static constexpr size_t SKSL_INCLUDE_sksl_rt_shader_LENGTH = sizeof(SKSL_INCLUDE_sksl_rt_shader);
diff --git a/src/sksl/sksl_rt_colorfilter.sksl b/src/sksl/sksl_rt_colorfilter.sksl
new file mode 100644
index 0000000..1f6183a
--- /dev/null
+++ b/src/sksl/sksl_rt_colorfilter.sksl
@@ -0,0 +1,5 @@
+half4 sample(shader s, float2 coords);
+
+// TODO(skbug.com/11813): Implement sample() that takes a color
+// half4 sample(colorFilter f, half4 color);
+// half4 sample(shader s, half4 color, float2 coords);
diff --git a/src/sksl/sksl_rt_shader.sksl b/src/sksl/sksl_rt_shader.sksl
new file mode 100644
index 0000000..b386b30
--- /dev/null
+++ b/src/sksl/sksl_rt_shader.sksl
@@ -0,0 +1,11 @@
+layout(builtin=15) float4 sk_FragCoord;
+
+half4 sample(shader s);
+half4 sample(shader s, float3x3 transform);
+half4 sample(shader s, float2 coords);
+
+// TODO(skbug.com/11813): Implement sample() that takes a color
+// half4 sample(colorFilter f, half4 color);
+// half4 sample(shader s, half4 color);
+// half4 sample(shader s, half4 color, float3x3 transform);
+// half4 sample(shader s, half4 color, float2 coords);