//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// vk_utils:
//    Helper functions for the Vulkan Renderer.
//

#ifndef LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_
#define LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_

#include <atomic>
#include <limits>
#include <queue>
#include "common/unsafe_buffers.h"

#include "GLSLANG/ShaderLang.h"
#include "common/FixedVector.h"
#include "common/Optional.h"
#include "common/PackedEnums.h"
#include "common/SimpleMutex.h"
#include "common/WorkerThread.h"
#include "common/backtrace_utils.h"
#include "common/debug.h"
#include "libANGLE/Error.h"
#include "libANGLE/Observer.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/serial_utils.h"
#include "libANGLE/renderer/vulkan/SecondaryCommandBuffer.h"
#include "libANGLE/renderer/vulkan/SecondaryCommandPool.h"
#include "libANGLE/renderer/vulkan/VulkanSecondaryCommandBuffer.h"
#include "libANGLE/renderer/vulkan/vk_wrapper.h"
#include "platform/autogen/FeaturesVk_autogen.h"
#include "vulkan/vulkan_fuchsia_ext.h"

#define ANGLE_GL_OBJECTS_X(PROC) \
    PROC(Buffer)                 \
    PROC(Context)                \
    PROC(Framebuffer)            \
    PROC(MemoryObject)           \
    PROC(Program)                \
    PROC(ProgramExecutable)      \
    PROC(ProgramPipeline)        \
    PROC(Query)                  \
    PROC(Renderbuffer)           \
    PROC(Sampler)                \
    PROC(Semaphore)              \
    PROC(Texture)                \
    PROC(TransformFeedback)      \
    PROC(VertexArray)

#define ANGLE_PRE_DECLARE_OBJECT(OBJ) class OBJ;

namespace egl
{
class Display;
class Image;
class ShareGroup;
}  // namespace egl

namespace gl
{
class ProgramExecutable;
struct RasterizerState;
struct SwizzleState;
struct VertexAttribute;
class VertexBinding;

ANGLE_GL_OBJECTS_X(ANGLE_PRE_DECLARE_OBJECT)
}  // namespace gl

#define ANGLE_PRE_DECLARE_VK_OBJECT(OBJ) class OBJ##Vk;

namespace rx
{
class DisplayVk;
class ImageVk;
class ProgramExecutableVk;
class RenderbufferVk;
class RenderTargetVk;
class RenderPassCache;
class ShareGroupVk;
}  // namespace rx

namespace angle
{
egl::Error ToEGL(Result result, EGLint errorCode);
}  // namespace angle

namespace rx
{
ANGLE_GL_OBJECTS_X(ANGLE_PRE_DECLARE_VK_OBJECT)

const char *VulkanResultString(VkResult result);

constexpr size_t kMaxVulkanLayers = 20;
using VulkanLayerVector           = angle::FixedVector<const char *, kMaxVulkanLayers>;

// Verify that validation layers are available.
bool GetAvailableValidationLayers(const std::vector<VkLayerProperties> &layerProps,
                                  bool mustHaveLayers,
                                  VulkanLayerVector *enabledLayerNames);

enum class TextureDimension
{
    TEX_2D,
    TEX_CUBE,
    TEX_3D,
    TEX_2D_ARRAY,
};

enum class BufferUsageType
{
    Static      = 0,
    Dynamic     = 1,
    InvalidEnum = 2,
    EnumCount   = InvalidEnum,
};

// A maximum offset of 4096 covers almost every Vulkan driver on desktop (80%) and mobile (99%). The
// next highest values to meet native drivers are 16 bits or 32 bits.
constexpr uint32_t kAttributeOffsetMaxBits = 16;
constexpr uint32_t kInvalidMemoryTypeIndex = UINT32_MAX;
constexpr uint32_t kInvalidMemoryHeapIndex = UINT32_MAX;

namespace vk
{
class Renderer;

constexpr VkImageUsageFlags kImageUsageTransferBits =
    VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;

// Used for memory allocation tracking.
enum class MemoryAllocationType;

enum class MemoryHostVisibility
{
    NonVisible,
    Visible
};

// Encapsulate the graphics family index and VkQueue index (as seen in vkGetDeviceQueue API
// arguments) into one integer so that we can easily pass around without introduce extra overhead..
class DeviceQueueIndex final
{
  public:
    constexpr DeviceQueueIndex()
        : mFamilyIndex(kInvalidQueueFamilyIndex), mQueueIndex(kInvalidQueueIndex)
    {}
    constexpr DeviceQueueIndex(uint32_t familyIndex)
        : mFamilyIndex((int8_t)familyIndex), mQueueIndex(kInvalidQueueIndex)
    {
        ASSERT(static_cast<uint32_t>(mFamilyIndex) == familyIndex);
    }
    DeviceQueueIndex(uint32_t familyIndex, uint32_t queueIndex)
        : mFamilyIndex((int8_t)familyIndex), mQueueIndex((int8_t)queueIndex)
    {
        // Ensure the value we actually don't truncate the useful bits.
        ASSERT(static_cast<uint32_t>(mFamilyIndex) == familyIndex);
        ASSERT(static_cast<uint32_t>(mQueueIndex) == queueIndex);
    }
    DeviceQueueIndex(const DeviceQueueIndex &other) { *this = other; }

    DeviceQueueIndex &operator=(const DeviceQueueIndex &other)
    {
        mValue = other.mValue;
        return *this;
    }

    constexpr uint32_t familyIndex() const { return mFamilyIndex; }
    constexpr uint32_t queueIndex() const { return mQueueIndex; }

    bool operator==(const DeviceQueueIndex &other) const { return mValue == other.mValue; }
    bool operator!=(const DeviceQueueIndex &other) const { return mValue != other.mValue; }

  private:
    static constexpr int8_t kInvalidQueueFamilyIndex = -1;
    static constexpr int8_t kInvalidQueueIndex       = -1;
    // The expectation is that these indices are small numbers that could easily fit into int8_t.
    // int8_t is used instead of uint8_t because we need to handle VK_QUEUE_FAMILY_FOREIGN_EXT and
    // VK_QUEUE_FAMILY_EXTERNAL properly which are essentially are negative values.
    union
    {
        struct
        {
            int8_t mFamilyIndex;
            int8_t mQueueIndex;
        };
        uint16_t mValue;
    };
};
static constexpr DeviceQueueIndex kInvalidDeviceQueueIndex = DeviceQueueIndex();
static constexpr DeviceQueueIndex kForeignDeviceQueueIndex =
    DeviceQueueIndex(VK_QUEUE_FAMILY_FOREIGN_EXT);
static constexpr DeviceQueueIndex kExternalDeviceQueueIndex =
    DeviceQueueIndex(VK_QUEUE_FAMILY_EXTERNAL);
static_assert(kForeignDeviceQueueIndex.familyIndex() == VK_QUEUE_FAMILY_FOREIGN_EXT);
static_assert(kExternalDeviceQueueIndex.familyIndex() == VK_QUEUE_FAMILY_EXTERNAL);
static_assert(kInvalidDeviceQueueIndex.familyIndex() == VK_QUEUE_FAMILY_IGNORED);

// A packed attachment index interface with vulkan API
class PackedAttachmentIndex final
{
  public:
    explicit constexpr PackedAttachmentIndex(uint32_t index) : mAttachmentIndex(index) {}
    constexpr PackedAttachmentIndex(const PackedAttachmentIndex &other)            = default;
    constexpr PackedAttachmentIndex &operator=(const PackedAttachmentIndex &other) = default;

    constexpr uint32_t get() const { return mAttachmentIndex; }
    PackedAttachmentIndex &operator++()
    {
        ++mAttachmentIndex;
        return *this;
    }
    constexpr bool operator==(const PackedAttachmentIndex &other) const
    {
        return mAttachmentIndex == other.mAttachmentIndex;
    }
    constexpr bool operator!=(const PackedAttachmentIndex &other) const
    {
        return mAttachmentIndex != other.mAttachmentIndex;
    }
    constexpr bool operator<(const PackedAttachmentIndex &other) const
    {
        return mAttachmentIndex < other.mAttachmentIndex;
    }

  private:
    uint32_t mAttachmentIndex;
};
using PackedAttachmentCount                                    = PackedAttachmentIndex;
static constexpr PackedAttachmentIndex kAttachmentIndexInvalid = PackedAttachmentIndex(-1);
static constexpr PackedAttachmentIndex kAttachmentIndexZero    = PackedAttachmentIndex(0);

// Prepend ptr to the pNext chain at chainStart
template <typename VulkanStruct1, typename VulkanStruct2>
void AddToPNextChain(VulkanStruct1 *chainStart, VulkanStruct2 *ptr)
{
    // Catch bugs where this function is called with `&pointer` instead of `pointer`.
    static_assert(!std::is_pointer<VulkanStruct1>::value);
    static_assert(!std::is_pointer<VulkanStruct2>::value);

    ASSERT(ptr->pNext == nullptr);

    VkBaseOutStructure *localPtr = reinterpret_cast<VkBaseOutStructure *>(chainStart);
    ptr->pNext                   = localPtr->pNext;
    localPtr->pNext              = reinterpret_cast<VkBaseOutStructure *>(ptr);
}

// Append ptr to the end of the chain
template <typename VulkanStruct1, typename VulkanStruct2>
void AppendToPNextChain(VulkanStruct1 *chainStart, VulkanStruct2 *ptr)
{
    static_assert(!std::is_pointer<VulkanStruct1>::value);
    static_assert(!std::is_pointer<VulkanStruct2>::value);

    if (!ptr)
    {
        return;
    }

    VkBaseOutStructure *endPtr = reinterpret_cast<VkBaseOutStructure *>(chainStart);
    while (endPtr->pNext)
    {
        endPtr = endPtr->pNext;
    }
    endPtr->pNext = reinterpret_cast<VkBaseOutStructure *>(ptr);
}

class QueueSerialIndexAllocator final
{
  public:
    QueueSerialIndexAllocator() : mLargestIndexEverAllocated(0)
    {
        // Start with every index is free
        mFreeIndexBitSetArray.set();
        ASSERT(mFreeIndexBitSetArray.all());
    }
    SerialIndex allocate()
    {
        std::lock_guard<angle::SimpleMutex> lock(mMutex);
        if (mFreeIndexBitSetArray.none())
        {
            ERR() << "Run out of queue serial index. All " << kMaxQueueSerialIndexCount
                  << " indices are used.";
            return kInvalidQueueSerialIndex;
        }
        SerialIndex index = static_cast<SerialIndex>(mFreeIndexBitSetArray.first());
        ASSERT(index < kMaxQueueSerialIndexCount);
        mFreeIndexBitSetArray.reset(index);
        // Increase mLargestIndexEverAllocated to include the newly allocated index.
        mLargestIndexEverAllocated = std::max<size_t>(mLargestIndexEverAllocated, index);
        return index;
    }

    void release(SerialIndex index)
    {
        std::lock_guard<angle::SimpleMutex> lock(mMutex);
        ASSERT(index <= mLargestIndexEverAllocated);
        ASSERT(!mFreeIndexBitSetArray.test(index));
        mFreeIndexBitSetArray.set(index);
        // mLargestIndexEverAllocated is for optimization. Even if we released queueIndex, we may
        // still have resources still have serial the index. Thus do not decrement
        // mLargestIndexEverAllocated here. The only downside is that we may get into slightly less
        // optimal code path in GetBatchCountUpToSerials.
    }

    size_t getLargestIndexEverAllocated() const
    {
        return mLargestIndexEverAllocated.load(std::memory_order_consume);
    }

  private:
    angle::BitSetArray<kMaxQueueSerialIndexCount> mFreeIndexBitSetArray;
    std::atomic<size_t> mLargestIndexEverAllocated;
    angle::SimpleMutex mMutex;
};

class [[nodiscard]] ScopedQueueSerialIndex final : angle::NonCopyable
{
  public:
    ScopedQueueSerialIndex() : mIndex(kInvalidQueueSerialIndex), mIndexAllocator(nullptr) {}
    ~ScopedQueueSerialIndex()
    {
        if (mIndex != kInvalidQueueSerialIndex)
        {
            ASSERT(mIndexAllocator != nullptr);
            mIndexAllocator->release(mIndex);
        }
    }

    void init(SerialIndex index, QueueSerialIndexAllocator *indexAllocator)
    {
        ASSERT(mIndex == kInvalidQueueSerialIndex);
        ASSERT(index != kInvalidQueueSerialIndex);
        ASSERT(indexAllocator != nullptr);
        mIndex          = index;
        mIndexAllocator = indexAllocator;
    }

    SerialIndex get() const { return mIndex; }

  private:
    SerialIndex mIndex;
    QueueSerialIndexAllocator *mIndexAllocator;
};

class RefCountedEventsGarbageRecycler;
// Abstracts error handling. Implemented by ContextVk for GL, DisplayVk for EGL, worker threads,
// CLContextVk etc.
class ErrorContext : angle::NonCopyable
{
  public:
    ErrorContext(Renderer *renderer);
    virtual ~ErrorContext();

    virtual void handleError(VkResult result,
                             const char *file,
                             const char *function,
                             unsigned int line) = 0;
    VkDevice getDevice() const;
    Renderer *getRenderer() const { return mRenderer; }
    const angle::FeaturesVk &getFeatures() const;

    const angle::VulkanPerfCounters &getPerfCounters() const { return mPerfCounters; }
    angle::VulkanPerfCounters &getPerfCounters() { return mPerfCounters; }
    const DeviceQueueIndex &getDeviceQueueIndex() const { return mDeviceQueueIndex; }

  protected:
    Renderer *const mRenderer;
    DeviceQueueIndex mDeviceQueueIndex;
    angle::VulkanPerfCounters mPerfCounters;
};

// Abstract global operations that are handled differently between EGL and OpenCL.
class GlobalOps : angle::NonCopyable
{
  public:
    enum class Api : uint8_t
    {
        Egl    = 0,
        OpenCL = 1,

        InvalidEnum = 2,
        EnumCount   = InvalidEnum,
    };

    virtual ~GlobalOps() = default;

    virtual void putBlob(const angle::BlobCacheKey &key, const angle::MemoryBuffer &value) = 0;
    virtual bool getBlob(const angle::BlobCacheKey &key, angle::BlobCacheValue *valueOut)  = 0;

    virtual std::shared_ptr<angle::WaitableEvent> postMultiThreadWorkerTask(
        const std::shared_ptr<angle::Closure> &task) = 0;

    virtual void notifyDeviceLost() = 0;

    virtual GlobalOps::Api getFrontendApi() const = 0;
};

class RenderPassDesc;

#if ANGLE_USE_CUSTOM_VULKAN_OUTSIDE_RENDER_PASS_CMD_BUFFERS
using OutsideRenderPassCommandBuffer = priv::SecondaryCommandBuffer;
#else
using OutsideRenderPassCommandBuffer = VulkanSecondaryCommandBuffer;
#endif
#if ANGLE_USE_CUSTOM_VULKAN_RENDER_PASS_CMD_BUFFERS
using RenderPassCommandBuffer = priv::SecondaryCommandBuffer;
#else
using RenderPassCommandBuffer = VulkanSecondaryCommandBuffer;
#endif

struct SecondaryCommandPools
{
    SecondaryCommandPool outsideRenderPassPool;
    SecondaryCommandPool renderPassPool;
};

VkImageAspectFlags GetDepthStencilAspectFlags(const angle::Format &format);
VkImageAspectFlags GetFormatAspectFlags(const angle::Format &format);

template <typename T>
struct ImplTypeHelper;

// clang-format off
#define ANGLE_IMPL_TYPE_HELPER_GL(OBJ) \
template<>                             \
struct ImplTypeHelper<gl::OBJ>         \
{                                      \
    using ImplType = OBJ##Vk;          \
};
// clang-format on

ANGLE_GL_OBJECTS_X(ANGLE_IMPL_TYPE_HELPER_GL)

template <>
struct ImplTypeHelper<egl::Display>
{
    using ImplType = DisplayVk;
};

template <>
struct ImplTypeHelper<egl::Image>
{
    using ImplType = ImageVk;
};

template <>
struct ImplTypeHelper<egl::ShareGroup>
{
    using ImplType = ShareGroupVk;
};

template <typename T>
using GetImplType = typename ImplTypeHelper<T>::ImplType;

template <typename T>
GetImplType<T> *GetImpl(const T *glObject)
{
    return GetImplAs<GetImplType<T>>(glObject);
}

template <typename T>
GetImplType<T> *SafeGetImpl(const T *glObject)
{
    return SafeGetImplAs<GetImplType<T>>(glObject);
}

// Reference to a deleted object. The object is due to be destroyed at some point in the future.
// |mHandleType| determines the type of the object and which destroy function should be called.
class GarbageObject
{
  public:
    GarbageObject();
    GarbageObject(GarbageObject &&other);
    GarbageObject &operator=(GarbageObject &&rhs);

    bool valid() const { return mHandle != VK_NULL_HANDLE; }
    void destroy(Renderer *renderer);

    template <typename DerivedT, typename HandleT>
    static GarbageObject Get(WrappedObject<DerivedT, HandleT> *object)
    {
        static_assert(HandleTypeHelper<DerivedT>::kHandleType != HandleType::CommandBuffer);
        static_assert(HandleTypeHelper<DerivedT>::kHandleType != HandleType::Sampler);
        // Using c-style cast here to avoid conditional compile for MSVC 32-bit
        //  which fails to compile with reinterpret_cast, requiring static_cast.
        return GarbageObject(HandleTypeHelper<DerivedT>::kHandleType,
                             (GarbageHandle)(object->release()));
    }

  private:
    VK_DEFINE_NON_DISPATCHABLE_HANDLE(GarbageHandle)
    GarbageObject(HandleType handleType, GarbageHandle handle);

    HandleType mHandleType;
    GarbageHandle mHandle;
};

template <typename T>
GarbageObject GetGarbage(T *obj)
{
    return GarbageObject::Get(obj);
}

// A list of garbage objects. Has no object lifetime information.
using GarbageObjects = std::vector<GarbageObject>;

class MemoryProperties final : angle::NonCopyable
{
  public:
    MemoryProperties();

    void init(VkPhysicalDevice physicalDevice);
    bool hasLazilyAllocatedMemory() const;
    VkResult findCompatibleMemoryIndex(Renderer *renderer,
                                       const VkMemoryRequirements &memoryRequirements,
                                       VkMemoryPropertyFlags requestedMemoryPropertyFlags,
                                       bool isExternalMemory,
                                       VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                                       uint32_t *indexOut) const;
    void destroy();

    uint32_t getHeapIndexForMemoryType(uint32_t memoryType) const
    {
        if (memoryType == kInvalidMemoryTypeIndex)
        {
            return kInvalidMemoryHeapIndex;
        }

        ASSERT(memoryType < getMemoryTypeCount());
        return ANGLE_UNSAFE_TODO(mMemoryProperties.memoryTypes[memoryType]).heapIndex;
    }

    VkDeviceSize getHeapSizeForMemoryType(uint32_t memoryType) const
    {
        uint32_t heapIndex = ANGLE_UNSAFE_TODO(mMemoryProperties.memoryTypes[memoryType]).heapIndex;
        return ANGLE_UNSAFE_TODO(mMemoryProperties.memoryHeaps[heapIndex]).size;
    }

    const VkMemoryType &getMemoryType(uint32_t memoryTypeIndex) const
    {
        return ANGLE_UNSAFE_TODO(mMemoryProperties.memoryTypes[memoryTypeIndex]);
    }

    uint32_t getMemoryHeapCount() const { return mMemoryProperties.memoryHeapCount; }
    uint32_t getMemoryTypeCount() const { return mMemoryProperties.memoryTypeCount; }

    uint32_t findTileMemoryTypeIndex() const;

    void log(std::ostringstream &out) const;

  private:
    VkPhysicalDeviceMemoryProperties mMemoryProperties;
};

// Similar to StagingImage, for Buffers.
class StagingBuffer final : angle::NonCopyable
{
  public:
    StagingBuffer();
    void release(ContextVk *contextVk);
    void collectGarbage(Renderer *renderer, const QueueSerial &queueSerial);
    void destroy(Renderer *renderer);

    angle::Result init(ErrorContext *context,
                       VkDeviceSize size,
                       StagingUsage usage,
                       const int initValue);

    Buffer &getBuffer() { return mBuffer; }
    const Buffer &getBuffer() const { return mBuffer; }
    size_t getSize() const { return mSize; }

  private:
    Buffer mBuffer;
    Allocation mAllocation;
    size_t mSize;
};

angle::Result InitMappableAllocation(ErrorContext *context,
                                     const Allocator &allocator,
                                     Allocation *allocation,
                                     VkDeviceSize size,
                                     int value,
                                     VkMemoryPropertyFlags memoryPropertyFlags);

VkResult AllocateBufferMemory(ErrorContext *context,
                              vk::MemoryAllocationType memoryAllocationType,
                              VkMemoryPropertyFlags requestedMemoryPropertyFlags,
                              VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                              const void *extraAllocationInfo,
                              Buffer *buffer,
                              uint32_t *memoryTypeIndexOut,
                              DeviceMemory *deviceMemoryOut,
                              VkDeviceSize *sizeOut);

VkResult AllocateImageMemory(ErrorContext *context,
                             vk::MemoryAllocationType memoryAllocationType,
                             VkMemoryPropertyFlags memoryPropertyFlags,
                             VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                             const void *extraAllocationInfo,
                             Image *image,
                             uint32_t *memoryTypeIndexOut,
                             DeviceMemory *deviceMemoryOut,
                             VkDeviceSize *sizeOut);

VkResult AllocateImageMemoryWithRequirements(ErrorContext *context,
                                             vk::MemoryAllocationType memoryAllocationType,
                                             VkMemoryPropertyFlags memoryPropertyFlags,
                                             const VkMemoryRequirements &memoryRequirements,
                                             const void *extraAllocationInfo,
                                             const VkBindImagePlaneMemoryInfoKHR *extraBindInfo,
                                             Image *image,
                                             uint32_t *memoryTypeIndexOut,
                                             DeviceMemory *deviceMemoryOut);

VkResult AllocateImageMemoryFromTileHeap(ErrorContext *context,
                                         MemoryAllocationType memoryAllocationType,
                                         VkMemoryPropertyFlags requestedMemoryPropertyFlags,
                                         VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                                         Image *image,
                                         uint32_t *memoryTypeIndexOut,
                                         DeviceMemory *deviceMemoryOut,
                                         VkDeviceSize *sizeOut);

VkResult AllocateBufferMemoryWithRequirements(ErrorContext *context,
                                              MemoryAllocationType memoryAllocationType,
                                              VkMemoryPropertyFlags memoryPropertyFlags,
                                              const VkMemoryRequirements &memoryRequirements,
                                              const void *extraAllocationInfo,
                                              Buffer *buffer,
                                              VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                                              uint32_t *memoryTypeIndexOut,
                                              DeviceMemory *deviceMemoryOut);

angle::Result InitExternalSharedFDMemory(
    ErrorContext *context,
    const VkExternalMemoryHandleTypeFlagBits externalMemoryHandleType,
    const int32_t sharedBufferFD,
    VkMemoryPropertyFlags memoryProperties,
    Buffer *buffer,
    VkMemoryPropertyFlags *memoryPropertyFlagsOut,
    uint32_t *memoryTypeIndexOut,
    DeviceMemory *deviceMemoryOut,
    VkDeviceSize *sizeOut);

angle::Result GetHostPointerMemoryRequirements(ErrorContext *context,
                                               void *hostPtr,
                                               VkMemoryRequirements &memRequirements,
                                               Buffer *buffer);

angle::Result InitExternalHostMemory(ErrorContext *context,
                                     void *hostPtr,
                                     VkMemoryPropertyFlags memoryProperties,
                                     Buffer *buffer,
                                     VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                                     uint32_t *memoryTypeIndexOut,
                                     DeviceMemory *deviceMemoryOut,
                                     VkDeviceSize *sizeOut);

gl::TextureType Get2DTextureType(uint32_t layerCount, GLint samples);

enum class RecordingMode
{
    Start,
    Append,
};

// Helper class to handle RAII patterns for initialization. Requires that T have a destroy method
// that takes a VkDevice and returns void.
template <typename T>
class [[nodiscard]] DeviceScoped final : angle::NonCopyable
{
  public:
    explicit DeviceScoped(VkDevice device) : mDevice(device) {}
    DeviceScoped(DeviceScoped &&other) : mDevice(other.mDevice), mVar(std::move(other.mVar)) {}
    ~DeviceScoped() { mVar.destroy(mDevice); }

    const T &get() const { return mVar; }
    T &get() { return mVar; }

    T &&release() { return std::move(mVar); }

  private:
    VkDevice mDevice;
    T mVar;
};

template <typename T>
class [[nodiscard]] AllocatorScoped final : angle::NonCopyable
{
  public:
    AllocatorScoped(const Allocator &allocator) : mAllocator(allocator) {}
    ~AllocatorScoped() { mVar.destroy(mAllocator); }

    const T &get() const { return mVar; }
    T &get() { return mVar; }

    T &&release() { return std::move(mVar); }

  private:
    const Allocator &mAllocator;
    T mVar;
};

// Similar to DeviceScoped, but releases objects instead of destroying them. Requires that T have a
// release method that takes a ContextVk * and returns void.
template <typename T>
class [[nodiscard]] ContextScoped final : angle::NonCopyable
{
  public:
    ContextScoped(ContextVk *contextVk) : mContextVk(contextVk) {}
    ~ContextScoped() { mVar.release(mContextVk); }

    const T &get() const { return mVar; }
    T &get() { return mVar; }

    T &&release() { return std::move(mVar); }

  private:
    ContextVk *mContextVk;
    T mVar;
};

template <typename T>
class [[nodiscard]] RendererScoped final : angle::NonCopyable
{
  public:
    RendererScoped(Renderer *renderer) : mRenderer(renderer) {}
    ~RendererScoped() { mVar.release(mRenderer); }

    const T &get() const { return mVar; }
    T &get() { return mVar; }

    T &&release() { return std::move(mVar); }

  private:
    Renderer *mRenderer;
    T mVar;
};

template <typename, class>
class SharedPtr;

// This is a very simple RefCount class that has no autoreleasing.
template <typename T>
class RefCounted : angle::NonCopyable
{
  public:
    RefCounted() : mRefCount(0) {}
    template <class... Args>
    explicit RefCounted(Args &&...args) : mRefCount(0), mObject(std::forward<Args>(args)...)
    {}
    explicit RefCounted(T &&newObject) : mRefCount(0), mObject(std::move(newObject)) {}
    ~RefCounted() { ASSERT(mRefCount == 0 && !mObject.valid()); }

    RefCounted(RefCounted &&copy) : mRefCount(copy.mRefCount), mObject(std::move(copy.mObject))
    {
        ASSERT(this != &copy);
        copy.mRefCount = 0;
    }

    RefCounted &operator=(RefCounted &&rhs)
    {
        std::swap(mRefCount, rhs.mRefCount);
        mObject = std::move(rhs.mObject);
        return *this;
    }

    void addRef()
    {
        ASSERT(mRefCount != std::numeric_limits<uint32_t>::max());
        mRefCount++;
    }

    uint32_t getAndReleaseRef()
    {
        assertIsReferenced();
        return mRefCount--;
    }

    T &get() { return mObject; }
    const T &get() const { return mObject; }

    ANGLE_INLINE void assertIsReferenced() const { ASSERT(mRefCount != 0); }
    ANGLE_INLINE void assertIsRefCountAsExpected(uint32_t expectedRefCount)
    {
        ASSERT(mRefCount == expectedRefCount);
    }

  private:
    friend class SharedPtr<T, RefCounted<T>>;
    // This is used by SharedPtr::unique
    bool isLastReferenceCount() const { return mRefCount == 1; }

    uint32_t mRefCount;
    T mObject;
};

// Atomic version of RefCounted.  Used in the descriptor set and pipeline layout caches, which are
// accessed by link jobs.  No std::move is allowed due to the atomic ref count.
template <typename T>
class AtomicRefCounted : angle::NonCopyable
{
  public:
    AtomicRefCounted() : mRefCount(0) {}
    explicit AtomicRefCounted(T &&newObject) : mRefCount(0), mObject(std::move(newObject)) {}
    ~AtomicRefCounted() { ASSERT(mRefCount == 0 && !mObject.valid()); }

    void addRef()
    {
        ASSERT(mRefCount != std::numeric_limits<uint32_t>::max());
        mRefCount.fetch_add(1, std::memory_order_relaxed);
    }

    // Performs acquire-release memory synchronization. When result is "1", the object is
    // guaranteed to be no longer in use by other threads, and may be safely destroyed or updated.
    unsigned int getAndReleaseRef()
    {
        const unsigned int prevValue = mRefCount.fetch_sub(1, std::memory_order_acq_rel);
        ASSERT(prevValue != 0);
        return prevValue;
    }

    T &get() { return mObject; }
    const T &get() const { return mObject; }

  private:
    friend class SharedPtr<T, AtomicRefCounted<T>>;
    // This is used by SharedPtr::unique, so needs strong ordering.
    bool isLastReferenceCount() const { return mRefCount.load(std::memory_order_acquire) == 1; }

    std::atomic_uint mRefCount;
    T mObject;
};

// This is intended to have same interface as std::shared_ptr except this must used in thread safe
// environment.
template <typename>
class WeakPtr;
template <typename T, class RefCountedStorage = RefCounted<T>>
class SharedPtr final
{
  public:
    SharedPtr() : mRefCounted(nullptr), mDevice(VK_NULL_HANDLE) {}
    SharedPtr(VkDevice device, T &&object) : mDevice(device)
    {
        mRefCounted = new RefCountedStorage(std::move(object));
        mRefCounted->addRef();
    }
    SharedPtr(VkDevice device, const WeakPtr<T> &other)
        : mRefCounted(other.mRefCounted), mDevice(device)
    {
        if (mRefCounted)
        {
            // There must already have another SharedPtr holding onto the underline object when
            // WeakPtr is valid.
            mRefCounted->assertIsReferenced();
            mRefCounted->addRef();
        }
    }
    ~SharedPtr() { reset(); }

    SharedPtr(const SharedPtr &other) : mRefCounted(nullptr), mDevice(VK_NULL_HANDLE)
    {
        *this = other;
    }

    SharedPtr(SharedPtr &&other) : mRefCounted(nullptr), mDevice(VK_NULL_HANDLE)
    {
        *this = std::move(other);
    }

    template <class... Args>
    static SharedPtr<T, RefCountedStorage> MakeShared(VkDevice device, Args &&...args)
    {
        SharedPtr<T, RefCountedStorage> newObject;
        newObject.mRefCounted = new RefCountedStorage(std::forward<Args>(args)...);
        newObject.mRefCounted->addRef();
        newObject.mDevice = device;
        return newObject;
    }

    void reset()
    {
        if (mRefCounted)
        {
            releaseRef();
            mRefCounted = nullptr;
            mDevice     = VK_NULL_HANDLE;
        }
    }

    SharedPtr &operator=(SharedPtr &&other)
    {
        if (mRefCounted)
        {
            releaseRef();
        }
        mRefCounted       = other.mRefCounted;
        mDevice           = other.mDevice;
        other.mRefCounted = nullptr;
        other.mDevice     = VK_NULL_HANDLE;
        return *this;
    }

    SharedPtr &operator=(const SharedPtr &other)
    {
        if (mRefCounted)
        {
            releaseRef();
        }
        mRefCounted = other.mRefCounted;
        mDevice     = other.mDevice;
        if (mRefCounted)
        {
            mRefCounted->addRef();
        }
        return *this;
    }

    operator bool() const { return mRefCounted != nullptr; }

    T &operator*() const
    {
        ASSERT(mRefCounted != nullptr);
        return mRefCounted->get();
    }

    T *operator->() const { return get(); }

    T *get() const
    {
        ASSERT(mRefCounted != nullptr);
        return &mRefCounted->get();
    }

    bool unique() const
    {
        ASSERT(mRefCounted != nullptr);
        return mRefCounted->isLastReferenceCount();
    }

    bool owner_equal(const SharedPtr<T> &other) const { return mRefCounted == other.mRefCounted; }

    uint32_t getRefCount() const { return mRefCounted->getRefCount(); }

  private:
    void releaseRef()
    {
        ASSERT(mRefCounted != nullptr);
        unsigned int refCount = mRefCounted->getAndReleaseRef();
        if (refCount == 1)
        {
            mRefCounted->get().destroy(mDevice);
            SafeDelete(mRefCounted);
        }
    }

    friend class WeakPtr<T>;
    RefCountedStorage *mRefCounted;
    VkDevice mDevice;
};

template <typename T>
using AtomicSharedPtr = SharedPtr<T, AtomicRefCounted<T>>;

// This is intended to have same interface as std::weak_ptr
template <typename T>
class WeakPtr final
{
  public:
    using RefCountedStorage = RefCounted<T>;

    WeakPtr() : mRefCounted(nullptr) {}

    WeakPtr(const SharedPtr<T> &other) : mRefCounted(other.mRefCounted) {}

    void reset() { mRefCounted = nullptr; }

    operator bool() const
    {
        // There must have another SharedPtr holding onto the underline object when WeakPtr is
        // valid.
        if (mRefCounted != nullptr)
        {
            mRefCounted->assertIsReferenced();
        }
        return mRefCounted != nullptr;
    }

    T *operator->() const { return get(); }

    T *get() const
    {
        ASSERT(mRefCounted != nullptr);
        mRefCounted->assertIsReferenced();
        return &mRefCounted->get();
    }

    long use_count() const
    {
        ASSERT(mRefCounted != nullptr);
        // There must have another SharedPtr holding onto the underline object when WeakPtr is
        // valid.
        mRefCounted->assertIsReferenced();
        return mRefCounted->getRefCount();
    }
    bool owner_equal(const SharedPtr<T> &other) const
    {
        // There must have another SharedPtr holding onto the underlying object when WeakPtr is
        // valid.
        if (mRefCounted != nullptr)
        {
            mRefCounted->assertIsReferenced();
        }
        return mRefCounted == other.mRefCounted;
    }

  private:
    friend class SharedPtr<T>;
    RefCountedStorage *mRefCounted;
};

// Helper class to share ref-counted Vulkan objects.  Requires that T have a destroy method
// that takes a VkDevice and returns void.
template <typename T>
class Shared final : angle::NonCopyable
{
  public:
    Shared() : mRefCounted(nullptr) {}
    ~Shared() { ASSERT(mRefCounted == nullptr); }

    Shared(Shared &&other) { *this = std::move(other); }
    Shared &operator=(Shared &&other)
    {
        ASSERT(this != &other);
        mRefCounted       = other.mRefCounted;
        other.mRefCounted = nullptr;
        return *this;
    }

    void set(VkDevice device, RefCounted<T> *refCounted)
    {
        if (mRefCounted)
        {
            if (mRefCounted->getAndReleaseRef() == 1)
            {
                mRefCounted->get().destroy(device);
                SafeDelete(mRefCounted);
            }
        }

        mRefCounted = refCounted;

        if (mRefCounted)
        {
            mRefCounted->addRef();
        }
    }

    void setUnreferenced(RefCounted<T> *refCounted)
    {
        ASSERT(!mRefCounted);
        ASSERT(refCounted);

        mRefCounted = refCounted;
        mRefCounted->addRef();
    }

    void assign(VkDevice device, T &&newObject)
    {
        set(device, new RefCounted<T>(std::move(newObject)));
    }

    void copy(VkDevice device, const Shared<T> &other) { set(device, other.mRefCounted); }

    void copyUnreferenced(const Shared<T> &other) { setUnreferenced(other.mRefCounted); }

    void reset(VkDevice device) { set(device, nullptr); }

    template <typename RecyclerT>
    void resetAndRecycle(RecyclerT *recycler)
    {
        if (mRefCounted)
        {
            if (mRefCounted->getAndReleaseRef() == 1)
            {
                ASSERT(mRefCounted->get().valid());
                recycler->recycle(std::move(mRefCounted->get()));
                SafeDelete(mRefCounted);
            }

            mRefCounted = nullptr;
        }
    }

    template <typename OnRelease>
    void resetAndRelease(OnRelease *onRelease)
    {
        if (mRefCounted)
        {
            if (mRefCounted->getAndReleaseRef() == 1)
            {
                ASSERT(mRefCounted->get().valid());
                (*onRelease)(std::move(mRefCounted->get()));
                SafeDelete(mRefCounted);
            }

            mRefCounted = nullptr;
        }
    }

    bool isReferenced() const
    {
        // If reference is zero, the object should have been deleted.  I.e. if the object is not
        // nullptr, it should have a reference.
        if (mRefCounted != nullptr)
        {
            mRefCounted->assertIsReferenced();
        }
        return mRefCounted != nullptr;
    }

    T &get()
    {
        ASSERT(mRefCounted != nullptr);
        mRefCounted->assertIsReferenced();
        return mRefCounted->get();
    }
    const T &get() const
    {
        ASSERT(mRefCounted != nullptr);
        mRefCounted->assertIsReferenced();
        return mRefCounted->get();
    }

  private:
    RefCounted<T> *mRefCounted;
};

template <typename T, typename StorageT = std::deque<T>>
class Recycler final : angle::NonCopyable
{
  public:
    Recycler() = default;
    Recycler(StorageT &&storage) { mObjectFreeList = std::move(storage); }

    void recycle(T &&garbageObject)
    {
        // Recycling invalid objects is pointless and potentially a bug.
        ASSERT(garbageObject.valid());
        mObjectFreeList.emplace_back(std::move(garbageObject));
    }

    void recycle(StorageT &&garbageObjects)
    {
        // Recycling invalid objects is pointless and potentially a bug.
        ASSERT(!garbageObjects.empty());
        mObjectFreeList.insert(mObjectFreeList.end(), garbageObjects.begin(), garbageObjects.end());
        ASSERT(garbageObjects.empty());
    }

    void refill(StorageT &&garbageObjects)
    {
        ASSERT(!garbageObjects.empty());
        ASSERT(mObjectFreeList.empty());
        mObjectFreeList.swap(garbageObjects);
    }

    void fetch(T *outObject)
    {
        ASSERT(!empty());
        *outObject = std::move(mObjectFreeList.back());
        mObjectFreeList.pop_back();
    }

    void destroy(VkDevice device)
    {
        while (!mObjectFreeList.empty())
        {
            T &object = mObjectFreeList.back();
            object.destroy(device);
            mObjectFreeList.pop_back();
        }
    }

    bool empty() const { return mObjectFreeList.empty(); }

  private:
    StorageT mObjectFreeList;
};


using ShaderModulePtr = SharedPtr<ShaderModule>;
using ShaderModuleMap = gl::ShaderMap<ShaderModulePtr>;

angle::Result InitShaderModule(ErrorContext *context,
                               ShaderModulePtr *shaderModulePtr,
                               const uint32_t *shaderCode,
                               size_t shaderCodeSize);

void MakeDebugUtilsLabel(GLenum source, const char *marker, VkDebugUtilsLabelEXT *label);

constexpr size_t kUnpackedDepthIndex   = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS;
constexpr size_t kUnpackedStencilIndex = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS + 1;
constexpr gl::AttachmentsMask kDepthStencilAttachmentsMask({kUnpackedDepthIndex,
                                                            kUnpackedStencilIndex});
constexpr uint32_t kUnpackedColorBuffersMask =
    angle::BitMask<uint32_t>(gl::IMPLEMENTATION_MAX_DRAW_BUFFERS);

class ClearValuesArray final
{
  public:
    ClearValuesArray();
    ~ClearValuesArray();

    ClearValuesArray(const ClearValuesArray &other);
    ClearValuesArray &operator=(const ClearValuesArray &rhs);

    void store(uint32_t index, VkImageAspectFlags aspectFlags, const VkClearValue &clearValue);
    void storeNoDepthStencil(uint32_t index, const VkClearValue &clearValue);

    void reset(size_t index)
    {
        mValues[index] = {};
        mEnabled.reset(index);
    }

    bool test(size_t index) const { return mEnabled.test(index); }
    bool testDepth() const { return mEnabled.test(kUnpackedDepthIndex); }
    bool testStencil() const { return mEnabled.test(kUnpackedStencilIndex); }
    gl::DrawBufferMask getColorMask() const;

    const VkClearValue &operator[](size_t index) const { return mValues[index]; }

    float getDepthValue() const { return mValues[kUnpackedDepthIndex].depthStencil.depth; }
    uint32_t getStencilValue() const { return mValues[kUnpackedStencilIndex].depthStencil.stencil; }

    const VkClearValue *data() const { return mValues.data(); }
    bool empty() const { return mEnabled.none(); }
    bool any() const { return mEnabled.any(); }

  private:
    gl::AttachmentArray<VkClearValue> mValues;
    gl::AttachmentsMask mEnabled;
};

// Defines Serials for Vulkan objects.
#define ANGLE_VK_SERIAL_OP(X) \
    X(Buffer)                 \
    X(Image)                  \
    X(ImageOrBufferView)      \
    X(Sampler)

#define ANGLE_DEFINE_VK_SERIAL_TYPE(Type)                                     \
    class Type##Serial                                                        \
    {                                                                         \
      public:                                                                 \
        constexpr Type##Serial() : mSerial(kInvalid) {}                       \
        constexpr explicit Type##Serial(uint64_t serial) : mSerial(serial) {} \
                                                                              \
        constexpr bool operator==(const Type##Serial &other) const            \
        {                                                                     \
            ASSERT(mSerial != kInvalid || other.mSerial != kInvalid);         \
            return mSerial == other.mSerial;                                  \
        }                                                                     \
        constexpr bool operator!=(const Type##Serial &other) const            \
        {                                                                     \
            ASSERT(mSerial != kInvalid || other.mSerial != kInvalid);         \
            return mSerial != other.mSerial;                                  \
        }                                                                     \
        constexpr uint64_t getValue() const                                   \
        {                                                                     \
            return mSerial;                                                   \
        }                                                                     \
        constexpr bool valid() const                                          \
        {                                                                     \
            return mSerial != kInvalid;                                       \
        }                                                                     \
                                                                              \
      private:                                                                \
        uint64_t mSerial;                                                     \
        static constexpr uint64_t kInvalid = 0;                               \
    };                                                                        \
    static constexpr Type##Serial kInvalid##Type##Serial = Type##Serial();

ANGLE_VK_SERIAL_OP(ANGLE_DEFINE_VK_SERIAL_TYPE)

#define ANGLE_DECLARE_GEN_VK_SERIAL(Type) Type##Serial generate##Type##Serial();

class ResourceSerialFactory final : angle::NonCopyable
{
  public:
    ResourceSerialFactory();
    ~ResourceSerialFactory();

    ANGLE_VK_SERIAL_OP(ANGLE_DECLARE_GEN_VK_SERIAL)

  private:
    uint64_t issueSerial();

    // Kept atomic so it can be accessed from multiple Context threads at once.
    std::atomic<uint64_t> mCurrentUniqueSerial;
};

#if defined(ANGLE_ENABLE_PERF_COUNTER_OUTPUT)
constexpr bool kOutputCumulativePerfCounters = ANGLE_ENABLE_PERF_COUNTER_OUTPUT;
#else
constexpr bool kOutputCumulativePerfCounters = false;
#endif

// Performance and resource counters.
struct RenderPassPerfCounters
{
    // load/storeOps. Includes ops for resolve attachment. Maximum value = 2.
    uint8_t colorLoadOpClears;
    uint8_t colorLoadOpLoads;
    uint8_t colorLoadOpNones;
    uint8_t colorStoreOpStores;
    uint8_t colorStoreOpNones;
    uint8_t depthLoadOpClears;
    uint8_t depthLoadOpLoads;
    uint8_t depthLoadOpNones;
    uint8_t depthStoreOpStores;
    uint8_t depthStoreOpNones;
    uint8_t stencilLoadOpClears;
    uint8_t stencilLoadOpLoads;
    uint8_t stencilLoadOpNones;
    uint8_t stencilStoreOpStores;
    uint8_t stencilStoreOpNones;
    // Number of unresolve and resolve operations.  Maximum value for color =
    // gl::IMPLEMENTATION_MAX_DRAW_BUFFERS and for depth/stencil = 1 each.
    uint8_t colorAttachmentUnresolves;
    uint8_t colorAttachmentResolves;
    uint8_t depthAttachmentUnresolves;
    uint8_t depthAttachmentResolves;
    uint8_t stencilAttachmentUnresolves;
    uint8_t stencilAttachmentResolves;
    // Whether the depth/stencil attachment is using a read-only layout.
    uint8_t readOnlyDepthStencil;
};

// A Vulkan image level index.
using LevelIndex = gl::LevelIndexWrapper<uint32_t>;

// Ensure viewport is within Vulkan requirements
void ClampViewport(VkViewport *viewport);

constexpr bool IsDynamicDescriptor(VkDescriptorType descriptorType)
{
    switch (descriptorType)
    {
        case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
        case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
            return true;
        default:
            return false;
    }
}

constexpr bool IsUniformBuffer(const VkDescriptorType descriptorType)
{
    return descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
           descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
}

constexpr bool IsStorageBuffer(const VkDescriptorType descriptorType)
{
    return descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ||
           descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
}

void ApplyPipelineCreationFeedback(ErrorContext *context,
                                   const VkPipelineCreationFeedback &feedback);

angle::Result SetDebugUtilsObjectName(ContextVk *contextVk,
                                      VkObjectType objectType,
                                      uint64_t handle,
                                      const std::string &label);

// This defines enum for VkPipelineStageFlagBits so that we can use it to compare and index into
// array.
enum class PipelineStage : uint32_t
{
    // Bellow are ordered based on Graphics Pipeline Stages
    TopOfPipe              = 0,
    DrawIndirect           = 1,
    VertexInput            = 2,
    VertexShader           = 3,
    TessellationControl    = 4,
    TessellationEvaluation = 5,
    GeometryShader         = 6,
    TransformFeedback      = 7,
    FragmentShadingRate    = 8,
    EarlyFragmentTest      = 9,
    FragmentShader         = 10,
    LateFragmentTest       = 11,
    ColorAttachmentOutput  = 12,

    // Compute specific pipeline Stage
    ComputeShader = 13,

    // Transfer specific pipeline Stage
    Transfer     = 14,
    BottomOfPipe = 15,

    // Host specific pipeline stage
    Host = 16,

    InvalidEnum = 17,
    EnumCount   = InvalidEnum,
};
using PipelineStagesMask = angle::PackedEnumBitSet<PipelineStage, uint32_t>;

PipelineStage GetPipelineStage(gl::ShaderType stage);
}  // namespace vk

#if !defined(ANGLE_SHARED_LIBVULKAN)
// Lazily load entry points for each extension as necessary.
void InitDebugUtilsEXTFunctions(VkInstance instance);
void InitTransformFeedbackEXTFunctions(VkDevice device);
void InitRenderPass2KHRFunctions(VkDevice device);

#    if defined(ANGLE_PLATFORM_FUCHSIA)
// VK_FUCHSIA_imagepipe_surface
void InitImagePipeSurfaceFUCHSIAFunctions(VkInstance instance);
#    endif

#    if defined(ANGLE_PLATFORM_ANDROID)
// VK_ANDROID_external_memory_android_hardware_buffer
void InitExternalMemoryHardwareBufferANDROIDFunctions(VkDevice device);
#    endif

// VK_KHR_external_semaphore_fd
void InitExternalSemaphoreFdFunctions(VkDevice device);

// VK_EXT_device_fault
void InitDeviceFaultFunctions(VkDevice device);

// VK_EXT_host_query_reset
void InitHostQueryResetFunctions(VkDevice device);

// VK_KHR_external_fence_fd
void InitExternalFenceFdFunctions(VkDevice device);

// VK_KHR_shared_presentable_image
void InitGetSwapchainStatusKHRFunctions(VkDevice device);

// VK_EXT_extended_dynamic_state
void InitExtendedDynamicStateEXTFunctions(VkDevice device);

// VK_EXT_extended_dynamic_state2
void InitExtendedDynamicState2EXTFunctions(VkDevice device);

// VK_EXT_vertex_input_dynamic_state
void InitVertexInputDynamicStateEXTFunctions(VkDevice device);

// VK_KHR_dynamic_rendering
void InitDynamicRenderingFunctions(VkDevice device);

// VK_KHR_dynamic_rendering_local_read
void InitDynamicRenderingLocalReadFunctions(VkDevice device);

// VK_KHR_fragment_shading_rate
void InitFragmentShadingRateKHRInstanceFunction(VkInstance instance);
void InitFragmentShadingRateKHRDeviceFunction(VkDevice device);

// VK_KHR_maintenance5
void InitMaintenance5Functions(VkDevice device);

// VK_QCOM_tile_memory_heap
void InitTileMemoryHeapFunctions(VkDevice device);

// VK_GOOGLE_display_timing
void InitGetPastPresentationTimingGoogleFunction(VkDevice device);

// VK_EXT_host_image_copy
void InitHostImageCopyFunctions(VkDevice device);

// VK_EXT_image_compression_control
void InitImageCompressionControlFunctions(VkDevice device);

// VK_KHR_Synchronization2
void InitSynchronization2Functions(VkDevice device);

// VK_KHR_external_memory_fd
void InitExternalMemoryFdFunctions(VkDevice device);

// VK_EXT_external_memory_host
void InitExternalMemoryHostFunctions(VkDevice device);

// VK_KHR_buffer_device_address
void InitBufferDeviceAddressFunctions(VkDevice device);

#endif  // !defined(ANGLE_SHARED_LIBVULKAN)

// Promoted to Vulkan 1.1
void InitGetPhysicalDeviceProperties2KHRFunctionsFromCore();
void InitExternalFenceCapabilitiesFunctionsFromCore();
void InitExternalSemaphoreCapabilitiesFunctionsFromCore();
void InitSamplerYcbcrKHRFunctionsFromCore();
void InitGetMemoryRequirements2KHRFunctionsFromCore();
void InitBindMemory2KHRFunctionsFromCore();

// Promoted to KHR
void InitGetImageSubresourceLayoutEXTFunctionFromKHR();

GLenum CalculateGenerateMipmapFilter(ContextVk *contextVk, angle::FormatID formatID);

bool HasRequiredGlobalPriority(
    const VkQueueFamilyGlobalPriorityProperties &globalPriorityProperties,
    VkQueueGlobalPriorityEXT requiredGlobalPriority);

namespace gl_vk
{
inline VkRect2D GetRect(const gl::Rectangle &source)
{
    return {{source.x, source.y},
            {static_cast<uint32_t>(source.width), static_cast<uint32_t>(source.height)}};
}
VkFilter GetFilter(const GLenum filter);
VkSamplerMipmapMode GetSamplerMipmapMode(const GLenum filter);
VkSamplerAddressMode GetSamplerAddressMode(const GLenum wrap);
VkPrimitiveTopology GetPrimitiveTopology(gl::PrimitiveMode mode);
VkPolygonMode GetPolygonMode(const gl::PolygonMode polygonMode);
VkCullModeFlagBits GetCullMode(const gl::RasterizerState &rasterState);
VkFrontFace GetFrontFace(GLenum frontFace, bool invertCullFace);
VkSampleCountFlagBits GetSamples(GLint sampleCount, bool limitSampleCountTo2);
VkComponentSwizzle GetSwizzle(const GLenum swizzle);
VkCompareOp GetCompareOp(const GLenum compareFunc);
VkStencilOp GetStencilOp(const GLenum compareOp);
VkLogicOp GetLogicOp(const GLenum logicOp);

constexpr gl::ShaderMap<VkShaderStageFlagBits> kShaderStageMap = {
    {gl::ShaderType::Vertex, VK_SHADER_STAGE_VERTEX_BIT},
    {gl::ShaderType::TessControl, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT},
    {gl::ShaderType::TessEvaluation, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT},
    {gl::ShaderType::Fragment, VK_SHADER_STAGE_FRAGMENT_BIT},
    {gl::ShaderType::Geometry, VK_SHADER_STAGE_GEOMETRY_BIT},
    {gl::ShaderType::Compute, VK_SHADER_STAGE_COMPUTE_BIT},
};

void GetOffset(const gl::Offset &glOffset, VkOffset3D *vkOffset);
void GetExtent(const gl::Extents &glExtent, VkExtent3D *vkExtent);
VkImageType GetImageType(gl::TextureType textureType);
VkImageViewType GetImageViewType(gl::TextureType textureType);
VkColorComponentFlags GetColorComponentFlags(bool red, bool green, bool blue, bool alpha);
VkShaderStageFlags GetShaderStageFlags(gl::ShaderBitSet activeShaders);

void GetViewport(const gl::Rectangle &viewport,
                 float nearPlane,
                 float farPlane,
                 bool invertViewport,
                 bool upperLeftOrigin,
                 GLint renderAreaHeight,
                 VkViewport *viewportOut);

void GetExtentsAndLayerCount(gl::TextureType textureType,
                             const gl::Extents &extents,
                             VkExtent3D *extentsOut,
                             uint32_t *layerCountOut);

vk::LevelIndex GetLevelIndex(gl::LevelIndex levelGL, gl::LevelIndex baseLevel);

VkImageTiling GetTilingMode(gl::TilingMode tilingMode);

VkFormat GetAstcDecodeMode(const GLenum astcDecodePrecision);

VkImageCompressionFixedRateFlagsEXT ConvertEGLFixedRateToVkFixedRate(
    const EGLenum eglCompressionRate,
    const angle::FormatID actualFormatID);
}  // namespace gl_vk

namespace vk_gl
{
// The Vulkan back-end will not support a sample count of 1, because of a Vulkan specification
// restriction:
//
//   If the image was created with VkImageCreateInfo::samples equal to VK_SAMPLE_COUNT_1_BIT, the
//   instruction must: have MS = 0.
//
// This restriction was tracked in http://anglebug.com/42262827 and Khronos-private Vulkan
// specification issue https://gitlab.khronos.org/vulkan/vulkan/issues/1925.
//
// In addition, the Vulkan back-end will not support sample counts of 32 or 64, since there are no
// standard sample locations for those sample counts.
constexpr unsigned int kSupportedSampleCounts = (VK_SAMPLE_COUNT_2_BIT | VK_SAMPLE_COUNT_4_BIT |
                                                 VK_SAMPLE_COUNT_8_BIT | VK_SAMPLE_COUNT_16_BIT);

// Find set bits in sampleCounts and add the corresponding sample count to the set.
void AddSampleCounts(VkSampleCountFlags sampleCounts, gl::SupportedSampleSet *outSet);
// Return the maximum sample count with a bit set in |sampleCounts|.
GLuint GetMaxSampleCount(VkSampleCountFlags sampleCounts);
// Return a supported sample count that's at least as large as the requested one.
GLuint GetSampleCount(VkSampleCountFlags supportedCounts, GLuint requestedCount);

gl::LevelIndex GetLevelIndex(vk::LevelIndex levelVk, gl::LevelIndex baseLevel);

GLenum ConvertVkFixedRateToGLFixedRate(const VkImageCompressionFixedRateFlagsEXT vkCompressionRate);
GLint ConvertCompressionFlagsToGLFixedRates(
    VkImageCompressionFixedRateFlagsEXT imageCompressionFixedRateFlags,
    GLsizei bufSize,
    GLint *rates);

EGLenum ConvertVkFixedRateToEGLFixedRate(
    const VkImageCompressionFixedRateFlagsEXT vkCompressionRate);
std::vector<EGLint> ConvertCompressionFlagsToEGLFixedRate(
    VkImageCompressionFixedRateFlagsEXT imageCompressionFixedRateFlags,
    size_t rateSize);
}  // namespace vk_gl

enum class RenderPassClosureReason
{
    // Don't specify the reason (it should already be specified elsewhere)
    AlreadySpecifiedElsewhere,

    // Implicit closures due to flush/wait/etc.
    SurfaceUnMakeCurrent,

    // Closure due to switching rendering to another framebuffer.
    FramebufferBindingChange,
    FramebufferChange,
    NewRenderPass,

    // Incompatible use of resource in the same render pass
    BufferUseThenXfbWrite,
    XfbWriteThenVertexIndexBuffer,
    XfbWriteThenIndirectDrawBuffer,
    XfbResumeAfterDrawBasedClear,
    DepthStencilUseInFeedbackLoop,
    DepthStencilWriteAfterFeedbackLoop,
    PipelineBindWhileXfbActive,
    XfbWriteThenTextureBuffer,

    // Use of resource after render pass
    BufferWriteThenOutOfRPRead,
    BufferUseThenOutOfRPWrite,
    ImageUseThenOutOfRPRead,
    ImageUseThenOutOfRPWrite,
    XfbWriteThenUniformBufferRead,
    XfbWriteThenIndirectDispatchBuffer,
    ImageAttachmentThenComputeRead,
    GraphicsTextureImageAccessThenComputeAccess,
    BeginNonRenderPassQuery,
    EndNonRenderPassQuery,
    TimestampQuery,
    EndRenderPassQuery,

    // Synchronization
    BufferUseThenReleaseToExternal,
    ImageUseThenReleaseToExternal,
    GLMemoryBarrierThenStorageResource,
    StorageResourceUseThenGLMemoryBarrier,
    SyncObjectInit,
    SyncObjectClientWait,
    ForeignImageRelease,

    // Closures that ANGLE could have avoided, but doesn't for simplicity or optimization of more
    // common cases.
    XfbPause,
    FramebufferFetchEmulation,
    ColorBufferWithEmulatedAlphaInvalidate,
    GenerateMipmapOnCPU,
    CopyTextureOnCPU,
    TextureReformatToRenderable,
    OutOfReservedQueueSerialForOutsideCommands,

    // VK_QCOM_tile_memory_heap
    TileMemorySimulatedClear,

    // UtilsVk
    GenerateMipmapWithDraw,
    PrepareForBlit,
    PrepareForImageCopy,
    TemporaryForClearTexture,
    TemporaryForImageClear,
    TemporaryForImageCopy,
    TemporaryForMSRTTUnresolve,

    // LegacyDithering requires updating the render pass
    LegacyDithering,

    // Flushing and submitting the command buffer requires render pass closure.
    SubmitCommands,

    InvalidEnum,
    EnumCount = InvalidEnum,
};
std::ostream &operator<<(std::ostream &os, const RenderPassClosureReason reason);

enum class QueueSubmitReason
{
    // Flush/Finish/Wait
    EGLBindTexImage,
    EGLSwapBuffers,
    EGLWaitClient,
    GLFinish,
    GLFlush,
    GLReadPixels,

    // Context/Surface
    AcquireNextImage,
    ContextChange,
    ContextDestruction,
    ContextPriorityChange,
    SurfaceUnMakeCurrent,

    // Buffer/Image
    CopyBufferToImageOneOff,
    CopyBufferToSurfaceImage,
    CopySurfaceImageToBuffer,
    ForeignImageRelease,
    ImageUseThenReleaseToExternal,
    InitializeMemory,
    TextureReformatToRenderable,
    CopyTextureOnCPU,
    GenerateMipmapOnCPU,

    // Sync/Query/Timestamp
    ExternalSemaphoreSignal,
    GetQueryResult,
    GetTimestamp,
    SyncCPUGPUTime,
    SyncObjectInit,
    SyncObjectClientWait,
    SyncObjectWithFdInit,
    DeviceLocalBufferMap,
    BufferWriteThenMap,
    BufferInUseWhenSynchronizedMap,
    WaitSemaphore,

    // In case of memory budget issues, pending garbage needs to be freed.
    ExcessivePendingGarbage,
    OutOfMemory,

    // In case of reaching the render pass limit in the command buffer, it should be submitted.
    RenderPassCountLimitReached,
    RenderPassCommandLimitReached,

    // Outside command buffer submission
    BufferToImageUpdateLimitReached,
    ForceSubmitStagedTexture,

    // Others
    DeferredFlush,
    TileMemoryFallback,

    InvalidEnum,
    EnumCount = InvalidEnum,
};
std::ostream &operator<<(std::ostream &os, const QueueSubmitReason reason);

// The scope of synchronization for a sync object.  Synchronization is done between the signal
// entity (src) and the entities waiting on the signal (dst)
//
// - For GL fence sync objects, src is the current context and dst is host / the rest of share
// group.
// - For EGL fence sync objects, src is the current context and dst is host / all other contexts.
// - For EGL global fence sync objects (which is an ANGLE extension), src is all contexts who have
//   previously made a submission to the queue used by the current context and dst is host / all
//   other contexts.
enum class SyncFenceScope
{
    CurrentContextToShareGroup,
    CurrentContextToAllContexts,
    AllContextsToAllContexts,
};

}  // namespace rx

#define ANGLE_VK_TRY(context, command)                                                   \
    do                                                                                   \
    {                                                                                    \
        auto ANGLE_LOCAL_VAR = command;                                                  \
        if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS))                               \
        {                                                                                \
            (context)->handleError(ANGLE_LOCAL_VAR, __FILE__, ANGLE_FUNCTION, __LINE__); \
            return angle::Result::Stop;                                                  \
        }                                                                                \
    } while (0)

#define ANGLE_VK_CHECK(context, test, error) ANGLE_VK_TRY(context, test ? VK_SUCCESS : error)

#define ANGLE_VK_CHECK_MATH(context, result) \
    ANGLE_VK_CHECK(context, result, VK_ERROR_VALIDATION_FAILED_EXT)

#define ANGLE_VK_CHECK_ALLOC(context, result) \
    ANGLE_VK_CHECK(context, result, VK_ERROR_OUT_OF_HOST_MEMORY)

#define ANGLE_VK_UNREACHABLE(context) \
    UNREACHABLE();                    \
    ANGLE_VK_CHECK(context, false, VK_ERROR_FEATURE_NOT_PRESENT)

// Returns VkResult in the case of an error.
#define VK_RESULT_TRY(command)                             \
    do                                                     \
    {                                                      \
        auto ANGLE_LOCAL_VAR = command;                    \
        if (ANGLE_UNLIKELY(ANGLE_LOCAL_VAR != VK_SUCCESS)) \
        {                                                  \
            return ANGLE_LOCAL_VAR;                        \
        }                                                  \
    } while (0)

#define VK_RESULT_CHECK(test, error) VK_RESULT_TRY((test) ? VK_SUCCESS : (error))

// NVIDIA uses special formatting for the driver version:
// Major: 10
// Minor: 8
// Sub-minor: 8
// patch: 6
#define ANGLE_VK_VERSION_MAJOR_NVIDIA(version) (((uint32_t)(version) >> 22) & 0x3ff)
#define ANGLE_VK_VERSION_MINOR_NVIDIA(version) (((uint32_t)(version) >> 14) & 0xff)
#define ANGLE_VK_VERSION_SUB_MINOR_NVIDIA(version) (((uint32_t)(version) >> 6) & 0xff)
#define ANGLE_VK_VERSION_PATCH_NVIDIA(version) ((uint32_t)(version) & 0x3f)

// Similarly for Intel on Windows:
// Major: 18
// Minor: 14
#define ANGLE_VK_VERSION_MAJOR_WIN_INTEL(version) (((uint32_t)(version) >> 14) & 0x3ffff)
#define ANGLE_VK_VERSION_MINOR_WIN_INTEL(version) ((uint32_t)(version) & 0x3fff)

#endif  // LIBANGLE_RENDERER_VULKAN_VK_UTILS_H_
