// Copyright 2019 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef SRC_DAWN_NATIVE_FORMAT_H_
#define SRC_DAWN_NATIVE_FORMAT_H_

#include <array>
#include <variant>

#include "absl/strings/str_format.h"
#include "src/dawn/common/TypedInteger.h"
#include "src/dawn/common/ityp_array.h"
#include "src/dawn/common/ityp_bitset.h"
#include "src/dawn/native/EnumClassBitmasks.h"
#include "src/dawn/native/Error.h"
#include "src/dawn/native/Subresource.h"
#include "src/dawn/native/dawn_platform.h"

// About multi-planar formats.
//
// Dawn supports additional multi-planar formats when the multiplanar-formats extension is enabled.
// When enabled, Dawn treats planar data as sub-resources (ie. 1 sub-resource == 1 view == 1 plane).
// A multi-planar format name encodes the channel mapping and order of planes. For example,
// R8BG8Biplanar420Unorm is YUV 4:2:0 where Plane 0 = R8, and Plane 1 = BG8.
//
// Requirements:
// * Plane aspects cannot be combined with color, depth, or stencil aspects.
// * Only compatible multi-planar formats of planes can be used with multi-planar texture
// formats.
// * Can't access multiple planes without creating per plane views (no color conversion).
// * Multi-planar format cannot be written or read without a per plane view.
//
// TODO(dawn:551): Consider moving this comment.

namespace dawn::native {

enum class FormatCapability : uint16_t {
    None = 0x0,
    Multisample = 0x1,
    Renderable = 0x2,
    Resolve = 0x4,
    StorageROnly = 0x8,   // Read-Only.
    StorageWOnly = 0x10,  // Write-Only
    StorageRW = 0x20,     // When set StorageR/WOnly will be set as well.
    PLS = 0x40,
    Blendable = 0x80,
};
}  // namespace dawn::native

template <>
struct wgpu::IsWGPUBitmask<dawn::native::FormatCapability> {
    static constexpr bool enable = true;
};

namespace dawn::native {

enum class Aspect : uint8_t;
class DeviceBase;

// This mirrors wgpu::TextureSampleType as a bitmask instead.
enum class SampleTypeBit : uint8_t {
    None = 0x0,
    Float = 0x1,
    UnfilterableFloat = 0x2,
    Depth = 0x4,
    Sint = 0x8,
    Uint = 0x10,
};

// Converts a wgpu::TextureSampleType to its bitmask representation.
SampleTypeBit SampleTypeToSampleTypeBit(wgpu::TextureSampleType sampleType);

struct TexelBlockInfo {
    uint32_t byteSize = 0;
    uint32_t width = 0;
    uint32_t height = 0;
};

enum class TextureComponentType {
    Float,
    Sint,
    Uint,
};

enum class TextureSubsampling {
    Undefined,
    e420,
    e422,
    e444,
};

struct RequiresFeature {
    wgpu::FeatureName feature;
    auto operator<=>(const RequiresFeature&) const = default;
};

struct CompatibilityMode {
    auto operator<=>(const CompatibilityMode&) const = default;
};

using UnsupportedReason =
    std::variant</* is supported */ std::monostate, RequiresFeature, CompatibilityMode>;

struct AspectInfo {
    TexelBlockInfo block;
    TextureComponentType baseType{};
    SampleTypeBit supportedSampleTypes{};
    wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;
};

// The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
// exact number of known format.
inline constexpr uint32_t kWebGPUFormatCount = 101;
inline constexpr uint32_t kDawnFormatCount = 8;
inline constexpr uint32_t kKnownFormatCount = kWebGPUFormatCount + kDawnFormatCount;

using FormatIndex = TypedInteger<struct FormatIndexT, uint32_t>;

struct Format;
using FormatTable = ityp::array<FormatIndex, Format, kKnownFormatCount>;

// A wgpu::TextureFormat along with all the information about it necessary for validation.
struct Format {
    wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;

    static const UnsupportedReason supported;
    FormatCapability caps = FormatCapability::None;

    // TODO(crbug.com/dawn/1332): These members could be stored in a Format capability matrix.
    bool isBC = false;
    bool isASTC = false;
    bool isCompressed = false;
    // A format can be known but not supported because it is part of a disabled extension.
    UnsupportedReason unsupportedReason;
    bool IsRenderable() const { return static_cast<bool>(caps & FormatCapability::Renderable); }
    bool IsBlendable() const { return static_cast<bool>(caps & FormatCapability::Blendable); }
    bool SupportsMultisample() const {
        return static_cast<bool>(caps & FormatCapability::Multisample);
    }
    bool SupportsResolveTarget() const {
        return static_cast<bool>(caps & FormatCapability::Resolve);
    }
    bool SupportsReadOnlyStorageUsage() const {
        return static_cast<bool>(caps & FormatCapability::StorageROnly);
    }
    bool SupportsWriteOnlyStorageUsage() const {
        return static_cast<bool>(caps & FormatCapability::StorageWOnly);
    }
    bool SupportsReadWriteStorageUsage() const {
        return static_cast<bool>(caps & FormatCapability::StorageRW);
    }
    bool SupportsStorageAttachment() const {
        return static_cast<bool>(caps & FormatCapability::PLS);
    }
    Aspect aspects{};
    // Only used for renderable color formats:
    uint8_t componentCount = 0;                  // number of color channels
    uint8_t renderTargetPixelByteCost = 0;       // byte cost of pixel in render targets
    uint8_t renderTargetComponentAlignment = 0;  // byte alignment for components in render targets

    bool IsSupported() const;
    bool IsColor() const;
    bool HasDepth() const;
    bool HasStencil() const;
    bool HasDepthOrStencil() const;
    bool HasAlphaChannel() const;
    bool IsSnorm() const;

    // IsMultiPlanar() returns true if the format allows selecting a plane index. This is only
    // allowed by multi-planar formats (ex. NV12).
    bool IsMultiPlanar() const;

    const AspectInfo& GetAspectInfo(wgpu::TextureAspect aspect) const;
    const AspectInfo& GetAspectInfo(Aspect aspect) const;

    // The index of the format in the list of all known formats: a unique number for each format
    // in [0, kKnownFormatCount)
    FormatIndex GetIndex() const;

    // baseFormat represents the memory layout of the format.
    // If two formats has the same baseFormat, they could copy to and be viewed as the other
    // format. Currently two formats have the same baseFormat if they differ only in sRGB-ness.
    wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined;
    // Additional view format a base format is compatible with. Only populated for true base
    // formats. Only stores a single view format because Dawn currently only supports sRGB format
    // reinterpretation.
    wgpu::TextureFormat baseViewFormat = wgpu::TextureFormat::Undefined;
    // Chroma subsampling used by multi-planar formats (e.g. 4:2:0 is 1/2 horizontal and 1/2
    // vertical resolution for UV plane, 4:2:2 is 1/2 horizontal and 1/1 vertical resolution).
    TextureSubsampling subSampling = TextureSubsampling::Undefined;

    // Returns true if the formats are copy compatible.
    // Currently means they differ only in sRGB-ness.
    bool CopyCompatibleWith(const Format& otherFormat) const;

    // Returns true if the formats are texture view format compatible.
    // Currently means they differ only in sRGB-ness.
    bool ViewCompatibleWith(const Format& otherFormat) const;

  private:
    // Used to store the aspectInfo for one or more planes. For single plane "color" formats,
    // only the first aspect info or aspectInfo[0] is valid. For depth-stencil, the first aspect
    // info is depth and the second aspect info is stencil. For multi-planar formats,
    // aspectInfo[i] is the ith plane.
    std::array<AspectInfo, kMaxPlanesPerFormat> aspectInfo{};

    friend FormatTable BuildFormatTable(const DeviceBase* device);
};

class FormatSet : public ityp::bitset<FormatIndex, kKnownFormatCount> {
    using Base = ityp::bitset<FormatIndex, kKnownFormatCount>;

  public:
    using Base::Base;
    using Base::operator[];

    bool operator[](const Format& format) const;
    typename Base::reference operator[](const Format& format);
};

// Implementation details of the format table in the device.

// Returns the index of a format in the FormatTable.
FormatIndex ComputeFormatIndex(wgpu::TextureFormat format);
// Builds the format table with the extensions enabled on the device.
FormatTable BuildFormatTable(const DeviceBase* device);

absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
    const UnsupportedReason& value,
    const absl::FormatConversionSpec& spec,
    absl::FormatSink* s);

}  // namespace dawn::native

template <>
struct wgpu::IsWGPUBitmask<dawn::native::SampleTypeBit> {
    static constexpr bool enable = true;
};

#endif  // SRC_DAWN_NATIVE_FORMAT_H_
