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);