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

////////////////////////////////////////////////////////////////////////////////////
//	Vulkan Image Buffer

#include "VulkanImageBuffer.h"

namespace Vulkan {

ImageBuffer::ImageBuffer()
    : m_owner(nullptr)
    , m_width(0)
    , m_height(0)
    , m_usage(0)
    , m_format(VK_FORMAT_R8G8B8A8_UNORM)
    , m_aspectMask(VK_IMAGE_ASPECT_COLOR_BIT)
    , m_mipLevels(0)
    , m_samples(VK_SAMPLE_COUNT_1_BIT)
    , m_view(nullptr)
    , m_image(nullptr)
    , m_memory(nullptr)
{
}

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

// virtual
void ImageBuffer::Destroy()
{
    if (m_view)
        vkDestroyImageView(m_owner->m_device, m_view, m_owner->m_allocator);
    if (m_image)
        vkDestroyImage(m_owner->m_device, m_image, m_owner->m_allocator);
    if (m_memory)
        vkFreeMemory(m_owner->m_device, m_memory, m_owner->m_allocator);
    m_view = nullptr;
    m_image = nullptr;
    m_memory = nullptr;
}

// virtual
void ImageBuffer::Initialize(Device*               device,
                             uint32_t              width,
                             uint32_t              height,
                             VkImageUsageFlags     usage,
                             VkImageAspectFlags    aspectMask,
                             VkFormat              imageFormat,
                             uint32_t              mipLevels,
                             VkSampleCountFlagBits samples)
{
    m_owner      = device;
    m_width      = width;
    m_height     = height;
    m_usage      = usage;
    m_aspectMask = aspectMask;
    m_format     = imageFormat;
    m_mipLevels  = mipLevels;
    m_samples    = samples;

    CreateImage();
    CreateView();
}

// virtual
void ImageBuffer::CreateImage()
{
    //auto usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    // Create a handle for the image
    VkImageCreateInfo imageInfo {};
    imageInfo.sType         = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType     = VK_IMAGE_TYPE_2D;
    imageInfo.extent.width  = m_width;
    imageInfo.extent.height = m_height;
    imageInfo.extent.depth  = 1;
    imageInfo.mipLevels     = m_mipLevels;
    imageInfo.arrayLayers   = 1;
    imageInfo.format        = m_format;
    imageInfo.tiling        = VK_IMAGE_TILING_OPTIMAL; // tiling;
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    imageInfo.usage         = m_usage;
    imageInfo.samples       = m_samples;
    imageInfo.sharingMode   = VK_SHARING_MODE_EXCLUSIVE;
    m_owner->CheckResult(vkCreateImage(m_owner->m_device, &imageInfo, m_owner->m_allocator, &m_image), "failed to create image!");

    // Allocate the memory for the image
    VkMemoryRequirements memoryRequirements {};
    vkGetImageMemoryRequirements(m_owner->m_device, m_image, &memoryRequirements);
    VkMemoryAllocateInfo allocInfo {};
    allocInfo.sType           = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize  = memoryRequirements.size;
    allocInfo.memoryTypeIndex = m_owner->FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    m_owner->CheckResult(vkAllocateMemory(m_owner->m_device, &allocInfo, m_owner->m_allocator, &m_memory), "failed to allocate image memory!");

    // Bind it
    m_owner->CheckResult(vkBindImageMemory(m_owner->m_device, m_image, m_memory, 0), "failed to bind image memory!");
}

// virtual
void ImageBuffer::CreateMipmaps()
{
    // Check if image format supports linear blitting
    VkFormatProperties formatProperties;
    vkGetPhysicalDeviceFormatProperties(m_owner->m_physicalDevice, m_format, &formatProperties);

    if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT))
        m_owner->CheckResult(VK_ERROR_UNKNOWN, "texture image format does not support linear blitting!");

    auto commandBuffer = m_owner->BeginSingleTimeCommands();

    VkImageMemoryBarrier barrier {};
    barrier.sType                           = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.image                           = m_image;
    barrier.srcQueueFamilyIndex             = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex             = VK_QUEUE_FAMILY_IGNORED;
    barrier.subresourceRange.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount     = 1;
    barrier.subresourceRange.levelCount     = 1;

    int32_t mipWidth  = m_width;
    int32_t mipHeight = m_height;

    for (uint32_t i = 1; i < m_mipLevels; i++)
    {
        barrier.subresourceRange.baseMipLevel = i - 1;
        barrier.oldLayout                     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        barrier.newLayout                     = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        barrier.srcAccessMask                 = VK_ACCESS_TRANSFER_WRITE_BIT;
        barrier.dstAccessMask                 = VK_ACCESS_TRANSFER_READ_BIT;

        vkCmdPipelineBarrier(commandBuffer.m_commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);

        VkImageBlit blit {};
        blit.srcOffsets[0]                 = { 0, 0, 0 };
        blit.srcOffsets[1]                 = { mipWidth, mipHeight, 1 };
        blit.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
        blit.srcSubresource.mipLevel       = i - 1;
        blit.srcSubresource.baseArrayLayer = 0;
        blit.srcSubresource.layerCount     = 1;
        blit.dstOffsets[0]                 = { 0, 0, 0 };
        blit.dstOffsets[1]                 = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
        blit.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
        blit.dstSubresource.mipLevel       = i;
        blit.dstSubresource.baseArrayLayer = 0;
        blit.dstSubresource.layerCount     = 1;

        vkCmdBlitImage(commandBuffer.m_commandBuffer, m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);

        barrier.oldLayout     = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        barrier.newLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

        vkCmdPipelineBarrier(commandBuffer.m_commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);

        if (mipWidth > 1)
            mipWidth /= 2;
        if (mipHeight > 1)
            mipHeight /= 2;
    }

    barrier.subresourceRange.baseMipLevel = m_mipLevels - 1;
    barrier.oldLayout                     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrier.newLayout                     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    barrier.srcAccessMask                 = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask                 = VK_ACCESS_SHADER_READ_BIT;

    vkCmdPipelineBarrier(commandBuffer.m_commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}

// virtual
void ImageBuffer::CreateView()
{
    // Create a view to the image
    VkImageViewCreateInfo viewInfo {};
    viewInfo.sType                           = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    viewInfo.image                           = m_image;
    viewInfo.viewType                        = VK_IMAGE_VIEW_TYPE_2D;
    viewInfo.format                          = m_format;
    viewInfo.subresourceRange.aspectMask     = m_aspectMask;
    viewInfo.subresourceRange.baseMipLevel   = 0;
    viewInfo.subresourceRange.levelCount     = m_mipLevels;
    viewInfo.subresourceRange.baseArrayLayer = 0;
    viewInfo.subresourceRange.layerCount     = 1;
    m_owner->CheckResult(vkCreateImageView(m_owner->m_device, &viewInfo, m_owner->m_allocator, &m_view), "failed to create texture image view!");
}

} // Vulkan namespace
