| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h" |
| |
| #include "src/core/SkBlendModePriv.h" |
| #include "src/gpu/Blend.h" |
| #include "src/gpu/KeyBuilder.h" |
| #include "src/gpu/ganesh/GrFragmentProcessor.h" |
| #include "src/gpu/ganesh/GrProcessorUnitTest.h" |
| #include "src/gpu/ganesh/SkGr.h" |
| #include "src/gpu/ganesh/glsl/GrGLSLBlend.h" |
| #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" |
| |
| // Some of the CPU implementations of blend modes differ from the GPU enough that |
| // we can't use the CPU implementation to implement constantOutputForConstantInput. |
| static inline bool does_cpu_blend_impl_match_gpu(SkBlendMode mode) { |
| // The non-separable modes differ too much. So does SoftLight. ColorBurn differs too much on our |
| // test iOS device, but we just disable it across the board since it might differ on untested |
| // GPUs. |
| return mode <= SkBlendMode::kLastSeparableMode && mode != SkBlendMode::kSoftLight && |
| mode != SkBlendMode::kColorBurn; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| class BlendFragmentProcessor : public GrFragmentProcessor { |
| public: |
| static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> src, |
| std::unique_ptr<GrFragmentProcessor> dst, |
| SkBlendMode mode, |
| bool shareBlendLogic) { |
| return std::unique_ptr<GrFragmentProcessor>( |
| new BlendFragmentProcessor(std::move(src), std::move(dst), mode, shareBlendLogic)); |
| } |
| |
| const char* name() const override { return "Blend"; } |
| |
| std::unique_ptr<GrFragmentProcessor> clone() const override; |
| |
| private: |
| BlendFragmentProcessor(std::unique_ptr<GrFragmentProcessor> src, |
| std::unique_ptr<GrFragmentProcessor> dst, |
| SkBlendMode mode, |
| bool shareBlendLogic) |
| : INHERITED(kBlendFragmentProcessor_ClassID, OptFlags(src.get(), dst.get(), mode)) |
| , fMode(mode) |
| , fShareBlendLogic(shareBlendLogic) { |
| this->setIsBlendFunction(); |
| this->registerChild(std::move(src)); |
| this->registerChild(std::move(dst)); |
| } |
| |
| BlendFragmentProcessor(const BlendFragmentProcessor& that) |
| : INHERITED(that) |
| , fMode(that.fMode) |
| , fShareBlendLogic(that.fShareBlendLogic) {} |
| |
| #if GR_TEST_UTILS |
| SkString onDumpInfo() const override { |
| return SkStringPrintf("(fMode=%s)", SkBlendMode_Name(fMode)); |
| } |
| #endif |
| |
| static OptimizationFlags OptFlags(const GrFragmentProcessor* src, |
| const GrFragmentProcessor* dst, SkBlendMode mode) { |
| OptimizationFlags flags; |
| switch (mode) { |
| case SkBlendMode::kClear: |
| flags = kNone_OptimizationFlags; |
| break; |
| |
| // Just propagates src, and its flags: |
| case SkBlendMode::kSrc: |
| flags = ProcessorOptimizationFlags(src) & |
| ~kConstantOutputForConstantInput_OptimizationFlag; |
| break; |
| |
| // Just propagates dst, and its flags: |
| case SkBlendMode::kDst: |
| flags = ProcessorOptimizationFlags(dst) & |
| ~kConstantOutputForConstantInput_OptimizationFlag; |
| break; |
| |
| // Produces opaque if both src and dst are opaque. These also will modulate the child's |
| // output by either the input color or alpha. However, if the child is not compatible |
| // with the coverage as alpha then it may produce a color that is not valid premul. |
| case SkBlendMode::kSrcIn: |
| case SkBlendMode::kDstIn: |
| case SkBlendMode::kModulate: |
| if (src && dst) { |
| flags = ProcessorOptimizationFlags(src) & ProcessorOptimizationFlags(dst) & |
| kPreservesOpaqueInput_OptimizationFlag; |
| } else if (src) { |
| flags = ProcessorOptimizationFlags(src) & |
| ~kConstantOutputForConstantInput_OptimizationFlag; |
| } else if (dst) { |
| flags = ProcessorOptimizationFlags(dst) & |
| ~kConstantOutputForConstantInput_OptimizationFlag; |
| } else { |
| flags = kNone_OptimizationFlags; |
| } |
| break; |
| |
| // Produces zero when both are opaque, indeterminate if one is opaque. |
| case SkBlendMode::kSrcOut: |
| case SkBlendMode::kDstOut: |
| case SkBlendMode::kXor: |
| flags = kNone_OptimizationFlags; |
| break; |
| |
| // Is opaque if the dst is opaque. |
| case SkBlendMode::kSrcATop: |
| flags = ProcessorOptimizationFlags(dst) & kPreservesOpaqueInput_OptimizationFlag; |
| break; |
| |
| // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque. |
| case SkBlendMode::kDstATop: |
| case SkBlendMode::kScreen: |
| flags = ProcessorOptimizationFlags(src) & kPreservesOpaqueInput_OptimizationFlag; |
| break; |
| |
| // These modes are all opaque if either src or dst is opaque. All the advanced modes |
| // compute alpha as src-over. |
| case SkBlendMode::kSrcOver: |
| case SkBlendMode::kDstOver: |
| case SkBlendMode::kPlus: |
| case SkBlendMode::kOverlay: |
| case SkBlendMode::kDarken: |
| case SkBlendMode::kLighten: |
| case SkBlendMode::kColorDodge: |
| case SkBlendMode::kColorBurn: |
| case SkBlendMode::kHardLight: |
| case SkBlendMode::kSoftLight: |
| case SkBlendMode::kDifference: |
| case SkBlendMode::kExclusion: |
| case SkBlendMode::kMultiply: |
| case SkBlendMode::kHue: |
| case SkBlendMode::kSaturation: |
| case SkBlendMode::kColor: |
| case SkBlendMode::kLuminosity: |
| flags = (ProcessorOptimizationFlags(src) | ProcessorOptimizationFlags(dst)) & |
| kPreservesOpaqueInput_OptimizationFlag; |
| break; |
| } |
| if (does_cpu_blend_impl_match_gpu(mode) && |
| (!src || src->hasConstantOutputForConstantInput()) && |
| (!dst || dst->hasConstantOutputForConstantInput())) { |
| flags |= kConstantOutputForConstantInput_OptimizationFlag; |
| } |
| return flags; |
| } |
| |
| void onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const override { |
| if (fShareBlendLogic) { |
| b->add32(GrGLSLBlend::BlendKey(fMode)); |
| } else { |
| b->add32((int)fMode); |
| } |
| } |
| |
| bool onIsEqual(const GrFragmentProcessor& other) const override { |
| const BlendFragmentProcessor& cs = other.cast<BlendFragmentProcessor>(); |
| return fMode == cs.fMode; |
| } |
| |
| SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& input) const override { |
| const auto* src = this->childProcessor(0); |
| const auto* dst = this->childProcessor(1); |
| |
| SkPMColor4f srcColor = ConstantOutputForConstantInput(src, input); |
| SkPMColor4f dstColor = ConstantOutputForConstantInput(dst, input); |
| |
| return SkBlendMode_Apply(fMode, srcColor, dstColor); |
| } |
| |
| std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override; |
| |
| SkBlendMode fMode; |
| bool fShareBlendLogic; |
| |
| GR_DECLARE_FRAGMENT_PROCESSOR_TEST |
| |
| using INHERITED = GrFragmentProcessor; |
| }; |
| |
| ///////////////////////////////////////////////////////////////////// |
| |
| |
| GR_DEFINE_FRAGMENT_PROCESSOR_TEST(BlendFragmentProcessor) |
| |
| #if GR_TEST_UTILS |
| std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::TestCreate(GrProcessorTestData* d) { |
| // Create one or two random fragment processors. |
| std::unique_ptr<GrFragmentProcessor> src(GrProcessorUnitTest::MakeOptionalChildFP(d)); |
| std::unique_ptr<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d)); |
| if (d->fRandom->nextBool()) { |
| std::swap(src, dst); |
| } |
| bool shareLogic = d->fRandom->nextBool(); |
| |
| SkBlendMode mode = |
| static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); |
| return std::unique_ptr<GrFragmentProcessor>( |
| new BlendFragmentProcessor(std::move(src), std::move(dst), mode, shareLogic)); |
| } |
| #endif |
| |
| std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::clone() const { |
| return std::unique_ptr<GrFragmentProcessor>(new BlendFragmentProcessor(*this)); |
| } |
| |
| std::unique_ptr<GrFragmentProcessor::ProgramImpl> BlendFragmentProcessor::onMakeProgramImpl() |
| const { |
| class Impl : public ProgramImpl { |
| public: |
| void emitCode(EmitArgs& args) override { |
| GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
| const BlendFragmentProcessor& bfp = args.fFp.cast<BlendFragmentProcessor>(); |
| const SkBlendMode mode = bfp.fMode; |
| |
| // Invoke src/dst with our input color (or substitute input color if no child FP) |
| SkString srcColor = this->invokeChild(0, args); |
| SkString dstColor = this->invokeChild(1, args); |
| |
| if (bfp.fShareBlendLogic) { |
| // Use a blend expression that may rely on uniforms. |
| std::string blendExpr = GrGLSLBlend::BlendExpression(&args.fFp, |
| args.fUniformHandler, |
| &fBlendUniform, |
| srcColor.c_str(), |
| dstColor.c_str(), |
| mode); |
| fragBuilder->codeAppendf("return %s;", blendExpr.c_str()); |
| } else { |
| // Blend src and dst colors together using a hardwired built-in blend function. |
| fragBuilder->codeAppendf("return %s(%s, %s);", |
| skgpu::BlendFuncName(mode), |
| srcColor.c_str(), |
| dstColor.c_str()); |
| } |
| } |
| |
| void onSetData(const GrGLSLProgramDataManager& pdman, |
| const GrFragmentProcessor& fp) override { |
| const BlendFragmentProcessor& bfp = fp.cast<BlendFragmentProcessor>(); |
| if (bfp.fShareBlendLogic) { |
| GrGLSLBlend::SetBlendModeUniformData(pdman, fBlendUniform, bfp.fMode); |
| } |
| } |
| |
| UniformHandle fBlendUniform; |
| }; |
| |
| return std::make_unique<Impl>(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| std::unique_ptr<GrFragmentProcessor> GrBlendFragmentProcessor::Make( |
| std::unique_ptr<GrFragmentProcessor> src, |
| std::unique_ptr<GrFragmentProcessor> dst, |
| SkBlendMode mode, |
| bool shareBlendLogic) { |
| // These modes simplify dramatically in the shader, but only if we bypass the shared logic: |
| if (mode == SkBlendMode::kClear || mode == SkBlendMode::kSrc || mode == SkBlendMode::kDst) { |
| shareBlendLogic = false; |
| } |
| |
| return BlendFragmentProcessor::Make(std::move(src), std::move(dst), mode, shareBlendLogic); |
| } |