text: #include "graphics/Pipeline.h"
  
  #include <fstream>
  
  #include <spdlog/spdlog.h>
  
  Pipeline::Pipeline(const CreateInfo& createInfo)
  {
      Initialise(createInfo);
  }
  
  Pipeline::~Pipeline() noexcept
  {
      Destroy();
  }
  
  void Pipeline::Initialise(const CreateInfo& createInfo)
  {
      m_vulkanContext = createInfo.vulkanContext;
  
      const VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo{
          .sType= VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
          .primitiveRestartEnable = VK_FALSE,
      };
      
      const VkViewport viewport{
          .x = 0.0f,
          .y = 0.0f,
          .width = createInfo.viewportSize.x,
          .height = createInfo.viewportSize.y,
          .minDepth = 0.0f,
          .maxDepth = 1.0f,
      };
      
      const VkRect2D scissorRect{
          .offset = VkOffset2D{ 0, 0 },
          .extent = VkExtent2D{ 
              static_cast<std::uint32_t>(createInfo.viewportSize.x),
              static_cast<std::uint32_t>(createInfo.viewportSize.y),
          },
      };
      
      const VkPipelineViewportStateCreateInfo viewportStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .viewportCount = 1u,
          .pViewports = &viewport,
          .scissorCount = 1u,
          .pScissors = &scissorRect,
      };
      
      const VkPipelineRasterizationStateCreateInfo rasterisationStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .depthClampEnable = VK_FALSE,
          .rasterizerDiscardEnable = VK_FALSE,
          .polygonMode = VK_POLYGON_MODE_FILL,
          .cullMode = VK_CULL_MODE_NONE,
          .frontFace = VK_FRONT_FACE_CLOCKWISE,
          .depthBiasEnable = VK_FALSE,
          .depthBiasConstantFactor = 0.0f,
          .depthBiasClamp = 0.0f,
          .depthBiasSlopeFactor = 0.0f,
          .lineWidth = 1.0f,
      };
      
      const VkPipelineMultisampleStateCreateInfo multisampleStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
          .sampleShadingEnable = VK_FALSE,
          .minSampleShading = 1.0f,
          .pSampleMask = nullptr,
          .alphaToCoverageEnable = VK_FALSE,
          .alphaToOneEnable = VK_FALSE,
      };
      
      const VkPipelineColorBlendAttachmentState colourBlendAttachment{
          .blendEnable = VK_FALSE,
          .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
          .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
          .colorBlendOp = VK_BLEND_OP_ADD,
          .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
          .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
          .alphaBlendOp = VK_BLEND_OP_ADD,
          .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
      };
      
      const VkPipelineColorBlendStateCreateInfo colourBlendStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .logicOpEnable = VK_FALSE,
          .logicOp = VK_LOGIC_OP_COPY,
          .attachmentCount = 1u,
          .pAttachments = &colourBlendAttachment,
          .blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f },
      };
      
      const VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .depthTestEnable = VK_TRUE,
          .depthWriteEnable = VK_TRUE,
          .depthCompareOp = VK_COMPARE_OP_LESS,
          .depthBoundsTestEnable = VK_FALSE,
          .stencilTestEnable = VK_FALSE,
          .front = VkStencilOpState{ },
          .back = VkStencilOpState{ },
          .minDepthBounds = 0.0f,
          .maxDepthBounds = 1.0f,
      };
      
      if (!InitialiseShaders(createInfo))
      {
          return;
      }
      
      std::array<VkPipelineShaderStageCreateInfo, 2u> shaderStageCreateInfos{ };
      
      shaderStageCreateInfos[0u] = VkPipelineShaderStageCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .stage = VK_SHADER_STAGE_VERTEX_BIT,
          .module = m_vertexShaderModule,
          .pName = "main",
          .pSpecializationInfo = nullptr,
      };
      
      shaderStageCreateInfos[1u] = VkPipelineShaderStageCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
          .module = m_fragmentShaderModule,
          .pName = "main",
          .pSpecializationInfo = nullptr,
      };
      
      const VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo{
          .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .vertexBindingDescriptionCount = 0u,
          .pVertexBindingDescriptions = nullptr,
          .vertexAttributeDescriptionCount = 0u,
          .pVertexAttributeDescriptions = nullptr,
      };
      
      const VkGraphicsPipelineCreateInfo pipelineCreateInfo{
          .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .stageCount = static_cast<std::uint32_t>(shaderStageCreateInfos.size()),
          .pStages = shaderStageCreateInfos.data(),
          .pVertexInputState = &vertexInputStateCreateInfo,
          .pInputAssemblyState = &inputAssemblyStateCreateInfo,
          .pTessellationState = nullptr,
          .pViewportState = &viewportStateCreateInfo,
          .pRasterizationState = &rasterisationStateCreateInfo,
          .pMultisampleState = &multisampleStateCreateInfo,
          .pDepthStencilState = &depthStencilStateCreateInfo,
          .pColorBlendState = &colourBlendStateCreateInfo,
          .pDynamicState = nullptr,
          .layout = createInfo.pipelineLayout,
          .renderPass = createInfo.renderPass,
          .subpass = createInfo.subpassIndex,
          .basePipelineHandle = VK_NULL_HANDLE,
          .basePipelineIndex = -1,
      };
      
      if (vkCreateGraphicsPipelines(createInfo.vulkanContext->GetLogicalDevice(), VK_NULL_HANDLE, 1u, &pipelineCreateInfo, nullptr, &m_handle) != VK_SUCCESS)
      {
          spdlog::critical("Failed to create graphics pipeline.");
      }
  }
  
  void Pipeline::Destroy() noexcept
  {
      if (m_handle != VK_NULL_HANDLE)
      {
          vkDestroyShaderModule(m_vulkanContext->GetLogicalDevice(), m_vertexShaderModule, nullptr);
          m_vertexShaderModule = nullptr;
  
          vkDestroyShaderModule(m_vulkanContext->GetLogicalDevice(), m_fragmentShaderModule, nullptr);
          m_fragmentShaderModule = nullptr;
  
          vkDestroyPipeline(m_vulkanContext->GetLogicalDevice(), m_handle, nullptr);
          m_handle = VK_NULL_HANDLE;
  
          m_vulkanContext = nullptr;
          m_areShadersValid = false;
      }
  }
  
  [[nodiscard]] std::vector<std::byte> Pipeline::ReadShaderFile(const std::string_view filepath)
  {
      std::ifstream shaderFile(filepath.data(), std::ios::ate | std::ios::binary);
  
      if (!shaderFile.is_open())
      {
          spdlog::warn("Failed to open shader file {}.", filepath);
  
          return { };
      }
  
      const std::size_t fileSize = static_cast<std::size_t>(shaderFile.tellg());
      std::vector<std::byte> fileBuffer(fileSize);
  
      shaderFile.seekg(0u);
      shaderFile.read(reinterpret_cast<char*>(fileBuffer.data()), fileSize);
      shaderFile.close();
  
      return fileBuffer;
  }
  
  [[nodiscard]] bool Pipeline::InitialiseShaders(const CreateInfo& createInfo)
  {
      const auto vertexShaderData = ReadShaderFile(createInfo.vertexFilepath);
      const auto fragmentShaderData = ReadShaderFile(createInfo.fragmentFilepath);
  
      if (vertexShaderData.empty() || fragmentShaderData.empty())
      {
          return false;
      }
  
      spdlog::info("Vertex shader loaded. Size: {} bytes.", vertexShaderData.size());
      spdlog::info("Fragment shader loaded. Size: {} bytes.", fragmentShaderData.size());
  
      m_vertexShaderModule = CreateShaderModule(vertexShaderData);
      m_fragmentShaderModule = CreateShaderModule(fragmentShaderData);
  
      if (m_vertexShaderModule == VK_NULL_HANDLE || m_fragmentShaderModule == VK_NULL_HANDLE)
      {
          return false;
      }
  
      m_areShadersValid = true;
  
      return true;
  }
  
  [[nodiscard]] VkShaderModule Pipeline::CreateShaderModule(const std::vector<std::byte>& shaderData) const
  {
      VkShaderModule shaderModule = VK_NULL_HANDLE;
  
      const VkShaderModuleCreateInfo shaderModuleCreateInfo{
          .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
          .pNext = nullptr,
          .flags = 0,
          .codeSize = shaderData.size(),
          .pCode = reinterpret_cast<const std::uint32_t*>(shaderData.data()),
      };
  
      if (vkCreateShaderModule(m_vulkanContext->GetLogicalDevice(), &shaderModuleCreateInfo, nullptr, &shaderModule) != VK_SUCCESS)
      {
          spdlog::error("Failed to create shader module.");
  
          return VK_NULL_HANDLE;
      }
  
      return shaderModule;
  }