| /* | |
| * 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/SkBitmap.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/effects/SkRuntimeEffect.h" | |
| #include "src/core/SkCanvasPriv.h" | |
| #include "src/gpu/ganesh/GrCanvas.h" | |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" | |
| #include "src/gpu/ganesh/GrPaint.h" | |
| #include "src/gpu/ganesh/SkGr.h" | |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" | |
| #include "src/gpu/ganesh/effects/GrMatrixEffect.h" | |
| #include "src/gpu/ganesh/effects/GrTextureEffect.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" | |
| #include "src/gpu/ganesh/image/GrMippedBitmap.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| namespace { | |
| // Samples child with a uniform matrix (functionally identical to GrMatrixEffect) | |
| // Scales along Y | |
| class UniformMatrixEffect : public GrFragmentProcessor { | |
| public: | |
| inline 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(/*hasPerspective=*/false)); | |
| } | |
| const char* name() const override { return "UniformMatrixEffect"; } | |
| void onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const override {} | |
| bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; } | |
| std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; } | |
| std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override { | |
| class Impl : public ProgramImpl { | |
| public: | |
| void emitCode(EmitArgs& args) override { | |
| fMatrixVar = | |
| args.fUniformHandler->addUniform(&args.fFp, | |
| kFragment_GrShaderFlag, | |
| SkSLType::kFloat3x3, | |
| SkSL::SampleUsage::MatrixUniformName()); | |
| SkString sample = this->invokeChildWithMatrix(0, args); | |
| args.fFragBuilder->codeAppendf("return %s;\n", sample.c_str()); | |
| } | |
| private: | |
| void onSetData(const GrGLSLProgramDataManager& pdman, | |
| const GrFragmentProcessor& proc) override { | |
| pdman.setSkMatrix(fMatrixVar, SkMatrix::Scale(1, 0.5f)); | |
| } | |
| UniformHandle fMatrixVar; | |
| }; | |
| return std::make_unique<Impl>(); | |
| } | |
| }; | |
| // Samples child with explicit coords | |
| // Translates along Y | |
| class ExplicitCoordEffect : public GrFragmentProcessor { | |
| public: | |
| inline 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 onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const override {} | |
| bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; } | |
| std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; } | |
| std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override { | |
| class Impl : public ProgramImpl { | |
| public: | |
| 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 std::make_unique<Impl>(); | |
| } | |
| }; | |
| // Generates test pattern | |
| class TestPatternEffect : public GrFragmentProcessor { | |
| public: | |
| inline 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 onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const override {} | |
| bool onIsEqual(const GrFragmentProcessor& that) const override { return this == &that; } | |
| std::unique_ptr<GrFragmentProcessor> clone() const override { return nullptr; } | |
| std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override { | |
| class Impl : public ProgramImpl { | |
| public: | |
| 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 std::make_unique<Impl>(); | |
| } | |
| }; | |
| SkBitmap make_test_bitmap() { | |
| SkBitmap bitmap; | |
| bitmap.allocN32Pixels(64, 64); | |
| SkCanvas canvas(bitmap); | |
| SkFont font = ToolUtils::DefaultPortableFont(); | |
| 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 { | |
| kUniform, | |
| kExplicit, | |
| kDevice, | |
| }; | |
| static std::unique_ptr<GrFragmentProcessor> wrap(std::unique_ptr<GrFragmentProcessor> fp, | |
| EffectType effectType, | |
| int drawX, int drawY) { | |
| switch (effectType) { | |
| case kUniform: | |
| return std::make_unique<UniformMatrixEffect>(std::move(fp)); | |
| case kExplicit: | |
| return std::make_unique<ExplicitCoordEffect>(std::move(fp)); | |
| case kDevice: | |
| // Subtract out upper-left corner of draw so that device is effectively identity. | |
| fp = GrMatrixEffect::Make(SkMatrix::Translate(-drawX, -drawY), std::move(fp)); | |
| return GrFragmentProcessor::DeviceSpace(std::move(fp)); | |
| } | |
| SkUNREACHABLE; | |
| } | |
| } // namespace | |
| namespace skiagm { | |
| DEF_SIMPLE_GPU_GM_CAN_FAIL(fp_sample_chaining, rContext, canvas, errorMsg, 232, 306) { | |
| auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas); | |
| if (!sdc) { | |
| *errorMsg = GM::kErrorMsg_DrawSkippedGpuOnly; | |
| return DrawResult::kSkip; | |
| } | |
| SkBitmap bmp = make_test_bitmap(); | |
| 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 = std::get<0>(GrMakeCachedBitmapProxyView(rContext, | |
| GrMippedBitmap(bmp), | |
| /*label=*/"FpSampleChaining", | |
| skgpu::Mipmapped::kNo)); | |
| auto fp = GrTextureEffect::Make(std::move(view), bmp.alphaType()); | |
| #endif | |
| for (EffectType effectType : effects) { | |
| fp = wrap(std::move(fp), effectType, x, y); | |
| } | |
| GrPaint paint; | |
| paint.setColorFragmentProcessor(std::move(fp)); | |
| sdc->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({ kUniform }); // Scale Y axis by 2x (2 visible rows) | |
| draw({ kExplicit }); // Translate up by 8px | |
| nextRow(); | |
| // Second row: transform duplicated | |
| draw({ kUniform, kUniform }); // Scale Y axis by 4x (1 visible row) | |
| draw({ kExplicit, kExplicit }); // Translate up by 16px | |
| nextRow(); | |
| // Third row: Remember, these are applied inside out: | |
| draw({ kUniform, kExplicit }); // Scale Y by 2x and translate up by 8px | |
| draw({ kExplicit, kUniform }); // Scale Y by 2x and translate up by 16px | |
| nextRow(); | |
| // Fourth row: device space. | |
| draw({ kDevice, kUniform }); // Same as identity (uniform applied *before* | |
| // device so ignored). | |
| draw({ kExplicit, kUniform, kDevice }); // Scale Y by 2x and translate up by 16px | |
| draw({ kDevice, kExplicit, kUniform, kDevice }); // Identity, again. | |
| return DrawResult::kOk; | |
| } | |
| } // namespace skiagm |