/**
 *
 * @file src/render/vulkan/Vk_pipeline.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Lucas Guedon
 *
 * @date 2024-07-17
 */
#include "render/vulkan/Vk_pipeline.hpp"

/*!
 * \brief Computes the length of an array allocated on the stack
 */
#define LENGTH(tab) (sizeof(tab) / sizeof(*(tab)))

Vk_pipeline::Vk_pipeline() { }

Vk_pipeline::~Vk_pipeline() {
    free_pipeline();
}

void Vk_pipeline::init(VkDevice dev, QVulkanDeviceFunctions *dev_funcs, const QVulkanWindow *window,
                       const VkShaderModule &vert_shader, const VkShaderModule &frag_shader,
                       const VkPipelineCache &cache, const VkPipelineLayout &pipeline_layout,
                       const VkPipelineVertexInputStateCreateInfo *vertex_input, VkPrimitiveTopology topology) {
    _dev = dev;
    _dev_funcs = dev_funcs;

    // Graphics pipeline
    VkGraphicsPipelineCreateInfo pipeline_info;
    memset(&pipeline_info, 0, sizeof(pipeline_info));
    pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    // Set initialize parameter for the shader pipeline (for vertex shader and fragment shader)
    VkPipelineShaderStageCreateInfo shader_stages[2] = {
        { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .stage = VK_SHADER_STAGE_VERTEX_BIT,
          .module = vert_shader,
          .pName = "main",
          .pSpecializationInfo = nullptr },
        { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
          .module = frag_shader,
          .pName = "main",
          .pSpecializationInfo = nullptr }
    };
    pipeline_info.stageCount = 2;
    pipeline_info.pStages = shader_stages;
    pipeline_info.pVertexInputState = vertex_input;
    VkPipelineInputAssemblyStateCreateInfo input_assembly = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
        .topology = topology
    };
    pipeline_info.pInputAssemblyState = &input_assembly;

    // The viewport and scissor will be set dynamically via vkCmdSetViewport/Scissor.
    // This way the pipeline does not need to be touched when resizing the window.
    VkPipelineViewportStateCreateInfo viewport_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
        .viewportCount = 1,
        .scissorCount = 1
    };
    pipeline_info.pViewportState = &viewport_info;
    VkPipelineRasterizationStateCreateInfo rasterization_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
        .depthClampEnable = VK_FALSE,
        .rasterizerDiscardEnable = VK_FALSE,
        .polygonMode = VK_POLYGON_MODE_FILL,
        .cullMode = VK_CULL_MODE_NONE, // we want to render the front and back face
        .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
        .depthBiasEnable = VK_FALSE,
        .lineWidth = 1.0f
    };
    pipeline_info.pRasterizationState = &rasterization_info;
    VkPipelineMultisampleStateCreateInfo multisampe_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
        .rasterizationSamples = window->sampleCountFlagBits()
    };
    pipeline_info.pMultisampleState = &multisampe_info;
    VkPipelineDepthStencilStateCreateInfo depth_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
        .depthTestEnable = VK_TRUE,
        .depthWriteEnable = VK_TRUE,
        .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL
    };
    pipeline_info.pDepthStencilState = &depth_info;
    // no blend, write out all of rgba
    VkPipelineColorBlendAttachmentState attachment = {
        .colorWriteMask = 0xF,
    };
    VkPipelineColorBlendStateCreateInfo color_blending_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
        .attachmentCount = 1,
        .pAttachments = &attachment
    };
    pipeline_info.pColorBlendState = &color_blending_info;
    VkDynamicState dynamic_properties[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
    VkPipelineDynamicStateCreateInfo dynamic_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
        .dynamicStateCount = LENGTH(dynamic_properties),
        .pDynamicStates = dynamic_properties
    };
    pipeline_info.pDynamicState = &dynamic_info;
    pipeline_info.layout = pipeline_layout;
    pipeline_info.renderPass = window->defaultRenderPass();
    VkResult err = _dev_funcs->vkCreateGraphicsPipelines(dev, cache, 1, &pipeline_info, nullptr, &_pipeline);
    if (err != VK_SUCCESS)
        qFatal("Failed to create graphics pipeline: %d", err);
}

void Vk_pipeline::free_pipeline() {
    if (_pipeline) {
        _dev_funcs->vkDestroyPipeline(_dev, _pipeline, nullptr);
        _pipeline = VK_NULL_HANDLE;
    }
}

void Vk_pipeline::bind_pipeline(VkCommandBuffer *command_buffer) {
    _dev_funcs->vkCmdBindPipeline(*command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline);
}