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

#include "include/core/SkTypes.h"
#include "include/gpu/vk/GrVkVulkan.h"

#ifndef VK_VERSION_1_1
#error Skia requires the use of Vulkan 1.1 headers
#endif

#include <functional>
#include "include/gpu/GrTypes.h"

typedef intptr_t GrVkBackendMemory;

/**
 * Types for interacting with Vulkan resources created externally to Skia. GrBackendObjects for
 * Vulkan textures are really const GrVkImageInfo*
 */
struct GrVkAlloc {
    GrVkAlloc()
            : fMemory(VK_NULL_HANDLE)
            , fOffset(0)
            , fSize(0)
            , fFlags(0)
            , fBackendMemory(0)
            , fUsesSystemHeap(false) {}

    GrVkAlloc(VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, uint32_t flags)
            : fMemory(memory)
            , fOffset(offset)
            , fSize(size)
            , fFlags(flags)
            , fBackendMemory(0)
            , fUsesSystemHeap(false) {}

    VkDeviceMemory    fMemory;  // can be VK_NULL_HANDLE iff is an RT and is borrowed
    VkDeviceSize      fOffset;
    VkDeviceSize      fSize;    // this can be indeterminate iff Tex uses borrow semantics
    uint32_t          fFlags;
    GrVkBackendMemory fBackendMemory; // handle to memory allocated via GrVkMemoryAllocator.

    enum Flag {
        kNoncoherent_Flag = 0x1,   // memory must be flushed to device after mapping
        kMappable_Flag    = 0x2,   // memory is able to be mapped.
    };

    bool operator==(const GrVkAlloc& that) const {
        return fMemory == that.fMemory && fOffset == that.fOffset && fSize == that.fSize &&
               fFlags == that.fFlags && fUsesSystemHeap == that.fUsesSystemHeap;
    }

private:
    friend class GrVkHeap; // For access to usesSystemHeap
    bool fUsesSystemHeap;
};

// This struct is used to pass in the necessary information to create a VkSamplerYcbcrConversion
// object for an VkExternalFormatANDROID.
struct GrVkYcbcrConversionInfo {
    GrVkYcbcrConversionInfo()
            : fFormat(VK_FORMAT_UNDEFINED)
            , fExternalFormat(0)
            , fYcbcrModel(VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY)
            , fYcbcrRange(VK_SAMPLER_YCBCR_RANGE_ITU_FULL)
            , fXChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN)
            , fYChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN)
            , fChromaFilter(VK_FILTER_NEAREST)
            , fForceExplicitReconstruction(false) {}

    GrVkYcbcrConversionInfo(VkFormat format,
                            int64_t externalFormat,
                            VkSamplerYcbcrModelConversion ycbcrModel,
                            VkSamplerYcbcrRange ycbcrRange,
                            VkChromaLocation xChromaOffset,
                            VkChromaLocation yChromaOffset,
                            VkFilter chromaFilter,
                            VkBool32 forceExplicitReconstruction,
                            VkFormatFeatureFlags formatFeatures)
            : fFormat(format)
            , fExternalFormat(externalFormat)
            , fYcbcrModel(ycbcrModel)
            , fYcbcrRange(ycbcrRange)
            , fXChromaOffset(xChromaOffset)
            , fYChromaOffset(yChromaOffset)
            , fChromaFilter(chromaFilter)
            , fForceExplicitReconstruction(forceExplicitReconstruction)
            , fFormatFeatures(formatFeatures) {
        SkASSERT(fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY);
        // Either format or externalFormat must be specified.
        SkASSERT((fFormat != VK_FORMAT_UNDEFINED) ^ (externalFormat != 0));
    }

    GrVkYcbcrConversionInfo(VkSamplerYcbcrModelConversion ycbcrModel,
                            VkSamplerYcbcrRange ycbcrRange,
                            VkChromaLocation xChromaOffset,
                            VkChromaLocation yChromaOffset,
                            VkFilter chromaFilter,
                            VkBool32 forceExplicitReconstruction,
                            uint64_t externalFormat,
                            VkFormatFeatureFlags externalFormatFeatures)
            : GrVkYcbcrConversionInfo(VK_FORMAT_UNDEFINED, externalFormat, ycbcrModel, ycbcrRange,
                                      xChromaOffset, yChromaOffset, chromaFilter,
                                      forceExplicitReconstruction, externalFormatFeatures) {}

    bool operator==(const GrVkYcbcrConversionInfo& that) const {
        // Invalid objects are not required to have all other fields initialized or matching.
        if (!this->isValid() && !that.isValid()) {
            return true;
        }
        return this->fFormat == that.fFormat &&
               this->fExternalFormat == that.fExternalFormat &&
               this->fYcbcrModel == that.fYcbcrModel &&
               this->fYcbcrRange == that.fYcbcrRange &&
               this->fXChromaOffset == that.fXChromaOffset &&
               this->fYChromaOffset == that.fYChromaOffset &&
               this->fChromaFilter == that.fChromaFilter &&
               this->fForceExplicitReconstruction == that.fForceExplicitReconstruction;
    }
    bool operator!=(const GrVkYcbcrConversionInfo& that) const { return !(*this == that); }

    bool isValid() const { return fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY; }

    // Format of the source image. Must be set to VK_FORMAT_UNDEFINED for external images or
    // a valid image format otherwise.
    VkFormat                         fFormat;

    // The external format. Must be non-zero for external images, zero otherwise.
    // Should be compatible to be used in a VkExternalFormatANDROID struct.
    uint64_t                         fExternalFormat;

    VkSamplerYcbcrModelConversion    fYcbcrModel;
    VkSamplerYcbcrRange              fYcbcrRange;
    VkChromaLocation                 fXChromaOffset;
    VkChromaLocation                 fYChromaOffset;
    VkFilter                         fChromaFilter;
    VkBool32                         fForceExplicitReconstruction;

    // For external images format features here should be those returned by a call to
    // vkAndroidHardwareBufferFormatPropertiesANDROID
    VkFormatFeatureFlags             fFormatFeatures;
};

struct GrVkImageInfo {
    VkImage                  fImage;
    GrVkAlloc                fAlloc;
    VkImageTiling            fImageTiling;
    VkImageLayout            fImageLayout;
    VkFormat                 fFormat;
    uint32_t                 fLevelCount;
    uint32_t                 fCurrentQueueFamily;
    GrProtected              fProtected;
    GrVkYcbcrConversionInfo  fYcbcrConversionInfo;

    GrVkImageInfo()
            : fImage(VK_NULL_HANDLE)
            , fAlloc()
            , fImageTiling(VK_IMAGE_TILING_OPTIMAL)
            , fImageLayout(VK_IMAGE_LAYOUT_UNDEFINED)
            , fFormat(VK_FORMAT_UNDEFINED)
            , fLevelCount(0)
            , fCurrentQueueFamily(VK_QUEUE_FAMILY_IGNORED)
            , fProtected(GrProtected::kNo)
            , fYcbcrConversionInfo() {}

    GrVkImageInfo(VkImage image,
                  GrVkAlloc alloc,
                  VkImageTiling imageTiling,
                  VkImageLayout layout,
                  VkFormat format,
                  uint32_t levelCount,
                  uint32_t currentQueueFamily = VK_QUEUE_FAMILY_IGNORED,
                  GrProtected isProtected = GrProtected::kNo,
                  GrVkYcbcrConversionInfo ycbcrConversionInfo = GrVkYcbcrConversionInfo())
            : fImage(image)
            , fAlloc(alloc)
            , fImageTiling(imageTiling)
            , fImageLayout(layout)
            , fFormat(format)
            , fLevelCount(levelCount)
            , fCurrentQueueFamily(currentQueueFamily)
            , fProtected(isProtected)
            , fYcbcrConversionInfo(ycbcrConversionInfo) {}

    GrVkImageInfo(const GrVkImageInfo& info, VkImageLayout layout)
            : fImage(info.fImage)
            , fAlloc(info.fAlloc)
            , fImageTiling(info.fImageTiling)
            , fImageLayout(layout)
            , fFormat(info.fFormat)
            , fLevelCount(info.fLevelCount)
            , fCurrentQueueFamily(info.fCurrentQueueFamily)
            , fProtected(info.fProtected)
            , fYcbcrConversionInfo(info.fYcbcrConversionInfo) {}

    // This gives a way for a client to update the layout of the Image if they change the layout
    // while we're still holding onto the wrapped texture. They will first need to get a handle
    // to our internal GrVkImageInfo by calling getTextureHandle on a GrVkTexture.
    void updateImageLayout(VkImageLayout layout) { fImageLayout = layout; }

    bool operator==(const GrVkImageInfo& that) const {
        return fImage == that.fImage && fAlloc == that.fAlloc &&
               fImageTiling == that.fImageTiling && fImageLayout == that.fImageLayout &&
               fFormat == that.fFormat && fLevelCount == that.fLevelCount &&
               fCurrentQueueFamily == that.fCurrentQueueFamily && fProtected == that.fProtected &&
               fYcbcrConversionInfo == that.fYcbcrConversionInfo;
    }
};

using GrVkGetProc = std::function<PFN_vkVoidFunction(
        const char*, // function name
        VkInstance,  // instance or VK_NULL_HANDLE
        VkDevice     // device or VK_NULL_HANDLE
        )>;

/**
 * This object is wrapped in a GrBackendDrawableInfo and passed in as an argument to
 * drawBackendGpu() calls on an SkDrawable. The drawable will use this info to inject direct
 * Vulkan calls into our stream of GPU draws.
 *
 * The SkDrawable is given a secondary VkCommandBuffer in which to record draws. The GPU backend
 * will then execute that command buffer within a render pass it is using for its own draws. The
 * drawable is also given the attachment of the color index, a compatible VkRenderPass, and the
 * VkFormat of the color attachment so that it can make VkPipeline objects for the draws. The
 * SkDrawable must not alter the state of the VkRenderpass or sub pass.
 *
 * Additionally, the SkDrawable may fill in the passed in fDrawBounds with the bounds of the draws
 * that it submits to the command buffer. This will be used by the GPU backend for setting the
 * bounds in vkCmdBeginRenderPass. If fDrawBounds is not updated, we will assume that the entire
 * attachment may have been written to.
 *
 * The SkDrawable is always allowed to create its own command buffers and submit them to the queue
 * to render offscreen textures which will be sampled in draws added to the passed in
 * VkCommandBuffer. If this is done the SkDrawable is in charge of adding the required memory
 * barriers to the queue for the sampled images since the Skia backend will not do this.
 *
 * The VkImage is informational only and should not be used or modified in any ways.
 */
struct GrVkDrawableInfo {
    VkCommandBuffer fSecondaryCommandBuffer;
    uint32_t        fColorAttachmentIndex;
    VkRenderPass    fCompatibleRenderPass;
    VkFormat        fFormat;
    VkRect2D*       fDrawBounds;
    VkImage         fImage;
};

#endif
