Remove PipelineStageArgs and format-string handshake

There is now PipelineStage::ConvertProgram, which takes a collection of
callbacks, and processes an entire program. For program objects that may
need name mangling, the callbacks return the new name, which is recorded
and used for future references to that object (eg uniforms & functions).

The callbacks let the FP inject new elements programmatically:
  - Declare uniforms and get handles
  - Emit child functions
  - Invoke child processors for calls to sample()

In a follow-up CL, we can add an skslc `.rte -> .sksl` mode, where the
callbacks just emit the description() of the relevant element. We can
also follow the same pattern to emit declarations of types (structs,
enums), and global variables.

Change-Id: I81df68a2f41bcb48f866d37af3b77ad43e880236
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/367058
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
This commit is contained in:
Brian Osman 2021-02-05 16:30:34 -05:00 committed by Skia Commit-Bot
parent 78c30176c6
commit 690b6f3a92
4 changed files with 268 additions and 290 deletions

View File

@ -8,6 +8,9 @@
#include "src/gpu/GrShaderCaps.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLPipelineStageCodeGenerator.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "fuzz/Fuzz.h"
@ -24,8 +27,30 @@ bool FuzzSKSL2Pipeline(sk_sp<SkData> bytes) {
return false;
}
SkSL::PipelineStage::Args args;
SkSL::PipelineStage::ConvertProgram(*program, &args);
class Callbacks : public SkSL::PipelineStage::Callbacks {
using String = SkSL::String;
String declareUniform(const SkSL::VarDeclaration* decl) override {
return decl->var().name();
}
String defineFunction(const SkSL::FunctionDeclaration* decl, String /*body*/) override {
return decl->name();
}
String sampleChild(int index, String coords) override {
return SkSL::String::printf("sample(%d%s%s)", index, coords.empty() ? "" : ", ",
coords.c_str());
}
String sampleChildWithMatrix(int index, String matrix) override {
return SkSL::String::printf("sample(%d%s%s)", index, matrix.empty() ? "" : ", ",
matrix.c_str());
}
};
Callbacks callbacks;
SkSL::PipelineStage::ConvertProgram(*program, "coords", &callbacks);
return true;
}

View File

@ -14,6 +14,7 @@
#include "src/gpu/GrTexture.h"
#include "src/sksl/SkSLPipelineStageCodeGenerator.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
@ -21,76 +22,10 @@
class GrGLSLSkSLFP : public GrGLSLFragmentProcessor {
public:
GrGLSLSkSLFP(SkSL::PipelineStage::Args&& args) : fArgs(std::move(args)) {}
SkSL::String expandFormatArgs(
const SkSL::String& raw,
EmitArgs& args,
const char* sampleCoords,
std::vector<SkSL::PipelineStage::FormatArg>::const_iterator& fmtArg) {
SkSL::String result;
int substringStartIndex = 0;
for (size_t i = 0; i < raw.length(); ++i) {
char c = raw[i];
if (c == SkSL::PipelineStage::kFormatArgPlaceholder) {
result += SkSL::StringFragment(raw.c_str() + substringStartIndex,
i - substringStartIndex);
const SkSL::PipelineStage::FormatArg& arg = *fmtArg++;
switch (arg.fKind) {
case SkSL::PipelineStage::FormatArg::Kind::kCoords:
// See note about helper functions in emitCode
SkASSERT(sampleCoords);
result += sampleCoords ? sampleCoords : "float2(0)";
break;
case SkSL::PipelineStage::FormatArg::Kind::kUniform:
result += args.fUniformHandler->getUniformCStr(fUniformHandles[arg.fIndex]);
break;
case SkSL::PipelineStage::FormatArg::Kind::kChildProcessor: {
SkSL::String coords =
this->expandFormatArgs(arg.fCoords, args, sampleCoords, fmtArg);
result += this->invokeChild(arg.fIndex, args, coords).c_str();
break;
}
case SkSL::PipelineStage::FormatArg::Kind::kChildProcessorWithMatrix: {
const auto& fp(args.fFp.cast<GrSkSLFP>());
const auto& sampleUsages(fp.fEffect->fSampleUsages);
SkASSERT((size_t)arg.fIndex < sampleUsages.size());
const SkSL::SampleUsage& sampleUsage(sampleUsages[arg.fIndex]);
SkSL::String coords =
this->expandFormatArgs(arg.fCoords, args, sampleCoords, fmtArg);
result += this->invokeChildWithMatrix(
arg.fIndex, args,
sampleUsage.hasUniformMatrix() ? "" : coords)
.c_str();
break;
}
case SkSL::PipelineStage::FormatArg::Kind::kFunctionName:
SkASSERT((int) fFunctionNames.size() > arg.fIndex);
result += fFunctionNames[arg.fIndex].c_str();
break;
}
substringStartIndex = i + 1;
}
}
result += SkSL::StringFragment(raw.c_str() + substringStartIndex,
raw.length() - substringStartIndex);
return result;
}
void emitCode(EmitArgs& args) override {
const GrSkSLFP& fp = args.fFp.cast<GrSkSLFP>();
for (const auto& v : fp.fEffect->uniforms()) {
auto handle = args.fUniformHandler->addUniformArray(&fp,
kFragment_GrShaderFlag,
v.gpuType,
v.name.c_str(),
v.isArray() ? v.count : 0);
fUniformHandles.push_back(handle);
}
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
std::vector<SkString> childNames;
const GrSkSLFP& fp = args.fFp.cast<GrSkSLFP>();
const SkSL::Program& program = *fp.fEffect->fBaseProgram;
// We need to ensure that we emit each child's helper function at least once.
// Any child FP that isn't sampled won't trigger a call otherwise, leading to asserts later.
for (int i = 0; i < this->numChildProcessors(); ++i) {
@ -98,28 +33,89 @@ public:
this->emitChildFunction(i, args);
}
}
for (const auto& f : fArgs.fFunctions) {
fFunctionNames.push_back(
fragBuilder->getMangledFunctionName(SkString(f.fDecl->name()).c_str()));
auto fmtArgIter = f.fFormatArgs.cbegin();
// Helper functions can't refer to sample-coords directly (they're a parameter to main)
SkSL::String body =
this->expandFormatArgs(f.fBody, args, /*sampleCoords=*/nullptr, fmtArgIter);
SkASSERT(fmtArgIter == f.fFormatArgs.cend());
fragBuilder->emitFunction(f.fDecl,
fFunctionNames.back().c_str(),
body.c_str());
}
SkString coordsVarName = fragBuilder->newTmpVarName("coords");
class FPCallbacks : public SkSL::PipelineStage::Callbacks {
public:
FPCallbacks(GrGLSLSkSLFP* self, EmitArgs& args, const SkSL::Context& context)
: fSelf(self), fArgs(args), fContext(context) {}
using String = SkSL::String;
String declareUniform(const SkSL::VarDeclaration* decl) override {
const SkSL::Variable& var = decl->var();
if (var.type().isOpaque()) {
// Nothing to do. The only opaque type we should see is fragmentProcessor, and
// those (children) are handled specially, above.
SkASSERT(var.type() == *fContext.fTypes.fFragmentProcessor);
return String(var.name());
}
const SkSL::Type* type = &var.type();
bool isArray = false;
if (type->isArray()) {
type = &type->componentType();
isArray = true;
}
GrSLType gpuType;
SkAssertResult(SkSL::type_to_grsltype(fContext, *type, &gpuType));
const char* uniformName = nullptr;
auto handle =
fArgs.fUniformHandler->addUniformArray(&fArgs.fFp.cast<GrSkSLFP>(),
kFragment_GrShaderFlag,
gpuType,
SkString(var.name()).c_str(),
isArray ? var.type().columns() : 0,
&uniformName);
fSelf->fUniformHandles.push_back(handle);
return String(uniformName);
}
String defineFunction(const SkSL::FunctionDeclaration* decl, String body) override {
GrGLSLFPFragmentBuilder* fragBuilder = fArgs.fFragBuilder;
if (decl->name() == "main") {
fragBuilder->codeAppend(body.c_str());
return String("main");
} else {
SkString mangledName =
fragBuilder->getMangledFunctionName(SkString(decl->name()).c_str());
fragBuilder->emitFunction(decl, mangledName.c_str(), body.c_str());
return String(mangledName.c_str());
}
}
String sampleChild(int index, String coords) override {
return String(fSelf->invokeChild(index, fArgs, coords).c_str());
}
String sampleChildWithMatrix(int index, String matrix) override {
// If the child is sampled with a uniform matrix, we need to pass the empty string.
// 'invokeChildWithMatrix' will assert that the passed-in matrix matches the one
// extracted from the SkSL when the sample usages were determined. We've mangled
// the uniform names, though, so it won't match.
const GrFragmentProcessor* child = fArgs.fFp.childProcessor(index);
const bool hasUniformMatrix = child && child->sampleUsage().hasUniformMatrix();
return String(
fSelf->invokeChildWithMatrix(index, fArgs, hasUniformMatrix ? "" : matrix)
.c_str());
}
GrGLSLSkSLFP* fSelf;
EmitArgs& fArgs;
const SkSL::Context& fContext;
};
FPCallbacks callbacks(this, args, *program.fContext);
// Callback to define a function (and return its mangled name)
SkString coordsVarName = args.fFragBuilder->newTmpVarName("coords");
const char* coords = nullptr;
if (fp.referencesSampleCoords()) {
coords = coordsVarName.c_str();
fragBuilder->codeAppendf("float2 %s = %s;\n", coords, args.fSampleCoord);
args.fFragBuilder->codeAppendf("float2 %s = %s;\n", coords, args.fSampleCoord);
}
auto fmtArgIter = fArgs.fFormatArgs.cbegin();
fragBuilder->codeAppend(
this->expandFormatArgs(fArgs.fCode, args, coords, fmtArgIter).c_str());
SkASSERT(fmtArgIter == fArgs.fFormatArgs.cend());
SkSL::PipelineStage::ConvertProgram(program, coords, &callbacks);
}
void onSetData(const GrGLSLProgramDataManager& pdman,
@ -158,9 +154,7 @@ public:
}
}
SkSL::PipelineStage::Args fArgs;
std::vector<UniformHandle> fUniformHandles;
std::vector<SkString> fFunctionNames;
};
std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(GrContext_Base* context, sk_sp<SkRuntimeEffect> effect,
@ -208,10 +202,7 @@ void GrSkSLFP::addChild(std::unique_ptr<GrFragmentProcessor> child) {
}
GrGLSLFragmentProcessor* GrSkSLFP::onCreateGLSLInstance() const {
// Note: This is actually SkSL (again) but with inline format specifiers.
SkSL::PipelineStage::Args args;
SkSL::PipelineStage::ConvertProgram(*fEffect->fBaseProgram, &args);
return new GrGLSLSkSLFP(std::move(args));
return new GrGLSLSkSLFP();
}
void GrSkSLFP::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const {

View File

@ -30,14 +30,21 @@
#include "src/sksl/ir/SkSLVarDeclarations.h"
#include "src/sksl/ir/SkSLVariableReference.h"
#include <unordered_map>
#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU
namespace SkSL {
namespace PipelineStage {
class PipelineStageCodeGenerator {
public:
PipelineStageCodeGenerator(const Program& program, PipelineStage::Args* outArgs)
: fProgram(program), fArgs(outArgs) {}
PipelineStageCodeGenerator(const Program& program,
const char* sampleCoords,
Callbacks* callbacks)
: fProgram(program)
, fSampleCoords(sampleCoords)
, fCallbacks(callbacks) {}
void generateCode();
@ -51,12 +58,12 @@ private:
void writeType(const Type& type);
void writeFunctionDeclaration(const FunctionDeclaration& f);
void writeFunction(const FunctionDefinition& f);
void writeModifiers(const Modifiers& modifiers);
void writeVarDeclaration(const VarDeclaration& var);
void writeGlobalVarDeclaration(const GlobalVarDeclaration& g);
void writeExpression(const Expression& expr, Precedence parentPrecedence);
void writeFunctionCall(const FunctionCall& c);
@ -93,10 +100,15 @@ private:
StringStream fBuffer;
};
const Program& fProgram;
PipelineStage::Args* fArgs;
StringStream* fBuffer;
bool fCastReturnsToHalf = false;
const Program& fProgram;
const char* fSampleCoords;
Callbacks* fCallbacks;
std::unordered_map<const Variable*, String> fUniformNames;
std::unordered_map<const FunctionDeclaration*, String> fFunctionNames;
StringStream* fBuffer = nullptr;
bool fCastReturnsToHalf = false;
};
void PipelineStageCodeGenerator::write(const char* s) {
@ -121,11 +133,9 @@ void PipelineStageCodeGenerator::write(StringFragment s) {
void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
const FunctionDeclaration& function = c.function();
const ExpressionArray& arguments = c.arguments();
if (function.isBuiltin() && function.name() == "sample" &&
arguments[0]->type().typeKind() != Type::TypeKind::kSampler) {
if (function.isBuiltin() && function.name() == "sample") {
SkASSERT(arguments.size() <= 2);
SkDEBUGCODE(const Type& arg0Type = arguments[0]->type());
SkASSERT("fragmentProcessor" == arg0Type.name());
SkASSERT("fragmentProcessor" == arguments[0]->type().name());
SkASSERT(arguments[0]->is<VariableReference>());
int index = 0;
bool found = false;
@ -144,100 +154,85 @@ void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
}
}
SkASSERT(found);
size_t childCallIndex = fArgs->fFormatArgs.size();
this->write(PipelineStage::kFormatArgPlaceholderStr);
bool matrixCall = arguments.size() == 2 && arguments[1]->type().isMatrix();
fArgs->fFormatArgs.push_back(PipelineStage::FormatArg(
matrixCall ? PipelineStage::FormatArg::Kind::kChildProcessorWithMatrix
: PipelineStage::FormatArg::Kind::kChildProcessor,
index));
String coordsOrMatrix;
if (arguments.size() > 1) {
AutoOutputBuffer outputToBuffer(this);
this->writeExpression(*arguments[1], Precedence::kSequence);
fArgs->fFormatArgs[childCallIndex].fCoords = outputToBuffer.fBuffer.str();
coordsOrMatrix = outputToBuffer.fBuffer.str();
}
bool matrixCall = arguments.size() == 2 && arguments[1]->type().isMatrix();
if (matrixCall) {
this->write(fCallbacks->sampleChildWithMatrix(index, std::move(coordsOrMatrix)));
} else {
this->write(fCallbacks->sampleChild(index, std::move(coordsOrMatrix)));
}
return;
}
if (function.isBuiltin()) {
this->write(function.name());
this->write("(");
const char* separator = "";
for (const auto& arg : arguments) {
this->write(separator);
separator = ", ";
this->writeExpression(*arg, Precedence::kSequence);
}
this->write(")");
} else {
int index = 0;
for (const ProgramElement* e : fProgram.elements()) {
if (e->is<FunctionDefinition>()) {
if (&e->as<FunctionDefinition>().declaration() == &function) {
break;
}
++index;
}
}
this->write(PipelineStage::kFormatArgPlaceholderStr);
fArgs->fFormatArgs.push_back(
PipelineStage::FormatArg(PipelineStage::FormatArg::Kind::kFunctionName, index));
this->write("(");
const char* separator = "";
for (const std::unique_ptr<Expression>& arg : arguments) {
this->write(separator);
separator = ", ";
this->writeExpression(*arg, Precedence::kSequence);
}
this->write(")");
auto it = fFunctionNames.find(&function);
SkASSERT(it != fFunctionNames.end());
this->write(it->second);
}
this->write("(");
const char* separator = "";
for (const auto& arg : arguments) {
this->write(separator);
separator = ", ";
this->writeExpression(*arg, Precedence::kSequence);
}
this->write(")");
}
void PipelineStageCodeGenerator::writeVariableReference(const VariableReference& ref) {
switch (ref.variable()->modifiers().fLayout.fBuiltin) {
case SK_MAIN_COORDS_BUILTIN:
this->write(PipelineStage::kFormatArgPlaceholderStr);
fArgs->fFormatArgs.push_back(
PipelineStage::FormatArg(PipelineStage::FormatArg::Kind::kCoords));
break;
default: {
auto varIndexByFlag = [this, &ref](uint32_t flag) {
int index = 0;
bool found = false;
for (const ProgramElement* e : fProgram.elements()) {
if (found) {
break;
}
if (e->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = e->as<GlobalVarDeclaration>();
const Variable& var = global.declaration()->as<VarDeclaration>().var();
if (&var == ref.variable()) {
found = true;
break;
}
// Skip over fragmentProcessors (shaders).
// These are indexed separately from other globals.
if (var.modifiers().fFlags & flag &&
var.type() != *fProgram.fContext->fTypes.fFragmentProcessor) {
++index;
}
}
}
SkASSERT(found);
return index;
};
const Variable* var = ref.variable();
const Modifiers& modifiers = var->modifiers();
if (ref.variable()->modifiers().fFlags & Modifiers::kUniform_Flag) {
this->write(PipelineStage::kFormatArgPlaceholderStr);
fArgs->fFormatArgs.push_back(
PipelineStage::FormatArg(PipelineStage::FormatArg::Kind::kUniform,
varIndexByFlag(Modifiers::kUniform_Flag)));
} else if (ref.variable()->modifiers().fFlags & Modifiers::kVarying_Flag) {
this->write("_vtx_attr_");
this->write(to_string(varIndexByFlag(Modifiers::kVarying_Flag)));
} else {
this->write(ref.variable()->name());
if (modifiers.fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
this->write(fSampleCoords);
return;
}
auto varIndexByFlag = [this, &ref](uint32_t flag) {
int index = 0;
bool found = false;
for (const ProgramElement* e : fProgram.elements()) {
if (found) {
break;
}
if (e->is<GlobalVarDeclaration>()) {
const GlobalVarDeclaration& global = e->as<GlobalVarDeclaration>();
const Variable& var = global.declaration()->as<VarDeclaration>().var();
if (&var == ref.variable()) {
found = true;
break;
}
// Skip over fragmentProcessors (shaders).
// These are indexed separately from other globals.
if (var.modifiers().fFlags & flag &&
var.type() != *fProgram.fContext->fTypes.fFragmentProcessor) {
++index;
}
}
}
SkASSERT(found);
return index;
};
if (modifiers.fFlags & Modifiers::kUniform_Flag) {
auto it = fUniformNames.find(var);
SkASSERT(it != fUniformNames.end());
this->write(it->second);
} else if (modifiers.fFlags & Modifiers::kVarying_Flag) {
this->write("_vtx_attr_");
this->write(to_string(varIndexByFlag(Modifiers::kVarying_Flag)));
} else {
this->write(var->name());
}
}
@ -271,37 +266,48 @@ void PipelineStageCodeGenerator::writeReturnStatement(const ReturnStatement& r)
}
void PipelineStageCodeGenerator::writeFunction(const FunctionDefinition& f) {
PipelineStage::Function result;
if (f.declaration().name() == "main") {
// We allow public SkSL's main() to return half4 -or- float4 (ie vec4). When we emit
// our code in the processor, the surrounding code is going to expect half4, so we
// explicitly cast any returns (from main) to half4. This is only strictly necessary
// if the return type is float4 - injecting it unconditionally reduces the risk of an
// obscure bug.
fCastReturnsToHalf = true;
for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
this->writeStatement(*stmt);
this->writeLine();
}
fCastReturnsToHalf = false;
} else {
AutoOutputBuffer outputToBuffer(this);
result.fDecl = &f.declaration();
for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
this->writeStatement(*stmt);
this->writeLine();
}
AutoOutputBuffer body(this);
result.fBody = outputToBuffer.fBuffer.str();
result.fFormatArgs = std::move(fArgs->fFormatArgs);
fArgs->fFunctions.push_back(std::move(result));
// We allow public SkSL's main() to return half4 -or- float4 (ie vec4). When we emit
// our code in the processor, the surrounding code is going to expect half4, so we
// explicitly cast any returns (from main) to half4. This is only strictly necessary
// if the return type is float4 - injecting it unconditionally reduces the risk of an
// obscure bug.
bool isMain = f.declaration().name() == "main";
if (isMain) {
fCastReturnsToHalf = true;
}
for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) {
this->writeStatement(*stmt);
this->writeLine();
}
if (isMain) {
fCastReturnsToHalf = false;
}
String fnName = fCallbacks->defineFunction(&f.declaration(), body.fBuffer.str());
fFunctionNames.insert({&f.declaration(), std::move(fnName)});
}
void PipelineStageCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& g) {
const VarDeclaration& decl = g.declaration()->as<VarDeclaration>();
const Variable& var = decl.var();
if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
String uniformName = fCallbacks->declareUniform(&decl);
fUniformNames.insert({&var, std::move(uniformName)});
} else {
// TODO: Handle non-uniform global variable declarations. (skbug.com/11295)
}
}
void PipelineStageCodeGenerator::writeProgramElement(const ProgramElement& e) {
switch (e.kind()) {
case ProgramElement::Kind::kGlobalVar:
// All global variables are manually re-declared by the FP
this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>());
break;
case ProgramElement::Kind::kFunction:
this->writeFunction(e.as<FunctionDefinition>());
@ -310,17 +316,15 @@ void PipelineStageCodeGenerator::writeProgramElement(const ProgramElement& e) {
// Runtime effects don't allow calls to undefined functions, so prototypes are never
// necessary. If we do support them, they should emit calls to emitFunctionPrototype.
break;
case ProgramElement::Kind::kModifiers: {
const Modifiers& modifiers = e.as<ModifiersDeclaration>().modifiers();
this->writeModifiers(modifiers);
this->writeLine(";");
break;
}
case ProgramElement::Kind::kEnum:
// Custom types (enums and structs) are ignored (so they don't yet work in runtime effects).
// We need to emit their declarations (via callback), with name mangling support.
case ProgramElement::Kind::kEnum: // skbug.com/11296
case ProgramElement::Kind::kStructDefinition: // skbug.com/10939
case ProgramElement::Kind::kExtension:
case ProgramElement::Kind::kInterfaceBlock:
case ProgramElement::Kind::kModifiers:
case ProgramElement::Kind::kSection:
case ProgramElement::Kind::kStructDefinition:
default:
SkDEBUGFAILF("unsupported program element %s\n", e.description().c_str());
break;
@ -473,33 +477,6 @@ void PipelineStageCodeGenerator::writePostfixExpression(const PostfixExpression&
}
}
void PipelineStageCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) {
this->writeType(f.returnType());
this->write(" " + f.name() + "(");
const char* separator = "";
for (const auto& param : f.parameters()) {
this->write(separator);
separator = ", ";
this->writeModifiers(param->modifiers());
std::vector<int> sizes;
const Type* type = &param->type();
if (type->isArray()) {
sizes.push_back(type->columns());
type = &type->componentType();
}
this->writeType(*type);
this->write(" " + param->name());
for (int s : sizes) {
if (s == Type::kUnsizedArray) {
this->write("[]");
} else {
this->write("[" + to_string(s) + "]");
}
}
}
this->write(")");
}
void PipelineStageCodeGenerator::writeModifiers(const Modifiers& modifiers) {
if (modifiers.fFlags & Modifiers::kFlat_Flag) {
this->write("flat ");
@ -647,9 +624,6 @@ void PipelineStageCodeGenerator::writeForStatement(const ForStatement& f) {
}
void PipelineStageCodeGenerator::generateCode() {
StringStream mainBody;
fBuffer = &mainBody;
// Write all the program elements except for functions.
for (const ProgramElement* e : fProgram.elements()) {
if (!e->is<FunctionDefinition>()) {
@ -657,8 +631,7 @@ void PipelineStageCodeGenerator::generateCode() {
}
}
// Write the functions last.
// Why don't we write things in their original order? Because the Inliner likes to move function
// We always place FunctionDefinition elements last, because the inliner likes to move function
// bodies around. After inlining, code can inadvertently move upwards, above ProgramElements
// that the code relies on.
for (const ProgramElement* e : fProgram.elements()) {
@ -666,15 +639,16 @@ void PipelineStageCodeGenerator::generateCode() {
this->writeProgramElement(*e);
}
}
fArgs->fCode = mainBody.str();
}
void PipelineStage::ConvertProgram(const Program& program, Args* outArgs) {
PipelineStageCodeGenerator generator(program, outArgs);
void ConvertProgram(const Program& program,
const char* sampleCoords,
Callbacks* callbacks) {
PipelineStageCodeGenerator generator(program, sampleCoords, callbacks);
generator.generateCode();
}
} // namespace PipelineStage
} // namespace SkSL
#endif

View File

@ -10,54 +10,42 @@
#include "src/sksl/SkSLString.h"
#include <vector>
// TODO: This can now be used in SKSL_STANDALONE, with shim code for all of the callbacks.
#if !defined(SKSL_STANDALONE) && SK_SUPPORT_GPU
namespace SkSL {
class FunctionDeclaration;
struct Program;
class VarDeclaration;
class PipelineStage {
public:
// An invalid (otherwise unused) character to mark where FormatArgs are inserted
static constexpr char kFormatArgPlaceholder = '\001';
static constexpr const char* kFormatArgPlaceholderStr = "\001";
struct FormatArg {
enum class Kind {
kCoords,
kUniform,
kChildProcessor,
kChildProcessorWithMatrix,
kFunctionName
};
FormatArg(Kind kind, int index = 0) : fKind(kind), fIndex(index) {}
Kind fKind;
int fIndex;
String fCoords;
namespace PipelineStage {
class Callbacks {
public:
virtual ~Callbacks() = default;
virtual String declareUniform(const VarDeclaration*) = 0;
virtual String defineFunction(const FunctionDeclaration*, String body) = 0;
virtual String sampleChild(int index, String coords) = 0;
virtual String sampleChildWithMatrix(int index, String matrix) = 0;
};
/**
* Represents the arguments to GrGLSLShaderBuilder::emitFunction.
/*
* Processes 'program' for use in a GrFragmentProcessor, or other context that wants SkSL-like
* code as input. To support fragment processor usage, there are callbacks that allow elements
* to be declared programmatically and to rename those elements (mangling to avoid collisions).
*
* - Any reference to the main coords builtin variable will be replaced with 'sampleCoords'.
* - Each uniform variable declaration triggers a call to 'declareUniform', which should emit
* the declaration, and return the (possibly different) name to use for the variable.
* - Each function definition triggers a call to 'defineFunction', which should emit the
* definition, and return the (possibly different) name to use for calls to that function.
* - Each invocation of sample() triggers a call to 'sampleChild' or 'sampleChildWithMatrix',
* which should return the full text of the call expression.
*/
struct Function {
const FunctionDeclaration* fDecl;
String fBody;
std::vector<FormatArg> fFormatArgs;
};
struct Args {
String fCode;
std::vector<FormatArg> fFormatArgs;
std::vector<Function> fFunctions;
};
static void ConvertProgram(const Program& program, Args* outArgs);
};
void ConvertProgram(const Program& program,
const char* sampleCoords,
Callbacks* callbacks);
} // namespace PipelineStage
} // namespace SkSL