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>
This commit is contained in:
Brian Osman 2021-08-04 11:34:16 -04:00 committed by SkCQ
parent c18ee4e55a
commit eb0f29dba2
12 changed files with 392 additions and 174 deletions

View File

@ -17,6 +17,13 @@ Milestone 94
* Removed SK_SUPPORT_DEPRECATED_CLIPOPS build flag. Clips can only be intersect and difference.
https://review.skia.org/436565
* There is a new syntax for invoking (sampling) child effects in SkSL. Previously, children
(shaders, colorFilters, blenders) were invoked using different overloads of `sample`. That
syntax is deprecated (but still supported). Now, the child object can be invoked directly,
like a function. The arguments to these invocations are the same as the arguments that followed
the child in calls to `sample`.
https://review.skia.org/436517
* * *
Milestone 93

View File

@ -103,6 +103,8 @@ skia_sksl_sources = [
"$_src/sksl/ir/SkSLBlock.h",
"$_src/sksl/ir/SkSLBoolLiteral.h",
"$_src/sksl/ir/SkSLBreakStatement.h",
"$_src/sksl/ir/SkSLChildCall.cpp",
"$_src/sksl/ir/SkSLChildCall.h",
"$_src/sksl/ir/SkSLConstructor.cpp",
"$_src/sksl/ir/SkSLConstructor.h",
"$_src/sksl/ir/SkSLConstructorArray.cpp",

View File

@ -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 SkSL {
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 @@ public:
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 @@ public:
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 @@ public:
// 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 @@ template <typename T> bool TProgramVisitor<T>::visitExpression(typename T::Expre
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:

View File

@ -277,6 +277,11 @@ void Dehydrator::write(const Expression* e) {
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;

View File

@ -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 @@ void IRGenerator::copyIntrinsicIfNeeded(const FunctionDeclaration& function) {
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 @@ std::unique_ptr<Expression> IRGenerator::call(int offset,
}
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;

View File

@ -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 @@ std::unique_ptr<Expression> Inliner::inlineExpression(int offset,
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 @@ public:
}
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:

View File

@ -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 @@ private:
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 @@ void PipelineStageCodeGenerator::writeLine(skstd::string_view s) {
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 @@ void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
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 @@ void PipelineStageCodeGenerator::writeExpression(const Expression& expr,
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:

View File

@ -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 @@ private:
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 @@ Value SkVMGenerator::writeMatrixInverse4x4(const Value& m) {
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 @@ Value SkVMGenerator::writeExpression(const Expression& e) {
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:

View File

@ -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

View File

@ -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

View File

@ -29,6 +29,7 @@ public:
enum class Kind {
kBinary = (int) Statement::Kind::kLast + 1,
kBoolLiteral,
kChildCall,
kCodeString,
kConstructorArray,
kConstructorArrayCast,

View File

@ -1948,7 +1948,7 @@ DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleShader, r, ctxInfo) {
AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), default_settings(),
SkSL::ProgramKind::kRuntimeShader);
DSLGlobalVar shader(kUniform_Modifier, kShader_Type, "shader");
EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "sample(shader, float2(0.0, 0.0))");
EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "shader(float2(0.0, 0.0))");
{
ExpectError error(r, "no match for sample(shader, half4)");