/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef GrDistanceFieldGeoProc_DEFINED
#define GrDistanceFieldGeoProc_DEFINED

#if !defined(SK_DISABLE_SDF_TEXT)

#include "include/core/SkMatrix.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "src/base/SkArenaAlloc.h"
#include "src/gpu/ganesh/GrGeometryProcessor.h"
#include "src/gpu/ganesh/GrProcessorUnitTest.h"
#include "src/gpu/ganesh/GrSamplerState.h"

#include <cstdint>
#include <memory>

class GrSurfaceProxyView;
namespace skgpu { class KeyBuilder; }
struct GrShaderCaps;

enum GrDistanceFieldEffectFlags {
    kSimilarity_DistanceFieldEffectFlag   = 0x001, // ctm is similarity matrix
    kScaleOnly_DistanceFieldEffectFlag    = 0x002, // ctm has only scale and translate
    kPerspective_DistanceFieldEffectFlag  = 0x004, // ctm has perspective (and positions are x,y,w)
    kUseLCD_DistanceFieldEffectFlag       = 0x008, // use lcd text
    kBGR_DistanceFieldEffectFlag          = 0x010, // lcd display has bgr order
    kPortrait_DistanceFieldEffectFlag     = 0x020, // lcd display is in portrait mode
    kGammaCorrect_DistanceFieldEffectFlag = 0x040, // assume gamma-correct output (linear blending)
    kAliased_DistanceFieldEffectFlag      = 0x080, // monochrome output
    kWideColor_DistanceFieldEffectFlag    = 0x100, // use wide color (only for path)

    kInvalid_DistanceFieldEffectFlag      = 0x200,   // invalid state (for initialization)

    kUniformScale_DistanceFieldEffectMask = kSimilarity_DistanceFieldEffectFlag |
                                            kScaleOnly_DistanceFieldEffectFlag,
    // The subset of the flags relevant to GrDistanceFieldA8TextGeoProc
    kNonLCD_DistanceFieldEffectMask       = kSimilarity_DistanceFieldEffectFlag |
                                            kScaleOnly_DistanceFieldEffectFlag |
                                            kPerspective_DistanceFieldEffectFlag |
                                            kGammaCorrect_DistanceFieldEffectFlag |
                                            kAliased_DistanceFieldEffectFlag,
    // The subset of the flags relevant to GrDistanceFieldPathGeoProc
    kPath_DistanceFieldEffectMask         = kSimilarity_DistanceFieldEffectFlag |
                                            kScaleOnly_DistanceFieldEffectFlag |
                                            kPerspective_DistanceFieldEffectFlag |
                                            kGammaCorrect_DistanceFieldEffectFlag |
                                            kAliased_DistanceFieldEffectFlag |
                                            kWideColor_DistanceFieldEffectFlag,
    // The subset of the flags relevant to GrDistanceFieldLCDTextGeoProc
    kLCD_DistanceFieldEffectMask          = kSimilarity_DistanceFieldEffectFlag |
                                            kScaleOnly_DistanceFieldEffectFlag |
                                            kPerspective_DistanceFieldEffectFlag |
                                            kUseLCD_DistanceFieldEffectFlag |
                                            kBGR_DistanceFieldEffectFlag |
                                            kPortrait_DistanceFieldEffectFlag |
                                            kGammaCorrect_DistanceFieldEffectFlag,
};

/**
 * The output color of this effect is a modulation of the input color and a sample from a
 * distance field texture (using a smoothed step function near 0.5).
 * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input
 * coords are a custom attribute. Gamma correction is handled via a texture LUT.
 */
class GrDistanceFieldA8TextGeoProc : public GrGeometryProcessor {
public:
    inline static constexpr int kMaxTextures = 4;

    /** The local matrix should be identity if local coords are not required by the GrPipeline. */
#ifdef SK_GAMMA_APPLY_TO_A8
    static GrGeometryProcessor* Make(SkArenaAlloc* arena,
                                     const GrShaderCaps& caps,
                                     const GrSurfaceProxyView* views,
                                     int numActiveViews,
                                     GrSamplerState params,
                                     float lum,
                                     uint32_t flags,
                                     const SkMatrix& localMatrixIfUsesLocalCoords) {
        return arena->make([&](void* ptr) {
            return new (ptr) GrDistanceFieldA8TextGeoProc(
                    caps, views, numActiveViews, params, lum, flags, localMatrixIfUsesLocalCoords);
        });
    }
#else
    static GrGeometryProcessor* Make(SkArenaAlloc* arena,
                                     const GrShaderCaps& caps,
                                     const GrSurfaceProxyView* views,
                                     int numActiveViews,
                                     GrSamplerState params,
                                     uint32_t flags,
                                     const SkMatrix& localMatrixIfUsesLocalCoords) {
        return arena->make([&](void* ptr) {
            return new (ptr) GrDistanceFieldA8TextGeoProc(
                    caps, views, numActiveViews, params, flags, localMatrixIfUsesLocalCoords);
        });
    }
#endif

    ~GrDistanceFieldA8TextGeoProc() override {}

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

    void addNewViews(const GrSurfaceProxyView* views, int numViews, GrSamplerState);

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

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

private:
    class Impl;

    GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps,
                                 const GrSurfaceProxyView* views,
                                 int numActiveViews,
                                 GrSamplerState params,
#ifdef SK_GAMMA_APPLY_TO_A8
                                 float distanceAdjust,
#endif
                                 uint32_t flags,
                                 const SkMatrix& localMatrix);

    const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }

    TextureSampler   fTextureSamplers[kMaxTextures];
    SkISize          fAtlasDimensions;  // dimensions for all textures used with fTextureSamplers[].
    SkMatrix         fLocalMatrix;
    Attribute        fInPosition;
    Attribute        fInColor;
    Attribute        fInTextureCoords;
    uint32_t         fFlags;
#ifdef SK_GAMMA_APPLY_TO_A8
    float            fDistanceAdjust;
#endif

    GR_DECLARE_GEOMETRY_PROCESSOR_TEST

    using INHERITED = GrGeometryProcessor;
};

/**
 * The output color of this effect is a modulation of the input color and a sample from a
 * distance field texture (using a smoothed step function near 0.5).
 * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input
 * coords are a custom attribute. No gamma correct blending is applied. Used for paths only.
 */
class GrDistanceFieldPathGeoProc : public GrGeometryProcessor {
public:
    inline static constexpr int kMaxTextures = 4;

    /** The local matrix should be identity if local coords are not required by the GrPipeline. */
    static GrGeometryProcessor* Make(SkArenaAlloc* arena, const GrShaderCaps& caps,
                                     const GrSurfaceProxyView* views, int numActiveViews,
                                     GrSamplerState params, const SkMatrix& localMatrix,
                                     uint32_t flags) {
        return arena->make([&](void* ptr) {
            return new (ptr) GrDistanceFieldPathGeoProc(caps, views, numActiveViews,
                                                        params, localMatrix, flags);
        });
    }

    ~GrDistanceFieldPathGeoProc() override {}

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

    void addNewViews(const GrSurfaceProxyView*, int numActiveViews, GrSamplerState);

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

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

private:
    class Impl;

    GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
                               const GrSurfaceProxyView* views,
                               int numActiveViews,
                               GrSamplerState,
                               const SkMatrix& localMatrix,
                               uint32_t flags);

    const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }

    SkMatrix         fLocalMatrix;
    TextureSampler   fTextureSamplers[kMaxTextures];
    SkISize          fAtlasDimensions;  // dimensions for all textures used with fTextureSamplers[].
    Attribute        fInPosition;
    Attribute        fInColor;
    Attribute        fInTextureCoords;
    uint32_t         fFlags;

    GR_DECLARE_GEOMETRY_PROCESSOR_TEST

    using INHERITED = GrGeometryProcessor;
};

/**
 * The output color of this effect is a modulation of the input color and samples from a
 * distance field texture (using a smoothed step function near 0.5), adjusted for LCD displays.
 * It allows explicit specification of the filtering and wrap modes (GrSamplerState). The input
 * coords are a custom attribute. Gamma correction is handled via a texture LUT.
 */
class GrDistanceFieldLCDTextGeoProc : public GrGeometryProcessor {
public:
    inline static constexpr int kMaxTextures = 4;

    struct DistanceAdjust {
        SkScalar fR, fG, fB;
        static DistanceAdjust Make(SkScalar r, SkScalar g, SkScalar b) {
            DistanceAdjust result;
            result.fR = r; result.fG = g; result.fB = b;
            return result;
        }
        bool operator==(const DistanceAdjust& wa) const {
            return (fR == wa.fR && fG == wa.fG && fB == wa.fB);
        }
        bool operator!=(const DistanceAdjust& wa) const {
            return !(*this == wa);
        }
    };

    static GrGeometryProcessor* Make(SkArenaAlloc* arena,
                                     const GrShaderCaps& caps,
                                     const GrSurfaceProxyView* views,
                                     int numActiveViews,
                                     GrSamplerState params,
                                     DistanceAdjust distanceAdjust,
                                     uint32_t flags,
                                     const SkMatrix& localMatrixIfUsesLocalCoords) {
        return arena->make([&](void* ptr) {
            return new (ptr) GrDistanceFieldLCDTextGeoProc(caps, views, numActiveViews, params,
                                                           distanceAdjust, flags,
                                                           localMatrixIfUsesLocalCoords);
        });
    }

    ~GrDistanceFieldLCDTextGeoProc() override {}

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

    void addNewViews(const GrSurfaceProxyView*, int numActiveViews, GrSamplerState);

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

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

private:
    class Impl;

    GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps, const GrSurfaceProxyView* views,
                                  int numActiveViews, GrSamplerState params, DistanceAdjust wa,
                                  uint32_t flags, const SkMatrix& localMatrix);

    const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }

    TextureSampler   fTextureSamplers[kMaxTextures];
    SkISize          fAtlasDimensions;  // dimensions for all textures used with fTextureSamplers[].
    const SkMatrix   fLocalMatrix;
    DistanceAdjust   fDistanceAdjust;
    Attribute        fInPosition;
    Attribute        fInColor;
    Attribute        fInTextureCoords;
    uint32_t         fFlags;

    GR_DECLARE_GEOMETRY_PROCESSOR_TEST

    using INHERITED = GrGeometryProcessor;
};

#endif // !defined(SK_DISABLE_SDF_TEXT)

#endif
