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:
parent
c18ee4e55a
commit
eb0f29dba2
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
107
src/sksl/ir/SkSLChildCall.cpp
Normal file
107
src/sksl/ir/SkSLChildCall.cpp
Normal 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
|
70
src/sksl/ir/SkSLChildCall.h
Normal file
70
src/sksl/ir/SkSLChildCall.h
Normal 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
|
@ -29,6 +29,7 @@ public:
|
||||
enum class Kind {
|
||||
kBinary = (int) Statement::Kind::kLast + 1,
|
||||
kBoolLiteral,
|
||||
kChildCall,
|
||||
kCodeString,
|
||||
kConstructorArray,
|
||||
kConstructorArrayCast,
|
||||
|
@ -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)");
|
||||
|
Loading…
Reference in New Issue
Block a user