/*
 * 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 GrMtlCaps_DEFINED
#define GrMtlCaps_DEFINED

#include "include/private/SkTDArray.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/mtl/GrMtlAttachment.h"

#import <Metal/Metal.h>

class GrShaderCaps;
class GrMtlRenderTarget;

/**
 * Stores some capabilities of a Mtl backend.
 */
class GrMtlCaps : public GrCaps {
public:
    GrMtlCaps(const GrContextOptions& contextOptions, id<MTLDevice> device);

    bool isFormatSRGB(const GrBackendFormat&) const override;

    bool isFormatTexturable(const GrBackendFormat&, GrTextureType) const override;
    bool isFormatTexturable(MTLPixelFormat) const;

    bool isFormatCopyable(const GrBackendFormat&) const override { return true; }

    bool isFormatAsColorTypeRenderable(GrColorType ct, const GrBackendFormat& format,
                                       int sampleCount = 1) const override;
    bool isFormatRenderable(const GrBackendFormat& format, int sampleCount) const override;
    bool isFormatRenderable(MTLPixelFormat, int sampleCount) const;

    int getRenderTargetSampleCount(int requestedCount, const GrBackendFormat&) const override;
    int getRenderTargetSampleCount(int requestedCount, MTLPixelFormat) const;

    int maxRenderTargetSampleCount(const GrBackendFormat&) const override;
    int maxRenderTargetSampleCount(MTLPixelFormat) const;

    SupportedWrite supportedWritePixelsColorType(GrColorType surfaceColorType,
                                                 const GrBackendFormat& surfaceFormat,
                                                 GrColorType srcColorType) const override;

    SurfaceReadPixelsSupport surfaceSupportsReadPixels(const GrSurface*) const override;

    DstCopyRestrictions getDstCopyRestrictions(const GrRenderTargetProxy* src,
                                               GrColorType ct) const override;

    /**
     * Returns both a supported and most prefered stencil format to use in draws.
     */
    MTLPixelFormat preferredStencilFormat() const {
        return fPreferredStencilFormat;
    }

    bool canCopyAsBlit(MTLPixelFormat dstFormat, int dstSampleCount,
                       MTLPixelFormat srcFormat, int srcSampleCount,
                       const SkIRect& srcRect, const SkIPoint& dstPoint,
                       bool areDstSrcSameObj) const;

    bool canCopyAsResolve(MTLPixelFormat dstFormat, int dstSampleCount,
                          MTLPixelFormat srcFormat, int srcSampleCount,
                          bool srcIsRenderTarget, const SkISize srcDimensions,
                          const SkIRect& srcRect,
                          const SkIPoint& dstPoint,
                          bool areDstSrcSameObj) const;

    GrBackendFormat getBackendFormatFromCompressionType(SkImage::CompressionType) const override;

    MTLPixelFormat getFormatFromColorType(GrColorType colorType) const {
        int idx = static_cast<int>(colorType);
        return fColorTypeToFormatTable[idx];
    }

    GrSwizzle getWriteSwizzle(const GrBackendFormat&, GrColorType) const override;

    uint64_t computeFormatKey(const GrBackendFormat&) const override;

    GrProgramDesc makeDesc(GrRenderTarget*,
                           const GrProgramInfo&,
                           ProgramDescOverrideFlags) const override;
    MTLPixelFormat getStencilPixelFormat(const GrProgramDesc& desc);

    // if true, MTLStoreActionStoreAndMultiplesampleResolve is available
    bool storeAndMultisampleResolveSupport() const { return fStoreAndMultisampleResolveSupport; }

    // If true when doing MSAA draws, we will prefer to discard the MSAA attachment on load
    // and stores. The use of this feature for specific draws depends on the render target having a
    // resolve attachment, and if we need to load previous data the resolve attachment must
    // be readable in a shader. Otherwise we will just write out and store the MSAA attachment
    // like normal.
    bool preferDiscardableMSAAAttachment() const { return fPreferDiscardableMSAAAttachment; }
    bool renderTargetSupportsDiscardableMSAA(const GrMtlRenderTarget*) const;

#if GR_TEST_UTILS
    std::vector<TestFormatColorTypeCombination> getTestingCombinations() const override;
#endif
    void onDumpJSON(SkJSONWriter*) const override;

private:
    void initGPUFamily(id<MTLDevice> device);

    void initStencilFormat(id<MTLDevice> device);

    void initGrCaps(id<MTLDevice> device);
    void initShaderCaps();

    void applyDriverCorrectnessWorkarounds(const GrContextOptions&, const id<MTLDevice>);

    void initFormatTable();

    bool onSurfaceSupportsWritePixels(const GrSurface*) const override;
    bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                          const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
    GrBackendFormat onGetDefaultBackendFormat(GrColorType) const override;
    bool onAreColorTypeAndFormatCompatible(GrColorType, const GrBackendFormat&) const override;

    SupportedRead onSupportedReadPixelsColorType(GrColorType, const GrBackendFormat&,
                                                 GrColorType) const override;

    GrSwizzle onGetReadSwizzle(const GrBackendFormat&, GrColorType) const override;

    // ColorTypeInfo for a specific format
    struct ColorTypeInfo {
        GrColorType fColorType = GrColorType::kUnknown;
        enum {
            kUploadData_Flag = 0x1,
            // Does Ganesh itself support rendering to this colorType & format pair. Renderability
            // still additionally depends on if the format itself is renderable.
            kRenderable_Flag = 0x2,
        };
        uint32_t fFlags = 0;

        GrSwizzle fReadSwizzle;
        GrSwizzle fWriteSwizzle;
    };

    struct FormatInfo {
        uint32_t colorTypeFlags(GrColorType colorType) const {
            for (int i = 0; i < fColorTypeInfoCount; ++i) {
                if (fColorTypeInfos[i].fColorType == colorType) {
                    return fColorTypeInfos[i].fFlags;
                }
            }
            return 0;
        }

        enum {
            kTexturable_Flag  = 0x1,
            kRenderable_Flag  = 0x2, // Color attachment and blendable
            kMSAA_Flag        = 0x4,
            kResolve_Flag     = 0x8,
        };
        static const uint16_t kAllFlags = kTexturable_Flag | kRenderable_Flag |
                                          kMSAA_Flag | kResolve_Flag;

        uint16_t fFlags = 0;

        std::unique_ptr<ColorTypeInfo[]> fColorTypeInfos;
        int fColorTypeInfoCount = 0;
    };
#ifdef SK_BUILD_FOR_IOS
    static constexpr size_t kNumMtlFormats = 17;
#else
    static constexpr size_t kNumMtlFormats = 16;
#endif
    static size_t GetFormatIndex(MTLPixelFormat);
    FormatInfo fFormatTable[kNumMtlFormats];

    const FormatInfo& getFormatInfo(const MTLPixelFormat pixelFormat) const {
        size_t index = GetFormatIndex(pixelFormat);
        return fFormatTable[index];
    }

    MTLPixelFormat fColorTypeToFormatTable[kGrColorTypeCnt];
    void setColorType(GrColorType, std::initializer_list<MTLPixelFormat> formats);

    enum class GPUFamily {
        kMac,
        kApple,
    };
    bool isMac() { return fGPUFamily == GPUFamily::kMac; }
    bool isApple() { return fGPUFamily == GPUFamily::kApple; }
    bool getGPUFamily(id<MTLDevice> device, GPUFamily* gpuFamily, int* group);
    bool getGPUFamilyFromFeatureSet(id<MTLDevice> device, GrMtlCaps::GPUFamily* gpuFamily,
                                    int* group);

    GPUFamily fGPUFamily;
    int fFamilyGroup;

    SkTDArray<int> fSampleCounts;

    MTLPixelFormat fPreferredStencilFormat;

    bool fStoreAndMultisampleResolveSupport : 1;
    bool fPreferDiscardableMSAAAttachment : 1;

    using INHERITED = GrCaps;
};

#endif
