//
// Copyright 2018 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_helpers:
//   Helper utilitiy classes that manage Vulkan resources.

#ifndef LIBANGLE_RENDERER_VULKAN_VK_HELPERS_H_
#define LIBANGLE_RENDERER_VULKAN_VK_HELPERS_H_

#include "libANGLE/renderer/vulkan/CommandGraph.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"

namespace gl
{
class ImageIndex;
}  // namespace gl;

namespace rx
{
namespace vk
{
// A dynamic buffer is conceptually an infinitely long buffer. Each time you write to the buffer,
// you will always write to a previously unused portion. After a series of writes, you must flush
// the buffer data to the device. Buffer lifetime currently assumes that each new allocation will
// last as long or longer than each prior allocation.
//
// Dynamic buffers are used to implement a variety of data streaming operations in Vulkan, such
// as for immediate vertex array and element array data, uniform updates, and other dynamic data.
class DynamicBuffer : angle::NonCopyable
{
  public:
    DynamicBuffer(VkBufferUsageFlags usage, size_t minSize);
    ~DynamicBuffer();

    // Init is called after the buffer creation so that the alignment can be specified later.
    void init(size_t alignment, RendererVk *renderer);

    // This call will allocate a new region at the end of the buffer. It internally may trigger
    // a new buffer to be created (which is returned in 'newBufferAllocatedOut'. This param may
    // be nullptr.
    angle::Result allocate(Context *context,
                           size_t sizeInBytes,
                           uint8_t **ptrOut,
                           VkBuffer *handleOut,
                           uint32_t *offsetOut,
                           bool *newBufferAllocatedOut);

    // After a sequence of writes, call flush to ensure the data is visible to the device.
    angle::Result flush(Context *context);

    // After a sequence of writes, call invalidate to ensure the data is visible to the host.
    angle::Result invalidate(Context *context);

    // This releases resources when they might currently be in use.
    void release(RendererVk *renderer);

    // This releases all the buffers that have been allocated since this was last called.
    void releaseRetainedBuffers(RendererVk *renderer);

    // This frees resources immediately.
    void destroy(VkDevice device);

    VkBuffer getCurrentBufferHandle() const;

    // For testing only!
    void setMinimumSizeForTesting(size_t minSize);

  private:
    void unmap(VkDevice device);
    void reset();

    VkBufferUsageFlags mUsage;
    size_t mMinSize;
    Buffer mBuffer;
    DeviceMemory mMemory;
    uint32_t mNextAllocationOffset;
    uint32_t mLastFlushOrInvalidateOffset;
    size_t mSize;
    size_t mAlignment;
    uint8_t *mMappedMemory;

    std::vector<BufferAndMemory> mRetainedBuffers;
};

// Uses DescriptorPool to allocate descriptor sets as needed. If a descriptor pool becomes full, we
// allocate new pools internally as needed. RendererVk takes care of the lifetime of the discarded
// pools. Note that we used a fixed layout for descriptor pools in ANGLE. Uniform buffers must
// use set zero and combined Image Samplers must use set 1. We conservatively count each new set
// using the maximum number of descriptor sets and buffers with each allocation. Currently: 2
// (Vertex/Fragment) uniform buffers and 64 (MAX_ACTIVE_TEXTURES) image/samplers.

// This is an arbitrary max. We can change this later if necessary.
constexpr uint32_t kDefaultDescriptorPoolMaxSets = 2048;

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

    // The DynamicDescriptorPool only handles one pool size at at time.
    angle::Result init(Context *context, const VkDescriptorPoolSize &poolSize);
    void destroy(VkDevice device);

    // We use the descriptor type to help count the number of free sets.
    // By convention, sets are indexed according to the constants in vk_cache_utils.h.
    angle::Result allocateSets(Context *context,
                               const VkDescriptorSetLayout *descriptorSetLayout,
                               uint32_t descriptorSetCount,
                               VkDescriptorSet *descriptorSetsOut);

    // For testing only!
    void setMaxSetsPerPoolForTesting(uint32_t maxSetsPerPool);

  private:
    angle::Result allocateNewPool(Context *context);

    uint32_t mMaxSetsPerPool;
    uint32_t mCurrentSetsCount;
    DescriptorPool mCurrentDescriptorPool;
    VkDescriptorPoolSize mPoolSize;
    uint32_t mFreeDescriptorSets;
};

// This class' responsibility is to create index buffers needed to support line loops in Vulkan.
// In the setup phase of drawing, the createIndexBuffer method should be called with the
// current draw call parameters. If an element array buffer is bound for an indexed draw, use
// createIndexBufferFromElementArrayBuffer.
//
// If the user wants to draw a loop between [v1, v2, v3], we will create an indexed buffer with
// these indexes: [0, 1, 2, 3, 0] to emulate the loop.
class LineLoopHelper final : public vk::CommandGraphResource
{
  public:
    LineLoopHelper(RendererVk *renderer);
    ~LineLoopHelper();

    angle::Result getIndexBufferForDrawArrays(Context *context,
                                              const gl::DrawCallParams &drawCallParams,
                                              VkBuffer *bufferHandleOut,
                                              VkDeviceSize *offsetOut);
    angle::Result getIndexBufferForElementArrayBuffer(Context *context,
                                                      BufferVk *elementArrayBufferVk,
                                                      VkIndexType indexType,
                                                      int indexCount,
                                                      intptr_t elementArrayOffset,
                                                      VkBuffer *bufferHandleOut,
                                                      VkDeviceSize *bufferOffsetOut);
    angle::Result getIndexBufferForClientElementArray(Context *context,
                                                      const gl::DrawCallParams &drawCallParams,
                                                      VkBuffer *bufferHandleOut,
                                                      VkDeviceSize *bufferOffsetOut);

    void destroy(VkDevice device);

    static void Draw(uint32_t count, CommandBuffer *commandBuffer);

  private:
    DynamicBuffer mDynamicIndexBuffer;
};

class ImageHelper final : angle::NonCopyable
{
  public:
    ImageHelper();
    ImageHelper(ImageHelper &&other);
    ~ImageHelper();

    bool valid() const;

    angle::Result init(Context *context,
                       gl::TextureType textureType,
                       const gl::Extents &extents,
                       const Format &format,
                       GLint samples,
                       VkImageUsageFlags usage,
                       uint32_t mipLevels);
    angle::Result initMemory(Context *context,
                             const MemoryProperties &memoryProperties,
                             VkMemoryPropertyFlags flags);
    angle::Result initImageView(Context *context,
                                gl::TextureType textureType,
                                VkImageAspectFlags aspectMask,
                                const gl::SwizzleState &swizzleMap,
                                ImageView *imageViewOut,
                                uint32_t levelCount);
    angle::Result init2DStaging(Context *context,
                                const MemoryProperties &memoryProperties,
                                const Format &format,
                                const gl::Extents &extent,
                                StagingUsage usage);

    VkImageAspectFlags getAspectFlags() const;
    void release(Serial serial, RendererVk *renderer);
    void destroy(VkDevice device);
    void dumpResources(Serial serial, std::vector<GarbageObject> *garbageQueue);

    void init2DWeakReference(VkImage handle,
                             const gl::Extents &extents,
                             const Format &format,
                             GLint samples);
    void resetImageWeakReference();

    const Image &getImage() const;
    const DeviceMemory &getDeviceMemory() const;

    const gl::Extents &getExtents() const;
    const Format &getFormat() const;
    GLint getSamples() const;

    VkImageLayout getCurrentLayout() const { return mCurrentLayout; }
    void updateLayout(VkImageLayout layout) { mCurrentLayout = layout; }

    void changeLayoutWithStages(VkImageAspectFlags aspectMask,
                                VkImageLayout newLayout,
                                VkPipelineStageFlags srcStageMask,
                                VkPipelineStageFlags dstStageMask,
                                CommandBuffer *commandBuffer);

    void clearColor(const VkClearColorValue &color,
                    uint32_t mipLevel,
                    uint32_t levelCount,
                    CommandBuffer *commandBuffer);

    void clearDepthStencil(VkImageAspectFlags aspectFlags,
                           const VkClearDepthStencilValue &depthStencil,
                           CommandBuffer *commandBuffer);
    gl::Extents getSize(const gl::ImageIndex &index) const;

    static void Copy(ImageHelper *srcImage,
                     ImageHelper *dstImage,
                     const gl::Offset &srcOffset,
                     const gl::Offset &dstOffset,
                     const gl::Extents &copySize,
                     VkImageAspectFlags aspectMask,
                     CommandBuffer *commandBuffer);

  private:
    // Vulkan objects.
    Image mImage;
    DeviceMemory mDeviceMemory;

    // Image properties.
    gl::Extents mExtents;
    const Format *mFormat;
    GLint mSamples;

    // Current state.
    VkImageLayout mCurrentLayout;

    // Cached properties.
    uint32_t mLayerCount;
};
}  // namespace vk
}  // namespace rx

#endif  // LIBANGLE_RENDERER_VULKAN_VK_HELPERS_H_
