/*
 * 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/effects/GrBlendFragmentProcessor.h"

#include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/SkGr.h"
#include "src/gpu/effects/generated/GrConstColorProcessor.h"
#include "src/gpu/glsl/GrGLSLBlend.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"

using GrBlendFragmentProcessor::BlendBehavior;

// Some of the cpu implementations of blend modes differ too much 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-seperable modes differ too much. So does SoftLight. ColorBurn differs too much on our
    // test iOS device (but we just disable it across the aboard since it may happen on untested
    // GPUs).
    return mode <= SkBlendMode::kLastSeparableMode && mode != SkBlendMode::kSoftLight &&
           mode != SkBlendMode::kColorBurn;
}

static const char* BlendBehavior_Name(BlendBehavior behavior) {
    SkASSERT(unsigned(behavior) <= unsigned(BlendBehavior::kLastBlendBehavior));
    static constexpr const char* gStrings[] = {
        "Default",
        "Compose-One",
        "Compose-Two",
        "SkMode",
    };
    static_assert(SK_ARRAY_COUNT(gStrings) == size_t(BlendBehavior::kLastBlendBehavior) + 1);
    return gStrings[int(behavior)];
}

//////////////////////////////////////////////////////////////////////////////

class BlendFragmentProcessor : public GrFragmentProcessor {
public:
    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> src,
                                                     std::unique_ptr<GrFragmentProcessor> dst,
                                                     SkBlendMode mode, BlendBehavior behavior) {
        return std::unique_ptr<GrFragmentProcessor>(
                new BlendFragmentProcessor(std::move(src), std::move(dst), mode, behavior));
    }

    const char* name() const override { return "Blend"; }

    std::unique_ptr<GrFragmentProcessor> clone() const override;

    SkBlendMode getMode() const { return fMode; }
    BlendBehavior blendBehavior() const { return fBlendBehavior; }

private:
    BlendFragmentProcessor(std::unique_ptr<GrFragmentProcessor> src,
                             std::unique_ptr<GrFragmentProcessor> dst,
                             SkBlendMode mode, BlendBehavior behavior)
            : INHERITED(kBlendFragmentProcessor_ClassID, OptFlags(src.get(), dst.get(), mode))
            , fMode(mode)
            , fBlendBehavior(behavior) {
        if (fBlendBehavior == BlendBehavior::kDefault) {
            fBlendBehavior = (src && dst) ? BlendBehavior::kComposeTwoBehavior
                                          : BlendBehavior::kComposeOneBehavior;
        }
        this->registerChild(std::move(src));
        this->registerChild(std::move(dst));
    }

    BlendFragmentProcessor(const BlendFragmentProcessor& that)
            : INHERITED(kBlendFragmentProcessor_ClassID, ProcessorOptimizationFlags(&that))
            , fMode(that.fMode)
            , fBlendBehavior(that.fBlendBehavior) {
        this->cloneAndRegisterAllChildProcessors(that);
    }

#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:
            case SkBlendMode::kSrc:
            case SkBlendMode::kDst:
                SK_ABORT("Shouldn't have created a Blend FP as 'clear', 'src', or 'dst'.");
                flags = kNone_OptimizationFlags;
                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 = (dst ? ProcessorOptimizationFlags(dst) : kAll_OptimizationFlags) &
                         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 = (src ? ProcessorOptimizationFlags(src) : kAll_OptimizationFlags) &
                         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 = ((src ? ProcessorOptimizationFlags(src) : kAll_OptimizationFlags) |
                         (dst ? ProcessorOptimizationFlags(dst) : kAll_OptimizationFlags)) &
                         kPreservesOpaqueInput_OptimizationFlag;
                break;
        }
        if (does_cpu_blend_impl_match_gpu(mode) &&
            (src ? src->hasConstantOutputForConstantInput() : true) &&
            (dst ? dst->hasConstantOutputForConstantInput() : true)) {
            flags |= kConstantOutputForConstantInput_OptimizationFlag;
        }
        return flags;
    }

    void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
        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);

        switch (fBlendBehavior) {
            case BlendBehavior::kComposeOneBehavior: {
                SkPMColor4f srcColor = src ? ConstantOutputForConstantInput(src, SK_PMColor4fWHITE)
                                           : input;
                SkPMColor4f dstColor = dst ? ConstantOutputForConstantInput(dst, SK_PMColor4fWHITE)
                                           : input;
                return SkBlendMode_Apply(fMode, srcColor, dstColor);
            }

            case BlendBehavior::kComposeTwoBehavior: {
                SkPMColor4f opaqueInput = { input.fR, input.fG, input.fB, 1 };
                SkPMColor4f srcColor = ConstantOutputForConstantInput(src, opaqueInput);
                SkPMColor4f dstColor = ConstantOutputForConstantInput(dst, opaqueInput);
                SkPMColor4f result = SkBlendMode_Apply(fMode, srcColor, dstColor);
                return result * input.fA;
            }

            case BlendBehavior::kSkModeBehavior: {
                SkPMColor4f srcColor = src ? ConstantOutputForConstantInput(src, SK_PMColor4fWHITE)
                                           : input;
                SkPMColor4f dstColor = dst ? ConstantOutputForConstantInput(dst, input)
                                           : input;
                return SkBlendMode_Apply(fMode, srcColor, dstColor);
            }

            default:
                SK_ABORT("unrecognized blend behavior");
                return input;
        }
    }

    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;

    SkBlendMode fMode;
    BlendBehavior fBlendBehavior;

    GR_DECLARE_FRAGMENT_PROCESSOR_TEST

    typedef GrFragmentProcessor INHERITED;
};

/////////////////////////////////////////////////////////////////////

class GLBlendFragmentProcessor : public GrGLSLFragmentProcessor {
public:
    void emitCode(EmitArgs&) override;

private:
    typedef GrGLSLFragmentProcessor INHERITED;
};

/////////////////////////////////////////////////////////////////////

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);
    }

    SkBlendMode mode;
    BlendBehavior behavior;
    do {
        mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
        behavior = static_cast<BlendBehavior>(
                d->fRandom->nextRangeU(0, (int)BlendBehavior::kLastBlendBehavior));
    } while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode);
    return std::unique_ptr<GrFragmentProcessor>(
            new BlendFragmentProcessor(std::move(src), std::move(dst), mode, behavior));
}
#endif

std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::clone() const {
    return std::unique_ptr<GrFragmentProcessor>(new BlendFragmentProcessor(*this));
}

GrGLSLFragmentProcessor* BlendFragmentProcessor::onCreateGLSLInstance() const{
    return new GLBlendFragmentProcessor;
}

/////////////////////////////////////////////////////////////////////

void GLBlendFragmentProcessor::emitCode(EmitArgs& args) {
    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
    const BlendFragmentProcessor& cs = args.fFp.cast<BlendFragmentProcessor>();
    SkBlendMode mode = cs.getMode();
    BlendBehavior behavior = cs.blendBehavior();

    // Load the input color and make an opaque copy if needed.
    fragBuilder->codeAppendf("// Blend mode: %s (%s behavior)\n",
                             SkBlendMode_Name(mode), BlendBehavior_Name(behavior));

    SkString srcColor, dstColor;
    switch (behavior) {
        case BlendBehavior::kComposeOneBehavior:
            // Compose-one operations historically leave the alpha on the input color.
            srcColor = cs.childProcessor(0) ? this->invokeChild(0, "half4(1)", args)
                                            : SkString(args.fInputColor);
            dstColor = cs.childProcessor(1) ? this->invokeChild(1, "half4(1)", args)
                                            : SkString(args.fInputColor);
            break;

        case BlendBehavior::kComposeTwoBehavior:
            // Compose-two operations historically have forced the input color to opaque.
            // We're going to re-apply the input color's alpha below, so feed the *unpremul* RGB
            // to the children, to avoid double-applying alpha.
            fragBuilder->codeAppendf("half4 inputOpaque = unpremul(%s).rgb1;\n", args.fInputColor);
            srcColor = this->invokeChild(0, "inputOpaque", args);
            dstColor = this->invokeChild(1, "inputOpaque", args);
            break;

        case BlendBehavior::kSkModeBehavior:
            // SkModeColorFilter operations act like ComposeOne, but pass the input color to dst.
            srcColor = cs.childProcessor(0) ? this->invokeChild(0, "half4(1)", args)
                                            : SkString(args.fInputColor);
            dstColor = cs.childProcessor(1) ? this->invokeChild(1, args.fInputColor, args)
                                            : SkString(args.fInputColor);
            break;

        default:
            SK_ABORT("unrecognized blend behavior");
            break;
    }

    // Blend src and dst colors together.
    GrGLSLBlend::AppendMode(fragBuilder, srcColor.c_str(), dstColor.c_str(),
                            args.fOutputColor, mode);

    // Reapply alpha from input color if we are doing a compose-two.
    if (behavior == BlendBehavior::kComposeTwoBehavior) {
        fragBuilder->codeAppendf("%s *= %s.a;\n", args.fOutputColor, args.fInputColor);
    }
}

//////////////////////////////////////////////////////////////////////////////

std::unique_ptr<GrFragmentProcessor> GrBlendFragmentProcessor::Make(
        std::unique_ptr<GrFragmentProcessor> src,
        std::unique_ptr<GrFragmentProcessor> dst,
        SkBlendMode mode, BlendBehavior behavior) {
    switch (mode) {
        case SkBlendMode::kClear:
            return GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT);
        case SkBlendMode::kSrc:
            return GrFragmentProcessor::OverrideInput(std::move(src), SK_PMColor4fWHITE,
                                                      /*useUniform=*/false);
        case SkBlendMode::kDst:
            return GrFragmentProcessor::OverrideInput(std::move(dst), SK_PMColor4fWHITE,
                                                      /*useUniform=*/false);
        default:
            return BlendFragmentProcessor::Make(std::move(src), std::move(dst), mode, behavior);
    }
}
