/*
 * Copyright 2021 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_Caps_DEFINED
#define skgpu_graphite_Caps_DEFINED

#include "include/core/SkColorType.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/gpu/GpuTypes.h"
#include "include/private/base/SkAlign.h"
#include "include/private/base/SkAssert.h"
#include "src/base/SkEnumBitMask.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/Swizzle.h"
#include "src/gpu/graphite/ResourceTypes.h"
#include "src/text/gpu/SubRunControl.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>

class SkCapabilities;
enum class SkTextureCompressionType;

namespace SkSL { struct ShaderCaps; }

namespace skgpu { class ShaderErrorHandler; }

namespace skgpu::graphite {

class ComputePipelineDesc;
class GraphicsPipelineDesc;
class GraphiteResourceKey;
class RendererProvider;
class TextureInfo;
enum class DepthStencilFlags : int;
enum class PathRendererStrategy;
enum class TextureFormat : uint8_t;
struct AttachmentDesc;
struct ContextOptions;
struct RenderPassDesc;

struct ResourceBindingRequirements {
    /* The API of the backend currently in use. */
    BackendApi fBackendApi = BackendApi::kUnsupported;

    /* The required data layout rules for the contents of a uniform buffer. */
    Layout fUniformBufferLayout = Layout::kInvalid;

    /* The required data layout rules for the contents of a storage buffer. */
    Layout fStorageBufferLayout = Layout::kInvalid;

    /**
     * Whether combined texture-sampler types are supported. Backends that do not support combined
     * image samplers (i.e. sampler2D) require a texture and sampler object to be bound separately
     * and their binding indices explicitly specified in the shader text.
     */
    bool fSeparateTextureAndSamplerBinding = false;

    /**
     * Whether intrinsic constant information is stored as push constants (rather than normal UBO).
     * Currently only relevant or possibly true for Dawn or Vulkan.
     */
    bool fUsePushConstantsForIntrinsicConstants  = false;

    /**
     * Whether compute shader textures use separate index ranges from other resources (i.e. buffers)
     */
    bool fComputeUsesDistinctIdxRangesForTextures = false;

    /**
     * Define set indices. We assume that even if textures and samplers must be bound separately,
     * they will still be contained within the same set/group.
     */
    static constexpr int kUnassigned  = -1;
    int fUniformsSetIdx               = kUnassigned;
    int fTextureSamplerSetIdx         = kUnassigned;
    int fInputAttachmentSetIdx        = kUnassigned;
    /* Define uniform buffer bindings */
    int fIntrinsicBufferBinding       = kUnassigned;
    int fCombinedUniformBufferBinding = kUnassigned;
    int fGradientBufferBinding        = kUnassigned;
};

class Caps {
public:
    virtual ~Caps();

    const SkSL::ShaderCaps* shaderCaps() const { return fShaderCaps.get(); }

    sk_sp<SkCapabilities> capabilities() const;

#if defined(GPU_TEST_UTILS)
    std::string_view deviceName() const { return fDeviceName; }

    std::optional<PathRendererStrategy> requestedPathRendererStrategy() const {
        return fRequestedPathRendererStrategy;
    }
#endif

    virtual UniqueKey makeGraphicsPipelineKey(const GraphicsPipelineDesc&,
                                              const RenderPassDesc&) const = 0;
    virtual UniqueKey makeComputePipelineKey(const ComputePipelineDesc&) const = 0;


    virtual bool extractGraphicsDescs(const UniqueKey&,
                                      GraphicsPipelineDesc*,
                                      RenderPassDesc*,
                                      const RendererProvider*) const { return false; }

    virtual bool loadOpAffectsMSAAPipelines() const { return false; }

    bool avoidMSAA() const {
        // Publicly, treat avoiding MSAA due to device issues or due to client option equivalently.
        return fAvoidMSAA || fMaxInternalSampleCount == SampleCount::k1;
    }

    /* Returns whether multisampled render to single sampled is supported. */
    bool msaaRenderToSingleSampledSupport() const { return fMSAARenderToSingleSampledSupport; }

    /**
     * Returns whether a render pass can have MSAA/depth/stencil attachments and a resolve
     * attachment with mismatched sizes. Note: the MSAA attachment and the depth/stencil attachment
     * still need to match their sizes.
     * This also implies supporting partial load/resolve.
     */
    bool differentResolveAttachmentSizeSupport() const {
        return fDifferentResolveAttachmentSizeSupport;
    }

    /* Get required depth attachment dimensions for a givin color attachment info and dimensions. */
    virtual SkISize getDepthAttachmentDimensions(const TextureInfo&,
                                                 const SkISize colorAttachmentDimensions) const {
        return colorAttachmentDimensions;
    }

    bool isSampleCountSupported(TextureFormat, SampleCount) const;
    /* Return the TextureFormat that satisfies `dsFlags`. */
    TextureFormat getDepthStencilFormat(SkEnumBitMask<DepthStencilFlags>) const;

    TextureInfo getDefaultAttachmentTextureInfo(AttachmentDesc,
                                                Protected,
                                                Discardable) const;

    TextureInfo getDefaultSampledTextureInfo(SkColorType,
                                             Mipmapped,
                                             Protected,
                                             Renderable) const;

    TextureInfo getTextureInfoForSampledCopy(const TextureInfo&,  Mipmapped) const;

    TextureInfo getDefaultCompressedTextureInfo(SkTextureCompressionType,
                                                Mipmapped,
                                                Protected) const;

    TextureInfo getDefaultStorageTextureInfo(SkColorType) const;

    // Tries to return a sample count > 1 if needing MSAA to render into the target specification.
    // If the target is already multisampled, it will be that count; otherwise it will be the
    // highest supported sample count less than the configured max internal sample count.
    //
    // NOTE: If avoidMSAA() is true (either from ContextOptions or driver workarounds), the max
    // internal sample count is 1. In this case getCompatibleMSAASampleCount() returns k1 for single
    // sampled targets to show MSAA isn't supported.
    SampleCount getCompatibleMSAASampleCount(const TextureInfo&) const;

    // If true, the texture can be sampled within a shader (possibly with MSAA, although by default
    // we consider multisampled textures not to be sampleable because that requires backend-specific
    // shader code not exposed in SkSL).
    bool isTexturable(const TextureInfo&, bool allowMSAA=false) const;
    // If true, the texture can be rasterized and/or resolved to (possibly with MSAA)
    bool isRenderable(const TextureInfo&) const;
    // If true, the texture can be rasterized using multisample-render-to-single-sample features.
    bool isRenderableWithMSRTSS(const TextureInfo&) const;
    // If true, the texture can be the source of data copied to another texture or to a buffer.
    // If false, the texture can only be copied via drawing (which requires isTexturable()).
    bool isCopyableSrc(const TextureInfo&) const;
    // If true, the texture can be the destination of data copied from another texture or buffer.
    // If false, the texture can only be updated by drawing (which requires isRenderable()).
    bool isCopyableDst(const TextureInfo&) const;
    // If true, the texture can be used as a storage texture in compute shaders.
    bool isStorage(const TextureInfo&) const;

     /**
     * Backends can optionally override this method to return meaningful sampler conversion info.
     * By default, simply return a default ImmutableSamplerInfo (e.g. no immutable sampler).
     */
    virtual ImmutableSamplerInfo getImmutableSamplerInfo(const TextureInfo&) const {
        return {};
    }

    /* Returns a compressed label describing the immutable sampler for the Pipeline label */
    virtual std::string toString(const ImmutableSamplerInfo&) const { return ""; }

    // If true, uses experimental drawListLayer ordering.
    bool useDrawListLayer() const { return fDrawListLayer; }

    int maxTextureSize() const { return fMaxTextureSize; }

    virtual void buildKeyForTexture(SkISize dimensions,
                                    const TextureInfo&,
                                    ResourceType,
                                    GraphiteResourceKey*) const = 0;

    const ResourceBindingRequirements& resourceBindingRequirements() const {
        return fResourceBindingReqs;
    }

    /**
     * Returns the maximum number of varyings allowed in a render pipeline. Note that this is the
     * number of varying variables, not the total number of varying scalars.
     */
    int maxVaryings() const { return fMaxVaryings; }

    /**
     * Returns the required alignment in bytes for the offset into a uniform buffer when binding it
     * to a draw.
     */
    size_t requiredUniformBufferAlignment() const { return fRequiredUniformBufferAlignment; }

    /**
     * Returns the required alignment in bytes for the offset into a storage buffer when binding it
     * to a draw.
     */
    size_t requiredStorageBufferAlignment() const { return fRequiredStorageBufferAlignment; }

    /**
     * Returns the required alignment in bytes for the offset and size of copies involving a buffer.
     */
    size_t requiredTransferBufferAlignment() const { return fRequiredTransferBufferAlignment; }

    /* Returns the aligned rowBytes when transferring to or from a Texture */
    size_t getAlignedTextureDataRowBytes(size_t rowBytes) const {
        return SkAlignTo(rowBytes, fTextureDataRowBytesAlignment);
    }

    /**
     * When uploading to a full compressed texture do we need to pad the size out to a multiple of
     * the block width and height.
     */
    bool fullCompressedUploadSizeMustAlignToBlockDims() const {
        return fFullCompressedUploadSizeMustAlignToBlockDims;
    }

    /**
     * Determines the orientation of the NDC coordinates emitted by the vertex stage relative to
     * both Skia's presumed top-left Y-down system and the viewport coordinates (which are also
     * always top-left, Y-down for all supported backends).)
     *
     * If true is returned, then (-1,-1) in normalized device coords maps to the top-left of the
     * configured viewport and positive Y points down. This aligns with Skia's conventions.
     * If false is returned, then (-1,-1) in NDC maps to the bottom-left of the viewport and
     * positive Y points up (so NDC is flipped relative to sk_Position and the viewport coords).
     *
     * There is no backend difference in handling the X axis so it's assumed -1 maps to the left
     * edge and +1 maps to the right edge.
     */
    bool ndcYAxisPointsDown() const { return fNDCYAxisPointsDown; }

    bool clampToBorderSupport() const { return fClampToBorderSupport; }

    bool protectedSupport() const { return fProtectedSupport; }

    /* Supports BackendSemaphores */
    bool semaphoreSupport() const { return fSemaphoreSupport; }

    /* If false then calling Context::submit with SyncToCpu::kYes is an error. */
    bool allowCpuSync() const { return fAllowCpuSync; }

    /* Returns whether storage buffers are supported and to be preferred over uniform buffers. */
    bool storageBufferSupport() const { return fStorageBufferSupport; }

    /**
     * The gradient buffer is an unsized float array so it is only optimal memory-wise to use it if
     * the storage buffer memory layout is std430 or in metal, which is also the only supported
     * way the data is packed.
     */
    bool gradientBufferSupport() const {
        return fStorageBufferSupport &&
               (fResourceBindingReqs.fStorageBufferLayout == Layout::kStd430 ||
                fResourceBindingReqs.fStorageBufferLayout == Layout::kMetal);
    }

    /* Returns whether a draw buffer can be mapped. */
    bool drawBufferCanBeMapped() const { return fDrawBufferCanBeMapped; }

#if defined(GPU_TEST_UTILS)
    bool drawBufferCanBeMappedForReadback() const { return fDrawBufferCanBeMappedForReadback; }
#endif

    /**
     * Returns whether using Buffer::asyncMap() must be used to map buffers. map() may only be
     * called after asyncMap() is called and will fail if the asynchronous map is not complete. This
     * excludes premapped buffers for which map() can be called freely until the first unmap() call.
     */
    bool bufferMapsAreAsync() const { return fBufferMapsAreAsync; }

    /* Returns whether compute shaders are supported. */
    bool computeSupport() const { return fComputeSupport; }

    /**
     * Returns true if the given backend supports importing AHardwareBuffers. This will only
     * ever be supported on Android devices with API level >= 26.
     */
    bool supportsAHardwareBufferImages() const { return fSupportsAHardwareBufferImages; }

    /**
     * Enum representing the capabilities of the fixed function blend unit.
     */
    enum BlendEquationSupport : uint8_t {
        kBasic = 0,           /* Default bare minimum support. Allows selecting the operator that
                                 combines src + dst terms.*/
        kAdvancedNoncoherent, /* Additional fixed function support for specific SVG/PDF blend modes.
                                 Requires blend barriers.*/
        kAdvancedCoherent     /* Advanced blend equation support that does not require blend
                                 barriers and permits overlap.*/
    };
    /**
     * Return the level of hardware advanced blend mode support.
     */
    BlendEquationSupport blendEquationSupport() const { return fBlendEqSupport; }
    /**
     * Simple helper for indicating whether the hardware supports advanced blend modes at all
     * (coherent or noncoherent).
     */
    bool supportsHardwareAdvancedBlending() const {
        return fBlendEqSupport > BlendEquationSupport::kBasic;
    }

    /**
     * Includes the following dynamic state:
     *
     * * Line width, depth bias, depth bounds, stencil compare mask, stencil write mask and stencil
     *   reference.
     *   This set corresponds to Vulkan 1.0 dynamic state.  Blend constants does not depend on this
     *   flag as it is always dynamic with all graphite backends.
     *
     * * Depth test enable, depth write enable, depth compare op, depth bounds test enable, depth
     *   bias enable, stencil test enable and stencil op.
     *   This set corresponds to depth and stencil related state from VK_EXT_extended_dynamic_state
     *   and VK_EXT_extended_dynamic_state2.
     *
     * * Primitive topology and primitive restart enable.
     *   Note that the primitive topology _class_ is not dynamic.
     *   This set corresponds to input assembly state from VK_EXT_extended_dynamic_state and
     *   VK_EXT_extended_dynamic_state2.
     *
     * * Cull mode, front face and rasterizer discard.
     *   This set corresponds to rasterizer state from VK_EXT_extended_dynamic_state and
     *   VK_EXT_extended_dynamic_state2.
     */
    bool useBasicDynamicState() const { return fUseBasicDynamicState; }
    /**
     * Whether all vertex input state is dynamic.
     * This set corresponds to state from VK_EXT_vertex_input_dynamic_state.  This state is
     * equivalently pulled out of the shaders pipeline via VK_EXT_graphics_pipeline_library
     * (usePipelineLibraries()).
     */
    bool useVertexInputDynamicState() const { return fUseVertexInputDynamicState; }
    /**
     * Whether VK_EXT_graphics_pipeline_library should be used.  In this case, the "shaders" subset
     * of the pipeline is compiled separately, then fast-linked with the vertex input and fragment
     * output state to create the final library.  Currently, this is a detail of the Vulkan backend,
     * which helps VkPipelineCache hits (because the shaders pipeline hits the cache, and blend
     * state is patched in).  However, this is most useful once exposed to the front-end, such that
     * it can track the (fewer) shaders pipeline separately, have the complete pipelines point to
     * the shaders pipeline, avoid unnecessary cache look ups, and more.  (skbug.com/414645289)
     */
    bool usePipelineLibraries() const { return fUsePipelineLibraries; }

    bool supportsHostImageCopy() const { return fSupportsHostImageCopy; }

    skgpu::ShaderErrorHandler* shaderErrorHandler() const { return fShaderErrorHandler; }

    /**
     * Returns what method of dst read a draw should use for obtaining the dst color. Backends can
     * use the default implementation or override this method as needed.
     */
    virtual DstReadStrategy getDstReadStrategy() const;

    float minPathSizeForMSAA() const { return fMinMSAAPathSize; }
    float minDistanceFieldFontSize() const { return fMinDistanceFieldFontSize; }
    float glyphsAsPathsFontSize() const { return fGlyphsAsPathsFontSize; }

    size_t glyphCacheTextureMaximumBytes() const { return fGlyphCacheTextureMaximumBytes; }
    int maxPathAtlasTextureSize() const { return fMaxPathAtlasTextureSize; }

    bool allowMultipleAtlasTextures() const { return fAllowMultipleAtlasTextures; }
    bool supportBilerpFromGlyphAtlas() const { return fSupportBilerpFromGlyphAtlas; }

    bool requireOrderedRecordings() const { return fRequireOrderedRecordings; }

    sktext::gpu::SubRunControl getSubRunControl(bool useSDFTForSmallText) const;

    bool setBackendLabels() const { return fSetBackendLabels; }

    GpuStatsFlags supportedGpuStats() const { return fSupportedGpuStats; }

protected:
    Caps();

    /**
     * Subclasses must call this at the end of their init method in order to do final processing on
     * the caps.
     */
    void finishInitialization(const ContextOptions&);

#if defined(GPU_TEST_UTILS)
    void setDeviceName(std::string n) {
        fDeviceName = std::move(n);
    }
#endif

    /* ColorTypeInfo for a specific format. Used in format tables. */
    struct ColorTypeInfo {
        ColorTypeInfo() = default;
        ColorTypeInfo(SkColorType ct, SkColorType transferCt, uint32_t flags,
                      skgpu::Swizzle readSwizzle, skgpu::Swizzle writeSwizzle)
                : fColorType(ct)
                , fTransferColorType(transferCt)
                , fFlags(flags)
                , fReadSwizzle(readSwizzle)
                , fWriteSwizzle(writeSwizzle) {}

        SkColorType fColorType = kUnknown_SkColorType;
        SkColorType fTransferColorType = kUnknown_SkColorType;
        enum {
            kUploadData_Flag = 0x1,
            /**
             * Does Graphite 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;

        skgpu::Swizzle fReadSwizzle;
        skgpu::Swizzle fWriteSwizzle;
    };

    int fMaxTextureSize = 0;

    size_t fRequiredUniformBufferAlignment = 0;
    size_t fRequiredStorageBufferAlignment = 0;
    size_t fRequiredTransferBufferAlignment = 0;
    size_t fTextureDataRowBytesAlignment = 1;

    int fMaxVaryings = 0;

    std::unique_ptr<SkSL::ShaderCaps> fShaderCaps;

    bool fNDCYAxisPointsDown = false; // Most backends have NDC +Y pointing up
    bool fClampToBorderSupport = true;
    bool fProtectedSupport = false;
    bool fSemaphoreSupport = false;
    bool fAllowCpuSync = true;
    bool fStorageBufferSupport = false;
    bool fDrawBufferCanBeMapped = true;
    bool fBufferMapsAreAsync = false;
    bool fMSAARenderToSingleSampledSupport = false;
    bool fDifferentResolveAttachmentSizeSupport = false;
    bool fAvoidMSAA = false;
    bool fDrawListLayer = false;

    bool fComputeSupport = false;
    bool fSupportsAHardwareBufferImages = false;
    bool fFullCompressedUploadSizeMustAlignToBlockDims = false;

    // Dynamic state.  The granularity is less fine than Vulkan's, but there is still some
    // granularity to allow for some dynamic state to be disabled due to driver bugs without having
    // to disable everything.  Eventually, these can be used to create fewer pipelines in the first
    // place (b/414645289).
    bool fUseBasicDynamicState = false;
    bool fUseVertexInputDynamicState = false;
    bool fUsePipelineLibraries = false;

    // Whether it's possible to upload data to images using the CPU (host) instead of the device.
    // Under certain circumstances, it's more efficient to upload data in this way instead of
    // through a staging buffer.
    bool fSupportsHostImageCopy = false;

#if defined(GPU_TEST_UTILS)
    bool fDrawBufferCanBeMappedForReadback = true;
#endif

    ResourceBindingRequirements fResourceBindingReqs;
    BlendEquationSupport fBlendEqSupport = BlendEquationSupport::kBasic;

    GpuStatsFlags fSupportedGpuStats = GpuStatsFlags::kNone;

    //////////////////////////////////////////////////////////////////////////////////////////
    // Client-provided Caps

    /**
     * If present, use this object to report shader compilation failures. If not, report failures
     * via SkDebugf and assert.
     */
    ShaderErrorHandler* fShaderErrorHandler = nullptr;

#if defined(GPU_TEST_UTILS)
    std::string fDeviceName;
    std::optional<PathRendererStrategy> fRequestedPathRendererStrategy;
#endif

    // NOTE: This is a requested limit, the actual supported sample counts for a particular format
    // could be lower or higher.
    SampleCount fMaxInternalSampleCount = SampleCount::k4;

    size_t fGlyphCacheTextureMaximumBytes = 2048 * 1024 * 4;

    float fMinMSAAPathSize = 0;
    float fMinDistanceFieldFontSize = 18;
    float fGlyphsAsPathsFontSize = 324;

    int fMaxPathAtlasTextureSize = 8192;

    bool fAllowMultipleAtlasTextures = true;
    bool fSupportBilerpFromGlyphAtlas = false;

    bool fRequireOrderedRecordings = false;

    bool fSetBackendLabels = false;

private:
    // Validates format support and calls onGetDefaultTextureInfo if it would be valid, returning
    // a TextureInfo for the first format that is supported.
    TextureInfo getDefaultTextureInfo(SkEnumBitMask<TextureUsage> usage,
                                      SkSpan<const TextureFormat>,
                                      SampleCount,
                                      Mipmapped,
                                      Protected,
                                      Discardable) const;

    // Return a TextureInfo that is configured to support the given usages with the requested format
    // and other properties. This is only called if getTextureSupport() matches for kOptimal tiling.
    virtual TextureInfo onGetDefaultTextureInfo(SkEnumBitMask<TextureUsage> usage,
                                                TextureFormat,
                                                SampleCount,
                                                Mipmapped,
                                                Protected,
                                                Discardable) const = 0;

    // Return the supported TextureUsages and SampleCounts for a texture of the given format and
    // tiling, assuming the textures are created with the requisite usages.
    virtual std::pair<SkEnumBitMask<TextureUsage>, SkEnumBitMask<SampleCount>> getTextureSupport(
            TextureFormat format, Tiling) const = 0;

    // Return the mask of TextureUsages supported by the described texture, as well as its tiling
    // representation. Subclasses can assume that this will only be called on valid TextureInfos
    // and do not need to account for TextureFormat supported features; Caps will combine the usage
    // and format support automatically.
    virtual std::pair<SkEnumBitMask<TextureUsage>, Tiling> getTextureUsage(
            const TextureInfo&) const = 0;

    // Returns true if the texture supports all usages in `test`, checking its declared usages
    // against its format's supported usages and its sample count against its format's supported
    // sample counts.
    //
    // `allowMSAA=false` forces false to be returned for any info with a sample count > 1. The other
    // allow flags are validation checks that are asserted against (and presumably implicit in the
    // usages that a format supports).
    bool isSupported(const TextureInfo&,
                     SkEnumBitMask<TextureUsage> test,
                     bool allowMSAA,
                     bool allowExternal,
                     bool allowCompressed,
                     bool allowProtected) const;

    sk_sp<SkCapabilities> fCapabilities;
};

} // namespace skgpu::graphite

#endif // skgpu_graphite_Caps_DEFINED
