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: