Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkSLFunctionDeclaration.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 Google LLC.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
10#include "include/core/SkSpan.h"
22#include "src/sksl/SkSLString.h"
30
31#include <cstddef>
32#include <utility>
33
34using namespace skia_private;
35
36namespace SkSL {
37
38static bool check_modifiers(const Context& context, Position pos, ModifierFlags modifierFlags) {
39 const ModifierFlags permitted = ModifierFlag::kInline |
45 modifierFlags.checkPermittedFlags(context, pos, permitted);
46 if (modifierFlags.isInline() && modifierFlags.isNoInline()) {
47 context.fErrors->error(pos, "functions cannot be both 'inline' and 'noinline'");
48 return false;
49 }
50 return true;
51}
52
53static bool check_return_type(const Context& context, Position pos, const Type& returnType) {
54 ErrorReporter& errors = *context.fErrors;
55 if (returnType.isArray()) {
56 errors.error(pos, "functions may not return type '" + returnType.displayName() + "'");
57 return false;
58 }
59 if (context.fConfig->strictES2Mode() && returnType.isOrContainsArray()) {
60 errors.error(pos, "functions may not return structs containing arrays");
61 return false;
62 }
63 if (!context.fConfig->fIsBuiltinCode && returnType.componentType().isOpaque()) {
64 errors.error(pos, "functions may not return opaque type '" + returnType.displayName() +
65 "'");
66 return false;
67 }
68 return true;
69}
70
71static bool check_parameters(const Context& context,
72 TArray<std::unique_ptr<Variable>>& parameters,
73 ModifierFlags modifierFlags,
74 IntrinsicKind intrinsicKind) {
75 // Check modifiers on each function parameter.
76 for (auto& param : parameters) {
77 const Type& type = param->type();
79 LayoutFlags permittedLayoutFlags = LayoutFlag::kNone;
80 if (!type.isOpaque()) {
81 permittedFlags |= ModifierFlag::kOut;
82 }
83 if (type.isStorageTexture()) {
84 // We allow `readonly`, `writeonly` and `layout(pixel-format)` on storage textures.
86 permittedLayoutFlags |= LayoutFlag::kAllPixelFormats;
87
88 // Intrinsics are allowed to accept any pixel format, but user code must explicitly
89 // specify a pixel format like `layout(rgba32f)`.
90 if (intrinsicKind == kNotIntrinsic &&
91 !(param->layout().fFlags & LayoutFlag::kAllPixelFormats)) {
92 context.fErrors->error(param->fPosition, "storage texture parameters must specify "
93 "a pixel format layout-qualifier");
94 return false;
95 }
96 }
97 param->modifierFlags().checkPermittedFlags(context, param->modifiersPosition(),
98 permittedFlags);
99 param->layout().checkPermittedLayout(context, param->modifiersPosition(),
100 permittedLayoutFlags);
101 // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP
102 // parameters. You can pass other opaque types to functions safely; this restriction is
103 // specific to "child" objects.
104 if (type.isEffectChild() && !context.fConfig->fIsBuiltinCode) {
105 context.fErrors->error(param->fPosition, "parameters of type '" + type.displayName() +
106 "' not allowed");
107 return false;
108 }
109
110 // Pure functions should not change any state, and should be safe to eliminate if their
111 // result is not used; this is incompatible with out-parameters, so we forbid it here.
112 // (We don't exhaustively guard against pure functions changing global state in other ways,
113 // though, since they aren't allowed in user code.)
114 if (modifierFlags.isPure() && (param->modifierFlags() & ModifierFlag::kOut)) {
115 context.fErrors->error(param->modifiersPosition(),
116 "pure functions cannot have out parameters");
117 return false;
118 }
119 }
120 return true;
121}
122
123static bool type_is_valid_for_color(const Type& type) {
124 return type.isVector() && type.columns() == 4 && type.componentType().isFloat();
125}
126
127static bool type_is_valid_for_coords(const Type& type) {
128 return type.isVector() && type.highPrecision() && type.columns() == 2 &&
129 type.componentType().isFloat();
130}
131
132static bool check_main_signature(const Context& context, Position pos, const Type& returnType,
133 TArray<std::unique_ptr<Variable>>& parameters) {
134 ErrorReporter& errors = *context.fErrors;
135 ProgramKind kind = context.fConfig->fKind;
136
137 auto typeIsValidForAttributes = [](const Type& type) {
138 return type.isStruct() && type.name() == "Attributes";
139 };
140
141 auto typeIsValidForVaryings = [](const Type& type) {
142 return type.isStruct() && type.name() == "Varyings";
143 };
144
145 auto paramIsCoords = [&](int idx) {
146 const Variable& p = *parameters[idx];
147 return type_is_valid_for_coords(p.type()) && p.modifierFlags() == ModifierFlag::kNone;
148 };
149
150 auto paramIsColor = [&](int idx) {
151 const Variable& p = *parameters[idx];
152 return type_is_valid_for_color(p.type()) && p.modifierFlags() == ModifierFlag::kNone;
153 };
154
155 auto paramIsConstInAttributes = [&](int idx) {
156 const Variable& p = *parameters[idx];
157 return typeIsValidForAttributes(p.type()) && p.modifierFlags() == ModifierFlag::kConst;
158 };
159
160 auto paramIsConstInVaryings = [&](int idx) {
161 const Variable& p = *parameters[idx];
162 return typeIsValidForVaryings(p.type()) && p.modifierFlags() == ModifierFlag::kConst;
163 };
164
165 auto paramIsOutColor = [&](int idx) {
166 const Variable& p = *parameters[idx];
167 return type_is_valid_for_color(p.type()) && p.modifierFlags() == ModifierFlag::kOut;
168 };
169
170 switch (kind) {
173 // (half4|float4) main(half4|float4)
174 if (!type_is_valid_for_color(returnType)) {
175 errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
176 return false;
177 }
178 bool validParams = (parameters.size() == 1 && paramIsColor(0));
179 if (!validParams) {
180 errors.error(pos, "'main' parameter must be 'vec4', 'float4', or 'half4'");
181 return false;
182 }
183 break;
184 }
187 // (half4|float4) main(float2)
188 if (!type_is_valid_for_color(returnType)) {
189 errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
190 return false;
191 }
192 if (!(parameters.size() == 1 && paramIsCoords(0))) {
193 errors.error(pos, "'main' parameter must be 'float2' or 'vec2'");
194 return false;
195 }
196 break;
197 }
200 // (half4|float4) main(half4|float4, half4|float4)
201 if (!type_is_valid_for_color(returnType)) {
202 errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'");
203 return false;
204 }
205 if (!(parameters.size() == 2 && paramIsColor(0) && paramIsColor(1))) {
206 errors.error(pos, "'main' parameters must be (vec4|float4|half4, "
207 "vec4|float4|half4)");
208 return false;
209 }
210 break;
211 }
213 // Varyings main(const Attributes)
214 if (!typeIsValidForVaryings(returnType)) {
215 errors.error(pos, "'main' must return 'Varyings'.");
216 return false;
217 }
218 if (!(parameters.size() == 1 && paramIsConstInAttributes(0))) {
219 errors.error(pos, "'main' parameter must be 'const Attributes'.");
220 return false;
221 }
222 break;
223 }
225 // float2 main(const Varyings) -or- float2 main(const Varyings, out half4|float4)
226 if (!type_is_valid_for_coords(returnType)) {
227 errors.error(pos, "'main' must return: 'vec2' or 'float2'");
228 return false;
229 }
230 if (!((parameters.size() == 1 && paramIsConstInVaryings(0)) ||
231 (parameters.size() == 2 && paramIsConstInVaryings(0) && paramIsOutColor(1)))) {
232 errors.error(pos,
233 "'main' parameters must be (const Varyings, (out (half4|float4))?)");
234 return false;
235 }
236 break;
237 }
241 bool validParams = (parameters.size() == 0) ||
242 (parameters.size() == 1 && paramIsCoords(0));
243 if (!validParams) {
244 errors.error(pos, "shader 'main' must be main() or main(float2)");
245 return false;
246 }
247 break;
248 }
253 if (!returnType.matches(*context.fTypes.fVoid)) {
254 errors.error(pos, "'main' must return 'void'");
255 return false;
256 }
257 if (parameters.size()) {
258 errors.error(pos, "shader 'main' must have zero parameters");
259 return false;
260 }
261 break;
262 }
263 return true;
264}
265
266/**
267 * Given a concrete type (`float3`) and a generic type (`$genType`), returns the index of the
268 * concrete type within the generic type's typelist. Returns -1 if there is no match.
269 */
270static int find_generic_index(const Type& concreteType,
271 const Type& genericType,
272 bool allowNarrowing) {
273 SkSpan<const Type* const> genericTypes = genericType.coercibleTypes();
274 for (size_t index = 0; index < genericTypes.size(); ++index) {
275 if (concreteType.canCoerceTo(*genericTypes[index], allowNarrowing)) {
276 return index;
277 }
278 }
279 return -1;
280}
281
282/** Returns true if the types match, or if `concreteType` can be found in `maybeGenericType`. */
283static bool type_generically_matches(const Type& concreteType, const Type& maybeGenericType) {
284 return maybeGenericType.isGeneric()
285 ? find_generic_index(concreteType, maybeGenericType, /*allowNarrowing=*/false) != -1
286 : concreteType.matches(maybeGenericType);
287}
288
289/**
290 * Checks a parameter list (params) against the parameters of a function that was declared earlier
291 * (otherParams). Returns true if they match, even if the parameters in `otherParams` contain
292 * generic types.
293 */
294static bool parameters_match(SkSpan<const std::unique_ptr<Variable>> params,
295 SkSpan<Variable* const> otherParams) {
296 // If the param lists are different lengths, they're definitely not a match.
297 if (params.size() != otherParams.size()) {
298 return false;
299 }
300
301 // Figure out a consistent generic index (or bail if we find a contradiction).
302 int genericIndex = -1;
303 for (size_t i = 0; i < params.size(); ++i) {
304 const Type* paramType = &params[i]->type();
305 const Type* otherParamType = &otherParams[i]->type();
306
307 if (otherParamType->isGeneric()) {
308 int genericIndexForThisParam = find_generic_index(*paramType, *otherParamType,
309 /*allowNarrowing=*/false);
310 if (genericIndexForThisParam == -1) {
311 // The type wasn't a match for this generic at all; these params can't be a match.
312 return false;
313 }
314 if (genericIndex != -1 && genericIndex != genericIndexForThisParam) {
315 // The generic index mismatches from what we determined on a previous parameter.
316 return false;
317 }
318 genericIndex = genericIndexForThisParam;
319 }
320 }
321
322 // Now that we've determined a generic index (if we needed one), do a parameter check.
323 for (size_t i = 0; i < params.size(); i++) {
324 const Type* paramType = &params[i]->type();
325 const Type* otherParamType = &otherParams[i]->type();
326
327 // Make generic types concrete.
328 if (otherParamType->isGeneric()) {
329 SkASSERT(genericIndex != -1);
330 SkASSERT(genericIndex < (int)otherParamType->coercibleTypes().size());
331 otherParamType = otherParamType->coercibleTypes()[genericIndex];
332 }
333 // Detect type mismatches.
334 if (!paramType->matches(*otherParamType)) {
335 return false;
336 }
337 }
338 return true;
339}
340
341/**
342 * Checks for a previously existing declaration of this function, reporting errors if there is an
343 * incompatible symbol. Returns true and sets outExistingDecl to point to the existing declaration
344 * (or null if none) on success, returns false on error.
345 */
346static bool find_existing_declaration(const Context& context,
348 ModifierFlags modifierFlags,
349 IntrinsicKind intrinsicKind,
350 std::string_view name,
351 TArray<std::unique_ptr<Variable>>& parameters,
352 Position returnTypePos,
353 const Type* returnType,
354 FunctionDeclaration** outExistingDecl) {
355 auto invalidDeclDescription = [&]() -> std::string {
356 TArray<Variable*> paramPtrs;
357 paramPtrs.reserve_exact(parameters.size());
358 for (std::unique_ptr<Variable>& param : parameters) {
359 paramPtrs.push_back(param.get());
360 }
361 return FunctionDeclaration(context,
362 pos,
363 modifierFlags,
364 name,
365 std::move(paramPtrs),
366 returnType,
367 intrinsicKind)
368 .description();
369 };
370
371 ErrorReporter& errors = *context.fErrors;
372 Symbol* entry = context.fSymbolTable->findMutable(name);
373 *outExistingDecl = nullptr;
374 if (entry) {
375 if (!entry->is<FunctionDeclaration>()) {
376 errors.error(pos, "symbol '" + std::string(name) + "' was already defined");
377 return false;
378 }
379 for (FunctionDeclaration* other = &entry->as<FunctionDeclaration>(); other;
380 other = other->mutableNextOverload()) {
381 SkASSERT(name == other->name());
382 if (!parameters_match(parameters, other->parameters())) {
383 continue;
384 }
385 if (!type_generically_matches(*returnType, other->returnType())) {
386 errors.error(returnTypePos, "functions '" + invalidDeclDescription() + "' and '" +
387 other->description() + "' differ only in return type");
388 return false;
389 }
390 for (int i = 0; i < parameters.size(); i++) {
391 if (parameters[i]->modifierFlags() != other->parameters()[i]->modifierFlags() ||
392 parameters[i]->layout() != other->parameters()[i]->layout()) {
393 errors.error(parameters[i]->fPosition,
394 "modifiers on parameter " + std::to_string(i + 1) +
395 " differ between declaration and definition");
396 return false;
397 }
398 }
399 if (other->definition() || other->isIntrinsic() ||
400 modifierFlags != other->modifierFlags()) {
401 errors.error(pos, "duplicate definition of '" + invalidDeclDescription() + "'");
402 return false;
403 }
404 *outExistingDecl = other;
405 break;
406 }
407 if (!*outExistingDecl && entry->as<FunctionDeclaration>().isMain()) {
408 errors.error(pos, "duplicate definition of 'main'");
409 return false;
410 }
411 }
412 return true;
413}
414
417 ModifierFlags modifierFlags,
418 std::string_view name,
419 TArray<Variable*> parameters,
420 const Type* returnType,
421 IntrinsicKind intrinsicKind)
422 : INHERITED(pos, kIRNodeKind, name, /*type=*/nullptr)
423 , fDefinition(nullptr)
424 , fParameters(std::move(parameters))
425 , fReturnType(returnType)
426 , fModifierFlags(modifierFlags)
427 , fIntrinsicKind(intrinsicKind)
428 , fBuiltin(context.fConfig->fIsBuiltinCode)
429 , fIsMain(name == "main") {
430 int builtinColorIndex = 0;
431 for (const Variable* param : fParameters) {
432 // None of the parameters are allowed to be be null.
433 SkASSERT(param);
434
435 // Keep track of arguments to main for runtime effects.
436 if (fIsMain) {
439 // If this is a runtime shader, a float2 param is supposed to be the coords.
440 // For testing purposes, we have .sksl inputs that are treated as both runtime
441 // effects and fragment shaders. To make that work, fragment shaders are allowed to
442 // have a coords parameter as well.
443 if (type_is_valid_for_coords(param->type())) {
444 fHasMainCoordsParameter = true;
445 }
448 // If this is a runtime color filter or blender, the params are an input color,
449 // followed by a destination color for blenders.
450 if (type_is_valid_for_color(param->type())) {
451 switch (builtinColorIndex++) {
452 case 0: fHasMainInputColorParameter = true; break;
453 case 1: fHasMainDestColorParameter = true; break;
454 default: /* unknown color parameter */ break;
455 }
456 }
457 }
458 }
459 }
460}
461
464 const Modifiers& modifiers,
465 std::string_view name,
466 TArray<std::unique_ptr<Variable>> parameters,
467 Position returnTypePos,
468 const Type* returnType) {
469 // No layout flag is permissible on a function.
470 modifiers.fLayout.checkPermittedLayout(context, pos,
471 /*permittedLayoutFlags=*/LayoutFlag::kNone);
472
473 // If requested, apply the `noinline` modifier to every function. This allows us to test Runtime
474 // Effects without any inlining, even when the code is later added to a paint.
476 if (context.fConfig->fSettings.fForceNoInline) {
479 }
480
481 bool isMain = (name == "main");
484 FunctionDeclaration* decl = nullptr;
485 if (!check_modifiers(context, modifiers.fPosition, modifierFlags) ||
486 !check_return_type(context, returnTypePos, *returnType) ||
490 returnTypePos, returnType, &decl)) {
491 return nullptr;
492 }
493 TArray<Variable*> finalParameters;
494 finalParameters.reserve_exact(parameters.size());
495 for (std::unique_ptr<Variable>& param : parameters) {
496 finalParameters.push_back(context.fSymbolTable->takeOwnershipOfSymbol(std::move(param)));
497 }
498 if (decl) {
499 return decl;
500 }
501 return context.fSymbolTable->add(
502 context,
503 std::make_unique<FunctionDeclaration>(context,
504 pos,
506 name,
507 std::move(finalParameters),
510}
511
513 if ((this->isBuiltin() && !this->definition()) || this->isMain()) {
514 // Builtins without a definition (like `sin` or `sqrt`) must use their real names.
515 return std::string(this->name());
516 }
517 // Built-in functions can have a $ prefix, which will fail to compile in GLSL. Remove the
518 // $ and add a unique mangling specifier, so user code can't conflict with the name.
519 std::string_view name = this->name();
520 const char* builtinMarker = "";
521 if (skstd::starts_with(name, '$')) {
522 name.remove_prefix(1);
523 builtinMarker = "Q"; // a unique, otherwise-unused mangle character
524 }
525 // Rename function to `funcname_returntypeparamtypes`.
526 std::string result = std::string(name) + "_" + builtinMarker +
527 this->returnType().abbreviatedName();
528 for (const Variable* p : this->parameters()) {
529 result += p->type().abbreviatedName();
530 }
531 return result;
532}
533
535 std::string result = (fModifierFlags ? fModifierFlags.description() + " " : std::string()) +
536 this->returnType().displayName() + " " + std::string(this->name()) + "(";
537 auto separator = SkSL::String::Separator();
538 for (const Variable* p : this->parameters()) {
539 result += separator();
540 result += p->description();
541 }
542 result += ")";
543 return result;
544}
545
547 if (this->name() != f.name()) {
548 return false;
549 }
551 SkSpan<Variable* const> otherParameters = f.parameters();
552 if (parameters.size() != otherParameters.size()) {
553 return false;
554 }
555 for (size_t i = 0; i < parameters.size(); i++) {
556 if (!parameters[i]->type().matches(otherParameters[i]->type())) {
557 return false;
558 }
559 }
560 return true;
561}
562
564 ParamTypes* outParameterTypes,
565 const Type** outReturnType) const {
567 SkASSERT(SkToSizeT(arguments.size()) == parameters.size());
568
569 outParameterTypes->reserve_exact(arguments.size());
570 int genericIndex = -1;
571 for (int i = 0; i < arguments.size(); i++) {
572 // Non-generic parameters are final as-is.
573 const Type& parameterType = parameters[i]->type();
574 if (!parameterType.isGeneric()) {
575 outParameterTypes->push_back(&parameterType);
576 continue;
577 }
578 // We use the first generic parameter we find to lock in the generic index;
579 // e.g. if we find `float3` here, all `$genType`s will be assumed to be `float3`.
580 if (genericIndex == -1) {
581 genericIndex = find_generic_index(arguments[i]->type(), parameterType,
582 /*allowNarrowing=*/true);
583 if (genericIndex == -1) {
584 // The passed-in type wasn't a match for ANY of the generic possibilities.
585 // This function isn't a match at all.
586 return false;
587 }
588 }
589 outParameterTypes->push_back(parameterType.coercibleTypes()[genericIndex]);
590 }
591 // Apply the generic index to our return type.
592 const Type& returnType = this->returnType();
593 if (returnType.isGeneric()) {
594 if (genericIndex == -1) {
595 // We don't support functions with a generic return type and no other generics.
596 return false;
597 }
598 *outReturnType = returnType.coercibleTypes()[genericIndex];
599 } else {
600 *outReturnType = &returnType;
601 }
602 return true;
603}
604
605} // namespace SkSL
SkPoint pos
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr size_t SkToSizeT(S x)
Definition SkTo.h:31
const std::unique_ptr< Type > fVoid
const BuiltinTypes & fTypes
Definition SkSLContext.h:30
ErrorReporter * fErrors
Definition SkSLContext.h:36
SymbolTable * fSymbolTable
Definition SkSLContext.h:48
ProgramConfig * fConfig
Definition SkSLContext.h:33
void error(Position position, std::string_view msg)
bool matches(const FunctionDeclaration &f) const
bool determineFinalTypes(const ExpressionArray &arguments, ParamTypes *outParameterTypes, const Type **outReturnType) const
SkSpan< Variable *const > parameters() const
FunctionDeclaration(const Context &context, Position pos, ModifierFlags modifierFlags, std::string_view name, skia_private::TArray< Variable * > parameters, const Type *returnType, IntrinsicKind intrinsicKind)
std::string description() const override
const FunctionDefinition * definition() const
ModifierFlags modifierFlags() const
IntrinsicKind intrinsicKind() const
static FunctionDeclaration * Convert(const Context &context, Position pos, const Modifiers &modifiers, std::string_view name, skia_private::TArray< std::unique_ptr< Variable > > parameters, Position returnTypePos, const Type *returnType)
bool is() const
Definition SkSLIRNode.h:124
const T & as() const
Definition SkSLIRNode.h:133
std::string description() const
bool checkPermittedFlags(const Context &context, Position pos, ModifierFlags permittedModifierFlags) const
T * takeOwnershipOfSymbol(std::unique_ptr< T > symbol)
T * add(const Context &context, std::unique_ptr< T > symbol)
Symbol * findMutable(std::string_view name) const
std::string_view name() const
Definition SkSLSymbol.h:51
const Type & type() const
Definition SkSLSymbol.h:42
virtual bool isArray() const
Definition SkSLType.h:532
virtual const Type & componentType() const
Definition SkSLType.h:404
virtual bool isOrContainsArray() const
Definition SkSLType.h:578
bool isOpaque() const
Definition SkSLType.h:353
const char * abbreviatedName() const
Definition SkSLType.h:276
bool matches(const Type &other) const
Definition SkSLType.h:269
bool isGeneric() const
Definition SkSLType.h:500
virtual SkSpan< const Type *const > coercibleTypes() const
Definition SkSLType.h:476
bool canCoerceTo(const Type &other, bool allowNarrowing) const
Definition SkSLType.h:388
std::string displayName() const
Definition SkSLType.h:234
constexpr size_t size() const
Definition SkSpan_impl.h:95
int size() const
Definition SkTArray.h:416
void reserve_exact(int n)
Definition SkTArray.h:176
const EmbeddedViewParams * params
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
std::string void void auto Separator()
Definition SkSLString.h:30
static bool type_is_valid_for_coords(const Type &type)
static bool find_existing_declaration(const Context &context, Position pos, ModifierFlags modifierFlags, IntrinsicKind intrinsicKind, std::string_view name, TArray< std::unique_ptr< Variable > > &parameters, Position returnTypePos, const Type *returnType, FunctionDeclaration **outExistingDecl)
static bool type_generically_matches(const Type &concreteType, const Type &maybeGenericType)
static bool check_modifiers(const Context &context, Position pos, ModifierFlags modifierFlags)
static bool type_is_valid_for_color(const Type &type)
static bool check_main_signature(const Context &context, Position pos, const Type &returnType, TArray< std::unique_ptr< Variable > > &parameters)
static bool parameters_match(SkSpan< const std::unique_ptr< Variable > > params, SkSpan< Variable *const > otherParams)
IntrinsicKind FindIntrinsicKind(std::string_view functionName)
static bool check_return_type(const Context &context, Position pos, const Type &returnType)
static int find_generic_index(const Type &concreteType, const Type &genericType, bool allowNarrowing)
static bool check_parameters(const Context &context, TArray< std::unique_ptr< Variable > > &parameters, ModifierFlags modifierFlags, IntrinsicKind intrinsicKind)
constexpr bool starts_with(std::string_view str, std::string_view prefix)
Definition ref_ptr.h:256
bool checkPermittedLayout(const Context &context, Position pos, LayoutFlags permittedLayoutFlags) const
SkSL::Layout fLayout
SkSL::ModifierFlags fFlags
static bool IsRuntimeShader(ProgramKind kind)
ProgramSettings fSettings
static bool IsFragment(ProgramKind kind)
static bool IsRuntimeBlender(ProgramKind kind)
static bool IsRuntimeColorFilter(ProgramKind kind)