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

#include "include/core/SkBlendMode.h"
#include "include/core/SkRefCnt.h"
#include "include/private/base/SkAssert.h"
#include "src/base/SkRandom.h"
#include "src/gpu/Blend.h"
#include "src/gpu/KeyBuilder.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrProcessorAnalysis.h"
#include "src/gpu/ganesh/GrProcessorUnitTest.h"
#include "src/gpu/ganesh/GrShaderCaps.h"
#include "src/gpu/ganesh/GrXferProcessor.h"
#include "src/gpu/ganesh/glsl/GrGLSLBlend.h"
#include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"

#include <memory>
#include <string>

class GrGLSLProgramDataManager;
enum class GrClampType;

bool GrCustomXfermode::IsSupportedMode(SkBlendMode mode) {
    return (int)mode  > (int)SkBlendMode::kLastCoeffMode &&
           (int)mode <= (int)SkBlendMode::kLastMode;
}

///////////////////////////////////////////////////////////////////////////////
// Static helpers
///////////////////////////////////////////////////////////////////////////////

static constexpr skgpu::BlendEquation hw_blend_equation(SkBlendMode mode) {
    constexpr int kEqOffset = ((int)skgpu::BlendEquation::kOverlay - (int)SkBlendMode::kOverlay);
    static_assert((int)skgpu::BlendEquation::kOverlay == (int)SkBlendMode::kOverlay + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kDarken == (int)SkBlendMode::kDarken + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kLighten == (int)SkBlendMode::kLighten + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kColorDodge == (int)SkBlendMode::kColorDodge + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kColorBurn == (int)SkBlendMode::kColorBurn + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kHardLight == (int)SkBlendMode::kHardLight + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kSoftLight == (int)SkBlendMode::kSoftLight + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kDifference == (int)SkBlendMode::kDifference + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kExclusion == (int)SkBlendMode::kExclusion + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kMultiply == (int)SkBlendMode::kMultiply + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kHSLHue == (int)SkBlendMode::kHue + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kHSLSaturation == (int)SkBlendMode::kSaturation + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kHSLColor == (int)SkBlendMode::kColor + kEqOffset);
    static_assert((int)skgpu::BlendEquation::kHSLLuminosity == (int)SkBlendMode::kLuminosity + kEqOffset);

    // There's an illegal BlendEquation that corresponds to no SkBlendMode, hence the extra +1.
    static_assert(skgpu::kBlendEquationCnt == (int)SkBlendMode::kLastMode + 1 + 1 + kEqOffset);

    return static_cast<skgpu::BlendEquation>((int)mode + kEqOffset);
#undef EQ_OFFSET
}

static bool can_use_hw_blend_equation(skgpu::BlendEquation equation,
                                      GrProcessorAnalysisCoverage coverage, const GrCaps& caps) {
    if (!caps.advancedBlendEquationSupport()) {
        return false;
    }
    if (GrProcessorAnalysisCoverage::kLCD == coverage) {
        return false; // LCD coverage must be applied after the blend equation.
    }
    if (caps.isAdvancedBlendEquationDisabled(equation)) {
        return false;
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Xfer Processor
///////////////////////////////////////////////////////////////////////////////

class CustomXP : public GrXferProcessor {
public:
    CustomXP(SkBlendMode mode, skgpu::BlendEquation hwBlendEquation)
        : INHERITED(kCustomXP_ClassID)
        , fMode(mode)
        , fHWBlendEquation(hwBlendEquation) {}

    CustomXP(SkBlendMode mode, GrProcessorAnalysisCoverage coverage)
            : INHERITED(kCustomXP_ClassID, /*willReadDstColor=*/true, coverage)
            , fMode(mode)
            , fHWBlendEquation(skgpu::BlendEquation::kIllegal) {
    }

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

    std::unique_ptr<ProgramImpl> makeProgramImpl() const override;

    GrXferBarrierType xferBarrierType(const GrCaps&) const override;

private:
    bool hasHWBlendEquation() const { return skgpu::BlendEquation::kIllegal != fHWBlendEquation; }

    void onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const override;

    void onGetBlendInfo(skgpu::BlendInfo*) const override;

    bool onIsEqual(const GrXferProcessor& xpBase) const override;

    const SkBlendMode          fMode;
    const skgpu::BlendEquation fHWBlendEquation;

    using INHERITED = GrXferProcessor;
};

void CustomXP::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const {
    if (this->hasHWBlendEquation()) {
        SkASSERT(caps.fAdvBlendEqInteraction > 0);  // 0 will mean !xp.hasHWBlendEquation().
        b->addBool(true, "has hardware blend equation");
        b->add32(caps.fAdvBlendEqInteraction);
    } else {
        b->addBool(false, "has hardware blend equation");
        b->add32(GrGLSLBlend::BlendKey(fMode));
    }
}

std::unique_ptr<GrXferProcessor::ProgramImpl> CustomXP::makeProgramImpl() const {
    SkASSERT(this->willReadDstColor() != this->hasHWBlendEquation());

    class Impl : public ProgramImpl {
    private:
        void emitOutputsForBlendState(const EmitArgs& args) override {
            const CustomXP& xp = args.fXP.cast<CustomXP>();
            SkASSERT(xp.hasHWBlendEquation());

            GrGLSLXPFragmentBuilder* fragBuilder = args.fXPFragBuilder;
            fragBuilder->enableAdvancedBlendEquationIfNeeded(xp.fHWBlendEquation);

            // Apply coverage by multiplying it into the src color before blending. This will "just
            // work" automatically. (See analysisProperties())
            fragBuilder->codeAppendf("%s = %s * %s;",
                                     args.fOutputPrimary,
                                     args.fInputCoverage,
                                     args.fInputColor);
        }

        void emitBlendCodeForDstRead(GrGLSLXPFragmentBuilder* fragBuilder,
                                     GrGLSLUniformHandler* uniformHandler,
                                     const char* srcColor,
                                     const char* srcCoverage,
                                     const char* dstColor,
                                     const char* outColor,
                                     const char* outColorSecondary,
                                     const GrXferProcessor& proc) override {
            const CustomXP& xp = proc.cast<CustomXP>();
            SkASSERT(!xp.hasHWBlendEquation());

            std::string blendExpr = GrGLSLBlend::BlendExpression(
                    &xp, uniformHandler, &fBlendUniform, srcColor, dstColor, xp.fMode);
            fragBuilder->codeAppendf("%s = %s;", outColor, blendExpr.c_str());

            // Apply coverage.
            DefaultCoverageModulation(fragBuilder,
                                      srcCoverage,
                                      dstColor,
                                      outColor,
                                      outColorSecondary,
                                      xp);
        }

        void onSetData(const GrGLSLProgramDataManager& pdman,
                       const GrXferProcessor& proc) override {
            if (fBlendUniform.isValid()) {
                const CustomXP& xp = proc.cast<CustomXP>();
                GrGLSLBlend::SetBlendModeUniformData(pdman, fBlendUniform, xp.fMode);
            }
        }

        GrGLSLUniformHandler::UniformHandle fBlendUniform;
    };

    return std::make_unique<Impl>();
}

bool CustomXP::onIsEqual(const GrXferProcessor& other) const {
    const CustomXP& s = other.cast<CustomXP>();
    return fMode == s.fMode && fHWBlendEquation == s.fHWBlendEquation;
}

GrXferBarrierType CustomXP::xferBarrierType(const GrCaps& caps) const {
    if (this->hasHWBlendEquation() && !caps.advancedCoherentBlendEquationSupport()) {
        return kBlend_GrXferBarrierType;
    }
    return kNone_GrXferBarrierType;
}

void CustomXP::onGetBlendInfo(skgpu::BlendInfo* blendInfo) const {
    if (this->hasHWBlendEquation()) {
        blendInfo->fEquation = fHWBlendEquation;
    }
}

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

// See the comment above GrXPFactory's definition about this warning suppression.
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#endif
class CustomXPFactory : public GrXPFactory {
public:
    constexpr CustomXPFactory(SkBlendMode mode)
            : fMode(mode), fHWBlendEquation(hw_blend_equation(mode)) {}

private:
    sk_sp<const GrXferProcessor> makeXferProcessor(const GrProcessorAnalysisColor&,
                                                   GrProcessorAnalysisCoverage,
                                                   const GrCaps&,
                                                   GrClampType) const override;

    AnalysisProperties analysisProperties(const GrProcessorAnalysisColor&,
                                          const GrProcessorAnalysisCoverage&,
                                          const GrCaps&,
                                          GrClampType) const override;

    GR_DECLARE_XP_FACTORY_TEST

    SkBlendMode fMode;
    skgpu::BlendEquation fHWBlendEquation;

    using INHERITED = GrXPFactory;
};
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

sk_sp<const GrXferProcessor> CustomXPFactory::makeXferProcessor(
        const GrProcessorAnalysisColor&,
        GrProcessorAnalysisCoverage coverage,
        const GrCaps& caps,
        GrClampType clampType) const {
    SkASSERT(GrCustomXfermode::IsSupportedMode(fMode));
    if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) {
        return sk_sp<GrXferProcessor>(new CustomXP(fMode, fHWBlendEquation));
    }
    return sk_sp<GrXferProcessor>(new CustomXP(fMode, coverage));
}

GrXPFactory::AnalysisProperties CustomXPFactory::analysisProperties(
        const GrProcessorAnalysisColor&, const GrProcessorAnalysisCoverage& coverage,
        const GrCaps& caps, GrClampType clampType) const {
    /*
      The general SVG blend equation is defined in the spec as follows:

        Dca' = B(Sc, Dc) * Sa * Da + Y * Sca * (1-Da) + Z * Dca * (1-Sa)
        Da'  = X * Sa * Da + Y * Sa * (1-Da) + Z * Da * (1-Sa)

      (Note that Sca, Dca indicate RGB vectors that are premultiplied by alpha,
       and that B(Sc, Dc) is a mode-specific function that accepts non-multiplied
       RGB colors.)

      For every blend mode supported by this class, i.e. the "advanced" blend
      modes, X=Y=Z=1 and this equation reduces to the PDF blend equation.

      It can be shown that when X=Y=Z=1, these equations can modulate alpha for
      coverage.


      == Color ==

      We substitute Y=Z=1 and define a blend() function that calculates Dca' in
      terms of premultiplied alpha only:

        blend(Sca, Dca, Sa, Da) = {Dca : if Sa == 0,
                                   Sca : if Da == 0,
                                   B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa) : if
      Sa,Da != 0}

      And for coverage modulation, we use a post blend src-over model:

        Dca'' = f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca

      (Where f is the fractional coverage.)

      Next we show that canTweakAlphaForCoverage() is true by proving the
      following relationship:

        blend(f*Sca, Dca, f*Sa, Da) == f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca

      General case (f,Sa,Da != 0):

        f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca
          = f * (B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa)) + (1-f) * Dca  [Sa,Da !=
      0, definition of blend()]
          = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + f*Dca * (1-Sa) + Dca - f*Dca
          = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da + f*Dca - f*Dca * Sa + Dca - f*Dca
          = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da - f*Dca * Sa + Dca
          = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) - f*Dca * Sa + Dca
          = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa)
          = B(f*Sca/f*Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa)  [f!=0]
          = blend(f*Sca, Dca, f*Sa, Da)  [definition of blend()]

      Corner cases (Sa=0, Da=0, and f=0):

        Sa=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca
                = f * Dca + (1-f) * Dca  [Sa=0, definition of blend()]
                = Dca
                = blend(0, Dca, 0, Da)  [definition of blend()]
                = blend(f*Sca, Dca, f*Sa, Da)  [Sa=0]

        Da=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca
                = f * Sca + (1-f) * Dca  [Da=0, definition of blend()]
                = f * Sca  [Da=0]
                = blend(f*Sca, 0, f*Sa, 0)  [definition of blend()]
                = blend(f*Sca, Dca, f*Sa, Da)  [Da=0]

        f=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca
               = Dca  [f=0]
               = blend(0, Dca, 0, Da)  [definition of blend()]
               = blend(f*Sca, Dca, f*Sa, Da)  [f=0]

      == Alpha ==

      We substitute X=Y=Z=1 and define a blend() function that calculates Da':

        blend(Sa, Da) = Sa * Da + Sa * (1-Da) + Da * (1-Sa)
                      = Sa * Da + Sa - Sa * Da + Da - Da * Sa
                      = Sa + Da - Sa * Da

      We use the same model for coverage modulation as we did with color:

        Da'' = f * blend(Sa, Da) + (1-f) * Da

      And show that canTweakAlphaForCoverage() is true by proving the following
      relationship:

        blend(f*Sa, Da) == f * blend(Sa, Da) + (1-f) * Da


        f * blend(Sa, Da) + (1-f) * Da
          = f * (Sa + Da - Sa * Da) + (1-f) * Da
          = f*Sa + f*Da - f*Sa * Da + Da - f*Da
          = f*Sa - f*Sa * Da + Da
          = f*Sa + Da - f*Sa * Da
          = blend(f*Sa, Da)
    */
    if (can_use_hw_blend_equation(fHWBlendEquation, coverage, caps)) {
        if (caps.blendEquationSupport() == GrCaps::kAdvancedCoherent_BlendEquationSupport) {
            return AnalysisProperties::kCompatibleWithCoverageAsAlpha;
        } else {
            return AnalysisProperties::kCompatibleWithCoverageAsAlpha |
                   AnalysisProperties::kRequiresNonOverlappingDraws |
                   AnalysisProperties::kUsesNonCoherentHWBlending;
        }
    }
    return AnalysisProperties::kCompatibleWithCoverageAsAlpha |
           AnalysisProperties::kReadsDstInShader;
}

GR_DEFINE_XP_FACTORY_TEST(CustomXPFactory)
#if defined(GPU_TEST_UTILS)
const GrXPFactory* CustomXPFactory::TestGet(GrProcessorTestData* d) {
    int mode = d->fRandom->nextRangeU((int)SkBlendMode::kLastCoeffMode + 1,
                                      (int)SkBlendMode::kLastSeparableMode);

    return GrCustomXfermode::Get((SkBlendMode)mode);
}
#endif

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

const GrXPFactory* GrCustomXfermode::Get(SkBlendMode mode) {
    static constexpr const CustomXPFactory gOverlay(SkBlendMode::kOverlay);
    static constexpr const CustomXPFactory gDarken(SkBlendMode::kDarken);
    static constexpr const CustomXPFactory gLighten(SkBlendMode::kLighten);
    static constexpr const CustomXPFactory gColorDodge(SkBlendMode::kColorDodge);
    static constexpr const CustomXPFactory gColorBurn(SkBlendMode::kColorBurn);
    static constexpr const CustomXPFactory gHardLight(SkBlendMode::kHardLight);
    static constexpr const CustomXPFactory gSoftLight(SkBlendMode::kSoftLight);
    static constexpr const CustomXPFactory gDifference(SkBlendMode::kDifference);
    static constexpr const CustomXPFactory gExclusion(SkBlendMode::kExclusion);
    static constexpr const CustomXPFactory gMultiply(SkBlendMode::kMultiply);
    static constexpr const CustomXPFactory gHue(SkBlendMode::kHue);
    static constexpr const CustomXPFactory gSaturation(SkBlendMode::kSaturation);
    static constexpr const CustomXPFactory gColor(SkBlendMode::kColor);
    static constexpr const CustomXPFactory gLuminosity(SkBlendMode::kLuminosity);
    switch (mode) {
        case SkBlendMode::kOverlay:
            return &gOverlay;
        case SkBlendMode::kDarken:
            return &gDarken;
        case SkBlendMode::kLighten:
            return &gLighten;
        case SkBlendMode::kColorDodge:
            return &gColorDodge;
        case SkBlendMode::kColorBurn:
            return &gColorBurn;
        case SkBlendMode::kHardLight:
            return &gHardLight;
        case SkBlendMode::kSoftLight:
            return &gSoftLight;
        case SkBlendMode::kDifference:
            return &gDifference;
        case SkBlendMode::kExclusion:
            return &gExclusion;
        case SkBlendMode::kMultiply:
            return &gMultiply;
        case SkBlendMode::kHue:
            return &gHue;
        case SkBlendMode::kSaturation:
            return &gSaturation;
        case SkBlendMode::kColor:
            return &gColor;
        case SkBlendMode::kLuminosity:
            return &gLuminosity;
        default:
            SkASSERT(!GrCustomXfermode::IsSupportedMode(mode));
            return nullptr;
    }
}
