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

#ifndef GrVkMemory_DEFINED
#define GrVkMemory_DEFINED

#include "GrVkBuffer.h"
#include "SkTArray.h"
#include "SkTLList.h"
#include "vk/GrVkDefines.h"
#include "vk/GrVkTypes.h"

class GrVkGpu;

namespace GrVkMemory {
    /**
    * Allocates vulkan device memory and binds it to the gpu's device for the given object.
    * Returns true if allocation succeeded.
    */
    bool AllocAndBindBufferMemory(const GrVkGpu* gpu,
                                  VkBuffer buffer,
                                  GrVkBuffer::Type type,
                                  bool dynamic,
                                  GrVkAlloc* alloc);
    void FreeBufferMemory(const GrVkGpu* gpu, GrVkBuffer::Type type, const GrVkAlloc& alloc);

    bool AllocAndBindImageMemory(const GrVkGpu* gpu,
                                 VkImage image,
                                 bool linearTiling,
                                 GrVkAlloc* alloc);
    void FreeImageMemory(const GrVkGpu* gpu, bool linearTiling, const GrVkAlloc& alloc);

    VkPipelineStageFlags LayoutToPipelineStageFlags(const VkImageLayout layout);

    VkAccessFlags LayoutToSrcAccessMask(const VkImageLayout layout);

    void FlushMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, VkDeviceSize offset,
                          VkDeviceSize size);
    void InvalidateMappedAlloc(const GrVkGpu* gpu, const GrVkAlloc& alloc, VkDeviceSize offset,
                               VkDeviceSize size);
}

class GrVkFreeListAlloc {
public:
    GrVkFreeListAlloc(VkDeviceSize size, VkDeviceSize alignment)
        : fSize(size)
        , fAlignment(alignment)
        , fFreeSize(size)
        , fLargestBlockSize(size)
        , fLargestBlockOffset(0) {
        Block* block = fFreeList.addToTail();
        block->fOffset = 0;
        block->fSize = fSize;
    }
    ~GrVkFreeListAlloc() {
        this->reset();
    }

    VkDeviceSize size() const { return fSize; }
    VkDeviceSize alignment() const { return fAlignment; }
    VkDeviceSize freeSize() const { return fFreeSize; }
    VkDeviceSize largestBlockSize() const { return fLargestBlockSize; }

    bool unallocated() const { return fSize == fFreeSize; }

protected:
    bool alloc(VkDeviceSize requestedSize, VkDeviceSize* allocOffset, VkDeviceSize* allocSize);
    void free(VkDeviceSize allocOffset, VkDeviceSize allocSize);

    void reset() {
        fSize = 0;
        fAlignment = 0;
        fFreeSize = 0;
        fLargestBlockSize = 0;
        fFreeList.reset();
    }

    struct Block {
        VkDeviceSize fOffset;
        VkDeviceSize fSize;
    };
    typedef SkTLList<Block, 16> FreeList;

    VkDeviceSize   fSize;
    VkDeviceSize   fAlignment;
    VkDeviceSize   fFreeSize;
    VkDeviceSize   fLargestBlockSize;
    VkDeviceSize   fLargestBlockOffset;
    FreeList       fFreeList;
};

class GrVkSubHeap : public GrVkFreeListAlloc {
public:
    GrVkSubHeap(const GrVkGpu* gpu, uint32_t memoryTypeIndex, uint32_t heapIndex,
                VkDeviceSize size, VkDeviceSize alignment);
    ~GrVkSubHeap();

    uint32_t memoryTypeIndex() const { return fMemoryTypeIndex; }
    VkDeviceMemory memory() { return fAlloc; }

    bool alloc(VkDeviceSize requestedSize, GrVkAlloc* alloc);
    void free(const GrVkAlloc& alloc);

private:
    const GrVkGpu* fGpu;
#ifdef SK_DEBUG
    uint32_t       fHeapIndex;
#endif
    uint32_t       fMemoryTypeIndex;
    VkDeviceMemory fAlloc;

    typedef GrVkFreeListAlloc INHERITED;
};

class GrVkHeap {
public:
    enum Strategy {
        kSubAlloc_Strategy,       // alloc large subheaps and suballoc within them
        kSingleAlloc_Strategy     // alloc/recycle an individual subheap per object
    };

    GrVkHeap(const GrVkGpu* gpu, Strategy strategy, VkDeviceSize subHeapSize)
        : fGpu(gpu)
        , fSubHeapSize(subHeapSize)
        , fAllocSize(0)
        , fUsedSize(0) {
        if (strategy == kSubAlloc_Strategy) {
            fAllocFunc = &GrVkHeap::subAlloc;
        } else {
            fAllocFunc = &GrVkHeap::singleAlloc;
        }
    }

    ~GrVkHeap() {}

    VkDeviceSize allocSize() const { return fAllocSize; }
    VkDeviceSize usedSize() const { return fUsedSize; }

    bool alloc(VkDeviceSize size, VkDeviceSize alignment, uint32_t memoryTypeIndex,
               uint32_t heapIndex, GrVkAlloc* alloc) {
        SkASSERT(size > 0);
        alloc->fUsesSystemHeap = false;
        return (*this.*fAllocFunc)(size, alignment, memoryTypeIndex, heapIndex, alloc);
    }
    bool free(const GrVkAlloc& alloc);

private:
    typedef bool (GrVkHeap::*AllocFunc)(VkDeviceSize size, VkDeviceSize alignment,
                                        uint32_t memoryTypeIndex, uint32_t heapIndex,
                                        GrVkAlloc* alloc);

    bool subAlloc(VkDeviceSize size, VkDeviceSize alignment,
                  uint32_t memoryTypeIndex, uint32_t heapIndex,
                  GrVkAlloc* alloc);
    bool singleAlloc(VkDeviceSize size, VkDeviceSize alignment,
                     uint32_t memoryTypeIndex, uint32_t heapIndex,
                     GrVkAlloc* alloc);

    const GrVkGpu*         fGpu;
    VkDeviceSize           fSubHeapSize;
    VkDeviceSize           fAllocSize;
    VkDeviceSize           fUsedSize;
    AllocFunc              fAllocFunc;
    SkTArray<std::unique_ptr<GrVkSubHeap>> fSubHeaps;
};
#endif