From eb0f29dba2d229b30b595673e96286c380a4bde0 Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Wed, 4 Aug 2021 11:34:16 -0400 Subject: [PATCH] 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 Reviewed-by: Brian Salomon Reviewed-by: John Stiles --- RELEASE_NOTES.txt | 7 + gn/sksl.gni | 2 + src/sksl/SkSLAnalysis.cpp | 85 ++++++------ src/sksl/SkSLDehydrator.cpp | 5 + src/sksl/SkSLIRGenerator.cpp | 23 ++++ src/sksl/SkSLInliner.cpp | 16 +++ .../SkSLPipelineStageCodeGenerator.cpp | 127 +++++++++--------- src/sksl/codegen/SkSLVMCodeGenerator.cpp | 121 ++++++++--------- src/sksl/ir/SkSLChildCall.cpp | 107 +++++++++++++++ src/sksl/ir/SkSLChildCall.h | 70 ++++++++++ src/sksl/ir/SkSLExpression.h | 1 + tests/SkSLDSLTest.cpp | 2 +- 12 files changed, 392 insertions(+), 174 deletions(-) create mode 100644 src/sksl/ir/SkSLChildCall.cpp create mode 100644 src/sksl/ir/SkSLChildCall.h diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 26f2035adc..09e518d224 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -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 diff --git a/gn/sksl.gni b/gn/sksl.gni index eaba24c1c7..416b906478 100644 --- a/gn/sksl.gni +++ b/gn/sksl.gni @@ -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", diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp index 6b9dcc7385..bd2782e370 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 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() && - fc.arguments()[0]->as().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()) { - const FunctionCall& fc = e.as(); - 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() && - coords->as() - .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() && &e.as().child() == &fChild) { + // Determine the type of call at this site, and merge it with the accumulated state + const ExpressionArray& arguments = e.as().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() && + maybeCoords->as().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()) { - const FunctionDeclaration& f = e.as().function(); - if (f.intrinsicKind() == k_sample_IntrinsicKind) { - return true; - } + if (e.is()) { + 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 bool TProgramVisitor::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(); + 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 f99f39658f..bce5b94132 100644 --- a/src/sksl/SkSLDehydrator.cpp +++ b/src/sksl/SkSLDehydrator.cpp @@ -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; diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp index 40c1687a2a..20d6307803 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 @@ void IRGenerator::copyIntrinsicIfNeeded(const FunctionDeclaration& function) { std::unique_ptr 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()); + const Variable& child = *arguments[0]->as().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 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().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 215f3aa66b..7f29001994 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 @@ std::unique_ptr Inliner::inlineExpression(int offset, case Expression::Kind::kIntLiteral: case Expression::Kind::kFloatLiteral: return expression.clone(); + case Expression::Kind::kChildCall: { + const ChildCall& childCall = expression.as(); + return ChildCall::Make(*fContext, + offset, + childCall.type().clone(symbolTableForExpression), + childCall.child(), + argList(childCall.arguments())); + } case Expression::Kind::kConstructorArray: { const ConstructorArray& ctor = expression.as(); return ConstructorArray::Make(*fContext, offset, @@ -958,6 +967,13 @@ public: } break; } + case Expression::Kind::kChildCall: { + ChildCall& childCallExpr = (*expr)->as(); + for (std::unique_ptr& 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 145a430d63..094f52f3a2 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 @@ 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()); - int index = 0; - bool found = false; - for (const ProgramElement* p : fProgram.elements()) { - if (p->is()) { - const GlobalVarDeclaration& global = p->as(); - const VarDeclaration& decl = global.declaration()->as(); - if (&decl.var() == child->as().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()) { + const GlobalVarDeclaration& global = p->as(); + const VarDeclaration& decl = global.declaration()->as(); + 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()); + 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 b9e2333532..d4369c1d47 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 @@ 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()); - - auto fp_it = fVariableMap.find(child->as().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()); case Expression::Kind::kBoolLiteral: return fBuilder->splat(e.as().value() ? ~0 : 0); + case Expression::Kind::kChildCall: + return this->writeChildCall(e.as()); 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 0000000000..bda11596a2 --- /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 ChildCall::clone() const { + ExpressionArray cloned; + cloned.reserve_back(this->arguments().size()); + for (const std::unique_ptr& arg : this->arguments()) { + cloned.push_back(arg->clone()); + } + return std::make_unique(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& 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 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 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(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 0000000000..cf3c477a24 --- /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 Convert(const Context& context, + int offset, + const Variable& child, + ExpressionArray arguments); + + // Creates the child call; reports errors via ASSERT. + static std::unique_ptr 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 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 149d5e6e09..76073bbb53 100644 --- a/src/sksl/ir/SkSLExpression.h +++ b/src/sksl/ir/SkSLExpression.h @@ -29,6 +29,7 @@ public: enum class Kind { kBinary = (int) Statement::Kind::kLast + 1, kBoolLiteral, + kChildCall, kCodeString, kConstructorArray, kConstructorArrayCast, diff --git a/tests/SkSLDSLTest.cpp b/tests/SkSLDSLTest.cpp index 90362a669d..84741cd106 100644 --- a/tests/SkSLDSLTest.cpp +++ b/tests/SkSLDSLTest.cpp @@ -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)");