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

#ifndef skgpu_graphite_VulkanCaps_DEFINED
#define skgpu_graphite_VulkanCaps_DEFINED

#include "include/private/base/SkTArray.h"
#include "src/gpu/graphite/Caps.h"
#include "src/gpu/vk/VulkanInterface.h"
#include "src/gpu/vk/VulkanUtilsPriv.h"

namespace skgpu::graphite {
struct ContextOptions;
class VulkanTextureInfo;

class VulkanCaps final : public Caps {
public:
    VulkanCaps(const ContextOptions&,
               const skgpu::VulkanInterface*,
               VkPhysicalDevice,
               uint32_t physicalDeviceVersion,
               const VkPhysicalDeviceFeatures2*,
               const skgpu::VulkanExtensions*,
               Protected);
    ~VulkanCaps() override;

    // Override Caps's implementation in order to consult Vulkan-specific texture properties.
    DstReadStrategy getDstReadStrategy() const override;

    ImmutableSamplerInfo getImmutableSamplerInfo(const TextureInfo&) const override;
    std::string toString(const ImmutableSamplerInfo&) const override;

    UniqueKey makeGraphicsPipelineKey(const GraphicsPipelineDesc&,
                                      const RenderPassDesc&) const override;
    bool extractGraphicsDescs(const UniqueKey&,
                              GraphicsPipelineDesc*,
                              RenderPassDesc*,
                              const RendererProvider*) const override;
    UniqueKey makeComputePipelineKey(const ComputePipelineDesc&) const override { return {}; }

    void buildKeyForTexture(SkISize dimensions,
                            const TextureInfo&,
                            ResourceType,
                            GraphiteResourceKey*) const override;

    bool shouldAlwaysUseDedicatedImageMemory() const {
        return fShouldAlwaysUseDedicatedImageMemory;
    }

    // Returns whether a pure GPU accessible buffer is more performant to read than a buffer that is
    // also host visible. If so then in some cases we may prefer the cost of doing a copy to the
    // buffer. This typically would only be the case for buffers that are written once and read
    // many times on the gpu.
    bool gpuOnlyBuffersMorePerformant() const { return fGpuOnlyBuffersMorePerformant; }

    // For our CPU write and GPU read buffers (vertex, uniform, etc.), we should keep these buffers
    // persistently mapped. In general, the answer will be yes. The main case where we don't do this
    // is when using special memory that is DEVICE_LOCAL and HOST_VISIBLE on discrete GPUs.
    bool shouldPersistentlyMapCpuToGpuBuffers() const {
        return fShouldPersistentlyMapCpuToGpuBuffers;
    }

    bool supportsYcbcrConversion() const { return fSupportsYcbcrConversion; }
    bool supportsDeviceFaultInfo() const { return fSupportsDeviceFaultInfo; }

    // Whether a barrier is required before reading from input attachments (barrier is needed if
    // !coherent).
    bool isInputAttachmentReadCoherent() const { return fIsInputAttachmentReadCoherent; }
    // isInputAttachmentReadCoherent() is based on whether
    // VK_EXT_rasterization_order_attachment_access is supported, but is also enabled on a few
    // architectures where it's known a priori that input attachment reads are coherent. The
    // following determines whether that extension is enabled (in which case a pipeline creation
    // flag is necessary) or not. When disabled, a subpass self-dependency is needed instead.
    bool supportsRasterizationOrderColorAttachmentAccess() const {
        return fSupportsRasterizationOrderColorAttachmentAccess;
    }

    uint32_t maxVertexAttributes()   const { return fMaxVertexAttributes;   }
    uint64_t maxUniformBufferRange() const { return fMaxUniformBufferRange; }
    uint64_t maxStorageBufferRange() const { return fMaxStorageBufferRange; }

    const VkPhysicalDeviceMemoryProperties2& physicalDeviceMemoryProperties2() const {
        return fPhysicalDeviceMemoryProperties2;
    }

    bool mustLoadFullImageForMSAA() const { return fMustLoadFullImageForMSAA; }

    bool supportsFrameBoundary() const { return fSupportsFrameBoundary; }

    bool supportsPipelineCreationCacheControl() const {
        return fSupportsPipelineCreationCacheControl;
    }

    bool supportsOcclusionQueryPrecise() const { return fOcclusionQueryPrecise; }

    uint32_t timestampValidBits(uint32_t queueIndex) const {
        return fQueueFamilyTimestampValidBits[queueIndex];
    }

    float timestampPeriod() const { return fTimestampPeriod; }

private:
    void init(const ContextOptions&,
              const skgpu::VulkanInterface*,
              VkPhysicalDevice,
              uint32_t physicalDeviceVersion,
              const VkPhysicalDeviceFeatures2*,
              const skgpu::VulkanExtensions*,
              Protected);

    struct EnabledFeatures {
        // VkPhysicalDeviceFeatures
        bool fDualSrcBlend = false;
        // Vulkan 1.0 core:
        bool fOcclusionQueryPrecise = false;
        // Vulkan 1.1 core:
        bool fProtectedMemory = false;
        // From VkPhysicalDeviceSamplerYcbcrConversionFeatures or VkPhysicalDeviceVulkan11Features:
        bool fSamplerYcbcrConversion = false;
        // From VkPhysicalDeviceFaultFeaturesEXT:
        bool fDeviceFault = false;
        // From VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT:
        bool fAdvancedBlendModes = false;
        bool fCoherentAdvancedBlendModes = false;
        // From VK_EXT_rasterization_order_attachment_access:
        bool fRasterizationOrderColorAttachmentAccess = false;
        // From VkPhysicalDeviceExtendedDynamicStateFeaturesEXT or Vulkan 1.3 (no features):
        bool fExtendedDynamicState = false;
        // From VkPhysicalDeviceExtendedDynamicState2FeaturesEXT or Vulkan 1.3 (no features):
        bool fExtendedDynamicState2 = false;
        // From VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT:
        bool fVertexInputDynamicState = false;
        // From VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT:
        bool fGraphicsPipelineLibrary = false;
        // From VkPhysicalDeviceMultisampledRenderToSingleSampledFeaturesEXT:
        bool fMultisampledRenderToSingleSampled = false;
        // From VkPhysicalDeviceHostImageCopyFeatures:
        bool fHostImageCopy = false;
        // From VkPhysicalDeviceFrameBoundaryFeaturesEXT:
        bool fFrameBoundary = false;
        // From VkPhysicalDevicePipelineCreationCacheControlFeatures or
        // VkPhysicalDeviceVulkan13Features
        bool fPipelineCreationCacheControl = false;
        // From VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT
        bool fFormatRGBA10x6WithoutYCbCrSampler = false;
    };
    EnabledFeatures getEnabledFeatures(const VkPhysicalDeviceFeatures2*,
                                       uint32_t physicalDeviceVersion);

    struct PhysicalDeviceProperties {
        VkPhysicalDeviceProperties2 fBase;
        VkPhysicalDeviceDriverProperties fDriver;
        VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT fGpl;
        VkPhysicalDeviceHostImageCopyPropertiesEXT fHic;
        bool fHicHasShaderReadOnlyDstLayout = false;
    };
    void getProperties(const skgpu::VulkanInterface*,
                       VkPhysicalDevice,
                       uint32_t physicalDeviceVersion,
                       const skgpu::VulkanExtensions*,
                       const EnabledFeatures&,
                       PhysicalDeviceProperties*);

    void applyDriverCorrectnessWorkarounds(const PhysicalDeviceProperties&);

    void initShaderCaps(const EnabledFeatures, const uint32_t vendorID);

    void initFormatTable(const skgpu::VulkanInterface*,
                         VkPhysicalDevice,
                         const VkPhysicalDeviceProperties&,
                         const EnabledFeatures&);

    void initDepthStencilFormatTable(const skgpu::VulkanInterface*,
                                     VkPhysicalDevice,
                                     const VkPhysicalDeviceProperties&);

    TextureInfo onGetDefaultTextureInfo(SkEnumBitMask<TextureUsage> usage,
                                        TextureFormat,
                                        SampleCount,
                                        Mipmapped,
                                        Protected,
                                        Discardable) const override;
    std::pair<SkEnumBitMask<TextureUsage>, SkEnumBitMask<SampleCount>> getTextureSupport(
            TextureFormat format, Tiling) const override;
    std::pair<SkEnumBitMask<TextureUsage>, Tiling> getTextureUsage(
            const TextureInfo&) const override;

    // Struct that determines and stores which sample count quantities a VkFormat supports.
    struct SupportedSampleCounts {
        void initSampleCounts(const skgpu::VulkanInterface*,
                              const VulkanCaps&,
                              VkPhysicalDevice,
                              VkFormat,
                              VkImageUsageFlags);

        bool isSampleCountSupported(SampleCount requestedCount) const;

        VkSampleCountFlags fSampleCounts = 0;
    };

    // Struct that determines and stores useful information about VkFormats.
    struct FormatInfo {
        uint32_t colorTypeFlags(SkColorType colorType) const {
            for (int i = 0; i < fColorTypeInfoCount; ++i) {
                if (fColorTypeInfos[i].fColorType == colorType) {
                    return fColorTypeInfos[i].fFlags;
                }
            }
            return 0;
        }

        void init(const skgpu::VulkanInterface*, const VulkanCaps&, VkPhysicalDevice, VkFormat);

        bool isTexturable(VkImageTiling) const;
        bool isRenderable(VkImageTiling, SampleCount sampleCount) const;
        bool isStorage(VkImageTiling) const;
        bool isEfficientWithHostImageCopy(VkImageTiling, Protected) const;

        std::unique_ptr<ColorTypeInfo[]> fColorTypeInfos;
        int fColorTypeInfoCount = 0;

        VkFormatProperties fFormatProperties = {};
        SupportedSampleCounts fSupportedSampleCounts;
        /*
         * The VK_IMAGE_USAGE_HOST_TRANSFER_BIT flag may cause the image to be put in a suboptimal
         * physical layout.  In practice, images that could have had framebuffer compression end up
         * with framebuffer compression disabled.  Using `VkHostImageCopyDevicePerformanceQuery`, we
         * can determine if the layout is going to be suboptimal and avoid this flag.
         *
         * `fIsEfficientWithHostImageCopy` indicates whether the VK_IMAGE_USAGE_HOST_TRANSFER_BIT is
         * efficient for this format with the following assumptions:
         *
         * - Image tiling is VK_IMAGE_TILING_OPTIMAL (note that VK_IMAGE_TILING_LINEAR is always
         *   efficient for host image copy).
         * - Image type is 2D.
         * - Image create flags is 0.
         * - Image usage flags is a subset of VK_IMAGE_USAGE_SAMPLED_BIT |
         *                                    VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
         *                                    VK_IMAGE_USAGE_TRANSFER_DST_BIT
         */
        bool fIsEfficientWithHostImageCopy = false;

        // Indicates that a format is only supported if we are wrapping a texture with it.
        SkDEBUGCODE(bool fIsWrappedOnly = false;)
    };

    // Map VkFormat to FormatInfo.
    static const int kNumVkFormats = 24;
    FormatInfo fFormatTable[kNumVkFormats];

    FormatInfo& getFormatInfoForInit(VkFormat);
    const FormatInfo& getFormatInfo(VkFormat) const;

    // A more lightweight equivalent to FormatInfo for depth/stencil VkFormats.
    struct DepthStencilFormatInfo {
        void init(const skgpu::VulkanInterface*, const VulkanCaps&, VkPhysicalDevice, VkFormat);
        bool isDepthStencilSupported() const;

        VkFormatProperties fFormatProperties = {};
        SupportedSampleCounts fSupportedSampleCounts;
    };

    // Map depth/stencil VkFormats to DepthStencilFormatInfo.
    static const size_t kNumDepthStencilVkFormats = 5;
    DepthStencilFormatInfo fDepthStencilFormatTable[kNumDepthStencilVkFormats];

    DepthStencilFormatInfo& getDepthStencilFormatInfoForInit(VkFormat);
    const DepthStencilFormatInfo& getDepthStencilFormatInfo(VkFormat) const;

    uint32_t fMaxVertexAttributes;
    uint64_t fMaxUniformBufferRange;
    uint64_t fMaxStorageBufferRange;
    VkPhysicalDeviceMemoryProperties2 fPhysicalDeviceMemoryProperties2;

    // Various bools to define whether certain Vulkan features are supported.
    bool fSupportsMemorylessAttachments = false;
    bool fSupportsYcbcrConversion = false;
    bool fShouldAlwaysUseDedicatedImageMemory = false;
    bool fGpuOnlyBuffersMorePerformant = false;
    bool fShouldPersistentlyMapCpuToGpuBuffers = true;
    bool fSupportsDeviceFaultInfo = false;
    bool fSupportsRasterizationOrderColorAttachmentAccess = false;
    bool fIsInputAttachmentReadCoherent = false;
    bool fSupportsFrameBoundary = false;
    bool fSupportsPipelineCreationCacheControl = false;
    bool fOcclusionQueryPrecise = false;

    // Flags to enable workarounds for driver bugs
    bool fMustLoadFullImageForMSAA = false;

    skia_private::TArray<uint32_t> fQueueFamilyTimestampValidBits;
    float fTimestampPeriod = 1.0f;
};

} // namespace skgpu::graphite

#endif // skgpu_graphite_VulkanCaps_DEFINED
