SkSL: Allow invoking children (shaders, etc) like functions
Previously, you would declare child objects (shaders, colorFilters, etc.)
and "sample" them like this:
uniform shader input;
uniform colorFilter filter;
half4 main(float2 coord) {
half4 inColor = sample(input, coord);
return sample(filter, inColor);
}
With the new syntax, those child objects become directly callable,
reflecting the way that Skia assembles all parts of the paint (as functions)
in the overall fragment shader:
uniform shader input;
uniform colorFilter filter;
half4 main(float2 coord) {
half4 inColor = input(coord);
return filter(inColor);
}
Bug: skia:12302
Change-Id: Ia12351964dc5d2300660187933188e738671cd83
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/436517
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index 6b9dcc7..bd2782e 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -38,6 +38,7 @@
// Expressions
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
+#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h"
#include "src/sksl/ir/SkSLConstructorMatrixResize.h"
@@ -62,18 +63,13 @@
namespace {
-static bool is_sample_call_to_fp(const FunctionCall& fc, const Variable& fp) {
- const FunctionDeclaration& f = fc.function();
- return f.intrinsicKind() == k_sample_IntrinsicKind && fc.arguments().size() >= 1 &&
- fc.arguments()[0]->is<VariableReference>() &&
- fc.arguments()[0]->as<VariableReference>().variable() == &fp;
-}
-
-// Visitor that determines the merged SampleUsage for a given child 'fp' in the program.
+// Visitor that determines the merged SampleUsage for a given child in the program.
class MergeSampleUsageVisitor : public ProgramVisitor {
public:
- MergeSampleUsageVisitor(const Context& context, const Variable& fp, bool writesToSampleCoords)
- : fContext(context), fFP(fp), fWritesToSampleCoords(writesToSampleCoords) {}
+ MergeSampleUsageVisitor(const Context& context,
+ const Variable& child,
+ bool writesToSampleCoords)
+ : fContext(context), fChild(child), fWritesToSampleCoords(writesToSampleCoords) {}
SampleUsage visit(const Program& program) {
fUsage = SampleUsage(); // reset to none
@@ -85,43 +81,34 @@
protected:
const Context& fContext;
- const Variable& fFP;
+ const Variable& fChild;
const bool fWritesToSampleCoords;
SampleUsage fUsage;
int fElidedSampleCoordCount = 0;
bool visitExpression(const Expression& e) override {
- // Looking for sample(fp, ...)
- if (e.is<FunctionCall>()) {
- const FunctionCall& fc = e.as<FunctionCall>();
- if (is_sample_call_to_fp(fc, fFP)) {
- // Determine the type of call at this site, and merge it with the accumulated state
- if (fc.arguments().size() >= 2) {
- const Expression* coords = fc.arguments()[1].get();
- if (coords->type() == *fContext.fTypes.fFloat2) {
- // If the coords are a direct reference to the program's sample-coords,
- // and those coords are never modified, we can conservatively turn this
- // into PassThrough sampling. In all other cases, we consider it Explicit.
- if (!fWritesToSampleCoords && coords->is<VariableReference>() &&
- coords->as<VariableReference>()
- .variable()
- ->modifiers()
- .fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
- fUsage.merge(SampleUsage::PassThrough());
- ++fElidedSampleCoordCount;
- } else {
- fUsage.merge(SampleUsage::Explicit());
- }
- } else {
- // sample(fp, half4 inputColor) -> PassThrough
- fUsage.merge(SampleUsage::PassThrough());
- }
- } else {
- // sample(fp) -> PassThrough
+ // Looking for child(...)
+ if (e.is<ChildCall>() && &e.as<ChildCall>().child() == &fChild) {
+ // Determine the type of call at this site, and merge it with the accumulated state
+ const ExpressionArray& arguments = e.as<ChildCall>().arguments();
+ SkASSERT(arguments.size() >= 1);
+
+ const Expression* maybeCoords = arguments[0].get();
+ if (maybeCoords->type() == *fContext.fTypes.fFloat2) {
+ // If the coords are a direct reference to the program's sample-coords, and those
+ // coords are never modified, we can conservatively turn this into PassThrough
+ // sampling. In all other cases, we consider it Explicit.
+ if (!fWritesToSampleCoords && maybeCoords->is<VariableReference>() &&
+ maybeCoords->as<VariableReference>().variable()->modifiers().fLayout.fBuiltin ==
+ SK_MAIN_COORDS_BUILTIN) {
fUsage.merge(SampleUsage::PassThrough());
+ ++fElidedSampleCoordCount;
+ } else {
+ fUsage.merge(SampleUsage::Explicit());
}
- // NOTE: we don't return true here just because we found a sample call. We need to
- // process the entire program and merge across all encountered calls.
+ } else {
+ // child(inputColor) or child(srcColor, dstColor) -> PassThrough
+ fUsage.merge(SampleUsage::PassThrough());
}
}
@@ -149,17 +136,14 @@
using INHERITED = ProgramVisitor;
};
-// Visitor that searches for calls to sample() from a function other than main()
+// Visitor that searches for child calls from a function other than main()
class SampleOutsideMainVisitor : public ProgramVisitor {
public:
SampleOutsideMainVisitor() {}
bool visitExpression(const Expression& e) override {
- if (e.is<FunctionCall>()) {
- const FunctionDeclaration& f = e.as<FunctionCall>().function();
- if (f.intrinsicKind() == k_sample_IntrinsicKind) {
- return true;
- }
+ if (e.is<ChildCall>()) {
+ return true;
}
return INHERITED::visitExpression(e);
}
@@ -1199,6 +1183,7 @@
// These are completely disallowed in SkSL constant-(index)-expressions. GLSL allows
// calls to built-in functions where the arguments are all constant-expressions, but
// we don't guarantee that behavior. (skbug.com/10835)
+ case Expression::Kind::kChildCall:
case Expression::Kind::kExternalFunctionCall:
case Expression::Kind::kFunctionCall:
return true;
@@ -1311,6 +1296,14 @@
return (b.left() && this->visitExpressionPtr(b.left())) ||
(b.right() && this->visitExpressionPtr(b.right()));
}
+ case Expression::Kind::kChildCall: {
+ // We don't visit the child variable itself, just the arguments
+ auto& c = e.template as<ChildCall>();
+ for (auto& arg : c.arguments()) {
+ if (arg && this->visitExpressionPtr(arg)) { return true; }
+ }
+ return false;
+ }
case Expression::Kind::kConstructorArray:
case Expression::Kind::kConstructorArrayCast:
case Expression::Kind::kConstructorCompound:
diff --git a/src/sksl/SkSLDehydrator.cpp b/src/sksl/SkSLDehydrator.cpp
index f99f396..bce5b94 100644
--- a/src/sksl/SkSLDehydrator.cpp
+++ b/src/sksl/SkSLDehydrator.cpp
@@ -277,6 +277,11 @@
this->writeU8(b.value());
break;
}
+
+ case Expression::Kind::kChildCall:
+ SkDEBUGFAIL("unimplemented--not expected to be used from within an include file");
+ break;
+
case Expression::Kind::kCodeString:
SkDEBUGFAIL("shouldn't be able to receive kCodeString here");
break;
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 40c1687..20d6307 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -25,6 +25,7 @@
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
+#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLContinueStatement.h"
#include "src/sksl/ir/SkSLDiscardStatement.h"
@@ -1314,6 +1315,18 @@
std::unique_ptr<Expression> IRGenerator::call(int offset,
const FunctionDeclaration& function,
ExpressionArray arguments) {
+ if (function.intrinsicKind() == k_sample_IntrinsicKind && arguments.size() >= 1 &&
+ arguments[0]->type().isEffectChild()) {
+ // Translate old-style sample(child, ...) calls into new-style child(...) IR
+ SkASSERT(arguments[0]->is<VariableReference>());
+ const Variable& child = *arguments[0]->as<VariableReference>().variable();
+ ExpressionArray argumentsWithoutChild;
+ for (size_t i = 1; i < arguments.size(); i++) {
+ argumentsWithoutChild.push_back(std::move(arguments[i]));
+ }
+ return ChildCall::Convert(fContext, offset, child, std::move(argumentsWithoutChild));
+ }
+
if (function.isBuiltin()) {
if (function.intrinsicKind() == k_dFdy_IntrinsicKind) {
fInputs.fUseFlipRTUniform = true;
@@ -1413,6 +1426,16 @@
}
return this->call(offset, *functions[0], std::move(arguments));
}
+ case Expression::Kind::kVariableReference: {
+ if (!functionValue->type().isEffectChild()) {
+ this->errorReporter().error(offset, "not a function");
+ return nullptr;
+ }
+ return ChildCall::Convert(fContext,
+ offset,
+ *functionValue->as<VariableReference>().variable(),
+ std::move(arguments));
+ }
default:
this->errorReporter().error(offset, "not a function");
return nullptr;
diff --git a/src/sksl/SkSLInliner.cpp b/src/sksl/SkSLInliner.cpp
index 215f3aa..7f29001 100644
--- a/src/sksl/SkSLInliner.cpp
+++ b/src/sksl/SkSLInliner.cpp
@@ -16,6 +16,7 @@
#include "src/sksl/ir/SkSLBinaryExpression.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
+#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorArray.h"
#include "src/sksl/ir/SkSLConstructorArrayCast.h"
@@ -311,6 +312,14 @@
case Expression::Kind::kIntLiteral:
case Expression::Kind::kFloatLiteral:
return expression.clone();
+ case Expression::Kind::kChildCall: {
+ const ChildCall& childCall = expression.as<ChildCall>();
+ return ChildCall::Make(*fContext,
+ offset,
+ childCall.type().clone(symbolTableForExpression),
+ childCall.child(),
+ argList(childCall.arguments()));
+ }
case Expression::Kind::kConstructorArray: {
const ConstructorArray& ctor = expression.as<ConstructorArray>();
return ConstructorArray::Make(*fContext, offset,
@@ -958,6 +967,13 @@
}
break;
}
+ case Expression::Kind::kChildCall: {
+ ChildCall& childCallExpr = (*expr)->as<ChildCall>();
+ for (std::unique_ptr<Expression>& arg : childCallExpr.arguments()) {
+ this->visitExpression(&arg);
+ }
+ break;
+ }
case Expression::Kind::kConstructorArray:
case Expression::Kind::kConstructorArrayCast:
case Expression::Kind::kConstructorCompound:
diff --git a/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
index 145a430..094f52f 100644
--- a/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp
@@ -13,6 +13,7 @@
#include "src/sksl/SkSLOperators.h"
#include "src/sksl/SkSLStringStream.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
+#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorArrayCast.h"
#include "src/sksl/ir/SkSLDoStatement.h"
@@ -79,6 +80,7 @@
void writeStructDefinition(const StructDefinition& s);
void writeExpression(const Expression& expr, Precedence parentPrecedence);
+ void writeChildCall(const ChildCall& c);
void writeFunctionCall(const FunctionCall& c);
void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence);
void writeFieldAccess(const FieldAccess& f);
@@ -138,76 +140,74 @@
fBuffer->writeText("\n");
}
-void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
- const FunctionDeclaration& function = c.function();
+void PipelineStageCodeGenerator::writeChildCall(const ChildCall& c) {
const ExpressionArray& arguments = c.arguments();
- if (function.isBuiltin() && function.name() == "sample") {
- SkASSERT(arguments.size() >= 2);
- const Expression* child = arguments[0].get();
- SkASSERT(child->type().isEffectChild());
- SkASSERT(child->is<VariableReference>());
- int index = 0;
- bool found = false;
- for (const ProgramElement* p : fProgram.elements()) {
- if (p->is<GlobalVarDeclaration>()) {
- const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
- const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
- if (&decl.var() == child->as<VariableReference>().variable()) {
- found = true;
- } else if (decl.var().type().isEffectChild()) {
- ++index;
- }
+ SkASSERT(arguments.size() >= 1);
+ int index = 0;
+ bool found = false;
+ for (const ProgramElement* p : fProgram.elements()) {
+ if (p->is<GlobalVarDeclaration>()) {
+ const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
+ const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
+ if (&decl.var() == &c.child()) {
+ found = true;
+ } else if (decl.var().type().isEffectChild()) {
+ ++index;
}
- if (found) {
+ }
+ if (found) {
+ break;
+ }
+ }
+ SkASSERT(found);
+
+ // Shaders require a coordinate argument. Color filters require a color argument.
+ // Blenders require two color arguments.
+ String sampleOutput;
+ {
+ AutoOutputBuffer exprBuffer(this);
+ this->writeExpression(*arguments[0], Precedence::kSequence);
+
+ switch (c.child().type().typeKind()) {
+ case Type::TypeKind::kShader: {
+ SkASSERT(arguments.size() == 1);
+ SkASSERT(arguments[0]->type() == *fProgram.fContext->fTypes.fFloat2);
+ sampleOutput = fCallbacks->sampleShader(index, exprBuffer.fBuffer.str());
break;
}
- }
- SkASSERT(found);
+ case Type::TypeKind::kColorFilter: {
+ SkASSERT(arguments.size() == 1);
+ SkASSERT(arguments[0]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arguments[0]->type() == *fProgram.fContext->fTypes.fFloat4);
+ sampleOutput = fCallbacks->sampleColorFilter(index, exprBuffer.fBuffer.str());
+ break;
+ }
+ case Type::TypeKind::kBlender: {
+ SkASSERT(arguments.size() == 2);
+ SkASSERT(arguments[0]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arguments[0]->type() == *fProgram.fContext->fTypes.fFloat4);
+ SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
- // Shaders require a coordinate argument. Color filters require a color argument.
- // Blenders require two color arguments.
- String sampleOutput;
- {
- AutoOutputBuffer exprBuffer(this);
- this->writeExpression(*arguments[1], Precedence::kSequence);
+ AutoOutputBuffer exprBuffer2(this);
+ this->writeExpression(*arguments[1], Precedence::kSequence);
- switch (child->type().typeKind()) {
- case Type::TypeKind::kShader: {
- SkASSERT(arguments.size() == 2);
- SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fFloat2);
- sampleOutput = fCallbacks->sampleShader(index, exprBuffer.fBuffer.str());
- break;
- }
- case Type::TypeKind::kColorFilter: {
- SkASSERT(arguments.size() == 2);
- SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
- sampleOutput = fCallbacks->sampleColorFilter(index, exprBuffer.fBuffer.str());
- break;
- }
- case Type::TypeKind::kBlender: {
- SkASSERT(arguments.size() == 3);
- SkASSERT(arguments[1]->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arguments[1]->type() == *fProgram.fContext->fTypes.fFloat4);
- SkASSERT(arguments[2]->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arguments[2]->type() == *fProgram.fContext->fTypes.fFloat4);
-
- AutoOutputBuffer exprBuffer2(this);
- this->writeExpression(*arguments[2], Precedence::kSequence);
-
- sampleOutput = fCallbacks->sampleBlender(index, exprBuffer.fBuffer.str(),
- exprBuffer2.fBuffer.str());
- break;
- }
- default: {
- SkDEBUGFAILF("cannot sample from type '%s'",
- child->type().description().c_str());
- }
+ sampleOutput = fCallbacks->sampleBlender(index, exprBuffer.fBuffer.str(),
+ exprBuffer2.fBuffer.str());
+ break;
+ }
+ default: {
+ SkDEBUGFAILF("cannot sample from type '%s'",
+ c.child().type().description().c_str());
}
}
- this->write(sampleOutput);
- return;
}
+ this->write(sampleOutput);
+ return;
+}
+
+void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
+ const FunctionDeclaration& function = c.function();
if (function.isBuiltin()) {
this->write(function.name());
@@ -217,7 +217,7 @@
this->write("(");
const char* separator = "";
- for (const auto& arg : arguments) {
+ for (const auto& arg : c.arguments()) {
this->write(separator);
separator = ", ";
this->writeExpression(*arg, Precedence::kSequence);
@@ -435,6 +435,9 @@
case Expression::Kind::kIntLiteral:
this->write(expr.description());
break;
+ case Expression::Kind::kChildCall:
+ this->writeChildCall(expr.as<ChildCall>());
+ break;
case Expression::Kind::kConstructorArray:
case Expression::Kind::kConstructorArrayCast:
case Expression::Kind::kConstructorCompound:
diff --git a/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
index b9e2333..d4369c1 100644
--- a/src/sksl/codegen/SkSLVMCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
@@ -17,6 +17,7 @@
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLBoolLiteral.h"
#include "src/sksl/ir/SkSLBreakStatement.h"
+#include "src/sksl/ir/SkSLChildCall.h"
#include "src/sksl/ir/SkSLConstructor.h"
#include "src/sksl/ir/SkSLConstructorArray.h"
#include "src/sksl/ir/SkSLConstructorArrayCast.h"
@@ -181,6 +182,7 @@
Value writeExpression(const Expression& expr);
Value writeBinaryExpression(const BinaryExpression& b);
Value writeAggregationConstructor(const AnyConstructor& c);
+ Value writeChildCall(const ChildCall& c);
Value writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c);
Value writeConstructorMatrixResize(const ConstructorMatrixResize& c);
Value writeConstructorCast(const AnyConstructor& c);
@@ -865,76 +867,63 @@
return result;
}
+Value SkVMGenerator::writeChildCall(const ChildCall& c) {
+ auto child_it = fVariableMap.find(&c.child());
+ SkASSERT(child_it != fVariableMap.end());
+
+ const Expression* arg = c.arguments()[0].get();
+ Value argVal = this->writeExpression(*arg);
+ skvm::Color color;
+
+ switch (c.child().type().typeKind()) {
+ case Type::TypeKind::kShader: {
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type() == *fProgram.fContext->fTypes.fFloat2);
+ skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])};
+ color = fSampleShader(child_it->second, coord);
+ break;
+ }
+ case Type::TypeKind::kColorFilter: {
+ SkASSERT(c.arguments().size() == 1);
+ SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arg->type() == *fProgram.fContext->fTypes.fFloat4);
+ skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+ color = fSampleColorFilter(child_it->second, inColor);
+ break;
+ }
+ case Type::TypeKind::kBlender: {
+ SkASSERT(c.arguments().size() == 2);
+ SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arg->type() == *fProgram.fContext->fTypes.fFloat4);
+ skvm::Color srcColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+
+ arg = c.arguments()[1].get();
+ argVal = this->writeExpression(*arg);
+ SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
+ arg->type() == *fProgram.fContext->fTypes.fFloat4);
+ skvm::Color dstColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])};
+
+ color = fSampleBlender(child_it->second, srcColor, dstColor);
+ break;
+ }
+ default: {
+ SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str());
+ }
+ }
+
+ Value result(4);
+ result[0] = color.r;
+ result[1] = color.g;
+ result[2] = color.b;
+ result[3] = color.a;
+ return result;
+}
+
Value SkVMGenerator::writeIntrinsicCall(const FunctionCall& c) {
IntrinsicKind intrinsicKind = c.function().intrinsicKind();
SkASSERT(intrinsicKind != kNotIntrinsic);
const size_t nargs = c.arguments().size();
-
- if (intrinsicKind == k_sample_IntrinsicKind) {
- // Sample is very special. The first argument is a child (shader/colorFilter/blender),
- // which is opaque and can't be evaluated.
- SkASSERT(nargs >= 2);
- const Expression* child = c.arguments()[0].get();
- SkASSERT(child->type().isEffectChild());
- SkASSERT(child->is<VariableReference>());
-
- auto fp_it = fVariableMap.find(child->as<VariableReference>().variable());
- SkASSERT(fp_it != fVariableMap.end());
-
- // Shaders require a coordinate argument. Color filters require a color argument.
- // When we call sampleChild, the other value remains the incoming default.
- const Expression* arg = c.arguments()[1].get();
- Value argVal = this->writeExpression(*arg);
- skvm::Color color;
-
- switch (child->type().typeKind()) {
- case Type::TypeKind::kShader: {
- SkASSERT(nargs == 2);
- SkASSERT(arg->type() == *fProgram.fContext->fTypes.fFloat2);
- skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])};
- color = fSampleShader(fp_it->second, coord);
- break;
- }
- case Type::TypeKind::kColorFilter: {
- SkASSERT(nargs == 2);
- SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arg->type() == *fProgram.fContext->fTypes.fFloat4);
- skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]),
- f32(argVal[2]), f32(argVal[3])};
- color = fSampleColorFilter(fp_it->second, inColor);
- break;
- }
- case Type::TypeKind::kBlender: {
- SkASSERT(nargs == 3);
- SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arg->type() == *fProgram.fContext->fTypes.fFloat4);
- skvm::Color srcColor = {f32(argVal[0]), f32(argVal[1]),
- f32(argVal[2]), f32(argVal[3])};
-
- arg = c.arguments()[2].get();
- argVal = this->writeExpression(*arg);
- SkASSERT(arg->type() == *fProgram.fContext->fTypes.fHalf4 ||
- arg->type() == *fProgram.fContext->fTypes.fFloat4);
- skvm::Color dstColor = {f32(argVal[0]), f32(argVal[1]),
- f32(argVal[2]), f32(argVal[3])};
-
- color = fSampleBlender(fp_it->second, srcColor, dstColor);
- break;
- }
- default: {
- SkDEBUGFAILF("cannot sample from type '%s'", child->type().description().c_str());
- }
- }
-
- Value result(4);
- result[0] = color.r;
- result[1] = color.g;
- result[2] = color.b;
- result[3] = color.a;
- return result;
- }
-
const size_t kMaxArgs = 3; // eg: clamp, mix, smoothstep
Value args[kMaxArgs];
SkASSERT(nargs >= 1 && nargs <= SK_ARRAY_COUNT(args));
@@ -1336,6 +1325,8 @@
return this->writeBinaryExpression(e.as<BinaryExpression>());
case Expression::Kind::kBoolLiteral:
return fBuilder->splat(e.as<BoolLiteral>().value() ? ~0 : 0);
+ case Expression::Kind::kChildCall:
+ return this->writeChildCall(e.as<ChildCall>());
case Expression::Kind::kConstructorArray:
case Expression::Kind::kConstructorCompound:
case Expression::Kind::kConstructorStruct:
diff --git a/src/sksl/ir/SkSLChildCall.cpp b/src/sksl/ir/SkSLChildCall.cpp
new file mode 100644
index 0000000..bda1159
--- /dev/null
+++ b/src/sksl/ir/SkSLChildCall.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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/SkSLContext.h"
+#include "src/sksl/ir/SkSLChildCall.h"
+
+namespace SkSL {
+
+bool ChildCall::hasProperty(Property property) const {
+ for (const auto& arg : this->arguments()) {
+ if (arg->hasProperty(property)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::unique_ptr<Expression> ChildCall::clone() const {
+ ExpressionArray cloned;
+ cloned.reserve_back(this->arguments().size());
+ for (const std::unique_ptr<Expression>& arg : this->arguments()) {
+ cloned.push_back(arg->clone());
+ }
+ return std::make_unique<ChildCall>(fOffset, &this->type(), &this->child(), std::move(cloned));
+}
+
+String ChildCall::description() const {
+ String result = String(this->child().name()) + "(";
+ String separator;
+ for (const std::unique_ptr<Expression>& arg : this->arguments()) {
+ result += separator;
+ result += arg->description();
+ separator = ", ";
+ }
+ result += ")";
+ return result;
+}
+
+struct ChildCallSignature {
+ const Type* fReturnType = nullptr;
+ SkSTArray<2, const Type*> fParamTypes;
+};
+
+static ChildCallSignature child_call_signature(const Context& context, const Variable& child) {
+ const Type* half4 = context.fTypes.fHalf4.get();
+ const Type* float2 = context.fTypes.fFloat2.get();
+
+ switch (child.type().typeKind()) {
+ case Type::TypeKind::kBlender: return { half4, { half4, half4 } };
+ case Type::TypeKind::kColorFilter: return { half4, { half4 } };
+ case Type::TypeKind::kShader: return { half4, { float2 } };
+ default:
+ SkUNREACHABLE;
+ }
+}
+
+std::unique_ptr<Expression> ChildCall::Convert(const Context& context,
+ int offset,
+ const Variable& child,
+ ExpressionArray arguments) {
+ ChildCallSignature signature = child_call_signature(context, child);
+ skstd::string_view typeName = child.type().name();
+
+ // Reject function calls with the wrong number of arguments.
+ if (signature.fParamTypes.size() != arguments.size()) {
+ String msg = "call to '" + typeName + "' expected " +
+ to_string((int)signature.fParamTypes.size()) + " argument";
+ if (signature.fParamTypes.size() != 1) {
+ msg += "s";
+ }
+ msg += ", but found " + to_string(arguments.count());
+ context.errors().error(offset, msg);
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < arguments.size(); i++) {
+ // Coerce each argument to the proper type.
+ arguments[i] = signature.fParamTypes[i]->coerceExpression(std::move(arguments[i]), context);
+ if (!arguments[i]) {
+ return nullptr;
+ }
+ }
+
+ return Make(context, offset, signature.fReturnType, child, std::move(arguments));
+}
+
+std::unique_ptr<Expression> ChildCall::Make(const Context& context,
+ int offset,
+ const Type* returnType,
+ const Variable& child,
+ ExpressionArray arguments) {
+#ifdef SK_DEBUG
+ ChildCallSignature signature = child_call_signature(context, child);
+ SkASSERT(signature.fParamTypes.size() == arguments.size());
+ for (size_t i = 0; i < arguments.size(); i++) {
+ SkASSERT(arguments[i]->type() == *signature.fParamTypes[i]);
+ }
+#endif
+
+ return std::make_unique<ChildCall>(offset, returnType, &child, std::move(arguments));
+}
+
+} // namespace SkSL
diff --git a/src/sksl/ir/SkSLChildCall.h b/src/sksl/ir/SkSLChildCall.h
new file mode 100644
index 0000000..cf3c477
--- /dev/null
+++ b/src/sksl/ir/SkSLChildCall.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_CHILDCALL
+#define SKSL_CHILDCALL
+
+#include "include/private/SkTArray.h"
+#include "src/sksl/ir/SkSLExpression.h"
+#include "src/sksl/ir/SkSLVariable.h"
+
+namespace SkSL {
+
+/**
+ * A call to a child effect object (shader, color filter, or blender).
+ */
+class ChildCall final : public Expression {
+public:
+ static constexpr Kind kExpressionKind = Kind::kChildCall;
+
+ ChildCall(int offset, const Type* type, const Variable* child, ExpressionArray arguments)
+ : INHERITED(offset, kExpressionKind, type)
+ , fChild(*child)
+ , fArguments(std::move(arguments)) {}
+
+ // Performs type conversion on arguments, determines return type, and reports errors via the
+ // ErrorReporter.
+ static std::unique_ptr<Expression> Convert(const Context& context,
+ int offset,
+ const Variable& child,
+ ExpressionArray arguments);
+
+ // Creates the child call; reports errors via ASSERT.
+ static std::unique_ptr<Expression> Make(const Context& context,
+ int offset,
+ const Type* returnType,
+ const Variable& child,
+ ExpressionArray arguments);
+
+ const Variable& child() const {
+ return fChild;
+ }
+
+ ExpressionArray& arguments() {
+ return fArguments;
+ }
+
+ const ExpressionArray& arguments() const {
+ return fArguments;
+ }
+
+ bool hasProperty(Property property) const override;
+
+ std::unique_ptr<Expression> clone() const override;
+
+ String description() const override;
+
+private:
+ const Variable& fChild;
+ ExpressionArray fArguments;
+
+ using INHERITED = Expression;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/src/sksl/ir/SkSLExpression.h b/src/sksl/ir/SkSLExpression.h
index 149d5e6..76073bb 100644
--- a/src/sksl/ir/SkSLExpression.h
+++ b/src/sksl/ir/SkSLExpression.h
@@ -29,6 +29,7 @@
enum class Kind {
kBinary = (int) Statement::Kind::kLast + 1,
kBoolLiteral,
+ kChildCall,
kCodeString,
kConstructorArray,
kConstructorArrayCast,