/*
	VulkanFramework
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Vulkan Buffer

#include "VulkanBuffer.h"
#include <cstring>

namespace Vulkan {

Buffer::Buffer()
    : m_buffer(nullptr)
    , m_memory(nullptr)
{
}

// virtual
Buffer::~Buffer()
{
    Destroy();
}

// virtual
void Buffer::Initialize(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
{
    m_owner      = device;
    m_size       = size;
    m_usage      = usage;
    m_properties = properties;
    Create();
}

// virtual
void Buffer::Create()
{
    size_t BufferMemoryAlignment = 256;
    VkDeviceSize vertex_buffer_size_aligned = ((m_size - 1) / BufferMemoryAlignment + 1) * BufferMemoryAlignment;
    VkBufferCreateInfo buffer_info = {};
    buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    buffer_info.size = vertex_buffer_size_aligned;
    buffer_info.usage = m_usage;
    buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    m_owner->CheckResult(vkCreateBuffer(m_owner->m_device, &buffer_info, m_owner->m_allocator, &m_buffer));

    VkMemoryRequirements req;
    vkGetBufferMemoryRequirements(m_owner->m_device, m_buffer, &req);
    //BufferMemoryAlignment = (BufferMemoryAlignment > req.alignment) ? BufferMemoryAlignment : req.alignment;
    VkMemoryAllocateInfo alloc_info = {};
    alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    alloc_info.allocationSize = req.size;
    alloc_info.memoryTypeIndex = m_owner->FindMemoryType(req.memoryTypeBits, m_properties);

    m_owner->CheckResult(vkAllocateMemory(m_owner->m_device, &alloc_info, m_owner->m_allocator, &m_memory));

    m_owner->CheckResult(vkBindBufferMemory(m_owner->m_device, m_buffer, m_memory, 0));
    m_size = req.size;
}

// virtual
void Buffer::Destroy()
{
    if (m_buffer)
        vkDestroyBuffer(m_owner->m_device, m_buffer, m_owner->m_allocator);
    if (m_memory)
        vkFreeMemory(m_owner->m_device, m_memory, m_owner->m_allocator);
    m_buffer = nullptr;
    m_memory = nullptr;
    m_size = 0;
}

// virtual
void Buffer::Resize(size_t size)
{
    m_size = size;
    Destroy();
    Create();
}

// virtual
void Buffer::UploadDataUsingStagingBuffer(const uint8_t* data)
{
    Vulkan::Buffer staging;
    staging.Initialize(m_owner, m_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
    staging.UploadData(m_size, data);
    {
        auto commandBuffer = m_owner->BeginSingleTimeCommands();
        VkBufferCopy copyRegion {};
        copyRegion.size = m_size;
        vkCmdCopyBuffer(commandBuffer.m_commandBuffer, staging.m_buffer, m_buffer, 1, &copyRegion);
    }
    staging.Destroy();
}

// virtual
void Buffer::UploadData(size_t size, std::function<void(void*)> callback)
{
    if (!m_buffer || m_size < size)
        Resize(size);
    void* ptr;
    m_owner->CheckResult(vkMapMemory(m_owner->m_device, m_memory, 0, m_size, 0, (void**)(&ptr)));
    callback(ptr);
    VkMappedMemoryRange range{ VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0, m_memory, 0, VK_WHOLE_SIZE };
    m_owner->CheckResult(vkFlushMappedMemoryRanges(m_owner->m_device, 1, &range));
    vkUnmapMemory(m_owner->m_device, m_memory);
}

// virtual
void Buffer::UploadData(size_t size, const uint8_t* data)
{
    UploadData(size, [size, data](void* dst){ memcpy(dst, data, size); });
}

} // Vulkan namespace
