blob: 5af060629b99308f1188c7d87e544952454ad032 [file] [log] [blame]
/*
* 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/GrDirectContextPriv.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("return %s;\n", 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("return %s;\n", 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("return %s;\n", 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("return %s;\n", 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("return half2(coord).rg01;\n");
}
};
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"(
uniform shader child;
half4 main(float2 xy) {
return 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"(
uniform shader child;
uniform float3x3 matrix;
half4 main(float2 xy) {
return 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"(
uniform shader child;
uniform float3x3 matrix;
half4 main(float2 xy) {
float3x3 varMatrix = matrix * 0.5;
return sample(child, varMatrix);
}
)";
const char* gExplicitCoordSkSL = R"(
uniform shader child;
half4 main(float2 xy) {
return 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(SkSamplingOptions());
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
}