/* * 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 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 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 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 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 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 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 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 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 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 wrap(std::unique_ptr fp, EffectType effectType) { switch (effectType) { case kConstant: return std::unique_ptr(new ConstantMatrixEffect(std::move(fp))); case kUniform: return std::unique_ptr(new UniformMatrixEffect(std::move(fp))); case kVariable: return std::unique_ptr(new VariableMatrixEffect(std::move(fp))); case kExplicit: return std::unique_ptr(new 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 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(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.addColorFragmentProcessor(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 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 effectTypes) { auto shader = bmp.makeShader(); for (EffectType effectType : effectTypes) { SkRuntimeShaderBuilder builder(effects[effectType]); builder.child("child") = shader; switch (effectType) { case kUniform: builder.input("matrix") = SkMatrix::Scale(1.0f, 0.5f); break; case kVariable: builder.input("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 }