skia2/gm/fp_sample_chaining.cpp
Brian Osman a4b9169fb6 Remove 'in' variables from SkRuntimeEffect
Runtime effects previously allowed two kinds of global input variables:
'in' variables could be bool, int, or float. 'uniform' could be float,
vector, or matrix. Uniform variables worked like you'd expect, but 'in'
variables were baked into the program statically. There was a large
amount of machinery to make this work, and it meant that 'in' variables
needed to have values before we could make decisions about program
caching, and before we could catch some errors. It was also essentially
syntactic sugar over the client just inserting the value into their SkSL
as a string. Finally: No one was using the feature.

To simplify the mental model, and make the API much more predictable,
this CL removes 'in' variables entirely. We no longer need to
"specialize" runtime effect programs, which means we can catch more
errors up front (those not detected until optimization). All of the API
that referred to "inputs" (the previous term that unified 'in' and
'uniform') now just refers to "uniforms".

Bug: skia:10593
Change-Id: I971f620d868b259e652b3114f0b497c2620f4b0c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/309050
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
2020-08-10 22:00:44 +00:00

389 lines
16 KiB
C++

/*
* 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 "gm/gm.h"
#include "include/core/SkFont.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/gpu/GrBitmapTextureMaker.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrRenderTargetContextPriv.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/ops/GrFillRectOp.h"
#include "tools/ToolUtils.h"
// Samples child with a constant (literal) matrix
// Scales along X
class ConstantMatrixEffect : public GrFragmentProcessor {
public:
static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 3;
ConstantMatrixEffect(std::unique_ptr<GrFragmentProcessor> child)
: GrFragmentProcessor(CLASS_ID, kNone_OptimizationFlags) {
this->registerChild(std::move(child),
SkSL::SampleUsage::UniformMatrix(
"float3x3(float3(0.5, 0.0, 0.0), "
"float3(0.0, 1.0, 0.0), "
"float3(0.0, 0.0, 1.0))"));
}
const char* name() const override { return "ConstantMatrixEffect"; }
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; }
std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; }
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
SkString sample = this->invokeChildWithMatrix(0, args);
args.fFragBuilder->codeAppendf("%s = %s;\n", args.fOutputColor, sample.c_str());
}
};
return new Impl;
}
};
// Samples child with a uniform matrix (functionally identical to GrMatrixEffect)
// Scales along Y
class UniformMatrixEffect : public GrFragmentProcessor {
public:
static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 4;
UniformMatrixEffect(std::unique_ptr<GrFragmentProcessor> child)
: GrFragmentProcessor(CLASS_ID, kNone_OptimizationFlags) {
this->registerChild(std::move(child), SkSL::SampleUsage::UniformMatrix("matrix"));
}
const char* name() const override { return "UniformMatrixEffect"; }
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; }
std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; }
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
fMatrixVar = args.fUniformHandler->addUniform(&args.fFp, kFragment_GrShaderFlag,
kFloat3x3_GrSLType, "matrix");
SkString sample = this->invokeChildWithMatrix(0, args);
args.fFragBuilder->codeAppendf("%s = %s;\n", args.fOutputColor, sample.c_str());
}
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& proc) override {
pdman.setSkMatrix(fMatrixVar, SkMatrix::Scale(1, 0.5f));
}
UniformHandle fMatrixVar;
};
return new Impl;
}
};
// Samples child with a variable matrix
// Translates along X
// Typically, kVariable would be due to multiple sample(matrix) invocations, but this artificially
// uses kVariable with a single (constant) matrix.
class VariableMatrixEffect : public GrFragmentProcessor {
public:
static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 5;
VariableMatrixEffect(std::unique_ptr<GrFragmentProcessor> child)
: GrFragmentProcessor(CLASS_ID, kNone_OptimizationFlags) {
this->registerChild(std::move(child), SkSL::SampleUsage::VariableMatrix());
}
const char* name() const override { return "VariableMatrixEffect"; }
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; }
std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; }
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
SkString sample = this->invokeChildWithMatrix(
0, args, "float3x3(1, 0, 0, 0, 1, 0, 8, 0, 1)");
args.fFragBuilder->codeAppendf("%s = %s;\n", args.fOutputColor, sample.c_str());
}
};
return new Impl;
}
};
// Samples child with explicit coords
// Translates along Y
class ExplicitCoordEffect : public GrFragmentProcessor {
public:
static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 6;
ExplicitCoordEffect(std::unique_ptr<GrFragmentProcessor> child)
: GrFragmentProcessor(CLASS_ID, kNone_OptimizationFlags) {
this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
this->setUsesSampleCoordsDirectly();
}
const char* name() const override { return "ExplicitCoordEffect"; }
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; }
std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; }
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
args.fFragBuilder->codeAppendf("float2 coord = %s + float2(0, 8);",
args.fSampleCoord);
SkString sample = this->invokeChild(0, args, "coord");
args.fFragBuilder->codeAppendf("%s = %s;\n", args.fOutputColor, sample.c_str());
}
};
return new Impl;
}
};
// Generates test pattern
class TestPatternEffect : public GrFragmentProcessor {
public:
static constexpr GrProcessor::ClassID CLASS_ID = (GrProcessor::ClassID) 7;
TestPatternEffect() : GrFragmentProcessor(CLASS_ID, kNone_OptimizationFlags) {
this->setUsesSampleCoordsDirectly();
}
const char* name() const override { return "TestPatternEffect"; }
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; }
std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; }
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
class Impl : public GrGLSLFragmentProcessor {
void emitCode(EmitArgs& args) override {
auto fb = args.fFragBuilder;
fb->codeAppendf("float2 coord = %s / 64.0;", args.fSampleCoord);
fb->codeAppendf("coord = floor(coord * 4) / 3;");
fb->codeAppendf("%s = half4(half2(coord.rg), 0, 1);\n", args.fOutputColor);
}
};
return new Impl;
}
};
SkBitmap make_test_bitmap() {
SkBitmap bitmap;
bitmap.allocN32Pixels(64, 64);
SkCanvas canvas(bitmap);
SkFont font(ToolUtils::create_portable_typeface());
const char* alpha = "ABCDEFGHIJKLMNOP";
for (int i = 0; i < 16; ++i) {
int tx = i % 4,
ty = i / 4;
int x = tx * 16,
y = ty * 16;
SkPaint paint;
paint.setColor4f({ tx / 3.0f, ty / 3.0f, 0.0f, 1.0f });
canvas.drawRect(SkRect::MakeXYWH(x, y, 16, 16), paint);
paint.setColor4f({ (3-tx) / 3.0f, (3-ty)/3.0f, 1.0f, 1.0f });
canvas.drawSimpleText(alpha + i, 1, SkTextEncoding::kUTF8, x + 3, y + 13, font, paint);
}
return bitmap;
}
enum EffectType {
kConstant,
kUniform,
kVariable,
kExplicit,
};
static std::unique_ptr<GrFragmentProcessor> wrap(std::unique_ptr<GrFragmentProcessor> fp,
EffectType effectType) {
switch (effectType) {
case kConstant:
return std::make_unique<ConstantMatrixEffect>(std::move(fp));
case kUniform:
return std::make_unique<UniformMatrixEffect>(std::move(fp));
case kVariable:
return std::make_unique<VariableMatrixEffect>(std::move(fp));
case kExplicit:
return std::make_unique<ExplicitCoordEffect>(std::move(fp));
}
SkUNREACHABLE;
}
DEF_SIMPLE_GPU_GM(fp_sample_chaining, ctx, rtCtx, canvas, 380, 306) {
SkBitmap bmp = make_test_bitmap();
GrBitmapTextureMaker maker(ctx, bmp, GrImageTexGenPolicy::kDraw);
int x = 10, y = 10;
auto nextCol = [&] { x += (64 + 10); };
auto nextRow = [&] { x = 10; y += (64 + 10); };
auto draw = [&](std::initializer_list<EffectType> effects) {
// Enable TestPatternEffect to get a fully procedural inner effect. It's not quite as nice
// visually (no text labels in each box), but it avoids the extra GrMatrixEffect.
// Switching it on actually triggers *more* shader compilation failures.
#if 0
auto fp = std::unique_ptr<GrFragmentProcessor>(new TestPatternEffect());
#else
auto view = maker.view(GrMipmapped::kNo);
auto fp = GrTextureEffect::Make(std::move(view), maker.alphaType());
#endif
for (EffectType effectType : effects) {
fp = wrap(std::move(fp), effectType);
}
GrPaint paint;
paint.setColorFragmentProcessor(std::move(fp));
rtCtx->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::Translate(x, y),
SkRect::MakeIWH(64, 64));
nextCol();
};
// Reminder, in every case, the chain is more complicated than it seems, because the
// GrTextureEffect is wrapped in a GrMatrixEffect, which is subject to the same bugs that
// we're testing (particularly the bug about owner/base in UniformMatrixEffect).
// First row: no transform, then each one independently applied
draw({}); // Identity (4 rows and columns)
draw({ kConstant }); // Scale X axis by 2x (2 visible columns)
draw({ kUniform }); // Scale Y axis by 2x (2 visible rows)
draw({ kVariable }); // Translate left by 8px
draw({ kExplicit }); // Translate up by 8px
nextRow();
// Second row: transform duplicated
draw({ kConstant, kUniform }); // Scale XY by 2x (2 rows and columns)
draw({ kConstant, kConstant }); // Scale X axis by 4x (1 visible column)
draw({ kUniform, kUniform }); // Scale Y axis by 4x (1 visible row)
draw({ kVariable, kVariable }); // Translate left by 16px
draw({ kExplicit, kExplicit }); // Translate up by 16px
nextRow();
// Remember, these are applied inside out:
draw({ kConstant, kExplicit }); // Scale X by 2x and translate up by 8px
draw({ kConstant, kVariable }); // Scale X by 2x and translate left by 8px
draw({ kUniform, kVariable }); // Scale Y by 2x and translate left by 8px
draw({ kUniform, kExplicit }); // Scale Y by 2x and translate up by 8px
draw({ kVariable, kExplicit }); // Translate left and up by 8px
nextRow();
draw({ kExplicit, kExplicit, kConstant }); // Scale X by 2x and translate up by 16px
draw({ kVariable, kConstant }); // Scale X by 2x and translate left by 16px
draw({ kVariable, kVariable, kUniform }); // Scale Y by 2x and translate left by 16px
draw({ kExplicit, kUniform }); // Scale Y by 2x and translate up by 16px
draw({ kExplicit, kUniform, kVariable, kConstant }); // Scale XY by 2x and translate xy 16px
}
const char* gConstantMatrixSkSL = R"(
in shader child;
void main(float2 xy, inout half4 color) {
color = sample(child, float3x3(0.5, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0));
}
)";
const char* gUniformMatrixSkSL = R"(
in shader child;
uniform float3x3 matrix;
void main(float2 xy, inout half4 color) {
color = sample(child, matrix);
}
)";
// This form (uniform * constant) is currently detected as variable, thanks to our limited analysis
// when scanning for sample matrices. With that pulled into a separate local, it's highly unlikely
// we'll ever treat this as anything else.
const char* gVariableMatrixSkSL = R"(
in shader child;
uniform float3x3 matrix;
void main(float2 xy, inout half4 color) {
float3x3 varMatrix = matrix * 0.5;
color = sample(child, varMatrix);
}
)";
const char* gExplicitCoordSkSL = R"(
in shader child;
void main(float2 xy, inout half4 color) {
color = sample(child, xy + float2(0, 8));
}
)";
// Version of fp_sample_chaining that uses SkRuntimeEffect
DEF_SIMPLE_GM(sksl_sample_chaining, canvas, 380, 306) {
SkBitmap bmp = make_test_bitmap();
sk_sp<SkRuntimeEffect> effects[4] = {
std::get<0>(SkRuntimeEffect::Make(SkString(gConstantMatrixSkSL))),
std::get<0>(SkRuntimeEffect::Make(SkString(gUniformMatrixSkSL))),
std::get<0>(SkRuntimeEffect::Make(SkString(gVariableMatrixSkSL))),
std::get<0>(SkRuntimeEffect::Make(SkString(gExplicitCoordSkSL))),
};
canvas->translate(10, 10);
canvas->save();
auto nextCol = [&] { canvas->translate(64 + 10, 0); };
auto nextRow = [&] { canvas->restore(); canvas->translate(0, 64 + 10); canvas->save(); };
auto draw = [&](std::initializer_list<EffectType> effectTypes) {
auto shader = bmp.makeShader();
for (EffectType effectType : effectTypes) {
SkRuntimeShaderBuilder builder(effects[effectType]);
builder.child("child") = shader;
switch (effectType) {
case kUniform:
builder.uniform("matrix") = SkMatrix::Scale(1.0f, 0.5f);
break;
case kVariable:
builder.uniform("matrix") = SkMatrix::Translate(8, 0);
break;
default:
break;
}
shader = builder.makeShader(nullptr, true);
}
SkPaint paint;
paint.setShader(shader);
canvas->drawRect(SkRect::MakeWH(64, 64), paint);
nextCol();
};
// Reminder, in every case, the chain is more complicated than it seems, because the
// GrTextureEffect is wrapped in a GrMatrixEffect, which is subject to the same bugs that
// we're testing (particularly the bug about owner/base in UniformMatrixEffect).
// First row: no transform, then each one independently applied
draw({}); // Identity (4 rows and columns)
draw({ kConstant }); // Scale X axis by 2x (2 visible columns)
draw({ kUniform }); // Scale Y axis by 2x (2 visible rows)
draw({ kVariable }); // Translate left by 8px
draw({ kExplicit }); // Translate up by 8px
nextRow();
// Second row: transform duplicated
draw({ kConstant, kUniform }); // Scale XY by 2x (2 rows and columns)
draw({ kConstant, kConstant }); // Scale X axis by 4x (1 visible column)
draw({ kUniform, kUniform }); // Scale Y axis by 4x (1 visible row)
draw({ kVariable, kVariable }); // Translate left by 16px
draw({ kExplicit, kExplicit }); // Translate up by 16px
nextRow();
// Remember, these are applied inside out:
draw({ kConstant, kExplicit }); // Scale X by 2x and translate up by 8px
draw({ kConstant, kVariable }); // Scale X by 2x and translate left by 8px
draw({ kUniform, kVariable }); // Scale Y by 2x and translate left by 8px
draw({ kUniform, kExplicit }); // Scale Y by 2x and translate up by 8px
draw({ kVariable, kExplicit }); // Translate left and up by 8px
nextRow();
draw({ kExplicit, kExplicit, kConstant }); // Scale X by 2x and translate up by 16px
draw({ kVariable, kConstant }); // Scale X by 2x and translate left by 16px
draw({ kVariable, kVariable, kUniform }); // Scale Y by 2x and translate left by 16px
draw({ kExplicit, kUniform }); // Scale Y by 2x and translate up by 16px
draw({ kExplicit, kUniform, kVariable, kConstant }); // Scale XY by 2x and translate xy 16px
}