//
//  KRPipeline.cpp
//  Kraken Engine
//
//  Copyright 2024 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice, this list
//  of conditions and the following disclaimer in the documentation and/or other materials
//  provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//

#include "KRPipeline.h"
#include "assert.h"
#include "KRLight.h"
#include "KRDirectionalLight.h"
#include "KRSpotLight.h"
#include "KRPointLight.h"
#include "KRContext.h"
#include "KRRenderPass.h"

using namespace hydra;


const char* KRPipeline::KRENGINE_PUSH_CONSTANT_NAMES[] = {
    "material_ambient", // PushConstant::material_ambient
    "material_diffuse", // PushConstant::material_diffuse
    "material_specular", // PushConstant::material_specular
    "material_reflection", // PushConstant::material_reflection
    "material_alpha", // PushConstant::material_alpha
    "material_shininess", // PushConstant::material_shininess
    "light_position", // PushConstant::light_position
    "light_direction_model_space", // PushConstant::light_direction_model_space
    "light_direction_view_space", // PushConstant::light_direction_view_space
    "light_color", //    PushConstant::light_color
    "light_decay_start", //    PushConstant::light_decay_start
    "light_cutoff", //    PushConstant::light_cutoff
    "light_intensity", //    PushConstant::light_intensity
    "flare_size", //    PushConstant::flare_size
    "view_space_model_origin", //    PushConstant::view_space_model_origin
    "mvp_matrix", //    PushConstant::mvp
    "inv_projection_matrix", //    PushConstant::invp
    "inv_mvp_matrix", //    PushConstant::invmvp
    "inv_mvp_matrix_no_translate", //    PushConstant::invmvp_no_translate
    "model_view_inverse_transpose_matrix", //    PushConstant::model_view_inverse_transpose
    "model_inverse_transpose_matrix", //    PushConstant::model_inverse_transpose
    "model_view_matrix", //    PushConstant::model_view
    "model_matrix", //    PushConstant::model_matrix
    "projection_matrix", //    PushConstant::projection_matrix
    "camera_position_model_space", //    PushConstant::camerapos_model_space
    "viewport", //    PushConstant::viewport
    "diffuseTexture", //    PushConstant::diffusetexture
    "specularTexture", //    PushConstant::speculartexture
    "reflectionCubeTexture", //    PushConstant::reflectioncubetexture
    "reflectionTexture", //    PushConstant::reflectiontexture
    "normalTexture", //    PushConstant::normaltexture
    "diffuseTexture_Scale", //    PushConstant::diffusetexture_scale
    "specularTexture_Scale", //    PushConstant::speculartexture_scale
    "reflectionTexture_Scale", //    PushConstant::reflectiontexture_scale
    "normalTexture_Scale", //    PushConstant::normaltexture_scale
    "normalTexture_Scale", //    PushConstant::ambienttexture_scale
    "diffuseTexture_Offset", //    PushConstant::diffusetexture_offset
    "specularTexture_Offset", //    PushConstant::speculartexture_offset
    "reflectionTexture_Offset", //    PushConstant::reflectiontexture_offset
    "normalTexture_Offset", //    PushConstant::normaltexture_offset
    "ambientTexture_Offset", //    PushConstant::ambienttexture_offset
    "shadow_mvp1", //    PushConstant::shadow_mvp1
    "shadow_mvp2", //    PushConstant::shadow_mvp2
    "shadow_mvp3", //    PushConstant::shadow_mvp3
    "shadowTexture1", //    PushConstant::shadowtexture1
    "shadowTexture2", //    PushConstant::shadowtexture2
    "shadowTexture3", //    PushConstant::shadowtexture3
    "lightmapTexture", //    PushConstant::lightmaptexture
    "gbuffer_frame", //    PushConstant::gbuffer_frame
    "gbuffer_depth", //    PushConstant::gbuffer_depth
    "depthFrame", //    PushConstant::depth_frame
    "volumetricEnvironmentFrame", //    PushConstant::volumetric_environment_frame
    "renderFrame", //    PushConstant::render_frame
    "time_absolute", //    PushConstant::absolute_time
    "fog_near", //    PushConstant::fog_near
    "fog_far", //    PushConstant::fog_far
    "fog_density", //    PushConstant::fog_density
    "fog_color", //    PushConstant::fog_color
    "fog_scale", //    PushConstant::fog_scale
    "fog_density_premultiplied_exponential", //    PushConstant::density_premultiplied_exponential
    "fog_density_premultiplied_squared", //    PushConstant::density_premultiplied_squared
    "slice_depth_scale", //    PushConstant::slice_depth_scale
    "particle_origin", //    PushConstant::particle_origin
    "bone_transforms", //    PushConstant::bone_transforms
    "rim_color", // PushConstant::rim_color
    "rim_power", // PushConstant::rim_power
    "fade_color", // PushConstant::fade_color
};

KRPipeline::KRPipeline(KRContext& context, KrDeviceHandle deviceHandle, KRRenderPass* renderPass, Vector2i viewport_size, Vector2i scissor_size, const PipelineInfo& info, const char* szKey, const std::vector<KRShader*>& shaders, uint32_t vertexAttributes, ModelFormat modelFormat)
  : KRContextObject(context)
  , m_deviceHandle(deviceHandle)
{
  std::unique_ptr<KRDevice>& device = getContext().getDeviceManager()->getDevice(m_deviceHandle);

  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    pushConstants.buffer = nullptr;
    pushConstants.bufferSize = 0;
    memset(pushConstants.size, 0, kPushConstantCount);
    memset(pushConstants.offset, 0, kPushConstantCount * sizeof(int));
    pushConstants.layout = nullptr;
  }

  m_descriptorSetLayout = nullptr;
  m_pipelineLayout = nullptr;
  m_graphicsPipeline = nullptr;
  m_descriptorSets.reserve(KRENGINE_MAX_FRAMES_IN_FLIGHT);

  // TODO - Handle device removal

  strcpy(m_szKey, szKey);

  const int kMaxStages = 4;
  VkPipelineShaderStageCreateInfo stages[kMaxStages];
  memset(static_cast<void*>(stages), 0, sizeof(VkPipelineShaderStageCreateInfo) * kMaxStages);
  size_t stage_count = 0;

  std::vector<VkDescriptorSetLayoutBinding> uboLayoutBindings;

  // TODO - Refactor this...  These lookup tables should be in KRMesh...
  static const KRMesh::vertex_attrib_t attribute_mapping[KRMesh::KRENGINE_NUM_ATTRIBUTES] = {
    KRMesh::KRENGINE_ATTRIB_VERTEX,
    KRMesh::KRENGINE_ATTRIB_NORMAL,
    KRMesh::KRENGINE_ATTRIB_TANGENT,
    KRMesh::KRENGINE_ATTRIB_TEXUVA,
    KRMesh::KRENGINE_ATTRIB_TEXUVB,
    KRMesh::KRENGINE_ATTRIB_BONEINDEXES,
    KRMesh::KRENGINE_ATTRIB_BONEWEIGHTS,
    KRMesh::KRENGINE_ATTRIB_VERTEX,
    KRMesh::KRENGINE_ATTRIB_NORMAL,
    KRMesh::KRENGINE_ATTRIB_TANGENT,
    KRMesh::KRENGINE_ATTRIB_TEXUVA,
    KRMesh::KRENGINE_ATTRIB_TEXUVB,
  };

  uint32_t attribute_locations[KRMesh::KRENGINE_NUM_ATTRIBUTES] = {};

  uint32_t layout_binding_count = 0;
  for (KRShader* shader : shaders) {
    const SpvReflectShaderModule* reflection = shader->getReflection();
    layout_binding_count += reflection->descriptor_binding_count;
  }
  uboLayoutBindings.reserve(layout_binding_count);

  for (KRShader* shader : shaders) {
    VkShaderModule shaderModule;
    if (!shader->createShaderModule(device->m_logicalDevice, shaderModule)) {
      // failed! TODO - Error handling
    }
    const SpvReflectShaderModule* reflection = shader->getReflection();
    
    for (uint32_t b = 0; b < reflection->descriptor_binding_count; b++) {
      SpvReflectDescriptorBinding& binding_reflect = reflection->descriptor_bindings[b];
      VkDescriptorSetLayoutBinding& binding = uboLayoutBindings.emplace_back();
      memset(&binding, 0, sizeof(VkDescriptorSetLayoutBinding));
      binding.binding = binding_reflect.binding;
      // Note: VkDescriptorType and SpvReflectDescriptorType values match
      binding.descriptorType = static_cast<VkDescriptorType>(binding_reflect.descriptor_type);
      binding.descriptorCount = binding_reflect.count;
      binding.pImmutableSamplers = nullptr;
      binding.stageFlags = shader->getShaderStageFlagBits();
    }

    VkPipelineShaderStageCreateInfo& stageInfo = stages[stage_count++];
    stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    stageInfo.stage = shader->getShaderStageFlagBits();
    if (stageInfo.stage == VK_SHADER_STAGE_VERTEX_BIT) {

      for (uint32_t i = 0; i < reflection->input_variable_count; i++) {
        // TODO - We should have an interface to allow classes such as KRMesh to expose bindings
        SpvReflectInterfaceVariable& input_var = *reflection->input_variables[i];
        if (strcmp(input_var.name, "vertex_position") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_VERTEX] = input_var.location + 1;
        } else if (strcmp(input_var.name, "vertex_normal") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_NORMAL] = input_var.location + 1;
        } else if (strcmp(input_var.name, "vertex_tangent") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_TANGENT] = input_var.location + 1;
        } else if (strcmp(input_var.name, "vertex_uv") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_TEXUVA] = input_var.location + 1;
        } else if (strcmp(input_var.name, "vertex_lightmap_uv") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_TEXUVB] = input_var.location + 1;
        } else if (strcmp(input_var.name, "bone_indexes") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_BONEINDEXES] = input_var.location + 1;
        } else if (strcmp(input_var.name, "bone_weights") == 0) {
          attribute_locations[KRMesh::KRENGINE_ATTRIB_BONEWEIGHTS] = input_var.location + 1;
        }
      }
    }

    initPushConstantStage(shader->getShaderStage(), reflection);
    initDescriptorSetStage(shader->getShaderStage(), reflection);
    stageInfo.module = shaderModule;
    stageInfo.pName = "main";
  }

  VkVertexInputBindingDescription bindingDescription{};
  bindingDescription.binding = 0;
  bindingDescription.stride = KRMesh::VertexSizeForAttributes(vertexAttributes);
  bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

  uint32_t vertexAttributeCount = 0;
  VkVertexInputAttributeDescription vertexAttributeDescriptions[KRMesh::KRENGINE_NUM_ATTRIBUTES]{};

  for (int i = KRMesh::KRENGINE_ATTRIB_VERTEX; i < KRMesh::KRENGINE_NUM_ATTRIBUTES; i++) {
    KRMesh::vertex_attrib_t mesh_attrib = static_cast<KRMesh::vertex_attrib_t>(i);
    int location_attrib = attribute_mapping[i];
    if (KRMesh::has_vertex_attribute(vertexAttributes, (KRMesh::vertex_attrib_t)i) && attribute_locations[location_attrib]) {
      VkVertexInputAttributeDescription& desc = vertexAttributeDescriptions[vertexAttributeCount++];
      desc.binding = 0;
      desc.location = attribute_locations[location_attrib] - 1;
      desc.format = KRMesh::AttributeVulkanFormat(mesh_attrib);
      desc.offset = KRMesh::AttributeOffset(mesh_attrib, vertexAttributes);
    }
  }

  VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
  vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  vertexInputInfo.vertexBindingDescriptionCount = 1;
  vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
  vertexInputInfo.vertexAttributeDescriptionCount = vertexAttributeCount;
  vertexInputInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions;

  VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
  inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
  switch (modelFormat) {
  case ModelFormat::KRENGINE_MODEL_FORMAT_INDEXED_TRIANGLES:
  case ModelFormat::KRENGINE_MODEL_FORMAT_TRIANGLES:
    inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    break;
  case ModelFormat::KRENGINE_MODEL_FORMAT_INDEXED_STRIP:
  case ModelFormat::KRENGINE_MODEL_FORMAT_STRIP:
    inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
    break;
  }

  inputAssembly.primitiveRestartEnable = VK_FALSE;

  VkViewport viewport{};
  viewport.x = 0.0f;
  viewport.y = 0.0f;
  viewport.width = static_cast<float>(viewport_size.x);
  viewport.height = static_cast<float>(viewport_size.y);
  viewport.minDepth = 0.0f;
  viewport.maxDepth = 1.0f;

  VkRect2D scissor{};
  scissor.offset = { 0, 0 };
  scissor.extent.width = scissor_size.x;
  scissor.extent.height = scissor_size.y;

  VkPipelineViewportStateCreateInfo viewportState{};
  viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
  viewportState.viewportCount = 1;
  viewportState.pViewports = &viewport;
  viewportState.scissorCount = 1;
  viewportState.pScissors = &scissor;

  VkPipelineRasterizationStateCreateInfo rasterizer{};
  rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  rasterizer.depthClampEnable = VK_FALSE;
  rasterizer.rasterizerDiscardEnable = VK_FALSE;
  rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
  rasterizer.lineWidth = 1.0f;
  switch (info.cullMode) {
  case CullMode::kCullBack:
    rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
    break;
  case CullMode::kCullFront:
    rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT;
    break;
  case CullMode::kCullNone:
    rasterizer.cullMode = VK_CULL_MODE_NONE;
    break;
  }
  rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
  rasterizer.depthBiasEnable = VK_FALSE;
  rasterizer.depthBiasConstantFactor = 0.0f;
  rasterizer.depthBiasClamp = 0.0f;
  rasterizer.depthBiasSlopeFactor = 0.0f;

  VkPipelineMultisampleStateCreateInfo multisampling{};
  multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
  multisampling.sampleShadingEnable = VK_FALSE;
  multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
  multisampling.minSampleShading = 1.0f;
  multisampling.pSampleMask = nullptr;
  multisampling.alphaToCoverageEnable = VK_FALSE;
  multisampling.alphaToOneEnable = VK_FALSE;

  VkPipelineColorBlendAttachmentState colorBlendAttachment{};
  colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;

  switch (info.rasterMode) {
  case RasterMode::kOpaque:
  case RasterMode::kOpaqueLessTest:
  case RasterMode::kOpaqueNoTest:
  case RasterMode::kOpaqueNoDepthWrite:
    colorBlendAttachment.blendEnable = VK_FALSE;
    colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
    colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
    colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
    break;
  case RasterMode::kAlphaBlend:
  case RasterMode::kAlphaBlendNoTest:
    colorBlendAttachment.blendEnable = VK_TRUE;
    colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
    colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
    break;
  case RasterMode::kAdditive:
  case RasterMode::kAdditiveNoTest:
    colorBlendAttachment.blendEnable = VK_TRUE;
    colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
    colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
    colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
    break;
  }
  colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
  colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
  colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

  VkPipelineColorBlendStateCreateInfo colorBlending{};
  colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
  colorBlending.logicOpEnable = VK_FALSE;
  colorBlending.logicOp = VK_LOGIC_OP_COPY;
  colorBlending.attachmentCount = 1;
  colorBlending.pAttachments = &colorBlendAttachment;
  colorBlending.blendConstants[0] = 0.0f;
  colorBlending.blendConstants[1] = 0.0f;
  colorBlending.blendConstants[2] = 0.0f;
  colorBlending.blendConstants[3] = 0.0f;

  if (uboLayoutBindings.size()) {
    VkDescriptorSetLayoutCreateInfo layoutInfo{};
    layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    layoutInfo.bindingCount = uboLayoutBindings.size();
    layoutInfo.pBindings = uboLayoutBindings.data();

    if (vkCreateDescriptorSetLayout(device->m_logicalDevice, &layoutInfo, nullptr, &m_descriptorSetLayout) != VK_SUCCESS) {
      // failed! TODO - Error handling
    }
  }

  VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
  pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  if (uboLayoutBindings.size()) {
    pipelineLayoutInfo.setLayoutCount = 1;
    pipelineLayoutInfo.pSetLayouts = &m_descriptorSetLayout;
  } else {
    pipelineLayoutInfo.setLayoutCount = 0;
    pipelineLayoutInfo.pSetLayouts = nullptr;
  }
  pipelineLayoutInfo.pushConstantRangeCount = 0;
  pipelineLayoutInfo.pPushConstantRanges = nullptr;

  if (vkCreatePipelineLayout(device->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
    // failed! TODO - Error handling
  }

  int iStage = 0;
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.buffer) {
      VkPipelineLayoutCreateInfo pushConstantsLayoutInfo{};
      pushConstantsLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
      pushConstantsLayoutInfo.setLayoutCount = 0;
      pushConstantsLayoutInfo.pSetLayouts = nullptr;
      pushConstantsLayoutInfo.pushConstantRangeCount = 0;
      pushConstantsLayoutInfo.pPushConstantRanges = nullptr;

      // TODO - We need to support push constants for other shader stages
      VkPushConstantRange push_constant{};
      push_constant.offset = 0;
      push_constant.size = pushConstants.bufferSize;
      push_constant.stageFlags = getShaderStageFlagBitsFromShaderStage(static_cast<ShaderStage>(iStage));
      pushConstantsLayoutInfo.pPushConstantRanges = &push_constant;
      pushConstantsLayoutInfo.pushConstantRangeCount = 1;

      if (vkCreatePipelineLayout(device->m_logicalDevice, &pushConstantsLayoutInfo, nullptr, &pushConstants.layout) != VK_SUCCESS) {
        // failed! TODO - Error handling
      }
    }
    iStage++;
  }

  VkPipelineDepthStencilStateCreateInfo depthStencil{};
  depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
  switch (info.rasterMode) {
  case RasterMode::kOpaque:
  case RasterMode::kOpaqueLessTest:
    depthStencil.depthTestEnable = VK_TRUE;
    depthStencil.depthWriteEnable = VK_TRUE;
    break;
  case RasterMode::kOpaqueNoTest:
    depthStencil.depthTestEnable = VK_FALSE;
    depthStencil.depthWriteEnable = VK_TRUE;
    break;
  case RasterMode::kOpaqueNoDepthWrite:
  case RasterMode::kAlphaBlend:
  case RasterMode::kAdditive:
    depthStencil.depthTestEnable = VK_TRUE;
    depthStencil.depthWriteEnable = VK_FALSE;
    break;
  case RasterMode::kAlphaBlendNoTest:
  case RasterMode::kAdditiveNoTest:
    depthStencil.depthTestEnable = VK_FALSE;
    depthStencil.depthWriteEnable = VK_FALSE;
    break;
  }

  if (info.rasterMode == RasterMode::kOpaqueLessTest) {
    depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
  } else {
    depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
  }

  depthStencil.depthBoundsTestEnable = VK_FALSE;
  depthStencil.minDepthBounds = 0.0f;
  depthStencil.maxDepthBounds = 1.0f;
  depthStencil.stencilTestEnable = VK_FALSE;
  depthStencil.front = {};
  depthStencil.back = {};

  VkGraphicsPipelineCreateInfo pipelineInfo{};
  pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
  pipelineInfo.stageCount = stage_count;
  pipelineInfo.pStages = stages;
  pipelineInfo.pVertexInputState = &vertexInputInfo;
  pipelineInfo.pInputAssemblyState = &inputAssembly;
  pipelineInfo.pViewportState = &viewportState;
  pipelineInfo.pRasterizationState = &rasterizer;
  pipelineInfo.pMultisampleState = &multisampling;
  pipelineInfo.pDepthStencilState = &depthStencil;
  pipelineInfo.pColorBlendState = &colorBlending;
  pipelineInfo.pDynamicState = nullptr;
  pipelineInfo.layout = m_pipelineLayout;
  pipelineInfo.renderPass = renderPass->m_renderPass;
  pipelineInfo.subpass = 0;
  pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
  pipelineInfo.basePipelineIndex = -1;

  if (vkCreateGraphicsPipelines(device->m_logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_graphicsPipeline) != VK_SUCCESS) {
    // Failed! TODO - Error handling
  }
}

KRPipeline::~KRPipeline()
{
  if (m_graphicsPipeline) {
    // TODO: vkDestroyPipeline(device, m_graphicsPipeline, nullptr);
  }
  if (m_pipelineLayout) {
    // TODO: vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
  }
  if (m_descriptorSetLayout) {
    // TODO: vkDestroyDescriptorSetLayout(device, m_descriptorSetLayout, nullptr);
  }

  if (getContext().getPipelineManager()->m_active_pipeline == this) {
    getContext().getPipelineManager()->m_active_pipeline = NULL;
  }
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.layout) {
      // TODO: vkDestroyPipelineLayout(device, pushConstants.layout, nullptr);
    }
    if (pushConstants.buffer) {
      delete pushConstants.buffer;
      pushConstants.buffer = nullptr;
    }
  }
}

void KRPipeline::initPushConstantStage(ShaderStage stage, const SpvReflectShaderModule* reflection)
{
  PushConstantInfo& pushConstants = m_stages[static_cast<int>(stage)].pushConstants;
  for (int i = 0; i < reflection->push_constant_block_count; i++) {
    const SpvReflectBlockVariable& block = reflection->push_constant_blocks[i];
    if (stricmp(block.name, "constants") == 0) {
      if (block.size > 0) {
        pushConstants.buffer = (__uint8_t*)malloc(block.size);
        memset(pushConstants.buffer, 0, block.size);
        pushConstants.bufferSize = block.size;

        // Get push constant offsets
        for (int iUniform = 0; iUniform < kPushConstantCount; iUniform++) {
          for (int iMember = 0; iMember < block.member_count; iMember++) {
            const SpvReflectBlockVariable& member = block.members[iMember];
            if (stricmp(KRENGINE_PUSH_CONSTANT_NAMES[iUniform], member.name) == 0) {
              pushConstants.offset[iUniform] = member.offset;
              pushConstants.size[iUniform] = member.size;
            }
          }
        }
      }
    }
  }
}

void KRPipeline::initDescriptorSetStage(ShaderStage stage, const SpvReflectShaderModule* reflection)
{
  std::vector<DescriptorSetInfo>& descriptorSets = m_stages[static_cast<int>(stage)].descriptorSets;
  descriptorSets.reserve(reflection->descriptor_set_count);
  for (int i = 0; i < reflection->descriptor_set_count; i++) {
    SpvReflectDescriptorSet descriptorSet = reflection->descriptor_sets[i];
    DescriptorSetInfo& descriptorSetInfo = descriptorSets.emplace_back();
    descriptorSetInfo.bindings.reserve(descriptorSet.binding_count);
    for (int j = 0; j < descriptorSet.binding_count; j++) {
      SpvReflectDescriptorBinding& binding = *descriptorSet.bindings[j];
      DescriptorBinding& descriptorQuery = descriptorSetInfo.bindings.emplace_back();
      
      switch (binding.descriptor_type) {
      case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
        {
          ImageDescriptorInfo& imageInfo = descriptorQuery.emplace<ImageDescriptorInfo>();
          imageInfo.name = binding.name;
          imageInfo.texture = nullptr;
          imageInfo.sampler = nullptr;
        }
        break;
      case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
        {
          UniformBufferDescriptorInfo& bufferInfo = descriptorQuery.emplace<UniformBufferDescriptorInfo>();
          bufferInfo.name = binding.name;
          bufferInfo.buffer = nullptr;
        }
        break;
      default:
        // Not supported
        // TODO - Error handling
        break;
      }
    }
  }
}

bool KRPipeline::hasPushConstant(PushConstant location) const
{
  for (const StageInfo& stageInfo : m_stages) {
    const PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)]) {
      return true;
    }
  }
  return false;
}

void KRPipeline::setPushConstant(PushConstant location, float value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      float* constant = (float*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}


void KRPipeline::setPushConstant(PushConstant location, int value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      int* constant = (int*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}

void KRPipeline::setPushConstant(PushConstant location, const Vector2& value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      Vector2* constant = (Vector2*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}
void KRPipeline::setPushConstant(PushConstant location, const Vector3& value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      Vector3* constant = (Vector3*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}

void KRPipeline::setPushConstant(PushConstant location, const Vector4& value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      Vector4* constant = (Vector4*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}

void KRPipeline::setPushConstant(PushConstant location, const Matrix4& value)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.size[static_cast<size_t>(location)] == sizeof(value)) {
      Matrix4* constant = (Matrix4*)(pushConstants.buffer + pushConstants.offset[static_cast<size_t>(location)]);
      *constant = value;
    }
  }
}

void KRPipeline::setPushConstant(PushConstant location, const Matrix4* value, const size_t count)
{
  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    // TODO - Vulkan refactoring
    // GLDEBUG(glUniformMatrix4fv(pushConstants.offset[KRPipeline::PushConstant::bone_transforms], (int)bones.size(), GL_FALSE, bone_mats));
  }
}

void KRPipeline::updateDescriptorBinding()
{
  // TODO - Implement
  // Vulkan Refactoring
}

bool KRPipeline::bind(VkCommandBuffer& commandBuffer, KRCamera& camera, const KRViewport& viewport, const Matrix4& matModel, const std::vector<KRPointLight*>* point_lights, const std::vector<KRDirectionalLight*>* directional_lights, const std::vector<KRSpotLight*>* spot_lights, const KRRenderPass* renderPass)
{
  updateDescriptorBinding();
  updateDescriptorSets();
  bindDescriptorSets(commandBuffer);
  setPushConstant(PushConstant::absolute_time, getContext().getAbsoluteTime());

  int light_directional_count = 0;
  //int light_point_count = 0;
  //int light_spot_count = 0;
  // TODO - Need to support multiple lights and more light types in forward rendering
  if (renderPass->getType() != RenderPassType::RENDER_PASS_DEFERRED_LIGHTS && renderPass->getType() != RenderPassType::RENDER_PASS_DEFERRED_GBUFFER && renderPass->getType() != RenderPassType::RENDER_PASS_DEFERRED_OPAQUE && renderPass->getType() != RenderPassType::RENDER_PASS_SHADOWMAP) {


    if (directional_lights) {
      for (std::vector<KRDirectionalLight*>::const_iterator light_itr = directional_lights->begin(); light_itr != directional_lights->end(); light_itr++) {
        KRDirectionalLight* directional_light = (*light_itr);
        if (light_directional_count == 0) {
          int cShadowBuffers = directional_light->getShadowBufferCount();
          if (hasPushConstant(PushConstant::shadowtexture1) && cShadowBuffers > 0) {
            // TODO - Vulkan Refactoring.  Note: Sampler needs clamp-to-edge and linear filtering
            if (m_pContext->getTextureManager()->selectTexture(0 /*GL_TEXTURE_2D*/, 3, directional_light->getShadowTextures()[0])) {
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            }
          }

          if (hasPushConstant(PushConstant::shadowtexture2) && cShadowBuffers > 1 && camera.settings.m_cShadowBuffers > 1) {
            // TODO - Vulkan Refactoring.  Note: Sampler needs clamp-to-edge and linear filtering
            if (m_pContext->getTextureManager()->selectTexture(0 /*GL_TEXTURE_2D*/, 4, directional_light->getShadowTextures()[1])) {
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            }
          }

          if (hasPushConstant(PushConstant::shadowtexture3) && cShadowBuffers > 2 && camera.settings.m_cShadowBuffers > 2) {
            // TODO - Vulkan Refactoring.  Note: Sampler needs clamp-to-edge and linear filtering
            if (m_pContext->getTextureManager()->selectTexture(0 /*GL_TEXTURE_2D*/, 5, directional_light->getShadowTextures()[2])) {
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
              GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
            }
          }

          Matrix4 matBias;
          matBias.translate(1.0, 1.0, 1.0);
          matBias.scale(0.5);
          for (int iShadow = 0; iShadow < cShadowBuffers; iShadow++) {
            setPushConstant(static_cast<PushConstant>(static_cast<int>(PushConstant::shadow_mvp1) + iShadow), matModel * directional_light->getShadowViewports()[iShadow].getViewProjectionMatrix() * matBias);
          }

          if (hasPushConstant(PushConstant::light_direction_model_space)) {
            Matrix4 inverseModelMatrix = matModel;
            inverseModelMatrix.invert();

            // Bind the light direction vector
            Vector3 lightDirObject = Matrix4::Dot(inverseModelMatrix, directional_light->getWorldLightDirection());
            lightDirObject.normalize();
            setPushConstant(PushConstant::light_direction_model_space, lightDirObject);
          }
        }

        light_directional_count++;
      }
    }

    //light_point_count = point_lights.size();
    //light_spot_count = spot_lights.size();
  }

  if (hasPushConstant(PushConstant::camerapos_model_space)) {
    Matrix4 inverseModelMatrix = matModel;
    inverseModelMatrix.invert();

    if (hasPushConstant(PushConstant::camerapos_model_space)) {
      // Transform location of camera to object space for calculation of specular halfVec
      Vector3 cameraPosObject = Matrix4::Dot(inverseModelMatrix, viewport.getCameraPosition());
      setPushConstant(PushConstant::camerapos_model_space, cameraPosObject);
    }
  }

  if (hasPushConstant(PushConstant::mvp) || hasPushConstant(KRPipeline::PushConstant::invmvp)) {
    // Bind our modelmatrix variable to be a uniform called mvpmatrix in our shaderprogram
    Matrix4 mvpMatrix = matModel * viewport.getViewProjectionMatrix();
    setPushConstant(PushConstant::mvp, mvpMatrix);

    if (hasPushConstant(KRPipeline::PushConstant::invmvp)) {
      setPushConstant(KRPipeline::PushConstant::invmvp, Matrix4::Invert(mvpMatrix));
    }
  }

  if (hasPushConstant(KRPipeline::PushConstant::view_space_model_origin) || hasPushConstant(PushConstant::model_view_inverse_transpose) || hasPushConstant(KRPipeline::PushConstant::model_view)) {
    Matrix4 matModelView = matModel * viewport.getViewMatrix();
    setPushConstant(PushConstant::model_view, matModelView);


    if (hasPushConstant(KRPipeline::PushConstant::view_space_model_origin)) {
      Vector3 view_space_model_origin = Matrix4::Dot(matModelView, Vector3::Zero()); // Origin point of model space is the light source position.  No perspective, so no w divide required
      setPushConstant(PushConstant::view_space_model_origin, view_space_model_origin);
    }

    if (hasPushConstant(PushConstant::model_view_inverse_transpose)) {
      Matrix4 matModelViewInverseTranspose = matModelView;
      matModelViewInverseTranspose.transpose();
      matModelViewInverseTranspose.invert();
      setPushConstant(PushConstant::model_view_inverse_transpose, matModelViewInverseTranspose);
    }
  }

  if (hasPushConstant(PushConstant::model_inverse_transpose)) {
    Matrix4 matModelInverseTranspose = matModel;
    matModelInverseTranspose.transpose();
    matModelInverseTranspose.invert();
    setPushConstant(PushConstant::model_inverse_transpose, matModelInverseTranspose);
  }

  if (hasPushConstant(KRPipeline::PushConstant::invp)) {
    setPushConstant(PushConstant::invp, viewport.getInverseProjectionMatrix());
  }

  if (hasPushConstant(KRPipeline::PushConstant::invmvp_no_translate)) {
    Matrix4 matInvMVPNoTranslate = matModel * viewport.getViewMatrix();;
    // Remove the translation
    matInvMVPNoTranslate.getPointer()[3] = 0;
    matInvMVPNoTranslate.getPointer()[7] = 0;
    matInvMVPNoTranslate.getPointer()[11] = 0;
    matInvMVPNoTranslate.getPointer()[12] = 0;
    matInvMVPNoTranslate.getPointer()[13] = 0;
    matInvMVPNoTranslate.getPointer()[14] = 0;
    matInvMVPNoTranslate.getPointer()[15] = 1.0;
    matInvMVPNoTranslate = matInvMVPNoTranslate * viewport.getProjectionMatrix();
    matInvMVPNoTranslate.invert();
    setPushConstant(PushConstant::invmvp_no_translate, matInvMVPNoTranslate);
  }

  setPushConstant(PushConstant::model_matrix, matModel);
  if (hasPushConstant(PushConstant::projection_matrix)) {
    setPushConstant(PushConstant::projection_matrix, viewport.getProjectionMatrix());
  }

  if (hasPushConstant(PushConstant::viewport)) {
    setPushConstant(PushConstant::viewport, Vector4::Create(
      (float)0.0,
      (float)0.0,
      (float)viewport.getSize().x,
      (float)viewport.getSize().y
    )
    );
  }

  // Fog parameters
  setPushConstant(PushConstant::fog_near, camera.settings.fog_near);
  setPushConstant(PushConstant::fog_far, camera.settings.fog_far);
  setPushConstant(PushConstant::fog_density, camera.settings.fog_density);
  setPushConstant(PushConstant::fog_color, camera.settings.fog_color);

  if (hasPushConstant(PushConstant::fog_scale)) {
    setPushConstant(PushConstant::fog_scale, 1.0f / (camera.settings.fog_far - camera.settings.fog_near));
  }
  if (hasPushConstant(PushConstant::density_premultiplied_exponential)) {
    setPushConstant(PushConstant::density_premultiplied_exponential, -camera.settings.fog_density * 1.442695f); // -fog_density / log(2)
  }
  if (hasPushConstant(PushConstant::density_premultiplied_squared)) {
    setPushConstant(PushConstant::density_premultiplied_squared, (float)(-camera.settings.fog_density * camera.settings.fog_density * 1.442695)); // -fog_density * fog_density / log(2)
  }

  // Sets the diffuseTexture variable to the first texture unit
  setPushConstant(PushConstant::diffusetexture, 0);

  // Sets the specularTexture variable to the second texture unit
  setPushConstant(PushConstant::speculartexture, 1);

  // Sets the normalTexture variable to the third texture unit
  setPushConstant(PushConstant::normaltexture, 2);

  // Sets the shadowTexture variable to the fourth texture unit
  setPushConstant(PushConstant::shadowtexture1, 3);
  setPushConstant(PushConstant::shadowtexture2, 4);
  setPushConstant(PushConstant::shadowtexture3, 5);
  setPushConstant(PushConstant::reflectioncubetexture, 4);
  setPushConstant(PushConstant::lightmaptexture, 5);
  setPushConstant(PushConstant::gbuffer_frame, 6);
  setPushConstant(PushConstant::gbuffer_depth, 7); // Texture unit 7 is used for reading the depth buffer in gBuffer pass #2 and in post-processing pass
  setPushConstant(PushConstant::reflectiontexture, 7); // Texture unit 7 is used for the reflection map textures in gBuffer pass #3 and when using forward rendering
  setPushConstant(PushConstant::depth_frame, 0);
  setPushConstant(PushConstant::render_frame, 1);
  setPushConstant(PushConstant::volumetric_environment_frame, 2);

  for (StageInfo& stageInfo : m_stages) {
    PushConstantInfo& pushConstants = stageInfo.pushConstants;
    if (pushConstants.buffer) {
      vkCmdPushConstants(commandBuffer, pushConstants.layout, VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants.bufferSize, pushConstants.buffer);
    }
  }

  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline);

  return true;
}

void KRPipeline::updateDescriptorSets()
{
  if (m_descriptorSetLayout == VK_NULL_HANDLE) {
    // There are no descriptors
    return;
  }

  std::unique_ptr<KRDevice>& device = getContext().getDeviceManager()->getDevice(m_deviceHandle);

  // If the descriptor sets are not yet allocted, create them
  if (m_descriptorSets.size() == 0) {
    int descriptorSetCount = 0;
    for (int stage = 0; stage < static_cast<size_t>(ShaderStage::ShaderStageCount); stage++) {
      const StageInfo& stageInfo = m_stages[stage];
      descriptorSetCount += stageInfo.descriptorSets.size();
    }
    m_descriptorSets.resize(KRENGINE_MAX_FRAMES_IN_FLIGHT * descriptorSetCount, VK_NULL_HANDLE);
    std::vector<VkDescriptorSetLayout> layouts(KRENGINE_MAX_FRAMES_IN_FLIGHT, m_descriptorSetLayout);
    device->createDescriptorSets(layouts, m_descriptorSets);
  }

  // Update the descriptor sets
  // TODO - We should only do this when the descriptors have changed

  int descriptorSetCount = m_descriptorSets.size() / KRENGINE_MAX_FRAMES_IN_FLIGHT;
  int descriptorSetStart = (getContext().getCurrentFrame() % KRENGINE_MAX_FRAMES_IN_FLIGHT) * descriptorSetCount;
  int descriptorSetIndex = descriptorSetStart;

  std::vector<VkWriteDescriptorSet> descriptorWrites;
  std::vector<VkDescriptorBufferInfo> buffers;
  std::vector<VkDescriptorImageInfo> images;

  for (int stage = 0; stage < static_cast<size_t>(ShaderStage::ShaderStageCount); stage++) {
    StageInfo& stageInfo = m_stages[stage];
    for (DescriptorSetInfo& descriptorSetInfo : stageInfo.descriptorSets) {
      VkDescriptorSet descriptorSet = m_descriptorSets[descriptorSetIndex++];

      int bindingIndex = 0;
      for (DescriptorBinding& binding : descriptorSetInfo.bindings) {
        UniformBufferDescriptorInfo* buffer = std::get_if<UniformBufferDescriptorInfo>(&binding);
        ImageDescriptorInfo* image = std::get_if<ImageDescriptorInfo>(&binding);
        if (buffer) {
          VkDescriptorBufferInfo& bufferInfo = buffers.emplace_back(VkDescriptorBufferInfo{});
          bufferInfo.buffer = buffer->buffer->getBuffer();
          bufferInfo.offset = 0;
          bufferInfo.range = VK_WHOLE_SIZE;

          VkWriteDescriptorSet& descriptorWrite = descriptorWrites.emplace_back(VkWriteDescriptorSet{});
          descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
          descriptorWrite.dstSet = descriptorSet;
          descriptorWrite.dstBinding = bindingIndex;
          descriptorWrite.dstArrayElement = 0;
          descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
          descriptorWrite.descriptorCount = 1;
          descriptorWrite.pBufferInfo = &bufferInfo;
        } else if (image) {
          VkDescriptorImageInfo& imageInfo = images.emplace_back(VkDescriptorImageInfo{});
          imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
          imageInfo.imageView = image->texture->getFullImageView(m_deviceHandle);
          imageInfo.sampler = image->sampler->getSampler(m_deviceHandle);

          VkWriteDescriptorSet& descriptorWrite = descriptorWrites.emplace_back(VkWriteDescriptorSet{});
          descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
          descriptorWrite.dstSet = descriptorSet;
          descriptorWrite.dstBinding = bindingIndex++;
          descriptorWrite.dstArrayElement = 0;
          descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
          descriptorWrite.descriptorCount = 1;
          descriptorWrite.pImageInfo = &imageInfo;
        } else {
          // TODO - Error Handling
          assert(false);
        }
        bindingIndex++;
      }
    }
  }

  if (!descriptorWrites.empty()) {
    vkUpdateDescriptorSets(device->m_logicalDevice, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
  }
}

void KRPipeline::bindDescriptorSets(VkCommandBuffer& commandBuffer)
{
  if (m_descriptorSets.empty()) {
    return;
  }
  int descriptorSetCount = m_descriptorSets.size() / KRENGINE_MAX_FRAMES_IN_FLIGHT;
  int startDescriptorSet = (getContext().getCurrentFrame() % KRENGINE_MAX_FRAMES_IN_FLIGHT) * descriptorSetCount;
  VkDescriptorSet descriptorSet = m_descriptorSets[startDescriptorSet];
  if (descriptorSet == VK_NULL_HANDLE) {
    return;
  }
  // TODO - Vulkan Refactoring - Support multiple descriptor set binding
  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, descriptorSetCount, &descriptorSet, 0, nullptr);
}

const char* KRPipeline::getKey() const
{
  return m_szKey;
}

VkPipeline& KRPipeline::getPipeline()
{
  return m_graphicsPipeline;
}

void KRPipeline::setImageBinding(const std::string& name, KRTexture* texture, KRSampler* sampler)
{
  for (int stage = 0; stage < static_cast<size_t>(ShaderStage::ShaderStageCount); stage++) {
    StageInfo& stageInfo = m_stages[stage];
    for (DescriptorSetInfo& descriptorSetInfo : stageInfo.descriptorSets) {
      for (DescriptorBinding& binding : descriptorSetInfo.bindings) {
        ImageDescriptorInfo* image = std::get_if<ImageDescriptorInfo>(&binding);
        if (image) {
          if (image->name == name) {
            image->texture = texture;
            image->sampler = sampler;
          }
        }
      }
    }
  }
}
