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

#include "include/gpu/GrTypes.h"
#include "include/private/base/SkAssert.h"
#include "src/gpu/ganesh/GrUserStencilSettings.h"

#include <cstdint>

namespace skgpu {
class KeyBuilder;
}

enum class GrStencilTest : uint16_t {
    kAlways,
    kNever,
    kGreater,
    kGEqual,
    kLess,
    kLEqual,
    kEqual,
    kNotEqual
};
static constexpr int kGrStencilTestCount = 1 + (int)GrStencilTest::kNotEqual;

enum class GrStencilOp : uint8_t {
    kKeep,
    kZero,
    kReplace, // Replace stencil value with fRef (only the bits enabled in fWriteMask).
    kInvert,
    kIncWrap,
    kDecWrap,
    // NOTE: clamping occurs before the write mask. So if the MSB is zero and masked out, stencil
    // values will still wrap when using clamping ops.
    kIncClamp,
    kDecClamp
};
static constexpr int kGrStencilOpCount = 1 + (int)GrStencilOp::kDecClamp;

/**
 * This class defines concrete stencil settings that map directly to the underlying hardware. It
 * is deduced from user stencil settings, stencil clip status, and the number of bits in the
 * target stencil buffer.
 */
class GrStencilSettings {
public:
    GrStencilSettings() { this->setDisabled(); }
    GrStencilSettings(const GrUserStencilSettings& user, bool hasStencilClip, int numStencilBits) {
        this->reset(user, hasStencilClip, numStencilBits);
    }
    GrStencilSettings(const GrStencilSettings& that) { this->reset(that); }
    GrStencilSettings& operator=(const GrStencilSettings& that) { this->reset(that); return *this; }

    void invalidate() { fFlags |= kInvalid_PrivateFlag; }
    void setDisabled() { fFlags = kAll_StencilFlags; }
    void reset(const GrUserStencilSettings&, bool hasStencilClip, int numStencilBits);
    void reset(const GrStencilSettings&);

    bool isValid() const { return !(fFlags & kInvalid_PrivateFlag); }
    bool isDisabled() const { SkASSERT(this->isValid()); return fFlags & kDisabled_StencilFlag; }
    bool doesWrite() const { SkASSERT(this->isValid());
                             return !(fFlags & kNoModifyStencil_StencilFlag); }
    bool isTwoSided() const { SkASSERT(this->isValid());
                              return !(fFlags & kSingleSided_StencilFlag); }
    bool usesWrapOp() const { SkASSERT(this->isValid());
                              return !(fFlags & kNoWrapOps_StencilFlag); }

    void genKey(skgpu::KeyBuilder* b, bool includeRefsAndMasks) const;

    bool operator!=(const GrStencilSettings& that) const { return !(*this == that); }
    bool operator==(const GrStencilSettings&) const;

    struct Face : public GrTStencilFaceSettings<GrStencilTest, GrStencilOp> {
        void reset(const GrUserStencilSettings::Face&, bool useStencilClip, int numStencilBits);
        void setDisabled();
    };

    const Face& singleSidedFace() const {
        SkASSERT(!this->isDisabled());
        SkASSERT(!this->isTwoSided());
        return fCWFace;
    }
    // Returns the stencil settings for triangles that wind clockwise in "post-origin" space.
    // (i.e., the space that results after a potential y-axis flip on device space for bottom-left
    // origins.)
    const Face& postOriginCWFace(GrSurfaceOrigin origin) const {
        SkASSERT(this->isTwoSided());
        return (kTopLeft_GrSurfaceOrigin == origin) ? fCWFace : fCCWFace;
    }
    // Returns the stencil settings for triangles that wind counter-clockwise in "post-origin"
    // space. (i.e., the space that results after a potential y-axis flip on device space for
    // bottom-left origins.)
    const Face& postOriginCCWFace(GrSurfaceOrigin origin) const {
        SkASSERT(this->isTwoSided());
        return (kTopLeft_GrSurfaceOrigin == origin) ? fCCWFace : fCWFace;
    }

    /** Gets the user stencil settings to directly set the clip bit. */
    static const GrUserStencilSettings* SetClipBitSettings(bool setToInside);

private:
    // Internal flag for backends to optionally mark their tracked stencil state as invalid.
    // NOTE: This value is outside the declared range of GrStencilFlags, but since that type is
    // explicitly backed by 'int', it can still represent this constant. clang 11 complains about
    // mixing enum types in bit operations, so this works around that.
    inline static constexpr GrStencilFlags kInvalid_PrivateFlag =
            static_cast<GrStencilFlags>(kLast_StencilFlag << 1);

    uint32_t   fFlags;
    Face       fCWFace;
    Face       fCCWFace;
};

#endif
