/*
Magma - Abstraction layer over Khronos Vulkan API.
Copyright (C) 2018-2023 Victor Coda.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pch.h"
#pragma hdrstop
#include "multiAttachmentFramebuffer.h"
#include "../objects/device.h"
#include "../objects/imageAttachment.h"
#include "../objects/imageView.h"
#include "../objects/renderPass.h"
#include "../objects/framebuffer.h"
#include "../allocator/allocator.h"

namespace magma::aux
{
MultiAttachmentFramebuffer::MultiAttachmentFramebuffer(std::shared_ptr<Device> device,
    const std::initializer_list<VkFormat>& colorAttachmentFormats,
    const VkFormat depthStencilFormat, const VkExtent2D& extent, bool depthSampled,
    std::shared_ptr<Allocator> allocator /* nullptr */,
    const bool colorClearOp /* true */,
    const bool depthStencilClearOp /* true */,
    const std::vector<VkComponentMapping>& swizzles /* {} */):
    Framebuffer(*colorAttachmentFormats.begin(), depthStencilFormat, 1),
    colorClearOp(colorClearOp),
    depthStencilClearOp(depthStencilClearOp)
{   // Let it know what view format will be paired with the image
    Image::Initializer imageFormatList;
    imageFormatList.viewFormats.push_back(VK_FORMAT_UNDEFINED);
    uint32_t index = 0;
    for (const VkFormat colorFormat: colorAttachmentFormats)
    {   // Create color attachment
        imageFormatList.viewFormats.back() = colorFormat;
        constexpr bool colorSampled = true;
        std::unique_ptr<Image> color = std::make_unique<ColorAttachment>(device, colorFormat, extent, 1, 1, colorSampled,
            allocator, false, imageFormatList);
        // Create color view
        constexpr VkComponentMapping dontSwizzle = {
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY};
        attachmentViews.push_back(std::make_shared<UniqueImageView>(std::move(color),
            swizzles.empty() ? dontSwizzle : swizzles[index++]));
    }
    if (depthStencilFormat != VK_FORMAT_UNDEFINED)
    {   // Create depth/stencil attachment
        imageFormatList.viewFormats.back() = depthStencilFormat;
        std::unique_ptr<Image> depthStencil = std::make_unique<DepthStencilAttachment>(device, depthStencilFormat,
            extent, 1, 1, depthSampled, allocator, false, imageFormatList);
        // Create depth/stencil view
        attachmentViews.push_back(std::make_shared<UniqueImageView>(std::move(depthStencil)));
    }
    // Setup attachment descriptions
    std::vector<AttachmentDescription> attachmentDescriptions;
    for (const VkFormat format : colorAttachmentFormats)
    {
        attachmentDescriptions.emplace_back(format, 1,
            colorClearOp ? op::clearStore : op::store, // Clear (optionally) color, store
            op::dontCare, // Stencil not applicable
            VK_IMAGE_LAYOUT_UNDEFINED, // Don't care
            VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); // Color image will be transitioned to when a render pass instance ends
    }
    if (depthStencilFormat != VK_FORMAT_UNDEFINED)
    {   // Choose optimal depth/stencil layout
        const VkImageLayout finalLayout = optimalDepthStencilLayout(device, depthStencilFormat, depthSampled);
        attachmentDescriptions.emplace_back(depthStencilFormat, 1,
            depthStencilClearOp ? op::clearStore : op::loadStore, // Clear depth or preserve from a separate depth pass
            hasStencil() ? (depthStencilClearOp ? op::clearStore : op::loadStore) // Clear stencil or preserve from a separate depth pass
                         : op::dontCare, // No stencil aspect
            VK_IMAGE_LAYOUT_UNDEFINED, // Don't care
            finalLayout); // Depth image will be transitioned to when a render pass instance ends
    }
    // Create color/depth framebuffer
    renderPass = std::make_shared<RenderPass>(device, attachmentDescriptions, MAGMA_HOST_ALLOCATOR(allocator));
    framebuffer = std::make_shared<magma::Framebuffer>(renderPass, attachmentViews, MAGMA_HOST_ALLOCATOR(allocator), 0);
}

const std::shared_ptr<magma::Framebuffer>& MultiAttachmentFramebuffer::getDepthFramebuffer()
{
    lazyDepthRenderPass();
    return depthFramebuffer;
}

const std::shared_ptr<RenderPass>& MultiAttachmentFramebuffer::lazyDepthRenderPass() const
{
    if (!depthRenderPass && (depthStencilFormat != VK_FORMAT_UNDEFINED))
    {   // Lazy initialization of depth-only render pass
        AttachmentDescription depthAttachment(depthStencilFormat, 1,
            op::clearStore, // Clear depth, store
            hasStencil() ? op::clearStore : op::dontCare, // Clear stencil, store
            VK_IMAGE_LAYOUT_UNDEFINED, // Don't care
            VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); // Stay as attachment when a depth pass instance ends
        depthRenderPass = std::make_shared<RenderPass>(renderPass->getDevice(), depthAttachment, renderPass->getHostAllocator());
        depthFramebuffer = std::make_shared<magma::Framebuffer>(depthRenderPass, attachmentViews.back(), framebuffer->getHostAllocator(), 0);
    }
    return depthRenderPass;
}
} // namespace magma::aux
