Refactored SkSL function creation and error handling
This breaks up the giant IRGenerator::convertFunction method into more-
manageable chunks, moves the functionality into FunctionDeclaration,
and funnels the DSL through it so it receives the same error checking.
Change-Id: Icf2ac650ab3d5276d8c0134062a4e7e220f9bf32
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/402778
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
diff --git a/gn/sksl.gni b/gn/sksl.gni
index ad9a00d..266e8ff 100644
--- a/gn/sksl.gni
+++ b/gn/sksl.gni
@@ -132,6 +132,7 @@
"$_src/sksl/ir/SkSLForStatement.h",
"$_src/sksl/ir/SkSLFunctionCall.cpp",
"$_src/sksl/ir/SkSLFunctionCall.h",
+ "$_src/sksl/ir/SkSLFunctionDeclaration.cpp",
"$_src/sksl/ir/SkSLFunctionDeclaration.h",
"$_src/sksl/ir/SkSLFunctionDefinition.h",
"$_src/sksl/ir/SkSLFunctionPrototype.h",
diff --git a/include/sksl/DSLFunction.h b/include/sksl/DSLFunction.h
index 60f14e4..5aab49f 100644
--- a/include/sksl/DSLFunction.h
+++ b/include/sksl/DSLFunction.h
@@ -76,8 +76,8 @@
DSLExpression call(SkTArray<DSLExpression> args);
- const SkSL::Type* fReturnType;
- const SkSL::FunctionDeclaration* fDecl;
+ const SkSL::Type* fReturnType = nullptr;
+ const SkSL::FunctionDeclaration* fDecl = nullptr;
};
} // namespace dsl
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 67c9321..ffe4a5c 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -334,7 +334,7 @@
Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag;
}
// TODO(skbug.com/11301): Migrate above checks into building a mask of permitted layout flags
- this->checkModifiers(offset, modifiers, permitted, /*permittedLayoutFlags=*/~0);
+ CheckModifiers(fContext, offset, modifiers, permitted, /*permittedLayoutFlags=*/~0);
}
std::unique_ptr<Variable> IRGenerator::convertVar(int offset, const Modifiers& modifiers,
@@ -795,15 +795,17 @@
return ExpressionStatement::Make(fContext, std::move(result));
}
-void IRGenerator::checkModifiers(int offset,
+void IRGenerator::CheckModifiers(const Context& context,
+ int offset,
const Modifiers& modifiers,
int permittedModifierFlags,
int permittedLayoutFlags) {
+ ErrorReporter& errorReporter = context.fErrors;
int flags = modifiers.fFlags;
auto checkModifier = [&](Modifiers::Flag flag, const char* name) {
if (flags & flag) {
if (!(permittedModifierFlags & flag)) {
- this->errorReporter().error(offset, "'" + String(name) + "' is not permitted here");
+ errorReporter.error(offset, "'" + String(name) + "' is not permitted here");
}
flags &= ~flag;
}
@@ -824,8 +826,8 @@
auto checkLayout = [&](Layout::Flag flag, const char* name) {
if (layoutFlags & flag) {
if (!(permittedLayoutFlags & flag)) {
- this->errorReporter().error(
- offset, "layout qualifier '" + String(name) + "' is not permitted here");
+ errorReporter.error(offset, "layout qualifier '" + String(name) +
+ "' is not permitted here");
}
layoutFlags &= ~flag;
}
@@ -969,49 +971,15 @@
if (returnType == nullptr) {
return;
}
- if (returnType->isArray()) {
- this->errorReporter().error(
- f.fOffset, "functions may not return type '" + returnType->displayName() + "'");
- return;
- }
- if (this->strictES2Mode() && returnType->isOrContainsArray()) {
- this->errorReporter().error(f.fOffset,
- "functions may not return structs containing arrays");
- return;
- }
- if (!fIsBuiltinCode && !returnType->isVoid() && returnType->componentType().isOpaque()) {
- this->errorReporter().error(
- f.fOffset,
- "functions may not return opaque type '" + returnType->displayName() + "'");
- return;
- }
const ASTNode::FunctionData& funcData = f.getFunctionData();
bool isMain = (funcData.fName == "main");
- // Check function modifiers.
- this->checkModifiers(
- f.fOffset,
- funcData.fModifiers,
- Modifiers::kHasSideEffects_Flag | Modifiers::kInline_Flag | Modifiers::kNoInline_Flag,
- /*permittedLayoutFlags=*/0);
- if ((funcData.fModifiers.fFlags & Modifiers::kInline_Flag) &&
- (funcData.fModifiers.fFlags & Modifiers::kNoInline_Flag)) {
- this->errorReporter().error(f.fOffset, "functions cannot be both 'inline' and 'noinline'");
- }
-
- auto typeIsValidForColor = [&](const Type& type) {
- return type == *fContext.fTypes.fHalf4 || type == *fContext.fTypes.fFloat4;
- };
-
- // Check modifiers on each function parameter.
- std::vector<const Variable*> parameters;
+ std::vector<std::unique_ptr<Variable>> parameters;
+ parameters.reserve(funcData.fParameterCount);
for (size_t i = 0; i < funcData.fParameterCount; ++i) {
const ASTNode& param = *(iter++);
SkASSERT(param.fKind == ASTNode::Kind::kParameter);
const ASTNode::ParameterData& pd = param.getParameterData();
- this->checkModifiers(param.fOffset, pd.fModifiers,
- Modifiers::kConst_Flag | Modifiers::kIn_Flag | Modifiers::kOut_Flag,
- /*permittedLayoutFlags=*/0);
auto paramIter = param.begin();
const Type* type = this->convertType(*(paramIter++));
if (!type) {
@@ -1024,211 +992,40 @@
}
type = fSymbolTable->addArrayDimension(type, arraySize);
}
- // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP
- // parameters. You can pass other opaque types to functions safely; this restriction is
- // specific to "child" objects.
- if ((type->isEffectChild() || type->isFragmentProcessor()) && !fIsBuiltinCode) {
- this->errorReporter().error(
- param.fOffset, "parameters of type '" + type->displayName() + "' not allowed");
- return;
- }
- Modifiers m = pd.fModifiers;
- if (isMain && (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
- // a half4/float parameter is supposed to be the input color:
- if (*type == *fContext.fTypes.fFloat2) {
- m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
- } else if(typeIsValidForColor(*type)) {
- m.fLayout.fBuiltin = SK_INPUT_COLOR_BUILTIN;
- }
- }
- if (isMain && (this->programKind() == ProgramKind::kFragment)) {
- // For testing purposes, we have .sksl inputs that are treated as both runtime effects
- // and fragment shaders. To make that work, fragment shaders are allowed to have a
- // coords parameter. We turn it into sk_FragCoord.
- if (*type == *fContext.fTypes.fFloat2) {
- m.fLayout.fBuiltin = SK_FRAGCOORD_BUILTIN;
- }
- }
-
- const Variable* var = fSymbolTable->takeOwnershipOfSymbol(
- std::make_unique<Variable>(param.fOffset, this->modifiersPool().add(m), pd.fName,
- type, fIsBuiltinCode, Variable::Storage::kParameter));
- parameters.push_back(var);
+ parameters.push_back(std::make_unique<Variable>(param.fOffset,
+ this->modifiersPool().add(pd.fModifiers),
+ pd.fName,
+ type,
+ fIsBuiltinCode,
+ Variable::Storage::kParameter));
}
- auto paramIsCoords = [&](int idx) {
- const Variable* p = parameters[idx];
- return p->type() == *fContext.fTypes.fFloat2 &&
- p->modifiers().fFlags == 0 &&
- p->modifiers().fLayout.fBuiltin == (this->programKind() == ProgramKind::kFragment
- ? SK_FRAGCOORD_BUILTIN
- : SK_MAIN_COORDS_BUILTIN);
- };
-
- auto paramIsInputColor = [&](int idx) {
- return typeIsValidForColor(parameters[idx]->type()) &&
- parameters[idx]->modifiers().fFlags == 0 &&
- parameters[idx]->modifiers().fLayout.fBuiltin == SK_INPUT_COLOR_BUILTIN;
- };
-
- // Check the function signature of `main`.
- if (isMain) {
- switch (this->programKind()) {
- 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'");
- return;
- }
- bool validParams = (parameters.size() == 0) ||
- (parameters.size() == 1 && paramIsCoords(0));
- if (!validParams) {
- this->errorReporter().error(
- f.fOffset, ".fp 'main' must be declared main() or main(float2)");
- return;
- }
- break;
- }
- case ProgramKind::kGeneric:
- // No rules apply here
- break;
- case ProgramKind::kFragment: {
- bool validParams = (parameters.size() == 0) ||
- (parameters.size() == 1 && paramIsCoords(0));
- if (!validParams) {
- this->errorReporter().error(f.fOffset,
- "shader 'main' must be main() or main(float2)");
- return;
- }
- break;
- }
- case ProgramKind::kVertex:
- case ProgramKind::kGeometry:
- if (parameters.size()) {
- this->errorReporter().error(f.fOffset,
- "shader 'main' must have zero parameters");
- }
- break;
- }
+ // Conservatively assume all user-defined functions have side effects.
+ Modifiers declModifiers = funcData.fModifiers;
+ if (!fIsBuiltinCode) {
+ declModifiers.fFlags |= Modifiers::kHasSideEffects_Flag;
}
- // Find existing declarations and report conflicts.
- const FunctionDeclaration* decl = nullptr;
- const Symbol* entry = (*fSymbolTable)[funcData.fName];
- if (entry) {
- std::vector<const FunctionDeclaration*> functions;
- switch (entry->kind()) {
- case Symbol::Kind::kUnresolvedFunction:
- functions = entry->as<UnresolvedFunction>().functions();
- break;
- case Symbol::Kind::kFunctionDeclaration:
- functions.push_back(&entry->as<FunctionDeclaration>());
- break;
- default:
- this->errorReporter().error(f.fOffset,
- "symbol '" + funcData.fName + "' was already defined");
- return;
- }
- for (const FunctionDeclaration* other : functions) {
- SkASSERT(other->name() == funcData.fName);
- if (parameters.size() == other->parameters().size()) {
- bool match = true;
- for (size_t i = 0; i < parameters.size(); i++) {
- if (parameters[i]->type() != other->parameters()[i]->type()) {
- match = false;
- break;
- }
- }
- if (match) {
- if (*returnType != other->returnType()) {
- FunctionDeclaration newDecl(f.fOffset,
- this->modifiersPool().add(funcData.fModifiers),
- funcData.fName,
- parameters,
- returnType,
- fIsBuiltinCode);
- this->errorReporter().error(
- f.fOffset, "functions '" + newDecl.description() + "' and '" +
- other->description() + "' differ only in return type");
- return;
- }
- decl = other;
- for (size_t i = 0; i < parameters.size(); i++) {
- if (parameters[i]->modifiers() != other->parameters()[i]->modifiers()) {
- this->errorReporter().error(
- f.fOffset,
- "modifiers on parameter " + to_string((uint64_t)i + 1) +
- " differ between declaration and definition");
- return;
- }
- }
- if (other->definition() && !other->isBuiltin()) {
- this->errorReporter().error(
- f.fOffset, "duplicate definition of " + other->description());
- return;
- }
- break;
- }
- }
- }
+ if (fContext.fConfig->fSettings.fForceNoInline) {
+ // Apply the `noinline` modifier to every function. This allows us to test Runtime
+ // Effects without any inlining, even when the code is later added to a paint.
+ declModifiers.fFlags &= ~Modifiers::kInline_Flag;
+ declModifiers.fFlags |= Modifiers::kNoInline_Flag;
}
+
+ const FunctionDeclaration* decl = FunctionDeclaration::Convert(
+ fContext,
+ *fSymbolTable,
+ *fModifiers,
+ f.fOffset,
+ this->modifiersPool().add(declModifiers),
+ funcData.fName,
+ std::move(parameters),
+ returnType,
+ fIsBuiltinCode);
if (!decl) {
- // Conservatively assume all user-defined functions have side effects.
- Modifiers declModifiers = funcData.fModifiers;
- if (!fIsBuiltinCode) {
- declModifiers.fFlags |= Modifiers::kHasSideEffects_Flag;
- }
-
- if (fContext.fConfig->fSettings.fForceNoInline) {
- // Apply the `noinline` modifier to every function. This allows us to test Runtime
- // Effects without any inlining, even when the code is later added to a paint.
- declModifiers.fFlags &= ~Modifiers::kInline_Flag;
- declModifiers.fFlags |= Modifiers::kNoInline_Flag;
- }
-
- // Create a new declaration.
- decl = fSymbolTable->add(
- std::make_unique<FunctionDeclaration>(f.fOffset,
- this->modifiersPool().add(declModifiers),
- funcData.fName,
- parameters,
- returnType,
- fIsBuiltinCode));
+ return;
}
if (iter == f.end()) {
// If there's no body, we've found a prototype.
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 0691d00..b4ec32b 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -142,6 +142,12 @@
void pushSymbolTable();
void popSymbolTable();
+ static void CheckModifiers(const Context& context,
+ int offset,
+ const Modifiers& modifiers,
+ int permittedModifierFlags,
+ int permittedLayoutFlags);
+
const Context& fContext;
private:
@@ -158,10 +164,6 @@
*/
std::unique_ptr<ModifiersPool> releaseModifiers();
- void checkModifiers(int offset,
- const Modifiers& modifiers,
- int permittedModifierFlags,
- int permittedLayoutFlags);
void checkVarDeclaration(int offset,
const Modifiers& modifiers,
const Type* baseType,
diff --git a/src/sksl/dsl/DSLFunction.cpp b/src/sksl/dsl/DSLFunction.cpp
index 7e2971e..df74480 100644
--- a/src/sksl/dsl/DSLFunction.cpp
+++ b/src/sksl/dsl/DSLFunction.cpp
@@ -20,7 +20,7 @@
void DSLFunction::init(const DSLType& returnType, const char* name,
std::vector<DSLVar*> params) {
- std::vector<const Variable*> paramVars;
+ std::vector<std::unique_ptr<Variable>> paramVars;
paramVars.reserve(params.size());
bool isMain = !strcmp(name, "main");
auto typeIsValidForColor = [&](const SkSL::Type& type) {
@@ -59,20 +59,24 @@
}
}
}
- paramVars.push_back(&DSLWriter::Var(*param));
+ std::unique_ptr<SkSL::Variable> paramVar = DSLWriter::ParameterVar(*param);
+ SkASSERT(paramVar);
+ paramVars.push_back(std::move(paramVar));
param->fDeclaration = nullptr;
}
- SkSL::SymbolTable& symbols = *DSLWriter::SymbolTable();
- fDecl = symbols.add(std::make_unique<SkSL::FunctionDeclaration>(
- /*offset=*/-1,
- DSLWriter::Modifiers(SkSL::Modifiers()),
- isMain ? name : DSLWriter::Name(name),
- std::move(paramVars), fReturnType,
- /*builtin=*/false));
+ fDecl = SkSL::FunctionDeclaration::Convert(DSLWriter::Context(),
+ *DSLWriter::SymbolTable(),
+ *DSLWriter::IRGenerator().fModifiers, /*offset=*/-1,
+ DSLWriter::Modifiers(SkSL::Modifiers()),
+ isMain ? name : DSLWriter::Name(name),
+ std::move(paramVars), fReturnType,
+ /*isBuiltin=*/false);
}
void DSLFunction::define(DSLBlock block) {
- SkASSERT(fDecl);
+ if (!fDecl) {
+ return;
+ }
SkASSERTF(!fDecl->definition(), "function '%s' already defined", fDecl->description().c_str());
std::unique_ptr<Statement> body = block.release();
DSLWriter::IRGenerator().finalizeFunction(*fDecl, body.get());
diff --git a/src/sksl/dsl/priv/DSLWriter.cpp b/src/sksl/dsl/priv/DSLWriter.cpp
index d0de74c..16c0306 100644
--- a/src/sksl/dsl/priv/DSLWriter.cpp
+++ b/src/sksl/dsl/priv/DSLWriter.cpp
@@ -226,6 +226,22 @@
return *var.fVar;
}
+std::unique_ptr<SkSL::Variable> DSLWriter::ParameterVar(DSLVar& var) {
+ // This should only be called on undeclared parameter variables, but we allow the creation to go
+ // ahead regardless so we don't have to worry about null pointers potentially sneaking in and
+ // breaking things. DSLFunction is responsible for reporting errors for invalid parameters.
+ std::unique_ptr<SkSL::Variable> skslVar = DSLWriter::IRGenerator().convertVar(
+ /*offset=*/-1,
+ var.fModifiers.fModifiers,
+ &var.fType.skslType(),
+ var.fName,
+ /*isArray=*/false,
+ /*arraySize=*/nullptr,
+ var.fStorage);
+ var.fVar = skslVar.get();
+ return skslVar;
+}
+
std::unique_ptr<SkSL::Statement> DSLWriter::Declaration(DSLVar& var) {
Var(var);
return std::move(var.fDeclaration);
diff --git a/src/sksl/dsl/priv/DSLWriter.h b/src/sksl/dsl/priv/DSLWriter.h
index 3c379d0..cc5d7a8 100644
--- a/src/sksl/dsl/priv/DSLWriter.h
+++ b/src/sksl/dsl/priv/DSLWriter.h
@@ -86,11 +86,16 @@
static const SkSL::Modifiers* Modifiers(const SkSL::Modifiers& modifiers);
/**
- * Returns the SkSL variable corresponding to a DSLVar.
+ * Returns the SkSL variable corresponding to a (non-parameter) DSLVar.
*/
static const SkSL::Variable& Var(DSLVar& var);
/**
+ * Creates an SkSL variable corresponding to a parameter DSLVar.
+ */
+ static std::unique_ptr<SkSL::Variable> ParameterVar(DSLVar& var);
+
+ /**
* Returns the SkSL declaration corresponding to a DSLVar.
*/
static std::unique_ptr<SkSL::Statement> Declaration(DSLVar& var);
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.cpp b/src/sksl/ir/SkSLFunctionDeclaration.cpp
new file mode 100644
index 0000000..56c73cf
--- /dev/null
+++ b/src/sksl/ir/SkSLFunctionDeclaration.cpp
@@ -0,0 +1,298 @@
+/*
+ * 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/ir/SkSLFunctionDeclaration.h"
+
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLIRGenerator.h"
+#include "src/sksl/ir/SkSLUnresolvedFunction.h"
+
+namespace SkSL {
+
+static bool check_modifiers(const Context& context, int offset, const Modifiers& modifiers) {
+ IRGenerator::CheckModifiers(
+ context,
+ offset,
+ modifiers,
+ Modifiers::kHasSideEffects_Flag | Modifiers::kInline_Flag | Modifiers::kNoInline_Flag,
+ /*permittedLayoutFlags=*/0);
+ if ((modifiers.fFlags & Modifiers::kInline_Flag) &&
+ (modifiers.fFlags & Modifiers::kNoInline_Flag)) {
+ context.fErrors.error(offset, "functions cannot be both 'inline' and 'noinline'");
+ return false;
+ }
+ return true;
+}
+
+static bool check_return_type(const Context& context, int offset, const Type& returnType,
+ bool isBuiltin) {
+ ErrorReporter& errors = context.fErrors;
+ if (returnType.isArray()) {
+ errors.error(offset, "functions may not return type '" + returnType.displayName() + "'");
+ return false;
+ }
+ if (context.fConfig->strictES2Mode() && returnType.isOrContainsArray()) {
+ errors.error(offset, "functions may not return structs containing arrays");
+ return false;
+ }
+ if (!isBuiltin && !returnType.isVoid() && returnType.componentType().isOpaque()) {
+ errors.error(offset, "functions may not return opaque type '" + returnType.displayName() +
+ "'");
+ return false;
+ }
+ return true;
+}
+
+static bool check_parameters(const Context& context, ModifiersPool& modifiersPool,
+ std::vector<std::unique_ptr<Variable>>& parameters, bool isMain,
+ bool isBuiltin) {
+ auto typeIsValidForColor = [&](const Type& type) {
+ return type == *context.fTypes.fHalf4 || type == *context.fTypes.fFloat4;
+ };
+
+ // Check modifiers on each function parameter.
+ for (auto& param : parameters) {
+ IRGenerator::CheckModifiers(context, param->fOffset, param->modifiers(),
+ Modifiers::kConst_Flag | Modifiers::kIn_Flag |
+ Modifiers::kOut_Flag, /*permittedLayoutFlags=*/0);
+ const Type& type = param->type();
+ // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP
+ // parameters. You can pass other opaque types to functions safely; this restriction is
+ // specific to "child" objects.
+ if ((type.isEffectChild() || type.isFragmentProcessor()) && !isBuiltin) {
+ context.fErrors.error(param->fOffset, "parameters of type '" + type.displayName() +
+ "' not allowed");
+ return false;
+ }
+
+ Modifiers m = param->modifiers();
+ ProgramKind kind = context.fConfig->fKind;
+ if (isMain && (kind == ProgramKind::kRuntimeColorFilter ||
+ kind == ProgramKind::kRuntimeShader ||
+ kind == 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
+ // a half4/float parameter is supposed to be the input color:
+ if (type == *context.fTypes.fFloat2) {
+ m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
+ } else if(typeIsValidForColor(type)) {
+ m.fLayout.fBuiltin = SK_INPUT_COLOR_BUILTIN;
+ }
+ if (m.fLayout.fBuiltin) {
+ param->setModifiers(modifiersPool.add(m));
+ }
+ }
+ if (isMain && (kind == ProgramKind::kFragment)) {
+ // For testing purposes, we have .sksl inputs that are treated as both runtime effects
+ // and fragment shaders. To make that work, fragment shaders are allowed to have a
+ // coords parameter. We turn it into sk_FragCoord.
+ if (type == *context.fTypes.fFloat2) {
+ m.fLayout.fBuiltin = SK_FRAGCOORD_BUILTIN;
+ param->setModifiers(modifiersPool.add(m));
+ }
+ }
+ }
+ return true;
+}
+
+static bool check_main_signature(const Context& context, int offset, const Type& returnType,
+ std::vector<std::unique_ptr<Variable>>& parameters,
+ bool isBuiltin) {
+ ErrorReporter& errors = context.fErrors;
+ ProgramKind kind = context.fConfig->fKind;
+
+ auto typeIsValidForColor = [&](const Type& type) {
+ return type == *context.fTypes.fHalf4 || type == *context.fTypes.fFloat4;
+ };
+
+ auto paramIsCoords = [&](int idx) {
+ const Variable& p = *parameters[idx];
+ return p.type() == *context.fTypes.fFloat2 &&
+ p.modifiers().fFlags == 0 &&
+ p.modifiers().fLayout.fBuiltin == (kind == ProgramKind::kFragment
+ ? SK_FRAGCOORD_BUILTIN
+ : SK_MAIN_COORDS_BUILTIN);
+ };
+
+ auto paramIsInputColor = [&](int idx) {
+ return typeIsValidForColor(parameters[idx]->type()) &&
+ parameters[idx]->modifiers().fFlags == 0 &&
+ parameters[idx]->modifiers().fLayout.fBuiltin == SK_INPUT_COLOR_BUILTIN;
+ };
+
+ switch (kind) {
+ case ProgramKind::kRuntimeColorFilter: {
+ // (half4|float4) main(half4|float4)
+ if (!typeIsValidForColor(returnType)) {
+ errors.error(offset, "'main' must return: 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ bool validParams = (parameters.size() == 1 && paramIsInputColor(0));
+ if (!validParams) {
+ errors.error(offset, "'main' parameter must be 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kRuntimeShader: {
+ // (half4|float4) main(float2) -or- (half4|float4) main(float2, half4|float4)
+ if (!typeIsValidForColor(returnType)) {
+ errors.error(offset, "'main' must return: 'vec4', 'float4', or 'half4'");
+ return false;
+ }
+ bool validParams =
+ (parameters.size() == 1 && paramIsCoords(0)) ||
+ (parameters.size() == 2 && paramIsCoords(0) && paramIsInputColor(1));
+ if (!validParams) {
+ errors.error(offset, "'main' parameters must be (float2, (vec4|float4|half4)?)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kFragmentProcessor: {
+ if (returnType != *context.fTypes.fHalf4) {
+ errors.error(offset, ".fp 'main' must return 'half4'");
+ return false;
+ }
+ bool validParams = (parameters.size() == 0) ||
+ (parameters.size() == 1 && paramIsCoords(0));
+ if (!validParams) {
+ errors.error(offset, ".fp 'main' must be declared main() or main(float2)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kGeneric:
+ // No rules apply here
+ break;
+ case ProgramKind::kFragment: {
+ bool validParams = (parameters.size() == 0) ||
+ (parameters.size() == 1 && paramIsCoords(0));
+ if (!validParams) {
+ errors.error(offset, "shader 'main' must be main() or main(float2)");
+ return false;
+ }
+ break;
+ }
+ case ProgramKind::kVertex:
+ case ProgramKind::kGeometry:
+ if (parameters.size()) {
+ errors.error(offset, "shader 'main' must have zero parameters");
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
+/**
+ * Checks for a previously existing declaration of this function, reporting errors if there is an
+ * incompatible symbol. Returns true and sets outExistingDecl to point to the existing declaration
+ * (or null if none) on success, returns false on error.
+ */
+static bool find_existing_declaration(const Context& context, SymbolTable& symbols, int offset,
+ StringFragment name,
+ std::vector<std::unique_ptr<Variable>>& parameters,
+ const Type* returnType, bool isBuiltin,
+ const FunctionDeclaration** outExistingDecl) {
+ ErrorReporter& errors = context.fErrors;
+ const Symbol* entry = symbols[name];
+ *outExistingDecl = nullptr;
+ if (entry) {
+ std::vector<const FunctionDeclaration*> functions;
+ switch (entry->kind()) {
+ case Symbol::Kind::kUnresolvedFunction:
+ functions = entry->as<UnresolvedFunction>().functions();
+ break;
+ case Symbol::Kind::kFunctionDeclaration:
+ functions.push_back(&entry->as<FunctionDeclaration>());
+ break;
+ default:
+ errors.error(offset, "symbol '" + name + "' was already defined");
+ return false;
+ }
+ for (const FunctionDeclaration* other : functions) {
+ SkASSERT(name == other->name());
+ if (parameters.size() != other->parameters().size()) {
+ continue;
+ }
+ bool match = true;
+ for (size_t i = 0; i < parameters.size(); i++) {
+ if (parameters[i]->type() != other->parameters()[i]->type()) {
+ match = false;
+ break;
+ }
+ }
+ if (!match) {
+ continue;
+ }
+ if (*returnType != other->returnType()) {
+ std::vector<const Variable*> paramPtrs;
+ paramPtrs.reserve(parameters.size());
+ for (std::unique_ptr<Variable>& param : parameters) {
+ paramPtrs.push_back(param.get());
+ }
+ FunctionDeclaration invalidDecl(offset,
+ &other->modifiers(),
+ name,
+ std::move(paramPtrs),
+ returnType,
+ isBuiltin);
+ errors.error(offset,
+ "functions '" + invalidDecl.description() + "' and '" +
+ other->description() + "' differ only in return type");
+ return false;
+ }
+ for (size_t i = 0; i < parameters.size(); i++) {
+ if (parameters[i]->modifiers() != other->parameters()[i]->modifiers()) {
+ errors.error(offset,
+ "modifiers on parameter " + to_string((uint64_t)i + 1) +
+ " differ between declaration and definition");
+ return false;
+ }
+ }
+ if (other->definition() && !other->isBuiltin()) {
+ errors.error(offset, "duplicate definition of " + other->description());
+ return false;
+ }
+ *outExistingDecl = other;
+ break;
+ }
+ }
+ return true;
+}
+
+const FunctionDeclaration* FunctionDeclaration::Convert(const Context& context,
+ SymbolTable& symbols, ModifiersPool& modifiersPool, int offset, const Modifiers* modifiers,
+ StringFragment name, std::vector<std::unique_ptr<Variable>> parameters,
+ const Type* returnType, bool isBuiltin) {
+ bool isMain = (name == "main");
+
+ const FunctionDeclaration* decl = nullptr;
+ if (!check_modifiers(context, offset, *modifiers) ||
+ !check_return_type(context, offset, *returnType, isBuiltin) ||
+ !check_parameters(context, modifiersPool, parameters, isMain, isBuiltin) ||
+ (isMain && !check_main_signature(context, offset, *returnType, parameters, isBuiltin)) ||
+ !find_existing_declaration(context, symbols, offset, name, parameters, returnType,
+ isBuiltin, &decl)) {
+ return nullptr;
+ }
+ std::vector<const Variable*> finalParameters;
+ finalParameters.reserve(parameters.size());
+ for (auto& param : parameters) {
+ finalParameters.push_back(symbols.takeOwnershipOfSymbol(std::move(param)));
+ }
+ if (decl) {
+ return decl;
+ }
+ auto result = std::make_unique<FunctionDeclaration>(offset, modifiers, name,
+ std::move(finalParameters), returnType,
+ isBuiltin);
+ return symbols.add(std::move(result));
+}
+
+} // namespace SkSL
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.h b/src/sksl/ir/SkSLFunctionDeclaration.h
index 8dda492..da3b4e6 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.h
+++ b/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -9,6 +9,7 @@
#define SKSL_FUNCTIONDECLARATION
#include "include/private/SkSLModifiers.h"
+#include "include/private/SkSLProgramKind.h"
#include "include/private/SkSLSymbol.h"
#include "include/private/SkTArray.h"
#include "src/sksl/ir/SkSLExpression.h"
@@ -38,6 +39,16 @@
, fBuiltin(builtin)
, fIsMain(name == "main") {}
+ static const FunctionDeclaration* Convert(const Context& context,
+ SymbolTable& symbols,
+ ModifiersPool& modifiersPool,
+ int offset,
+ const Modifiers* modifiers,
+ StringFragment name,
+ std::vector<std::unique_ptr<Variable>> parameters,
+ const Type* returnType,
+ bool isBuiltin);
+
const Modifiers& modifiers() const {
return *fModifiers;
}
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
index 92796fb..717e386 100644
--- a/src/sksl/ir/SkSLVariable.h
+++ b/src/sksl/ir/SkSLVariable.h
@@ -56,6 +56,10 @@
return *fModifiers;
}
+ void setModifiers(const Modifiers* modifiers) {
+ fModifiers = modifiers;
+ }
+
bool isBuiltin() const {
return fBuiltin;
}
diff --git a/tests/SkDSLRuntimeEffectTest.cpp b/tests/SkDSLRuntimeEffectTest.cpp
index 0f65030..997ac8b 100644
--- a/tests/SkDSLRuntimeEffectTest.cpp
+++ b/tests/SkDSLRuntimeEffectTest.cpp
@@ -128,7 +128,8 @@
effect.start();
Var gColor(kUniform_Modifier, kFloat4_Type);
DeclareGlobal(gColor);
- Function(kHalf4_Type, "main").define(
+ Var p(kFloat2_Type, "p");
+ Function(kHalf4_Type, "main", p).define(
Return(Half4(gColor))
);
effect.end();
@@ -143,7 +144,8 @@
effect.start();
Var gColor(kUniform_Modifier, kInt4_Type);
DeclareGlobal(gColor);
- Function(kHalf4_Type, "main").define(
+ Var p(kFloat2_Type, "p");
+ Function(kHalf4_Type, "main", p).define(
Return(Half4(gColor) / 255)
);
effect.end();
@@ -158,7 +160,8 @@
// make sure we're not saturating unexpectedly.
{
effect.start();
- Function(kHalf4_Type, "main").define(
+ Var p(kFloat2_Type, "p");
+ Function(kHalf4_Type, "main", p).define(
Return(Half4(0.498 * (Half2(Swizzle(sk_FragCoord(), X, Y)) - 0.5), 0, 1))
);
effect.end();
diff --git a/tests/SkSLDSLTest.cpp b/tests/SkSLDSLTest.cpp
index dc806f6..fb80da3 100644
--- a/tests/SkSLDSLTest.cpp
+++ b/tests/SkSLDSLTest.cpp
@@ -1322,13 +1322,13 @@
DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLFunction, r, ctxInfo) {
AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/false);
- Var coords(kHalf2_Type, "coords");
+ Var coords(kFloat2_Type, "coords");
DSLFunction(kVoid_Type, "main", coords).define(
sk_FragColor() = Half4(coords, 0, 1)
);
REPORTER_ASSERT(r, DSLWriter::ProgramElements().size() == 1);
EXPECT_EQUAL(*DSLWriter::ProgramElements()[0],
- "void main(half2 coords) { (sk_FragColor = half4(coords, 0.0, 1.0)); }");
+ "void main(float2 coords) { (sk_FragColor = half4(half2(coords), 0.0, 1.0)); }");
{
DSLWriter::Reset();