blob: 1208a31bbad67fa535a7f055a9fdf2f4b4edd393 [file] [log] [blame]
/*
* 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/codegen/SkSLDSLCPPCodeGenerator.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCPPUniformCTypes.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/codegen/SkSLHCodeGenerator.h"
#include "src/sksl/ir/SkSLBlock.h"
#include "src/sksl/ir/SkSLEnum.h"
#include "src/sksl/ir/SkSLExpressionStatement.h"
#include <algorithm>
#if defined(SKSL_STANDALONE) || GR_TEST_UTILS
namespace SkSL {
static bool needs_uniform_var(const Variable& var) {
return var.modifiers().fFlags & Modifiers::kUniform_Flag;
}
static const char* get_scalar_type_name(const Context& context, const Type& type) {
if (type == *context.fTypes.fHalf) {
return "Half";
} else if (type == *context.fTypes.fFloat) {
return "Float";
} else if (type.isSigned()) {
return "Int";
} else if (type.isBoolean()) {
return "Bool";
}
// TODO: support for unsigned types
SkDEBUGFAIL("unsupported scalar type");
return "Float";
}
DSLCPPCodeGenerator::DSLCPPCodeGenerator(const Context* context, const Program* program,
ErrorReporter* errors, String name, OutputStream* out)
: INHERITED(context, program, errors, out)
, fName(std::move(name))
, fFullName(String::printf("Gr%s", fName.c_str()))
, fSectionAndParameterHelper(program, *errors) {
fLineEnding = "\n";
fTextureFunctionOverride = "sample";
}
void DSLCPPCodeGenerator::writef(const char* s, va_list va) {
static constexpr int BUFFER_SIZE = 1024;
va_list copy;
va_copy(copy, va);
char buffer[BUFFER_SIZE];
int length = std::vsnprintf(buffer, BUFFER_SIZE, s, va);
if (length < BUFFER_SIZE) {
fOut->write(buffer, length);
} else {
std::unique_ptr<char[]> heap(new char[length + 1]);
vsprintf(heap.get(), s, copy);
fOut->write(heap.get(), length);
}
va_end(copy);
}
void DSLCPPCodeGenerator::writef(const char* s, ...) {
va_list va;
va_start(va, s);
this->writef(s, va);
va_end(va);
}
void DSLCPPCodeGenerator::writeHeader() {
}
bool DSLCPPCodeGenerator::usesPrecisionModifiers() const {
return false;
}
static String default_value(const Type& type) {
if (type.isBoolean()) {
return "false";
}
switch (type.typeKind()) {
case Type::TypeKind::kScalar: return "0";
case Type::TypeKind::kVector: return type.name() + "(0)";
case Type::TypeKind::kMatrix: return type.name() + "(1)";
default: SK_ABORT("unsupported default_value type");
}
}
static String default_value(const Variable& var) {
if (var.modifiers().fLayout.fCType == SkSL::Layout::CType::kSkPMColor4f) {
return "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}";
}
return default_value(var.type());
}
static bool is_private(const Variable& var) {
const Modifiers& modifiers = var.modifiers();
return !(modifiers.fFlags & Modifiers::kUniform_Flag) &&
!(modifiers.fFlags & Modifiers::kIn_Flag) &&
var.storage() == Variable::Storage::kGlobal &&
modifiers.fLayout.fBuiltin == -1;
}
static bool is_uniform_in(const Variable& var) {
const Modifiers& modifiers = var.modifiers();
return (modifiers.fFlags & Modifiers::kUniform_Flag) &&
(modifiers.fFlags & Modifiers::kIn_Flag);
}
String DSLCPPCodeGenerator::formatRuntimeValue(const Type& type,
const Layout& layout,
const String& cppCode,
std::vector<String>* formatArgs) {
if (type.isArray()) {
String result("[");
const char* separator = "";
for (int i = 0; i < type.columns(); i++) {
result += separator + this->formatRuntimeValue(type.componentType(), layout,
"(" + cppCode + ")[" + to_string(i) +
"]", formatArgs);
separator = ",";
}
result += "]";
return result;
}
if (type.isFloat()) {
formatArgs->push_back(cppCode);
return "%f";
}
if (type == *fContext.fTypes.fInt) {
formatArgs->push_back(cppCode);
return "%d";
}
if (type == *fContext.fTypes.fBool) {
formatArgs->push_back("!!(" + cppCode + ")");
return "%d";
}
if (type == *fContext.fTypes.fFloat2 || type == *fContext.fTypes.fHalf2) {
formatArgs->push_back(cppCode + ".fX");
formatArgs->push_back(cppCode + ".fY");
return type.name() + "(%f, %f)";
}
if (type == *fContext.fTypes.fFloat3 || type == *fContext.fTypes.fHalf3) {
formatArgs->push_back(cppCode + ".fX");
formatArgs->push_back(cppCode + ".fY");
formatArgs->push_back(cppCode + ".fZ");
return type.name() + "(%f, %f, %f)";
}
if (type == *fContext.fTypes.fFloat4 || type == *fContext.fTypes.fHalf4) {
switch (layout.fCType) {
case Layout::CType::kSkPMColor:
formatArgs->push_back("SkGetPackedR32(" + cppCode + ") / 255.0");
formatArgs->push_back("SkGetPackedG32(" + cppCode + ") / 255.0");
formatArgs->push_back("SkGetPackedB32(" + cppCode + ") / 255.0");
formatArgs->push_back("SkGetPackedA32(" + cppCode + ") / 255.0");
break;
case Layout::CType::kSkPMColor4f:
formatArgs->push_back(cppCode + ".fR");
formatArgs->push_back(cppCode + ".fG");
formatArgs->push_back(cppCode + ".fB");
formatArgs->push_back(cppCode + ".fA");
break;
case Layout::CType::kSkV4:
formatArgs->push_back(cppCode + ".x");
formatArgs->push_back(cppCode + ".y");
formatArgs->push_back(cppCode + ".z");
formatArgs->push_back(cppCode + ".w");
break;
case Layout::CType::kSkRect:
case Layout::CType::kDefault:
formatArgs->push_back(cppCode + ".left()");
formatArgs->push_back(cppCode + ".top()");
formatArgs->push_back(cppCode + ".right()");
formatArgs->push_back(cppCode + ".bottom()");
break;
default:
SkASSERT(false);
}
return type.name() + "(%f, %f, %f, %f)";
}
if (type.isMatrix()) {
SkASSERT(type.componentType() == *fContext.fTypes.fFloat ||
type.componentType() == *fContext.fTypes.fHalf);
String format = type.name() + "(";
for (int c = 0; c < type.columns(); ++c) {
for (int r = 0; r < type.rows(); ++r) {
formatArgs->push_back(String::printf("%s.rc(%d, %d)", cppCode.c_str(), r, c));
format += "%f, ";
}
}
// Replace trailing ", " with ")".
format.pop_back();
format.back() = ')';
return format;
}
if (type.isEnum()) {
formatArgs->push_back("(int) " + cppCode);
return "%d";
}
if (type == *fContext.fTypes.fInt4 ||
type == *fContext.fTypes.fShort4) {
formatArgs->push_back(cppCode + ".left()");
formatArgs->push_back(cppCode + ".top()");
formatArgs->push_back(cppCode + ".right()");
formatArgs->push_back(cppCode + ".bottom()");
return type.name() + "(%d, %d, %d, %d)";
}
SkDEBUGFAILF("unsupported runtime value type '%s'\n", String(type.name()).c_str());
return "";
}
void DSLCPPCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
// Confirm that the component array only contains X/Y/Z/W.
SkASSERT(std::all_of(swizzle.components().begin(), swizzle.components().end(),
[](int8_t component) {
return component >= SwizzleComponent::X && component <= SwizzleComponent::W;
}));
if (fCPPMode) {
// no support for multiple swizzle components yet
SkASSERT(swizzle.components().size() == 1);
this->writeExpression(*swizzle.base(), Precedence::kPostfix);
switch (swizzle.components().front()) {
case SwizzleComponent::X: this->write(".left()"); break;
case SwizzleComponent::Y: this->write(".top()"); break;
case SwizzleComponent::Z: this->write(".right()"); break;
case SwizzleComponent::W: this->write(".bottom()"); break;
}
} else {
if (swizzle.components().size() == 1) {
// For single-element swizzles, we can generate nicer-looking code.
this->writeExpression(*swizzle.base(), Precedence::kPostfix);
switch (swizzle.components().front()) {
case SwizzleComponent::X: this->write(".x()"); break;
case SwizzleComponent::Y: this->write(".y()"); break;
case SwizzleComponent::Z: this->write(".z()"); break;
case SwizzleComponent::W: this->write(".w()"); break;
}
} else {
this->write("Swizzle(");
this->writeExpression(*swizzle.base(), Precedence::kSequence);
for (int8_t component : swizzle.components()) {
switch (component) {
case SwizzleComponent::X: this->write(", X"); break;
case SwizzleComponent::Y: this->write(", Y"); break;
case SwizzleComponent::Z: this->write(", Z"); break;
case SwizzleComponent::W: this->write(", W"); break;
}
}
this->write(")");
}
}
}
void DSLCPPCodeGenerator::writeTernaryExpression(const TernaryExpression& t,
Precedence parentPrecedence) {
if (fCPPMode) {
INHERITED::writeTernaryExpression(t, parentPrecedence);
} else {
this->write("Select(");
this->writeExpression(*t.test(), Precedence::kSequence);
this->write(", /*If True:*/ ");
this->writeExpression(*t.ifTrue(), Precedence::kSequence);
this->write(", /*If False:*/ ");
this->writeExpression(*t.ifFalse(), Precedence::kSequence);
this->write(")");
}
}
void DSLCPPCodeGenerator::writeVariableReference(const VariableReference& ref) {
const Variable& var = *ref.variable();
if (fCPPMode) {
this->write(var.name());
return;
}
switch (var.modifiers().fLayout.fBuiltin) {
case SK_MAIN_COORDS_BUILTIN:
this->write("sk_SampleCoord()");
fAccessSampleCoordsDirectly = true;
return;
case SK_FRAGCOORD_BUILTIN:
this->write("sk_FragCoord()");
return;
default:
break;
}
this->write(this->getVariableCppName(var));
}
int DSLCPPCodeGenerator::getChildFPIndex(const Variable& var) const {
int index = 0;
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const VarDeclaration& decl =
p->as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>();
if (&decl.var() == &var) {
return index;
} else if (decl.var().type().isFragmentProcessor()) {
++index;
}
}
}
SkDEBUGFAILF("child fragment processor for '%s' not found", var.description().c_str());
return 0;
}
void DSLCPPCodeGenerator::writeFunctionCall(const FunctionCall& c) {
const FunctionDeclaration& function = c.function();
if (function.isBuiltin() && function.name() == "sample") {
// The first argument to sample() must be a fragment processor. (Old-school samplers are no
// longer supported in FP files.)
const ExpressionArray& arguments = c.arguments();
SkASSERT(arguments.size() >= 1 && arguments.size() <= 3);
const Expression& fpArgument = *arguments.front();
SkASSERT(fpArgument.type().isFragmentProcessor());
// We can't look up the child FP index unless the fragment-processor is a real variable.
if (!fpArgument.is<VariableReference>()) {
fErrors.error(fpArgument.fOffset,
"sample()'s fragmentProcessor argument must be a variable reference");
return;
}
// Pass the index of the fragment processor, and all the other arguments as-is.
int childFPIndex = this->getChildFPIndex(*fpArgument.as<VariableReference>().variable());
this->writef("SampleChild(%d", childFPIndex);
for (int index = 1; index < arguments.count(); ++index) {
this->write(", ");
this->writeExpression(*arguments[index], Precedence::kSequence);
}
this->write(")");
return;
}
if (function.isBuiltin()) {
if (fCPPMode) {
this->write(function.name());
} else {
static const auto* kBuiltinNames = new std::unordered_map<String, String>{
{"abs", "Abs"},
{"all", "All"},
{"any", "Any"},
{"atan", "Atan"},
{"ceil", "Ceil"},
{"clamp", "Clamp"},
{"cos", "Cos"},
{"cross", "Cross"},
{"degrees", "Degrees"},
{"distance", "Distance"},
{"dot", "Dot"},
{"equal", "Equal"},
{"exp", "Exp"},
{"exp2", "Exp2"},
{"faceforward", "Faceforward"},
{"floor", "Floor"},
{"fract", "SkSL::dsl::Fract"},
{"greaterThan", "GreaterThan"},
{"greaterThanEqual", "GreaterThanEqual"},
{"inversesqrt", "Inversesqrt"},
{"inverse", "Inverse"},
{"length", "Length"},
{"lessThan", "LessThan"},
{"lessThanEqual", "LessThanEqual"},
{"log", "Log"},
{"max", "Max"},
{"min", "Min"},
{"mix", "Mix"},
{"mod", "Mod"},
{"normalize", "Normalize"},
{"not", "Not"},
{"pow", "Pow"},
{"radians", "Radians"},
{"reflect", "Reflect"},
{"refract", "Refract"},
{"saturate", "Saturate"},
{"sign", "Sign"},
{"sin", "Sin"},
{"smoothstep", "Smoothstep"},
{"sqrt", "Sqrt"},
{"step", "Step"},
{"tan", "Tan"},
{"unpremul", "Unpremul"}};
auto iter = kBuiltinNames->find(function.name());
if (iter == kBuiltinNames->end()) {
fErrors.error(c.fOffset,
"unrecognized built-in function '" + function.name() + "'");
return;
}
this->write(iter->second);
}
this->write("(");
const char* separator = "";
for (const std::unique_ptr<Expression>& argument : c.arguments()) {
this->write(separator);
separator = ", ";
this->writeExpression(*argument, Precedence::kSequence);
}
this->write(")");
return;
}
SK_ABORT("not yet implemented: helper function support for DSL");
}
void DSLCPPCodeGenerator::prepareHelperFunction(const FunctionDeclaration& decl) {
if (decl.isBuiltin() || decl.isMain()) {
return;
}
SK_ABORT("not yet implemented: helper functions in DSL");
}
void DSLCPPCodeGenerator::prototypeHelperFunction(const FunctionDeclaration& decl) {
SK_ABORT("not yet implemented: function prototypes in DSL");
}
void DSLCPPCodeGenerator::writeFunction(const FunctionDefinition& f) {
const FunctionDeclaration& decl = f.declaration();
if (decl.isBuiltin()) {
return;
}
fFunctionHeader.clear();
OutputStream* oldOut = fOut;
StringStream buffer;
fOut = &buffer;
if (decl.isMain()) {
fInMain = true;
this->writeFunctionBody(f.body()->as<Block>());
fInMain = false;
fOut = oldOut;
this->write(fFunctionHeader);
this->write(buffer.str());
} else {
SK_ABORT("not yet implemented: helper functions in DSL");
}
}
void DSLCPPCodeGenerator::writeFunctionBody(const Block& b) {
// At the top level of a function, DSL statements need to be emitted as individual C++
// statements instead of being comma-separated expressions in a Block. (You could technically
// emit the entire function as one big comma-separated Block, and it would work, but you'd wrap
// everything with an extra unnecessary scope.)
for (const std::unique_ptr<Statement>& stmt : b.children()) {
if (!stmt->isEmpty()) {
this->writeStatement(*stmt);
this->write(";\n");
}
}
}
void DSLCPPCodeGenerator::writeBlock(const Block& b) {
if (b.isEmpty()) {
// Write empty Blocks as an empty Statement, whether or not it was scoped.
// This is the simplest way to emit a valid Statement for an unscoped empty Block.
this->write("Statement()");
return;
}
if (b.isScope()) {
this->write("Block(");
}
const char* separator = "";
for (const std::unique_ptr<Statement>& stmt : b.children()) {
if (!stmt->isEmpty()) {
this->write(separator);
separator = ", ";
this->writeStatement(*stmt);
}
}
if (b.isScope()) {
this->write(")");
}
}
void DSLCPPCodeGenerator::writeReturnStatement(const ReturnStatement& r) {
this->write("Return(");
if (r.expression()) {
this->writeExpression(*r.expression(), Precedence::kTopLevel);
}
this->write(")");
}
void DSLCPPCodeGenerator::writeIfStatement(const IfStatement& stmt) {
this->write(stmt.isStatic() ? "StaticIf(" : "If(");
this->writeExpression(*stmt.test(), Precedence::kTopLevel);
this->write(", /*Then:*/ ");
this->writeStatement(*stmt.ifTrue());
if (stmt.ifFalse()) {
this->write(", /*Else:*/ ");
this->writeStatement(*stmt.ifFalse());
}
this->write(")");
}
static bool variable_exists_with_name(const std::unordered_map<const Variable*, String>& varMap,
const String& trialName) {
for (const auto& [varPtr, varName] : varMap) {
if (varName == trialName) {
return true;
}
}
return false;
}
const char* DSLCPPCodeGenerator::getVariableCppName(const Variable& var) {
String& cppName = fVariableCppNames[&var];
if (cppName.empty()) {
// Append a prefix to the variable name. This serves two purposes:
// - disambiguates variables with the same name that live in different SkSL scopes
// - gives the DSLVar a distinct name, leaving the original name free to be given to a
// C++ constant, for the case of layout-keys that need to work in C++ `when` expressions
// Probing for a unique name could be more efficient, but it really doesn't matter much;
// overlapping names are super rare, and we only compile DSLs in skslc at build time.
for (int prefix = 0;; ++prefix) {
String prefixedName = (prefix > 0 || !islower(var.name()[0]))
? String::printf("_%d_%.*s", prefix, (int)var.name().size(), var.name().data())
: String::printf("_%.*s", (int)var.name().size(), var.name().data());
if (!variable_exists_with_name(fVariableCppNames, prefixedName)) {
cppName = std::move(prefixedName);
break;
}
}
}
return cppName.c_str();
}
void DSLCPPCodeGenerator::writeCppInitialValue(const Variable& var) {
// `formatRuntimeValue` generates a C++ format string (which we don't need, since
// we're not formatting anything at runtime) and a vector of the arguments within
// the variable (which we do need, to fill in the Var's initial value).
std::vector<String> argumentList;
(void) this->formatRuntimeValue(var.type(), var.modifiers().fLayout,
var.name(), &argumentList);
this->write(this->getTypeName(var.type()));
this->write("(");
const char* separator = "";
for (const String& arg : argumentList) {
this->write(separator);
this->write(arg);
separator = ", ";
}
this->write(")");
}
void DSLCPPCodeGenerator::writeVarCtorExpression(const Variable& var) {
this->write(this->getDSLModifiers(var.modifiers()));
this->write(", ");
this->write(this->getDSLType(var.type()));
this->write(", \"");
this->write(var.name());
this->write("\"");
if (var.initialValue()) {
this->write(", ");
if (is_private(var)) {
// The initial value was calculated in C++ (see writePrivateVarValues). This value can
// be baked into the DSL as a constant.
this->writeCppInitialValue(var);
} else {
// Write the variable's initial-value expression in DSL.
this->writeExpression(*var.initialValue(), Precedence::kTopLevel);
}
}
}
void DSLCPPCodeGenerator::writeVar(const Variable& var) {
this->write("Var ");
this->write(this->getVariableCppName(var));
this->write("(");
this->writeVarCtorExpression(var);
this->write(");\n");
}
void DSLCPPCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl, bool global) {
const Variable& var = varDecl.var();
if (!global) {
// We want to divert our output into fFunctionHeader, but fFunctionHeader is just a
// String, not a StringStream. So instead, we divert into a temporary stream and append
// that stream into fFunctionHeader afterwards.
StringStream stream;
AutoOutputStream divert(this, &stream);
this->writeVar(var);
fFunctionHeader += stream.str();
} else {
// For global variables, we can write the Var directly into the code stream.
this->writeVar(var);
}
this->write("Declare(");
this->write(this->getVariableCppName(var));
this->write(")");
}
void DSLCPPCodeGenerator::writeForStatement(const ForStatement& f) {
// Emit loops of the form 'for (; test;)' as 'while (test)', which is probably how they started.
if (!f.initializer() && f.test() && !f.next()) {
this->write("While(");
this->writeExpression(*f.test(), Precedence::kTopLevel);
this->write(", ");
this->writeStatement(*f.statement());
this->write(")");
return;
}
this->write("For(");
if (f.initializer() && !f.initializer()->isEmpty()) {
this->writeStatement(*f.initializer());
this->write(", ");
} else {
this->write("Statement(), ");
}
if (f.test()) {
this->writeExpression(*f.test(), Precedence::kTopLevel);
this->write(", ");
} else {
this->write("Expression(), ");
}
if (f.next()) {
this->writeExpression(*f.next(), Precedence::kTopLevel);
this->write(", /*Body:*/ ");
} else {
this->write("Expression(), /*Body:*/ ");
}
this->writeStatement(*f.statement());
this->write(")");
}
void DSLCPPCodeGenerator::writeDoStatement(const DoStatement& d) {
this->write("Do(");
this->writeStatement(*d.statement());
this->write(", /*While:*/ ");
this->writeExpression(*d.test(), Precedence::kTopLevel);
this->write(")");
}
void DSLCPPCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
this->write(s.isStatic() ? "StaticSwitch(" : "Switch(");
this->writeExpression(*s.value(), Precedence::kTopLevel);
for (const std::unique_ptr<Statement>& stmt : s.cases()) {
const SwitchCase& c = stmt->as<SwitchCase>();
if (c.value()) {
this->write(",\n Case(");
this->writeExpression(*c.value(), Precedence::kTopLevel);
if (!c.statement()->isEmpty()) {
this->write(", ");
this->writeStatement(*c.statement());
}
} else {
this->write(",\n Default(");
if (!c.statement()->isEmpty()) {
this->writeStatement(*c.statement());
}
}
this->write(")");
}
this->write(")");
}
void DSLCPPCodeGenerator::writeCastConstructor(const AnyConstructor& c,
Precedence parentPrecedence) {
return this->writeAnyConstructor(c, parentPrecedence);
}
void DSLCPPCodeGenerator::writeAnyConstructor(const AnyConstructor& c,
Precedence parentPrecedence) {
if (c.type().isArray() || c.type().isStruct()) {
SK_ABORT("not yet supported: array/struct construction in DSL");
}
INHERITED::writeAnyConstructor(c, parentPrecedence);
}
String DSLCPPCodeGenerator::getTypeName(const Type& type) {
if (fCPPMode) {
return type.name();
}
switch (type.typeKind()) {
case Type::TypeKind::kScalar:
return get_scalar_type_name(fContext, type);
case Type::TypeKind::kVector: {
const Type& component = type.componentType();
const char* baseName = get_scalar_type_name(fContext, component);
return String::printf("%s%d", baseName, type.columns());
}
case Type::TypeKind::kMatrix: {
const Type& component = type.componentType();
const char* baseName = get_scalar_type_name(fContext, component);
return String::printf("%s%dx%d", baseName, type.columns(), type.rows());
}
case Type::TypeKind::kEnum:
return "Int";
default:
SK_ABORT("not yet supported: getTypeName of %s", type.displayName().c_str());
return type.name();
}
}
String DSLCPPCodeGenerator::getDSLType(const Type& type) {
switch (type.typeKind()) {
case Type::TypeKind::kScalar:
return String::printf("DSLType(k%s_Type)", get_scalar_type_name(fContext, type));
case Type::TypeKind::kVector: {
const Type& component = type.componentType();
const char* baseName = get_scalar_type_name(fContext, component);
return String::printf("DSLType(k%s%d_Type)", baseName, type.columns());
}
case Type::TypeKind::kMatrix: {
const Type& component = type.componentType();
const char* baseName = get_scalar_type_name(fContext, component);
return String::printf("DSLType(k%s%dx%d_Type)", baseName, type.columns(), type.rows());
}
case Type::TypeKind::kEnum:
return "DSLType(kInt_Type)";
case Type::TypeKind::kArray: {
const Type& component = type.componentType();
SkASSERT(type.columns() != Type::kUnsizedArray);
return String::printf("Array(%s, %d)", this->getDSLType(component).c_str(),
type.columns());
}
default:
SK_ABORT("not yet supported: getDSLType of %s", type.displayName().c_str());
return type.name();
}
}
String DSLCPPCodeGenerator::getDSLModifiers(const Modifiers& modifiers) {
String text;
// Uniform variables can have `in uniform` flags in FP file; that's not how they are
// represented in DSL, however. Transform `in uniform` modifiers to just `uniform`.
if (modifiers.fFlags & Modifiers::kUniform_Flag) {
text += "kUniform_Modifier | ";
} else if (modifiers.fFlags & Modifiers::kIn_Flag) {
text += "kIn_Modifier | ";
}
if ((modifiers.fFlags & Modifiers::kConst_Flag) ||
(modifiers.fLayout.fFlags & Layout::kKey_Flag)) {
text += "kConst_Modifier | ";
}
if (modifiers.fFlags & Modifiers::kOut_Flag) {
text += "kOut_Modifier | ";
}
if (modifiers.fFlags & Modifiers::kFlat_Flag) {
text += "kFlat_Modifier | ";
}
if (modifiers.fFlags & Modifiers::kNoPerspective_Flag) {
text += "kNoPerspective_Modifier | ";
}
if (text.empty()) {
return "kNo_Modifier";
}
// Eliminate trailing ` | `.
text.pop_back();
text.pop_back();
text.pop_back();
return text;
}
String DSLCPPCodeGenerator::getDefaultDSLValue(const Variable& var) {
// TODO: default_value returns half4(NaN) for colors, but DSL aborts if passed a literal NaN.
// Theoretically this really shouldn't matter.
switch (var.type().typeKind()) {
case Type::TypeKind::kScalar:
case Type::TypeKind::kVector: return this->getTypeName(var.type()) + "(0)";
case Type::TypeKind::kMatrix: return this->getTypeName(var.type()) + "(1)";
default: SK_ABORT("unsupported type: %s", var.type().description().c_str());
}
}
void DSLCPPCodeGenerator::writeStatement(const Statement& s) {
switch (s.kind()) {
case Statement::Kind::kBlock:
this->writeBlock(s.as<Block>());
break;
case Statement::Kind::kExpression:
this->writeExpression(*s.as<ExpressionStatement>().expression(), Precedence::kTopLevel);
break;
case Statement::Kind::kReturn:
this->writeReturnStatement(s.as<ReturnStatement>());
break;
case Statement::Kind::kVarDeclaration:
this->writeVarDeclaration(s.as<VarDeclaration>(), /*global=*/false);
break;
case Statement::Kind::kIf:
this->writeIfStatement(s.as<IfStatement>());
break;
case Statement::Kind::kFor:
this->writeForStatement(s.as<ForStatement>());
break;
case Statement::Kind::kDo:
this->writeDoStatement(s.as<DoStatement>());
break;
case Statement::Kind::kSwitch:
this->writeSwitchStatement(s.as<SwitchStatement>());
break;
case Statement::Kind::kBreak:
this->write("Break()");
break;
case Statement::Kind::kContinue:
this->write("Continue()");
break;
case Statement::Kind::kDiscard:
this->write("Discard()");
break;
case Statement::Kind::kInlineMarker:
case Statement::Kind::kNop:
this->write("Statement()");
break;
default:
SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
break;
}
}
void DSLCPPCodeGenerator::writeFloatLiteral(const FloatLiteral& f) {
this->write(to_string(f.value()));
this->write("f");
}
void DSLCPPCodeGenerator::writeSetting(const Setting& s) {
this->writef("sk_Caps.%s", s.name().c_str());
}
bool DSLCPPCodeGenerator::writeSection(const char* name, const char* prefix) {
const Section* s = fSectionAndParameterHelper.getSection(name);
if (s) {
this->writef("%s%s", prefix, s->text().c_str());
return true;
}
return false;
}
void DSLCPPCodeGenerator::writeProgramElement(const ProgramElement& p) {
switch (p.kind()) {
case ProgramElement::Kind::kSection:
return;
case ProgramElement::Kind::kGlobalVar: {
const GlobalVarDeclaration& decl = p.as<GlobalVarDeclaration>();
const Variable& var = decl.declaration()->as<VarDeclaration>().var();
// Don't write builtin uniforms.
if (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kUniform_Flag) ||
-1 != var.modifiers().fLayout.fBuiltin) {
return;
}
this->writeVarDeclaration(decl.declaration()->as<VarDeclaration>(), /*global=*/true);
this->write(";\n");
return;
}
case ProgramElement::Kind::kFunctionPrototype:
SK_ABORT("not yet implemented: function prototypes in DSL");
return;
default:
break;
}
INHERITED::writeProgramElement(p);
}
void DSLCPPCodeGenerator::addUniform(const Variable& var) {
if (!needs_uniform_var(var)) {
return;
}
const char* varCppName = this->getVariableCppName(var);
if (var.modifiers().fLayout.fWhen.fLength) {
// In cases where the `when` clause is true, we set up the Var normally.
this->writef(
"Var %s;\n"
"if (%.*s) {\n"
" Var(",
varCppName,
(int)var.modifiers().fLayout.fWhen.size(), var.modifiers().fLayout.fWhen.data());
this->writeVarCtorExpression(var);
this->writef(").swap(%s);\n ", varCppName);
} else {
this->writeVar(var);
}
this->writef("%.*sVar = VarUniformHandle(%s);\n",
(int)var.name().size(), var.name().data(), this->getVariableCppName(var));
if (var.modifiers().fLayout.fWhen.fLength) {
this->writef(" DeclareGlobal(%s);\n", varCppName);
// In cases where the `when` is false, we declare the Var as a const with a default value.
this->writef("} else {\n"
" Var(kConst_Modifier, %s, \"%.*s\", %s).swap(%s);\n"
" Declare(%s);\n"
"}\n",
this->getDSLType(var.type()).c_str(),
(int)var.name().size(), var.name().data(),
this->getDefaultDSLValue(var).c_str(),
varCppName, varCppName);
} else {
this->writef("DeclareGlobal(%s);\n", varCppName);
}
}
void DSLCPPCodeGenerator::writeInputVars() {
}
void DSLCPPCodeGenerator::writePrivateVars() {
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const Variable& var = global.declaration()->as<VarDeclaration>().var();
if (is_private(var)) {
if (var.type().isFragmentProcessor()) {
fErrors.error(global.fOffset,
"fragmentProcessor variables must be declared 'in'");
return;
}
this->writef("%s %.*s = %s;\n",
HCodeGenerator::FieldType(fContext, var.type(),
var.modifiers().fLayout).c_str(),
(int)var.name().size(), var.name().data(),
default_value(var).c_str());
} else if (var.modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
// An auto-tracked uniform in variable, so add a field to hold onto the prior
// state. Note that tracked variables must be uniform in's and that is validated
// before writePrivateVars() is called.
const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, var);
SkASSERT(mapper);
String name = HCodeGenerator::FieldName(String(var.name()).c_str());
// The member statement is different if the mapper reports a default value
if (mapper->defaultValue().size() > 0) {
this->writef("%s %sPrev = %s;\n",
Layout::CTypeToStr(mapper->ctype()), name.c_str(),
mapper->defaultValue().c_str());
} else {
this->writef("%s %sPrev;\n",
Layout::CTypeToStr(mapper->ctype()), name.c_str());
}
}
}
}
}
void DSLCPPCodeGenerator::writePrivateVarValues() {
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
if (is_private(decl.var()) && decl.value()) {
// This function writes class member variables.
// We need to emit plain C++ names and types, not DSL.
fCPPMode = true;
this->write(decl.var().name());
this->write(" = ");
this->writeExpression(*decl.value(), Precedence::kAssignment);
this->write(";\n");
fCPPMode = false;
}
}
}
}
static bool is_accessible(const Variable& var) {
const Type& type = var.type();
return !type.isFragmentProcessor() &&
Type::TypeKind::kOther != type.typeKind();
}
bool DSLCPPCodeGenerator::writeEmitCode(std::vector<const Variable*>& uniforms) {
this->writef(" void emitCode(EmitArgs& args) override {\n"
" [[maybe_unused]] const %s& _outer = args.fFp.cast<%s>();\n"
"\n"
" using namespace SkSL::dsl;\n"
" StartFragmentProcessor(this, &args);\n",
fFullName.c_str(), fFullName.c_str());
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
const Variable& var = decl.var();
if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
continue;
}
if (SectionAndParameterHelper::IsParameter(var) && is_accessible(var)) {
const char* varCppName = this->getVariableCppName(var);
this->writef("[[maybe_unused]] const auto& %.*s = _outer.%.*s;\n"
"Var %s(kConst_Modifier, %s, \"%.*s\", ",
(int)var.name().size(), var.name().data(),
(int)var.name().size(), var.name().data(),
varCppName, this->getDSLType(var.type()).c_str(),
(int)var.name().size(), var.name().data());
this->writeCppInitialValue(var);
this->writef(");\n"
"Declare(%s);\n", varCppName);
}
}
}
this->writePrivateVarValues();
for (const Variable* u : uniforms) {
this->addUniform(*u);
}
this->writeSection(kEmitCodeSection);
// Generate mangled names and argument lists for helper functions.
std::unordered_set<const FunctionDeclaration*> definedHelpers;
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<FunctionDefinition>()) {
const FunctionDeclaration* decl = &p->as<FunctionDefinition>().declaration();
definedHelpers.insert(decl);
this->prepareHelperFunction(*decl);
}
}
// Emit prototypes for defined helper functions that originally had prototypes in the FP file.
// (If a function was prototyped but never defined, we skip it, since it wasn't prepared above.)
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<FunctionPrototype>()) {
const FunctionDeclaration* decl = &p->as<FunctionPrototype>().declaration();
if (definedHelpers.find(decl) != definedHelpers.end()) {
this->prototypeHelperFunction(*decl);
}
}
}
bool result = INHERITED::generateCode();
this->write(" EndFragmentProcessor();\n"
" }\n");
return result;
}
void DSLCPPCodeGenerator::writeSetData(std::vector<const Variable*>& uniforms) {
const char* fullName = fFullName.c_str();
const Section* section = fSectionAndParameterHelper.getSection(kSetDataSection);
const char* pdman = section ? section->argument().c_str() : "pdman";
this->writef(" void onSetData(const GrGLSLProgramDataManager& %s, "
"const GrFragmentProcessor& _proc) override {\n",
pdman);
bool wroteProcessor = false;
for (const Variable* u : uniforms) {
if (is_uniform_in(*u)) {
if (!wroteProcessor) {
this->writef(" const %s& _outer = _proc.cast<%s>();\n", fullName, fullName);
wroteProcessor = true;
this->writef(" {\n");
}
const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, *u);
SkASSERT(mapper);
String nameString(u->name());
const char* name = nameString.c_str();
// Switches for setData behavior in the generated code
bool conditionalUniform = u->modifiers().fLayout.fWhen != "";
bool isTracked = u->modifiers().fLayout.fFlags & Layout::kTracked_Flag;
bool needsValueDeclaration = isTracked || !mapper->canInlineUniformValue();
String uniformName = HCodeGenerator::FieldName(name) + "Var";
String indent = " "; // 8 by default, 12 when nested for conditional uniforms
if (conditionalUniform) {
// Add a pre-check to make sure the uniform was emitted
// before trying to send any data to the GPU
this->writef(" if (%s.isValid()) {\n", uniformName.c_str());
indent += " ";
}
String valueVar = "";
if (needsValueDeclaration) {
valueVar.appendf("%sValue", name);
// Use AccessType since that will match the return type of _outer's public API.
String valueType = HCodeGenerator::AccessType(fContext, u->type(),
u->modifiers().fLayout);
this->writef("%s%s %s = _outer.%s;\n",
indent.c_str(), valueType.c_str(), valueVar.c_str(), name);
} else {
// Not tracked and the mapper only needs to use the value once
// so send it a safe expression instead of the variable name
valueVar.appendf("(_outer.%s)", name);
}
if (isTracked) {
String prevVar = HCodeGenerator::FieldName(name) + "Prev";
this->writef("%sif (%s) {\n"
"%s %s;\n"
"%s %s;\n"
"%s}\n", indent.c_str(),
mapper->dirtyExpression(valueVar, prevVar).c_str(), indent.c_str(),
mapper->saveState(valueVar, prevVar).c_str(), indent.c_str(),
mapper->setUniform(pdman, uniformName, valueVar).c_str(), indent.c_str());
} else {
this->writef("%s%s;\n", indent.c_str(),
mapper->setUniform(pdman, uniformName, valueVar).c_str());
}
if (conditionalUniform) {
// Close the earlier precheck block
this->writef(" }\n");
}
}
}
if (wroteProcessor) {
this->writef(" }\n");
}
if (section) {
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
const Variable& variable = decl.var();
if (needs_uniform_var(variable)) {
this->writef(" [[maybe_unused]] UniformHandle& %.*s = %.*sVar;\n",
(int)variable.name().size(), variable.name().data(),
(int)variable.name().size(), variable.name().data());
} else if (SectionAndParameterHelper::IsParameter(variable) &&
!variable.type().isFragmentProcessor()) {
if (!wroteProcessor) {
this->writef(" const %s& _outer = _proc.cast<%s>();\n",
fullName, fullName);
wroteProcessor = true;
}
if (!variable.type().isFragmentProcessor()) {
this->writef(" [[maybe_unused]] const auto& %.*s = _outer.%.*s;\n",
(int)variable.name().size(), variable.name().data(),
(int)variable.name().size(), variable.name().data());
}
}
}
}
this->writeSection(kSetDataSection);
}
this->write(" }\n");
}
void DSLCPPCodeGenerator::writeClone() {
if (!this->writeSection(kCloneSection)) {
if (fSectionAndParameterHelper.getSection(kFieldsSection)) {
fErrors.error(/*offset=*/0, "fragment processors with custom @fields must also have a "
"custom @clone");
}
this->writef("%s::%s(const %s& src)\n"
": INHERITED(k%s_ClassID, src.optimizationFlags())", fFullName.c_str(),
fFullName.c_str(), fFullName.c_str(), fFullName.c_str());
for (const Variable* param : fSectionAndParameterHelper.getParameters()) {
String fieldName = HCodeGenerator::FieldName(String(param->name()).c_str());
if (!param->type().isFragmentProcessor()) {
this->writef("\n, %s(src.%s)",
fieldName.c_str(),
fieldName.c_str());
}
}
this->writef(" {\n");
this->writef(" this->cloneAndRegisterAllChildProcessors(src);\n");
if (fAccessSampleCoordsDirectly) {
this->writef(" this->setUsesSampleCoordsDirectly();\n");
}
this->write("}\n");
this->writef("std::unique_ptr<GrFragmentProcessor> %s::clone() const {\n",
fFullName.c_str());
this->writef(" return std::make_unique<%s>(*this);\n",
fFullName.c_str());
this->write("}\n");
}
}
void DSLCPPCodeGenerator::writeDumpInfo() {
this->writef("#if GR_TEST_UTILS\n"
"SkString %s::onDumpInfo() const {\n", fFullName.c_str());
if (!this->writeSection(kDumpInfoSection)) {
if (fSectionAndParameterHelper.getSection(kFieldsSection)) {
fErrors.error(/*offset=*/0, "fragment processors with custom @fields must also have a "
"custom @dumpInfo");
}
String formatString;
std::vector<String> argumentList;
for (const Variable* param : fSectionAndParameterHelper.getParameters()) {
// dumpInfo() doesn't need to log child FPs.
if (param->type().isFragmentProcessor()) {
continue;
}
// Add this field onto the format string and argument list.
String fieldName = HCodeGenerator::FieldName(String(param->name()).c_str());
String runtimeValue = this->formatRuntimeValue(param->type(),
param->modifiers().fLayout,
param->name(),
&argumentList);
formatString.appendf("%s%s=%s",
formatString.empty() ? "" : ", ",
fieldName.c_str(),
runtimeValue.c_str());
}
if (!formatString.empty()) {
// Emit the finished format string and associated arguments.
this->writef(" return SkStringPrintf(\"(%s)\"", formatString.c_str());
for (const String& argument : argumentList) {
this->writef(", %s", argument.c_str());
}
this->write(");");
} else {
// No fields to dump at all; just return an empty string.
this->write(" return SkString();");
}
}
this->write("\n"
"}\n"
"#endif\n");
}
void DSLCPPCodeGenerator::writeTest() {
const Section* test = fSectionAndParameterHelper.getSection(kTestCodeSection);
if (test) {
this->writef(
"GR_DEFINE_FRAGMENT_PROCESSOR_TEST(%s);\n"
"#if GR_TEST_UTILS\n"
"std::unique_ptr<GrFragmentProcessor> %s::TestCreate(GrProcessorTestData* %s) {\n",
fFullName.c_str(),
fFullName.c_str(),
test->argument().c_str());
this->writeSection(kTestCodeSection);
this->write("}\n"
"#endif\n");
}
}
static int bits_needed(uint32_t v) {
int bits = 1;
while (v >= (1u << bits)) {
bits++;
}
return bits;
}
void DSLCPPCodeGenerator::writeGetKey() {
auto bitsForEnum = [&](const Type& type) {
for (const ProgramElement* e : fProgram.elements()) {
if (!e->is<Enum>() || type.name() != e->as<Enum>().typeName()) {
continue;
}
SKSL_INT minVal = 0, maxVal = 0;
auto gatherEnumRange = [&](StringFragment, SKSL_INT value) {
minVal = std::min(minVal, value);
maxVal = std::max(maxVal, value);
};
e->as<Enum>().foreach(gatherEnumRange);
if (minVal < 0) {
// Found a negative value in the enum, just use 32 bits
return 32;
}
SkASSERT(SkTFitsIn<uint32_t>(maxVal));
return bits_needed(maxVal);
}
SK_ABORT("Didn't find declaring element for enum type!");
return 32;
};
this->writef("void %s::onGetGLSLProcessorKey(const GrShaderCaps& caps, "
"GrProcessorKeyBuilder* b) const {\n",
fFullName.c_str());
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
const Variable& var = decl.var();
const Type& varType = var.type();
String nameString(var.name());
const char* name = nameString.c_str();
if (var.modifiers().fLayout.fFlags & Layout::kKey_Flag) {
if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
fErrors.error(var.fOffset, "layout(key) may not be specified on uniforms");
}
if (is_private(var)) {
this->writef("%s %.*s = ",
HCodeGenerator::FieldType(fContext, varType,
var.modifiers().fLayout).c_str(),
(int)var.name().size(), var.name().data());
if (decl.value()) {
fCPPMode = true;
this->writeExpression(*decl.value(), Precedence::kAssignment);
fCPPMode = false;
} else {
this->write(default_value(var));
}
this->write(";\n");
}
if (var.modifiers().fLayout.fWhen.fLength) {
this->writef("if (%s) {\n", String(var.modifiers().fLayout.fWhen).c_str());
}
if (varType == *fContext.fTypes.fHalf4) {
this->writef(" uint16_t red = SkFloatToHalf(%s.fR);\n",
HCodeGenerator::FieldName(name).c_str());
this->writef(" uint16_t green = SkFloatToHalf(%s.fG);\n",
HCodeGenerator::FieldName(name).c_str());
this->writef(" uint16_t blue = SkFloatToHalf(%s.fB);\n",
HCodeGenerator::FieldName(name).c_str());
this->writef(" uint16_t alpha = SkFloatToHalf(%s.fA);\n",
HCodeGenerator::FieldName(name).c_str());
this->writef(" b->add32(((uint32_t)red << 16) | green, \"%s.rg\");\n", name);
this->writef(" b->add32(((uint32_t)blue << 16) | alpha, \"%s.ba\");\n",
name);
} else if (varType == *fContext.fTypes.fHalf ||
varType == *fContext.fTypes.fFloat) {
this->writef(" b->add32(sk_bit_cast<uint32_t>(%s), \"%s\");\n",
HCodeGenerator::FieldName(name).c_str(), name);
} else if (varType.isBoolean()) {
this->writef(" b->addBool(%s, \"%s\");\n",
HCodeGenerator::FieldName(name).c_str(), name);
} else if (varType.isEnum()) {
this->writef(" b->addBits(%d, (uint32_t) %s, \"%s\");\n",
bitsForEnum(varType), HCodeGenerator::FieldName(name).c_str(),
name);
} else if (varType.isInteger()) {
this->writef(" b->add32((uint32_t) %s, \"%s\");\n",
HCodeGenerator::FieldName(name).c_str(), name);
} else {
SK_ABORT("NOT YET IMPLEMENTED: automatic key handling for %s\n",
varType.displayName().c_str());
}
if (var.modifiers().fLayout.fWhen.fLength) {
this->write("}\n");
}
}
}
}
this->write("}\n");
}
bool DSLCPPCodeGenerator::generateCode() {
std::vector<const Variable*> uniforms;
for (const ProgramElement* p : fProgram.elements()) {
if (p->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>();
const VarDeclaration& decl = global.declaration()->as<VarDeclaration>();
SkASSERT(decl.var().type().typeKind() != Type::TypeKind::kSampler);
if (decl.var().modifiers().fFlags & Modifiers::kUniform_Flag) {
uniforms.push_back(&decl.var());
}
if (is_uniform_in(decl.var())) {
// Validate the "uniform in" declarations to make sure they are fully supported,
// instead of generating surprising C++
const UniformCTypeMapper* mapper = UniformCTypeMapper::Get(fContext, decl.var());
if (mapper == nullptr) {
fErrors.error(decl.fOffset, String(decl.var().name())
+ "'s type is not supported for use as a 'uniform in'");
return false;
}
} else {
// If it's not a uniform_in, it's an error to be tracked
if (decl.var().modifiers().fLayout.fFlags & Layout::kTracked_Flag) {
fErrors.error(decl.fOffset, "Non-'in uniforms' cannot be tracked");
return false;
}
}
}
}
const char* baseName = fName.c_str();
const char* fullName = fFullName.c_str();
this->writef("%s\n", HCodeGenerator::GetHeader(fProgram, fErrors).c_str());
this->writef(kFragmentProcessorHeader, fullName);
this->write("/* TODO(skia:11854): DSLCPPCodeGenerator is currently a work in progress. */\n");
this->writef("#include \"%s.h\"\n\n", fullName);
this->writeSection(kCppSection);
this->writef("#include \"src/core/SkUtils.h\"\n"
"#include \"src/gpu/GrTexture.h\"\n"
"#include \"src/gpu/glsl/GrGLSLFragmentProcessor.h\"\n"
"#include \"src/gpu/glsl/GrGLSLFragmentShaderBuilder.h\"\n"
"#include \"src/gpu/glsl/GrGLSLProgramBuilder.h\"\n"
"#include \"src/sksl/SkSLCPP.h\"\n"
"#include \"src/sksl/SkSLUtil.h\"\n"
"#include \"src/sksl/dsl/priv/DSLFPs.h\"\n"
"#include \"src/sksl/dsl/priv/DSLWriter.h\"\n"
"\n"
"class GrGLSL%s : public GrGLSLFragmentProcessor {\n"
"public:\n"
" GrGLSL%s() {}\n",
baseName, baseName);
bool result = this->writeEmitCode(uniforms);
this->write("private:\n");
this->writeSetData(uniforms);
this->writePrivateVars();
for (const Variable* u : uniforms) {
if (needs_uniform_var(*u) && !(u->modifiers().fFlags & Modifiers::kIn_Flag)) {
this->writef(" UniformHandle %.*sVar;\n", (int)u->name().size(), u->name().data());
}
}
for (const Variable* param : fSectionAndParameterHelper.getParameters()) {
if (needs_uniform_var(*param)) {
this->writef(" UniformHandle %.*sVar;\n",
(int)param->name().size(), param->name().data());
}
}
this->writef("};\n"
"std::unique_ptr<GrGLSLFragmentProcessor> %s::onMakeProgramImpl() const {\n"
" return std::make_unique<GrGLSL%s>();\n"
"}\n",
fullName, baseName);
this->writeGetKey();
this->writef("bool %s::onIsEqual(const GrFragmentProcessor& other) const {\n"
" const %s& that = other.cast<%s>();\n"
" (void) that;\n",
fullName, fullName, fullName);
for (const auto& param : fSectionAndParameterHelper.getParameters()) {
if (param->type().isFragmentProcessor()) {
continue;
}
String nameString(param->name());
const char* name = nameString.c_str();
this->writef(" if (%s != that.%s) return false;\n",
HCodeGenerator::FieldName(name).c_str(),
HCodeGenerator::FieldName(name).c_str());
}
this->write(" return true;\n"
"}\n");
this->writeClone();
this->writeDumpInfo();
this->writeTest();
this->writeSection(kCppEndSection);
result &= 0 == fErrors.errorCount();
return result;
}
} // namespace SkSL
#endif // defined(SKSL_STANDALONE) || GR_TEST_UTILS