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

#ifndef GrProcessorSet_DEFINED
#define GrProcessorSet_DEFINED

#include "GrFragmentProcessor.h"
#include "GrPaint.h"
#include "GrProcessorAnalysis.h"
#include "SkTemplates.h"
#include "GrXferProcessor.h"

class GrAppliedClip;
class GrXPFactory;

class GrProcessorSet {
private:
    // Arbitrary constructor arg for empty set and analysis
    enum class Empty { kEmpty };

public:
    GrProcessorSet(GrPaint&&);
    GrProcessorSet(SkBlendMode);
    GrProcessorSet(std::unique_ptr<GrFragmentProcessor> colorFP);
    GrProcessorSet(GrProcessorSet&&);
    GrProcessorSet(const GrProcessorSet&) = delete;
    GrProcessorSet& operator=(const GrProcessorSet&) = delete;

    ~GrProcessorSet();

    int numColorFragmentProcessors() const { return fColorFragmentProcessorCnt; }
    int numCoverageFragmentProcessors() const {
        return this->numFragmentProcessors() - fColorFragmentProcessorCnt;
    }

    const GrFragmentProcessor* colorFragmentProcessor(int idx) const {
        SkASSERT(idx < fColorFragmentProcessorCnt);
        return fFragmentProcessors[idx + fFragmentProcessorOffset].get();
    }
    const GrFragmentProcessor* coverageFragmentProcessor(int idx) const {
        return fFragmentProcessors[idx + fColorFragmentProcessorCnt +
                                   fFragmentProcessorOffset].get();
    }

    const GrXferProcessor* xferProcessor() const {
        SkASSERT(this->isFinalized());
        return fXP.fProcessor;
    }
    sk_sp<const GrXferProcessor> refXferProcessor() const {
        SkASSERT(this->isFinalized());
        return sk_ref_sp(fXP.fProcessor);
    }

    std::unique_ptr<const GrFragmentProcessor> detachColorFragmentProcessor(int idx) {
        SkASSERT(idx < fColorFragmentProcessorCnt);
        return std::move(fFragmentProcessors[idx + fFragmentProcessorOffset]);
    }

    std::unique_ptr<const GrFragmentProcessor> detachCoverageFragmentProcessor(int idx) {
        return std::move(
                fFragmentProcessors[idx + fFragmentProcessorOffset + fColorFragmentProcessorCnt]);
    }

    /** Comparisons are only legal on finalized processor sets. */
    bool operator==(const GrProcessorSet& that) const;
    bool operator!=(const GrProcessorSet& that) const { return !(*this == that); }

    /**
     * This is used to report results of processor analysis when a processor set is finalized (see
     * below).
     */
    class Analysis {
    public:
        Analysis(const Analysis&) = default;
        Analysis() { *reinterpret_cast<uint32_t*>(this) = 0; }

        bool isInitialized() const { return fIsInitialized; }
        bool usesLocalCoords() const { return fUsesLocalCoords; }
        bool requiresDstTexture() const { return fRequiresDstTexture; }
        bool canCombineOverlappedStencilAndCover() const {
            return fCanCombineOverlappedStencilAndCover;
        }
        bool requiresBarrierBetweenOverlappingDraws() const {
            return fRequiresBarrierBetweenOverlappingDraws;
        }
        bool isCompatibleWithCoverageAsAlpha() const { return fCompatibleWithCoverageAsAlpha; }

        bool inputColorIsIgnored() const { return fInputColorType == kIgnored_InputColorType; }
        bool inputColorIsOverridden() const {
            return fInputColorType == kOverridden_InputColorType;
        }

    private:
        constexpr Analysis(Empty)
                : fUsesLocalCoords(false)
                , fCompatibleWithCoverageAsAlpha(true)
                , fRequiresDstTexture(false)
                , fCanCombineOverlappedStencilAndCover(true)
                , fRequiresBarrierBetweenOverlappingDraws(false)
                , fIsInitialized(true)
                , fInputColorType(kOriginal_InputColorType) {}
        enum InputColorType : uint32_t {
            kOriginal_InputColorType,
            kOverridden_InputColorType,
            kIgnored_InputColorType
        };

        // MSVS 2015 won't pack different underlying types
        using PackedBool = uint32_t;
        using PackedInputColorType = uint32_t;

        PackedBool fUsesLocalCoords : 1;
        PackedBool fCompatibleWithCoverageAsAlpha : 1;
        PackedBool fRequiresDstTexture : 1;
        PackedBool fCanCombineOverlappedStencilAndCover : 1;
        PackedBool fRequiresBarrierBetweenOverlappingDraws : 1;
        PackedBool fIsInitialized : 1;
        PackedInputColorType fInputColorType : 2;

        friend class GrProcessorSet;
    };
    GR_STATIC_ASSERT(sizeof(Analysis) <= sizeof(uint32_t));

    /**
     * This analyzes the processors given an op's input color and coverage as well as a clip. The
     * state of the processor set may change to an equivalent but more optimal set of processors.
     * This new state requires that the caller respect the returned 'inputColorOverride'. This is
     * indicated by the returned Analysis's inputColorIsOverriden(). 'inputColorOverride' will not
     * be written if the analysis does not override the input color.
     *
     * This must be called before the processor set is used to construct a GrPipeline and may only
     * be called once.
     *
     * This also puts the processors in "pending execution" state and must be called when an op
     * that owns a processor set is recorded to ensure pending and writes are propagated to
     * resources referred to by the processors. Otherwise, data hazards may occur.
     */
    Analysis finalize(const GrProcessorAnalysisColor& colorInput,
                      const GrProcessorAnalysisCoverage coverageInput, const GrAppliedClip*,
                      bool isMixedSamples, const GrCaps&, GrPixelConfigIsClamped,
                      GrColor* inputColorOverride);

    bool isFinalized() const { return SkToBool(kFinalized_Flag & fFlags); }

    /** These are valid only for non-LCD coverage. */
    static const GrProcessorSet& EmptySet();
    static GrProcessorSet MakeEmptySet();
    static constexpr const Analysis EmptySetAnalysis() { return Analysis(Empty::kEmpty); }

    SkString dumpProcessors() const;

    void visitProxies(const std::function<void(GrSurfaceProxy*)>& func) const {
        for (int i = 0; i < this->numFragmentProcessors(); ++i) {
            GrFragmentProcessor::TextureAccessIter iter(this->fragmentProcessor(i));
            while (const GrResourceIOProcessor::TextureSampler* sampler = iter.next()) {
                func(sampler->proxy());
            }
        }
    }

private:
    GrProcessorSet(Empty) : fXP((const GrXferProcessor*)nullptr), fFlags(kFinalized_Flag) {}

    int numFragmentProcessors() const {
        return fFragmentProcessors.count() - fFragmentProcessorOffset;
    }

    const GrFragmentProcessor* fragmentProcessor(int idx) const {
        return fFragmentProcessors[idx + fFragmentProcessorOffset].get();
    }

    // This absurdly large limit allows Analysis and this to pack fields together.
    static constexpr int kMaxColorProcessors = UINT8_MAX;

    enum Flags : uint16_t { kFinalized_Flag = 0x1 };

    union XP {
        XP(const GrXPFactory* factory) : fFactory(factory) {}
        XP(const GrXferProcessor* processor) : fProcessor(processor) {}
        explicit XP(XP&& that) : fProcessor(that.fProcessor) {
            SkASSERT(fProcessor == that.fProcessor);
            that.fProcessor = nullptr;
        }
        const GrXPFactory* fFactory;
        const GrXferProcessor* fProcessor;
    };

    const GrXPFactory* xpFactory() const {
        SkASSERT(!this->isFinalized());
        return fXP.fFactory;
    }

    SkAutoSTArray<4, std::unique_ptr<const GrFragmentProcessor>> fFragmentProcessors;
    XP fXP;
    uint8_t fColorFragmentProcessorCnt = 0;
    uint8_t fFragmentProcessorOffset = 0;
    uint8_t fFlags;
};

#endif