Added an API for creating RuntimeEffects using the SkSL DSL.

Change-Id: I305016d305455e2b90fe904d8da93cf7735cc38e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/389316
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
This commit is contained in:
Ethan Nicholas 2021-04-16 14:54:43 -04:00 committed by Skia Commit-Bot
parent 224e5e4d7e
commit 624a529fbd
28 changed files with 546 additions and 68 deletions

View File

@ -26,6 +26,7 @@ skia_sksl_sources = [
"$_include/sksl/DSLExpression.h",
"$_include/sksl/DSLFunction.h",
"$_include/sksl/DSLModifiers.h",
"$_include/sksl/DSLRuntimeEffects.h",
"$_include/sksl/DSLStatement.h",
"$_include/sksl/DSLType.h",
"$_include/sksl/DSLVar.h",
@ -80,6 +81,7 @@ skia_sksl_sources = [
"$_src/sksl/dsl/DSLCore.cpp",
"$_src/sksl/dsl/DSLExpression.cpp",
"$_src/sksl/dsl/DSLFunction.cpp",
"$_src/sksl/dsl/DSLRuntimeEffects.cpp",
"$_src/sksl/dsl/DSLStatement.cpp",
"$_src/sksl/dsl/DSLType.cpp",
"$_src/sksl/dsl/DSLVar.cpp",

View File

@ -245,6 +245,7 @@ tests_sources = [
"$_tests/SkColor4fTest.cpp",
"$_tests/SkColorSpaceXformStepsTest.cpp",
"$_tests/SkDOMTest.cpp",
"$_tests/SkDSLRuntimeEffectTest.cpp",
"$_tests/SkFixed15Test.cpp",
"$_tests/SkGaussFilterTest.cpp",
"$_tests/SkGlyphBufferTest.cpp",

View File

@ -126,6 +126,10 @@ public:
return Make(std::move(sksl), Options{});
}
static Result MakeForColorFilter(std::unique_ptr<SkSL::Program> program);
static Result MakeForShader(std::unique_ptr<SkSL::Program> program);
sk_sp<SkShader> makeShader(sk_sp<SkData> uniforms,
sk_sp<SkShader> children[],
size_t childCount,
@ -203,8 +207,13 @@ private:
std::vector<Varying>&& varyings,
uint32_t flags);
static Result Make(std::unique_ptr<SkSL::Program> program, SkSL::ProgramKind kind);
static Result Make(SkString sksl, const Options& options, SkSL::ProgramKind kind);
static Result Make(SkString sksl, std::unique_ptr<SkSL::Program> program,
const Options& options, SkSL::ProgramKind kind);
uint32_t hash() const { return fHash; }
bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); }
bool allowShader() const { return (fFlags & kAllowShader_Flag); }

View File

@ -41,6 +41,7 @@ private:
SkSL::Modifiers fModifiers;
friend DSLType Struct(const char* name, SkTArray<DSLField> fields);
friend class DSLFunction;
friend class DSLVar;
friend class DSLWriter;
};

View File

@ -0,0 +1,33 @@
/*
* 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_DSL_RUNTIME_EFFECTS
#define SKSL_DSL_RUNTIME_EFFECTS
#include "include/sksl/DSL.h"
class SkRuntimeEffect;
namespace SkSL {
class Compiler;
namespace dsl {
#ifndef SKSL_STANDALONE
void StartRuntimeShader(SkSL::Compiler* compiler);
sk_sp<SkRuntimeEffect> EndRuntimeShader();
#endif
} // namespace dsl
} // namespace SkSL
#endif

View File

@ -58,6 +58,7 @@ enum TypeConstant : uint8_t {
kInt2_Type,
kInt3_Type,
kInt4_Type,
kShader_Type,
kShort_Type,
kShort2_Type,
kShort3_Type,

View File

@ -40,6 +40,10 @@ public:
~DSLVar();
const char* name() const {
return fName;
}
DSLExpression x() {
return DSLExpression(*this).x();
}
@ -115,10 +119,6 @@ private:
*/
DSLVar(const char* name);
const char* name() const {
return fName;
}
DSLModifiers fModifiers;
// We only need to keep track of the type here so that we can create the SkSL::Variable. For
// predefined variables this field is unnecessary, so we don't bother tracking it and just set

View File

@ -109,31 +109,67 @@ static bool init_uniform_type(const SkSL::Context& ctx,
const SkSL::Type* type,
SkRuntimeEffect::Uniform* v) {
using Type = SkRuntimeEffect::Uniform::Type;
if (*type == *ctx.fTypes.fFloat) { v->type = Type::kFloat; return true; }
if (*type == *ctx.fTypes.fHalf) { v->type = Type::kFloat; return true; }
if (*type == *ctx.fTypes.fFloat2) { v->type = Type::kFloat2; return true; }
if (*type == *ctx.fTypes.fHalf2) { v->type = Type::kFloat2; return true; }
if (*type == *ctx.fTypes.fFloat3) { v->type = Type::kFloat3; return true; }
if (*type == *ctx.fTypes.fHalf3) { v->type = Type::kFloat3; return true; }
if (*type == *ctx.fTypes.fFloat4) { v->type = Type::kFloat4; return true; }
if (*type == *ctx.fTypes.fHalf4) { v->type = Type::kFloat4; return true; }
if (*type == *ctx.fTypes.fFloat2x2) { v->type = Type::kFloat2x2; return true; }
if (*type == *ctx.fTypes.fHalf2x2) { v->type = Type::kFloat2x2; return true; }
if (*type == *ctx.fTypes.fFloat3x3) { v->type = Type::kFloat3x3; return true; }
if (*type == *ctx.fTypes.fHalf3x3) { v->type = Type::kFloat3x3; return true; }
if (*type == *ctx.fTypes.fFloat4x4) { v->type = Type::kFloat4x4; return true; }
if (*type == *ctx.fTypes.fHalf4x4) { v->type = Type::kFloat4x4; return true; }
if (type == ctx.fTypes.fFloat.get()) { v->type = Type::kFloat; return true; }
if (type == ctx.fTypes.fHalf.get()) { v->type = Type::kFloat; return true; }
if (type == ctx.fTypes.fFloat2.get()) { v->type = Type::kFloat2; return true; }
if (type == ctx.fTypes.fHalf2.get()) { v->type = Type::kFloat2; return true; }
if (type == ctx.fTypes.fFloat3.get()) { v->type = Type::kFloat3; return true; }
if (type == ctx.fTypes.fHalf3.get()) { v->type = Type::kFloat3; return true; }
if (type == ctx.fTypes.fFloat4.get()) { v->type = Type::kFloat4; return true; }
if (type == ctx.fTypes.fHalf4.get()) { v->type = Type::kFloat4; return true; }
if (type == ctx.fTypes.fFloat2x2.get()) { v->type = Type::kFloat2x2; return true; }
if (type == ctx.fTypes.fHalf2x2.get()) { v->type = Type::kFloat2x2; return true; }
if (type == ctx.fTypes.fFloat3x3.get()) { v->type = Type::kFloat3x3; return true; }
if (type == ctx.fTypes.fHalf3x3.get()) { v->type = Type::kFloat3x3; return true; }
if (type == ctx.fTypes.fFloat4x4.get()) { v->type = Type::kFloat4x4; return true; }
if (type == ctx.fTypes.fHalf4x4.get()) { v->type = Type::kFloat4x4; return true; }
if (type == ctx.fTypes.fInt.get()) { v->type = Type::kInt; return true; }
if (type == ctx.fTypes.fInt2.get()) { v->type = Type::kInt2; return true; }
if (type == ctx.fTypes.fInt3.get()) { v->type = Type::kInt3; return true; }
if (type == ctx.fTypes.fInt4.get()) { v->type = Type::kInt4; return true; }
if (*type == *ctx.fTypes.fInt) { v->type = Type::kInt; return true; }
if (*type == *ctx.fTypes.fInt2) { v->type = Type::kInt2; return true; }
if (*type == *ctx.fTypes.fInt3) { v->type = Type::kInt3; return true; }
if (*type == *ctx.fTypes.fInt4) { v->type = Type::kInt4; return true; }
return false;
}
// TODO: Many errors aren't caught until we process the generated Program here. Catching those
// in the IR generator would provide better errors messages (with locations).
#define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& options,
SkSL::ProgramKind kind) {
std::unique_ptr<SkSL::Program> program;
{
// We keep this SharedCompiler in a separate scope to make sure it's destroyed before
// calling the Make overload at the end, which creates its own (non-reentrant)
// SharedCompiler instance
SkSL::SharedCompiler compiler;
SkSL::Program::Settings settings;
settings.fInlineThreshold = 0;
settings.fForceNoInline = options.forceNoInline;
settings.fAllowNarrowingConversions = true;
program = compiler->convertProgram(SkSL::ProgramKind::kRuntimeEffect,
SkSL::String(sksl.c_str(), sksl.size()),
settings);
// TODO: Many errors aren't caught until we process the generated Program here. Catching those
// in the IR generator would provide better errors messages (with locations).
#define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
if (!program) {
RETURN_FAILURE("%s", compiler->errorText().c_str());
}
}
return Make(std::move(sksl), std::move(program), options, kind);
}
SkRuntimeEffect::Result SkRuntimeEffect::Make(std::unique_ptr<SkSL::Program> program,
SkSL::ProgramKind kind) {
SkString source(program->description().c_str());
return Make(std::move(source), std::move(program), Options{}, kind);
}
SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl,
std::unique_ptr<SkSL::Program> program,
const Options& options,
SkSL::ProgramKind kind) {
SkSL::SharedCompiler compiler;
@ -141,15 +177,6 @@ SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl,
settings.fInlineThreshold = 0;
settings.fForceNoInline = options.forceNoInline;
settings.fAllowNarrowingConversions = true;
auto program =
compiler->convertProgram(kind, SkSL::String(sksl.c_str(), sksl.size()), settings);
// TODO: Many errors aren't caught until we process the generated Program here. Catching those
// in the IR generator would provide better errors messages (with locations).
#define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)}
if (!program) {
RETURN_FAILURE("%s", compiler->errorText().c_str());
}
const SkSL::FunctionDefinition* main = nullptr;
uint32_t flags = 0;
@ -291,6 +318,18 @@ SkRuntimeEffect::Result SkRuntimeEffect::MakeForShader(SkString sksl, const Opti
return result;
}
SkRuntimeEffect::Result SkRuntimeEffect::MakeForColorFilter(std::unique_ptr<SkSL::Program> program) {
auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeColorFilter);
SkASSERT(!result.effect || result.effect->allowColorFilter());
return result;
}
SkRuntimeEffect::Result SkRuntimeEffect::MakeForShader(std::unique_ptr<SkSL::Program> program) {
auto result = Make(std::move(program), SkSL::ProgramKind::kRuntimeShader);
SkASSERT(!result.effect || result.effect->allowShader());
return result;
}
sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect(SkString sksl) {
SK_BEGIN_REQUIRE_DENSE
struct Key {

View File

@ -10,8 +10,8 @@
#include "include/core/SkTypes.h"
#include "include/gpu/GrContextOptions.h"
#include "include/private/SkSLProgramKind.h"
#include "include/private/SkSLString.h"
#include "src/sksl/ir/SkSLProgram.h"
namespace GrShaderUtils {

View File

@ -38,7 +38,8 @@ public:
Context::Context(ErrorReporter& errors, const ShaderCapsClass& caps)
: fErrors(errors)
, fCaps(caps)
, fDefined_Expression(std::make_unique<DefinedExpression>(fTypes.fInvalid.get())) {}
, fDefined_Expression(std::make_unique<DefinedExpression>(fTypes.fInvalid.get())) {
SkASSERT(!Pool::IsAttached());
}
} // namespace SkSL

View File

@ -12,6 +12,7 @@
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLPool.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLType.h"
@ -27,6 +28,10 @@ class Context {
public:
Context(ErrorReporter& errors, const ShaderCapsClass& caps);
~Context() {
SkASSERT(!Pool::IsAttached());
}
// The Context holds all of the built-in types.
BuiltinTypes fTypes;

View File

@ -1411,8 +1411,7 @@ void IRGenerator::convertGlobalVarDeclarations(const ASTNode& decl) {
std::make_unique<StructDefinition>(decl.fOffset, *type));
}
}
fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(decl.fOffset,
std::move(stmt)));
fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(std::move(stmt)));
}
}
@ -2102,6 +2101,7 @@ void IRGenerator::findAndDeclareBuiltinVariables() {
};
BuiltinVariableScanner scanner(this);
SkASSERT(fProgramElements);
for (auto& e : *fProgramElements) {
scanner.visitProgramElement(*e);
}
@ -2131,6 +2131,8 @@ void IRGenerator::start(const ParsedModule& base,
const std::vector<std::unique_ptr<ExternalFunction>>* externalFunctions,
std::vector<std::unique_ptr<ProgramElement>>* elements,
std::vector<const ProgramElement*>* sharedElements) {
fProgramElements = elements;
fSharedElements = sharedElements;
fSymbolTable = base.fSymbols;
fIntrinsics = base.fIntrinsics.get();
if (fIntrinsics) {
@ -2160,8 +2162,7 @@ void IRGenerator::start(const ParsedModule& base,
auto decl = VarDeclaration::Make(fContext, var.get(), fContext.fTypes.fInt.get(),
/*arraySize=*/0, /*value=*/nullptr);
fSymbolTable->add(std::move(var));
fProgramElements->push_back(
std::make_unique<GlobalVarDeclaration>(/*offset=*/-1, std::move(decl)));
fProgramElements->push_back(std::make_unique<GlobalVarDeclaration>(std::move(decl)));
}
if (externalFunctions) {
@ -2220,9 +2221,6 @@ IRGenerator::IRBundle IRGenerator::convertProgram(
std::vector<std::unique_ptr<ProgramElement>> elements;
std::vector<const ProgramElement*> sharedElements;
fProgramElements = &elements;
fSharedElements = &sharedElements;
this->start(base, isBuiltinCode, externalFunctions, &elements, &sharedElements);
Parser parser(text, length, *fSymbolTable, this->errorReporter());

View File

@ -70,6 +70,10 @@ std::unique_ptr<Pool> Pool::Create() {
return pool;
}
bool Pool::IsAttached() {
return get_thread_local_memory_pool();
}
void Pool::attachToThread() {
VLOG("ATTACH Pool:0x%016llX\n", (uint64_t)fMemPool.get());
SkASSERT(get_thread_local_memory_pool() == nullptr);

View File

@ -47,6 +47,8 @@ public:
// the pool can be destroyed.
static void FreeMemory(void* ptr);
static bool IsAttached();
private:
void checkForLeaks();

View File

@ -332,7 +332,7 @@ std::unique_ptr<ProgramElement> Rehydrator::element() {
}
case Rehydrator::kVarDeclarations_Command: {
std::unique_ptr<Statement> decl = this->statement();
return std::make_unique<GlobalVarDeclaration>(/*offset=*/-1, std::move(decl));
return std::make_unique<GlobalVarDeclaration>(std::move(decl));
}
case Rehydrator::kStructDefinition_Command: {
const Symbol* type = this->symbol();

View File

@ -1278,6 +1278,8 @@ Value SkVMGenerator::writeFunctionCall(const FunctionCall& f) {
// This merges currentFunction().fReturned into fConditionMask. Lanes that conditionally
// returned in the current function would otherwise resume execution within the child.
ScopedCondition m(this, ~currentFunction().fReturned);
SkASSERTF(f.function().definition(), "no definition for function '%s'",
f.function().description().c_str());
this->writeFunction(*f.function().definition(), argVals, result.asSpan());
}

View File

@ -22,6 +22,11 @@ void DSLFunction::init(const DSLType& returnType, const char* name,
std::vector<DSLVar*> params) {
std::vector<const Variable*> paramVars;
paramVars.reserve(params.size());
bool isMain = !strcmp(name, "main");
auto typeIsValidForColor = [&](const SkSL::Type& type) {
return type == *DSLWriter::Context().fTypes.fHalf4 ||
type == *DSLWriter::Context().fTypes.fFloat4;
};
for (DSLVar* param : params) {
// This counts as declaring the variable; make sure it hasn't been previously declared and
// then kill its pending declaration statement. Otherwise the statement will hang around
@ -37,6 +42,22 @@ void DSLFunction::init(const DSLType& returnType, const char* name,
"initial values\n");
}
param->fDeclared = true;
param->fStorage = SkSL::VariableStorage::kParameter;
if (paramVars.empty()) {
SkSL::ProgramKind kind = DSLWriter::Context().fConfig->fKind;
if (isMain && (kind == ProgramKind::kRuntimeEffect ||
kind == ProgramKind::kFragmentProcessor)) {
const SkSL::Type& type = param->fType.skslType();
// We verify that the signature is fully correct later. For now, if this is an .fp
// or runtime effect of any flavor, a float2 param is supposed to be the coords, and
// a half4/float parameter is supposed to be the input color:
if (type == *DSLWriter::Context().fTypes.fFloat2) {
param->fModifiers.fModifiers.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN;
} else if (typeIsValidForColor(type)) {
param->fModifiers.fModifiers.fLayout.fBuiltin = SK_INPUT_COLOR_BUILTIN;
}
}
}
paramVars.push_back(&DSLWriter::Var(*param));
param->fDeclaration = nullptr;
}
@ -44,7 +65,7 @@ void DSLFunction::init(const DSLType& returnType, const char* name,
fDecl = symbols.add(std::make_unique<SkSL::FunctionDeclaration>(
/*offset=*/-1,
DSLWriter::Modifiers(SkSL::Modifiers()),
DSLWriter::Name(name),
isMain ? name : DSLWriter::Name(name),
std::move(paramVars), fReturnType,
/*builtin=*/false));
}
@ -61,6 +82,7 @@ void DSLFunction::define(DSLBlock block) {
DSLWriter::Compiler().setErrorCount(0);
SkASSERT(!DSLWriter::Compiler().errorCount());
}
fDecl->fDefinition = function.get();
DSLWriter::ProgramElements().push_back(std::move(function));
}

View File

@ -0,0 +1,47 @@
/*
* 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 "include/sksl/DSLRuntimeEffects.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/sksl/DSLCore.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLIRGenerator.h"
#include "src/sksl/dsl/priv/DSLWriter.h"
namespace SkSL {
namespace dsl {
#ifndef SKSL_STANDALONE
void StartRuntimeShader(SkSL::Compiler* compiler) {
Start(compiler, SkSL::ProgramKind::kRuntimeEffect);
SkSL::ProgramSettings& settings = DSLWriter::IRGenerator().fContext.fConfig->fSettings;
SkASSERT(settings.fInlineThreshold == SkSL::kDefaultInlineThreshold);
settings.fInlineThreshold = 0;
SkASSERT(!settings.fAllowNarrowingConversions);
settings.fAllowNarrowingConversions = true;
}
sk_sp<SkRuntimeEffect> EndRuntimeShader() {
std::unique_ptr<SkSL::Program> program = DSLWriter::ReleaseProgram();
auto result = SkRuntimeEffect::MakeForShader(std::move(program));
// TODO(skbug.com/11862): propagate errors properly
SkASSERTF(result.effect, "%s\n", result.errorText.c_str());
SkSL::ProgramSettings& settings = DSLWriter::IRGenerator().fContext.fConfig->fSettings;
settings.fInlineThreshold = SkSL::kDefaultInlineThreshold;
settings.fAllowNarrowingConversions = false;
End();
return result.effect;
}
#endif // SKSL_STANDALONE
} // namespace dsl
} // namespace SkSL

View File

@ -91,6 +91,8 @@ const SkSL::Type& DSLType::skslType() const {
return *context.fTypes.fInt3;
case kInt4_Type:
return *context.fTypes.fInt4;
case kShader_Type:
return *context.fTypes.fShader;
case kShort_Type:
return *context.fTypes.fShort;
case kShort2_Type:

View File

@ -9,6 +9,7 @@
#include "include/sksl/DSLModifiers.h"
#include "include/sksl/DSLType.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLUtil.h"
#include "src/sksl/dsl/priv/DSLWriter.h"
#include "src/sksl/ir/SkSLBinaryExpression.h"
@ -34,13 +35,23 @@ DSLVar::DSLVar(const char* name)
// correctly-named variable with the right type, so we just create a placeholder for it.
// TODO(skia/11330): we'll need to fix this when switching over to nodes.
fVar = DSLWriter::SymbolTable()->takeOwnershipOfIRNode(
std::make_unique<SkSL::Variable>(
/*offset=*/-1,
DSLWriter::IRGenerator().fModifiers->addToPool(SkSL::Modifiers()),
fName,
DSLWriter::Context().fTypes.fFloat2.get(),
/*builtin=*/true,
SkSL::VariableStorage::kGlobal));
std::make_unique<SkSL::Variable>(
/*offset=*/-1,
DSLWriter::IRGenerator().fModifiers->addToPool(
SkSL::Modifiers(
SkSL::Layout(/*flags=*/0, /*location=*/-1, /*offset=*/-1,
/*binding=*/-1, /*index=*/-1, /*set=*/-1,
SK_MAIN_COORDS_BUILTIN,
/*inputAttachmentIndex=*/-1,
Layout::kUnspecified_Primitive,
/*maxVertices=*/1, /*invocations=*/-1,
/*marker=*/"", /*when=*/"",
Layout::CType::kDefault),
SkSL::Modifiers::kNo_Flag)),
fName,
DSLWriter::Context().fTypes.fFloat2.get(),
/*builtin=*/true,
SkSL::VariableStorage::kGlobal));
return;
}
#endif
@ -62,7 +73,7 @@ DSLVar::DSLVar(DSLModifiers modifiers, DSLType type, const char* name, DSLExpres
: fModifiers(std::move(modifiers))
, fType(std::move(type))
, fRawName(name)
, fName(DSLWriter::Name(name))
, fName(fType.skslType().isOpaque() ? name : DSLWriter::Name(name))
, fInitialValue(std::move(initialValue))
, fStorage(Variable::Storage::kLocal)
, fDeclared(DSLWriter::Instance().fMarkVarsDeclared) {

View File

@ -34,25 +34,28 @@ namespace dsl {
DSLWriter::DSLWriter(SkSL::Compiler* compiler, SkSL::ProgramKind kind)
: fCompiler(compiler) {
SkSL::ParsedModule module = fCompiler->moduleForProgramKind(kind);
fConfig.fKind = kind;
fConfig = std::make_unique<ProgramConfig>();
fConfig->fKind = kind;
SkSL::IRGenerator& ir = *fCompiler->fIRGenerator;
fOldSymbolTable = ir.fSymbolTable;
fOldConfig = fCompiler->fContext->fConfig;
ir.fSymbolTable = module.fSymbols;
fCompiler->fContext->fConfig = &fConfig;
ir.pushSymbolTable();
fCompiler->fContext->fConfig = fConfig.get();
if (compiler->context().fCaps.useNodePools()) {
fPool = Pool::Create();
fPool->attachToThread();
}
ir.start(module, false, nullptr, &fProgramElements, &fSharedElements);
}
DSLWriter::~DSLWriter() {
SkSL::IRGenerator& ir = *fCompiler->fIRGenerator;
ir.fSymbolTable = fOldSymbolTable;
if (SymbolTable()) {
fCompiler->fIRGenerator->finish();
fProgramElements.clear();
} else {
// We should only be here with a null symbol table if ReleaseProgram was called
SkASSERT(fProgramElements.empty());
}
fCompiler->fContext->fConfig = fOldConfig;
fProgramElements.clear();
if (fPool) {
fPool->detachFromThread();
}
@ -198,8 +201,10 @@ void DSLWriter::ReportError(const char* msg, PositionInfo* info) {
const SkSL::Variable& DSLWriter::Var(DSLVar& var) {
if (!var.fVar) {
DSLWriter::IRGenerator().checkVarDeclaration(/*offset=*/-1, var.fModifiers.fModifiers,
&var.fType.skslType(), var.fStorage);
if (var.fStorage != SkSL::VariableStorage::kParameter) {
DSLWriter::IRGenerator().checkVarDeclaration(/*offset=*/-1, var.fModifiers.fModifiers,
&var.fType.skslType(), var.fStorage);
}
std::unique_ptr<SkSL::Variable> skslvar = DSLWriter::IRGenerator().convertVar(
/*offset=*/-1,
var.fModifiers.fModifiers,
@ -214,6 +219,10 @@ const SkSL::Variable& DSLWriter::Var(DSLVar& var) {
var.fDeclaration = DSLWriter::IRGenerator().convertVarDeclaration(
std::move(skslvar),
var.fInitialValue.release());
if (var.fStorage == Variable::Storage::kGlobal) {
DSLWriter::ProgramElements().push_back(std::make_unique<SkSL::GlobalVarDeclaration>(
std::move(var.fDeclaration)));
}
}
return *var.fVar;
}
@ -228,6 +237,27 @@ void DSLWriter::MarkDeclared(DSLVar& var) {
var.fDeclared = true;
}
std::unique_ptr<SkSL::Program> DSLWriter::ReleaseProgram() {
SkSL::IRGenerator& ir = IRGenerator();
IRGenerator::IRBundle bundle = ir.finish();
Pool* pool = Instance().fPool.get();
auto result = std::make_unique<SkSL::Program>(/*source=*/nullptr,
std::move(DSLWriter::Instance().fConfig),
Compiler().fContext,
std::move(bundle.fElements),
std::move(bundle.fSharedElements),
std::move(bundle.fModifiers),
std::move(bundle.fSymbolTable),
std::move(Instance().fPool),
bundle.fInputs);
if (pool) {
pool->detachFromThread();
}
SkASSERT(ProgramElements().empty());
SkASSERT(!SymbolTable());
return result;
}
#if !SK_SUPPORT_GPU || defined(SKSL_STANDALONE)
DSLWriter& DSLWriter::Instance() {

View File

@ -199,17 +199,19 @@ public:
return Instance().fMangle;
}
static std::unique_ptr<SkSL::Program> ReleaseProgram();
static DSLWriter& Instance();
static void SetInstance(std::unique_ptr<DSLWriter> instance);
private:
SkSL::ProgramConfig fConfig;
std::unique_ptr<SkSL::ProgramConfig> fConfig;
SkSL::Compiler* fCompiler;
std::unique_ptr<Pool> fPool;
std::shared_ptr<SkSL::SymbolTable> fOldSymbolTable;
SkSL::ProgramConfig* fOldConfig;
std::vector<std::unique_ptr<SkSL::ProgramElement>> fProgramElements;
std::vector<const SkSL::ProgramElement*> fSharedElements;
ErrorHandler* fErrorHandler = nullptr;
bool fMangle = true;
bool fMarkVarsDeclared = false;

View File

@ -182,6 +182,8 @@ private:
bool fBuiltin;
bool fIsMain;
friend class SkSL::dsl::DSLFunction;
using INHERITED = Symbol;
};

View File

@ -191,6 +191,14 @@ struct Program {
std::vector<std::unique_ptr<ProgramElement>>& ownedElements() { return fElements; }
const std::vector<std::unique_ptr<ProgramElement>>& ownedElements() const { return fElements; }
String description() const {
String result;
for (const auto& e : this->elements()) {
result += e->description();
}
return result;
}
std::unique_ptr<String> fSource;
std::unique_ptr<ProgramConfig> fConfig;
std::shared_ptr<Context> fContext;

View File

@ -105,8 +105,8 @@ class GlobalVarDeclaration final : public ProgramElement {
public:
static constexpr Kind kProgramElementKind = Kind::kGlobalVar;
GlobalVarDeclaration(int offset, std::unique_ptr<Statement> decl)
: INHERITED(offset, kProgramElementKind)
GlobalVarDeclaration(std::unique_ptr<Statement> decl)
: INHERITED(decl->fOffset, kProgramElementKind)
, fDeclaration(std::move(decl)) {
SkASSERT(this->declaration()->is<VarDeclaration>());
}
@ -120,7 +120,7 @@ public:
}
std::unique_ptr<ProgramElement> clone() const override {
return std::make_unique<GlobalVarDeclaration>(fOffset, this->declaration()->clone());
return std::make_unique<GlobalVarDeclaration>(this->declaration()->clone());
}
String description() const override {

View File

@ -22,6 +22,7 @@ class VarDeclaration;
namespace dsl {
class DSLCore;
class DSLFunction;
} // namespace dsl
enum class VariableStorage : int8_t {
@ -89,6 +90,7 @@ private:
using INHERITED = Symbol;
friend class dsl::DSLCore;
friend class dsl::DSLFunction;
friend class VariableReference;
};

View File

@ -0,0 +1,240 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkData.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/GrDirectContext.h"
#include "include/sksl/DSLRuntimeEffects.h"
#include "src/core/SkTLazy.h"
#include "src/gpu/GrColor.h"
#include "src/sksl/SkSLCompiler.h"
#include "tests/Test.h"
#include <algorithm>
#include <thread>
using namespace SkSL::dsl;
class DSLTestEffect {
public:
DSLTestEffect(skiatest::Reporter* r, sk_sp<SkSurface> surface)
: fReporter(r)
, fCaps(SkSL::ShaderCapsFactory::Standalone())
, fCompiler(std::make_unique<SkSL::Compiler>(fCaps.get()))
, fSurface(std::move(surface)) {}
void start() {
StartRuntimeShader(fCompiler.get());
}
void end() {
sk_sp<SkRuntimeEffect> effect = EndRuntimeShader();
SkASSERT(effect);
fBuilder.init(std::move(effect));
}
SkRuntimeShaderBuilder::BuilderUniform uniform(const char* name) {
return fBuilder->uniform(name);
}
SkRuntimeShaderBuilder::BuilderChild child(const char* name) {
return fBuilder->child(name);
}
using PreTestFn = std::function<void(SkCanvas*, SkPaint*)>;
void test(GrColor TL, GrColor TR, GrColor BL, GrColor BR,
PreTestFn preTestCallback = nullptr) {
auto shader = fBuilder->makeShader(nullptr, false);
if (!shader) {
REPORT_FAILURE(fReporter, "shader", SkString("Effect didn't produce a shader"));
return;
}
SkCanvas* canvas = fSurface->getCanvas();
SkPaint paint;
paint.setShader(std::move(shader));
paint.setBlendMode(SkBlendMode::kSrc);
canvas->save();
if (preTestCallback) {
preTestCallback(canvas, &paint);
}
canvas->drawPaint(paint);
canvas->restore();
GrColor actual[4];
SkImageInfo info = fSurface->imageInfo();
if (!fSurface->readPixels(info, actual, info.minRowBytes(), 0, 0)) {
REPORT_FAILURE(fReporter, "readPixels", SkString("readPixels failed"));
return;
}
GrColor expected[4] = {TL, TR, BL, BR};
if (0 != memcmp(actual, expected, sizeof(actual))) {
REPORT_FAILURE(fReporter, "Runtime effect didn't match expectations",
SkStringPrintf("\n"
"Expected: [ %08x %08x %08x %08x ]\n"
"Got : [ %08x %08x %08x %08x ]\n"
"SkSL:\n%s\n",
TL, TR, BL, BR, actual[0], actual[1], actual[2],
actual[3], fBuilder->effect()->source().c_str()));
}
}
void test(GrColor expected, PreTestFn preTestCallback = nullptr) {
this->test(expected, expected, expected, expected, preTestCallback);
}
private:
skiatest::Reporter* fReporter;
SkSL::ShaderCapsPointer fCaps;
std::unique_ptr<SkSL::Compiler> fCompiler;
sk_sp<SkSurface> fSurface;
SkTLazy<SkRuntimeShaderBuilder> fBuilder;
};
static void test_RuntimeEffect_Shaders(skiatest::Reporter* r, GrRecordingContext* rContext) {
SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
sk_sp<SkSurface> surface = rContext
? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
: SkSurface::MakeRaster(info);
REPORTER_ASSERT(r, surface);
using float4 = std::array<float, 4>;
using int4 = std::array<int, 4>;
DSLTestEffect effect(r, surface);
// Local coords
{
effect.start();
Var p(kFloat2_Type, "p");
Function(kHalf4_Type, "main", p).define(
Return(Half4(Half2(p - 0.5), 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
}
// Use of a simple uniform. (Draw twice with two values to ensure it's updated).
{
effect.start();
Var gColor(kUniform_Modifier, kFloat4_Type);
Function(kHalf4_Type, "main").define(
Return(Half4(gColor))
);
effect.end();
effect.uniform(gColor.name()) = float4{ 0.0f, 0.25f, 0.75f, 1.0f };
effect.test(0xFFBF4000);
effect.uniform(gColor.name()) = float4{ 1.0f, 0.0f, 0.0f, 0.498f };
effect.test(0x7F00007F); // Tests that we clamp to valid premul
}
// Same, with integer uniforms
{
effect.start();
Var gColor(kUniform_Modifier, kInt4_Type);
Function(kHalf4_Type, "main").define(
Return(Half4(gColor) / 255)
);
effect.end();
effect.uniform(gColor.name()) = int4{ 0x00, 0x40, 0xBF, 0xFF };
effect.test(0xFFBF4000);
effect.uniform(gColor.name()) = int4{ 0xFF, 0x00, 0x00, 0x7F };
effect.test(0x7F00007F); // Tests that we clamp to valid premul
}
// Test sk_FragCoord (device coords). Rotate the canvas to be sure we're seeing device coords.
// Since the surface is 2x2, we should see (0,0), (1,0), (0,1), (1,1). Multiply by 0.498 to
// make sure we're not saturating unexpectedly.
{
effect.start();
Function(kHalf4_Type, "main").define(
Return(Half4(0.498 * (Half2(Swizzle(sk_FragCoord(), X, Y)) - 0.5), 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF00007F, 0xFF007F00, 0xFF007F7F,
[](SkCanvas* canvas, SkPaint*) { canvas->rotate(45.0f); });
}
// Runtime effects should use relaxed precision rules by default
{
effect.start();
Var p(kFloat2_Type, "p");
Function(kHalf4_Type, "main", p).define(
Return(Float4(p - 0.5, 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
}
// ... and support *returning* float4, not just half4
{
effect.start();
Var p(kFloat2_Type, "p");
Function(kFloat4_Type, "main", p).define(
Return(Float4(p - 0.5, 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
}
// Mutating coords should work. (skbug.com/10918)
{
effect.start();
Var p(kFloat2_Type, "p");
Function(kFloat4_Type, "main", p).define(
p -= 0.5,
Return(Float4(p, 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
}
{
effect.start();
Var p1(kInOut_Modifier, kFloat2_Type, "p");
Function moveCoords(kVoid_Type, "moveCoords", p1);
moveCoords.define(
p1 -= 0.5
);
Var p2(kFloat2_Type, "p");
Function(kFloat4_Type, "main", p2).define(
moveCoords(p2),
Return(Float4(p2, 0, 1))
);
effect.end();
effect.test(0xFF000000, 0xFF0000FF, 0xFF00FF00, 0xFF00FFFF);
}
//
// Sampling children
//
// Sampling a null child should return the paint color
{
effect.start();
Var child(kUniform_Modifier, kShader_Type, "child");
Function(kFloat4_Type, "main").define(
Return(Sample(child))
);
effect.end();
effect.child("child") = nullptr;
effect.test(0xFF00FFFF,
[](SkCanvas*, SkPaint* paint) { paint->setColor4f({1.0f, 1.0f, 0.0f, 1.0f}); });
}
}
DEF_TEST(DSLRuntimeEffectSimple, r) {
test_RuntimeEffect_Shaders(r, nullptr);
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DSLRuntimeEffectSimple_GPU, r, ctxInfo) {
test_RuntimeEffect_Shaders(r, ctxInfo.directContext());
}

View File

@ -1492,7 +1492,7 @@ DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLModifiers, r, ctxInfo) {
// Uniforms do not need to be explicitly declared
}
DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSample, r, ctxInfo) {
DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleFragmentProcessor, r, ctxInfo) {
AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/true,
SkSL::ProgramKind::kFragmentProcessor);
DSLVar child(kUniform_Modifier, kFragmentProcessor_Type, "child");
@ -1510,6 +1510,20 @@ DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSample, r, ctxInfo) {
}
}
DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLSampleShader, r, ctxInfo) {
AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/true,
SkSL::ProgramKind::kRuntimeEffect);
DSLVar shader(kUniform_Modifier, kShader_Type, "shader");
EXPECT_EQUAL(Sample(shader), "sample(shader)");
EXPECT_EQUAL(Sample(shader, Float2(0, 0)), "sample(shader, float2(0.0, 0.0))");
EXPECT_EQUAL(Sample(shader, Float3x3(1)), "sample(shader, float3x3(1.0))");
{
ExpectError error(r, "error: no match for sample(shader, half4)\n");
Sample(shader, Half4(1)).release();
}
}
DEF_GPUTEST_FOR_MOCK_CONTEXT(DSLStruct, r, ctxInfo) {
AutoDSLContext context(ctxInfo.directContext()->priv().getGpu(), /*markVarsDeclared=*/false);