
#pragma once

#ifdef __APPLE__
const bool isApplePlatform = true;
#else
const bool isApplePlatform = false;
#endif

typedef struct VulkanDevice {
  VkPhysicalDevice gpu;
  VkPhysicalDeviceProperties gpuProperties;
  VkPhysicalDeviceMemoryProperties memoryProperties;

  VkQueueFamilyProperties *queueFamilyProperties;
  uint32_t queueFamilyPropertiesCount;

  uint32_t graphicsQueueFamilyIndex;

  VkDevice device;

  VkCommandPool commandPool;
} VulkanDevice;

VulkanDevice *createVulkanDevice(VkPhysicalDevice gpu) {
  VulkanDevice *device = (VulkanDevice *)malloc(sizeof(VulkanDevice));
  memset(device, 0, sizeof(VulkanDevice));

  device->gpu = gpu;
  vkGetPhysicalDeviceMemoryProperties(gpu, &device->memoryProperties);
  vkGetPhysicalDeviceProperties(gpu, &device->gpuProperties);

  vkGetPhysicalDeviceQueueFamilyProperties(gpu, &device->queueFamilyPropertiesCount, NULL);
  assert(device->queueFamilyPropertiesCount >= 1);

  device->queueFamilyProperties = (VkQueueFamilyProperties *)malloc(device->queueFamilyPropertiesCount * sizeof(VkQueueFamilyProperties));

  vkGetPhysicalDeviceQueueFamilyProperties(gpu, &device->queueFamilyPropertiesCount, device->queueFamilyProperties);
  assert(device->queueFamilyPropertiesCount >= 1);

  device->graphicsQueueFamilyIndex = UINT32_MAX;
  for (uint32_t i = 0; i < device->queueFamilyPropertiesCount; ++i) {
    if ((device->queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
      device->graphicsQueueFamilyIndex = i;
    }
  }

  float queuePriorities[1] = {0.0};
  VkDeviceQueueCreateInfo queue_info = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
  queue_info.queueCount = 1;
  queue_info.pQueuePriorities = queuePriorities;
  queue_info.queueFamilyIndex = device->graphicsQueueFamilyIndex;

  const char *deviceExtensions[] = {
      VK_KHR_SWAPCHAIN_EXTENSION_NAME,
  };
  VkDeviceCreateInfo deviceInfo = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
  deviceInfo.queueCreateInfoCount = 1;
  deviceInfo.pQueueCreateInfos = &queue_info;
  deviceInfo.enabledExtensionCount = sizeof(deviceExtensions) / sizeof(deviceExtensions[0]);
  deviceInfo.ppEnabledExtensionNames = deviceExtensions;
  VkResult res = vkCreateDevice(gpu, &deviceInfo, NULL, &device->device);

  assert(res == VK_SUCCESS);

  /* Create a command pool to allocate our command buffer from */
  VkCommandPoolCreateInfo cmd_pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
  cmd_pool_info.queueFamilyIndex = device->graphicsQueueFamilyIndex;
  cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  res = vkCreateCommandPool(device->device, &cmd_pool_info, NULL, &device->commandPool);
  assert(res == VK_SUCCESS);

  return device;
}
void destroyVulkanDevice(VulkanDevice *device) {
  free(device->queueFamilyProperties);

  if (device->commandPool) {
    vkDestroyCommandPool(device->device, device->commandPool, 0);
  }
  if (device->device) {
    vkDestroyDevice(device->device, 0);
  }
  free(device);
}

typedef struct SwapchainBuffers {
  VkImage image;
  VkImageView view;
} SwapchainBuffers;

typedef struct DepthBuffer {
  VkFormat format;

  VkImage image;
  VkDeviceMemory mem;
  VkImageView view;
} DepthBuffer;

typedef struct FrameBuffers {
  VkSwapchainKHR swap_chain;
  SwapchainBuffers *swap_chain_buffers;
  uint32_t swapchain_image_count;
  VkFramebuffer *framebuffers;

  uint32_t current_buffer;
  uint32_t current_frame;
  uint64_t num_swaps;

  VkExtent2D buffer_size;

  VkRenderPass render_pass;

  VkFormat format;
  DepthBuffer depth;
  VkSemaphore *present_complete_semaphore;
  VkSemaphore *render_complete_semaphore;
  VkFence *flight_fence;
} FrameBuffers;

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
  printf("%s\n", pCallbackData->pMessage);
  return VK_FALSE;
}

static VkDebugUtilsMessengerEXT debugMessenger = NULL;

#ifdef GLFW_INCLUDE_VULKAN
static VkInstance createVkInstance(bool enable_debug_layer) {

  // initialize the VkApplicationInfo structure
  VkApplicationInfo app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
  app_info.pApplicationName = "NanoVG";
  app_info.applicationVersion = 1;
  app_info.pEngineName = "NanoVG";
  app_info.engineVersion = 1;
  app_info.apiVersion = VK_API_VERSION_1_0;

  static const char *append_extensions[] = {
      VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
  };
  uint32_t append_extensions_count = sizeof(append_extensions) / sizeof(append_extensions[0]);
  if (!enable_debug_layer) {
    append_extensions_count = 0;
  }

  static const char *apple_extensions[] = {
    VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
  };
  uint32_t apple_extensions_count = sizeof(apple_extensions) / sizeof(apple_extensions[0]);
  if (!isApplePlatform) {
    apple_extensions_count = 0;
  }

  uint32_t extensions_count = 0;
  const char **glfw_extensions = glfwGetRequiredInstanceExtensions(&extensions_count);

  const char **extensions = (const char **)calloc(extensions_count + append_extensions_count + apple_extensions_count, sizeof(char *));

  for (uint32_t i = 0; i < extensions_count; ++i) {
    extensions[i] = glfw_extensions[i];
  }
  for (uint32_t i = 0; i < append_extensions_count; ++i) {
    extensions[extensions_count++] = append_extensions[i];
  }
  for (uint32_t i = 0; i < apple_extensions_count; ++i) {
    extensions[extensions_count++] = apple_extensions[i];
  }

  // initialize the VkInstanceCreateInfo structure
  VkInstanceCreateInfo inst_info = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
  inst_info.pApplicationInfo = &app_info;
  inst_info.enabledExtensionCount = extensions_count;
  inst_info.ppEnabledExtensionNames = extensions;
  if (isApplePlatform) {
    inst_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
  }

  static const char *instance_validation_layers[] = {
      "VK_LAYER_KHRONOS_validation"
  };

  if (enable_debug_layer) {
    inst_info.enabledLayerCount = sizeof(instance_validation_layers) / sizeof(instance_validation_layers[0]);
    inst_info.ppEnabledLayerNames = instance_validation_layers;

    uint32_t layerCount = 0;
    vkEnumerateInstanceLayerProperties(&layerCount, 0);
    VkLayerProperties *layerprop = (VkLayerProperties *)malloc(sizeof(VkLayerProperties) * layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, layerprop);
    printf("vkEnumerateInstanceLayerProperties:");
    for (uint32_t i = 0; i < layerCount; ++i) {
      printf("%s\n", layerprop[i].layerName);
    }
    free(layerprop);
  }

  VkInstance inst;
  VkResult res;
  res = vkCreateInstance(&inst_info, NULL, &inst);

  free((void*) extensions);

  if (res == VK_ERROR_INCOMPATIBLE_DRIVER) {
    printf("cannot find a compatible Vulkan ICD\n");
    exit(-1);
  } else if (res) {
    switch(res){
      case(VK_ERROR_OUT_OF_HOST_MEMORY):printf("VK_ERROR_OUT_OF_HOST_MEMORY\n");break;
      case(VK_ERROR_OUT_OF_DEVICE_MEMORY):printf("VK_ERROR_OUT_OF_DEVICE_MEMORY\n");break;
      case(VK_ERROR_INITIALIZATION_FAILED):printf("VK_ERROR_INITIALIZATION_FAILED\n");break;
      case(VK_ERROR_LAYER_NOT_PRESENT):printf("VK_ERROR_LAYER_NOT_PRESENT\n");break;
      case(VK_ERROR_EXTENSION_NOT_PRESENT):printf("VK_ERROR_EXTENSION_NOT_PRESENT\n");break;
      default:printf("unknown error %d\n", res);break;
    }
    exit(-1);
  }
  if (enable_debug_layer) {

    VkDebugUtilsMessengerCreateInfoEXT createInfo = {0};
    createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
    createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    createInfo.pfnUserCallback = debugCallback;
    PFN_vkCreateDebugUtilsMessengerEXT fn_vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(inst, "vkCreateDebugUtilsMessengerEXT");
    if (!fn_vkCreateDebugUtilsMessengerEXT) {
        printf("vkCreateDebugUtilsMessengerEXT not found\n");
    } else {
      VkResult res = fn_vkCreateDebugUtilsMessengerEXT(inst, &createInfo, NULL, &debugMessenger);
      if (res != VK_SUCCESS)
      {
        debugMessenger = NULL;
        printf("CreateDebugUtilsMessengerEXT failed (%d)\n", res);
      }
    }
  }
  return inst;
}
#endif

static void destroyDebugCallback(VkInstance instance) {
  if (!debugMessenger)
    return;
  PFN_vkDestroyDebugUtilsMessengerEXT fn_vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
  if (fn_vkDestroyDebugUtilsMessengerEXT)
  {
    fn_vkDestroyDebugUtilsMessengerEXT(instance, debugMessenger, NULL);
  }
}

VkCommandPool createCmdPool(VulkanDevice *device) {
  VkResult res;
  /* Create a command pool to allocate our command buffer from */
  VkCommandPoolCreateInfo cmd_pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
  cmd_pool_info.queueFamilyIndex = device->graphicsQueueFamilyIndex;
  cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  VkCommandPool cmd_pool;
  res = vkCreateCommandPool(device->device, &cmd_pool_info, NULL, &cmd_pool);
  assert(res == VK_SUCCESS);
  return cmd_pool;
}
VkCommandBuffer* createCmdBuffer(VkDevice device, VkCommandPool cmd_pool, uint32_t command_buffer_count) {

    VkResult res;
    VkCommandBufferAllocateInfo cmd = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
    cmd.commandPool = cmd_pool;
    cmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    cmd.commandBufferCount = command_buffer_count;

    VkCommandBuffer *cmd_buffer = calloc(command_buffer_count, sizeof (VkCommandBuffer));
    res = vkAllocateCommandBuffers(device, &cmd, cmd_buffer);
    assert(res == VK_SUCCESS);
    return cmd_buffer;
}

bool memory_type_from_properties(VkPhysicalDeviceMemoryProperties memoryProps, uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) {
  // Search memtypes to find first index with those properties
  for (uint32_t i = 0; i < memoryProps.memoryTypeCount; i++) {
    if ((typeBits & 1) == 1) {
      // Type is available, does it match user properties?
      if ((memoryProps.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) {
        *typeIndex = i;
        return true;
      }
    }
    typeBits >>= 1;
  }
  // No memory types matched, return failure
  return false;
}
DepthBuffer createDepthBuffer(const VulkanDevice *device, int width, int height) {
  VkResult res;
  DepthBuffer depth;
  depth.format = VK_FORMAT_D24_UNORM_S8_UINT;

  #define dformats 3
  const VkFormat depth_formats[dformats] = {VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT};
  VkImageTiling image_tilling;
  for (int i=0;i<dformats;i++){
    VkFormatProperties fprops;
    vkGetPhysicalDeviceFormatProperties(device->gpu, depth_formats[i], &fprops);

    if (fprops.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
      depth.format=depth_formats[i];
      image_tilling = VK_IMAGE_TILING_LINEAR;
      break;
    } else if (fprops.optimalTilingFeatures  & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
      depth.format=depth_formats[i];
      image_tilling = VK_IMAGE_TILING_OPTIMAL;
      break;
    }
    if(i==dformats-1){
      printf("Failed to find supported depth format!\n");
      exit(-1);
    }
  }

  const VkFormat depth_format = depth.format;

  VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
  image_info.imageType = VK_IMAGE_TYPE_2D;
  image_info.format = depth_format;
  image_info.tiling = image_tilling;
  image_info.extent.width = width;
  image_info.extent.height = height;
  image_info.extent.depth = 1;
  image_info.mipLevels = 1;
  image_info.arrayLayers = 1;
  image_info.samples = VK_SAMPLE_COUNT_1_BIT;
  image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  image_info.queueFamilyIndexCount = 0;
  image_info.pQueueFamilyIndices = NULL;
  image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
  VkMemoryAllocateInfo mem_alloc = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};

  VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
  view_info.format = depth_format;
  view_info.components.r = VK_COMPONENT_SWIZZLE_R;
  view_info.components.g = VK_COMPONENT_SWIZZLE_G;
  view_info.components.b = VK_COMPONENT_SWIZZLE_B;
  view_info.components.a = VK_COMPONENT_SWIZZLE_A;
  view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
  view_info.subresourceRange.baseMipLevel = 0;
  view_info.subresourceRange.levelCount = 1;
  view_info.subresourceRange.baseArrayLayer = 0;
  view_info.subresourceRange.layerCount = 1;
  view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;

  if (depth_format == VK_FORMAT_D16_UNORM_S8_UINT || depth_format == VK_FORMAT_D24_UNORM_S8_UINT ||
      depth_format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
    view_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
  }

  VkMemoryRequirements mem_reqs;

  /* Create image */
  res = vkCreateImage(device->device, &image_info, NULL, &depth.image);
  assert(res == VK_SUCCESS);

  vkGetImageMemoryRequirements(device->device, depth.image, &mem_reqs);

  mem_alloc.allocationSize = mem_reqs.size;
  /* Use the memory properties to determine the type of memory required */

  bool pass =
      memory_type_from_properties(device->memoryProperties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex);
  assert(pass);

  /* Allocate memory */
  res = vkAllocateMemory(device->device, &mem_alloc, NULL, &depth.mem);
  assert(res == VK_SUCCESS);

  /* Bind memory */
  res = vkBindImageMemory(device->device, depth.image, depth.mem, 0);
  assert(res == VK_SUCCESS);

  /* Create image view */
  view_info.image = depth.image;
  res = vkCreateImageView(device->device, &view_info, NULL, &depth.view);
  assert(res == VK_SUCCESS);

  return depth;
}

static void setupImageLayout(VkCommandBuffer cmdbuffer, VkImage image,
                             VkImageAspectFlags aspectMask,
                             VkImageLayout old_image_layout,
                             VkImageLayout new_image_layout) {

  VkImageMemoryBarrier image_memory_barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
  image_memory_barrier.oldLayout = old_image_layout;
  image_memory_barrier.newLayout = new_image_layout;
  image_memory_barrier.image = image;

  VkImageSubresourceRange subresourceRange = {aspectMask, 0, 1, 0, 1};
  image_memory_barrier.subresourceRange = subresourceRange;

  if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
    /* Make sure anything that was copying from this image has completed */
    image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
  }

  if (new_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
    image_memory_barrier.dstAccessMask =
        VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
  }

  if (new_image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
    image_memory_barrier.dstAccessMask =
        VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
  }

  if (new_image_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
    /* Make sure any Copy or CPU writes to image are flushed */
    image_memory_barrier.dstAccessMask =
        VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
  }

  VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier;

  VkPipelineStageFlags src_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  VkPipelineStageFlags dest_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;

  vkCmdPipelineBarrier(cmdbuffer, src_stages, dest_stages, 0, 0, NULL,
                       0, NULL, 1, pmemory_barrier);
}

SwapchainBuffers createSwapchainBuffers(const VulkanDevice *device, VkFormat format, VkCommandBuffer cmdbuffer, VkImage image) {

  VkResult res;
  SwapchainBuffers buffer;
  VkImageViewCreateInfo color_attachment_view = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
  color_attachment_view.format = format;
  color_attachment_view.components.r = VK_COMPONENT_SWIZZLE_R;
  color_attachment_view.components.g = VK_COMPONENT_SWIZZLE_G;
  color_attachment_view.components.b = VK_COMPONENT_SWIZZLE_B;
  color_attachment_view.components.a = VK_COMPONENT_SWIZZLE_A;
  VkImageSubresourceRange subresourceRange = {0};
  subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  subresourceRange.baseMipLevel = 0;
  subresourceRange.levelCount = 1;
  subresourceRange.baseArrayLayer = 0;
  subresourceRange.layerCount = 1;

  color_attachment_view.subresourceRange = subresourceRange;
  color_attachment_view.viewType = VK_IMAGE_VIEW_TYPE_2D;

  buffer.image = image;

  setupImageLayout(
      cmdbuffer, image, VK_IMAGE_ASPECT_COLOR_BIT,
      VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);

  color_attachment_view.image = buffer.image;

  res = vkCreateImageView(device->device, &color_attachment_view, NULL,
                          &buffer.view);
  assert(res == VK_SUCCESS);
  return buffer;
}

VkRenderPass createRenderPass(VkDevice device, VkFormat color_format, VkFormat depth_format) {
  VkAttachmentDescription attachments[2] = {{0}};
  attachments[0].format = color_format;
  attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
  attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
  attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
  attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

  attachments[1].format = depth_format;
  attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
  attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
  attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
  attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

  VkAttachmentReference color_reference = {0};
  color_reference.attachment = 0;
  color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

  VkAttachmentReference depth_reference = {0};
  depth_reference.attachment = 1;
  depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

  VkSubpassDescription subpass = {0};
  subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
  subpass.flags = 0;
  subpass.inputAttachmentCount = 0;
  subpass.pInputAttachments = NULL;
  subpass.colorAttachmentCount = 1;
  subpass.pColorAttachments = &color_reference;
  subpass.pResolveAttachments = NULL;
  subpass.pDepthStencilAttachment = &depth_reference;
  subpass.preserveAttachmentCount = 0;
  subpass.pPreserveAttachments = NULL;

  VkRenderPassCreateInfo rp_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
  rp_info.attachmentCount = 2;
  rp_info.pAttachments = attachments;
  rp_info.subpassCount = 1;
  rp_info.pSubpasses = &subpass;
  VkRenderPass render_pass;
  VkResult res;
  res = vkCreateRenderPass(device, &rp_info, NULL, &render_pass);
  assert(res == VK_SUCCESS);
  return render_pass;
}
FrameBuffers createFrameBuffers(const VulkanDevice *device, VkSurfaceKHR surface, VkQueue queue, int winWidth, int winHeight, VkSwapchainKHR oldSwapchain) {

  VkResult res;

  VkBool32 supportsPresent;
  vkGetPhysicalDeviceSurfaceSupportKHR(device->gpu, device->graphicsQueueFamilyIndex, surface, &supportsPresent);
  if (!supportsPresent) {
    exit(-1); //does not supported.
  }
  VkCommandBuffer *setup_cmd_buffer = createCmdBuffer(device->device, device->commandPool, 1);

  const VkCommandBufferBeginInfo cmd_buf_info = {
      VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
  };
  vkBeginCommandBuffer(setup_cmd_buffer[0], &cmd_buf_info);

  VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM;
  VkColorSpaceKHR colorSpace;
  {
    // Get the list of VkFormats that are supported:
    uint32_t formatCount;
    res = vkGetPhysicalDeviceSurfaceFormatsKHR(device->gpu, surface, &formatCount, NULL);
    assert(res == VK_SUCCESS);
    VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
    res = vkGetPhysicalDeviceSurfaceFormatsKHR(device->gpu, surface, &formatCount, surfFormats);
    assert(res == VK_SUCCESS);
    // If the format list includes just one entry of VK_FORMAT_UNDEFINED,
    // the surface has no preferred format.  Otherwise, at least one
    // supported format will be returned.
    if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) {
      colorFormat = VK_FORMAT_B8G8R8A8_UNORM;
    } else {
      assert(formatCount >= 1);
      colorFormat = surfFormats[0].format;
    }
    colorSpace = surfFormats[0].colorSpace;
    free(surfFormats);
  }
  colorFormat = VK_FORMAT_B8G8R8A8_UNORM;

  // Check the surface capabilities and formats
  VkSurfaceCapabilitiesKHR surfCapabilities;
  res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
      device->gpu, surface, &surfCapabilities);
  assert(res == VK_SUCCESS);

  VkExtent2D buffer_size;
  // width and height are either both -1, or both not -1.
  if (surfCapabilities.currentExtent.width == (uint32_t)-1) {
    buffer_size.width = winWidth;
    buffer_size.height = winHeight;
  } else {
    // If the surface size is defined, the swap chain size must match
    buffer_size = surfCapabilities.currentExtent;
  }

  DepthBuffer depth = createDepthBuffer(device, buffer_size.width, buffer_size.height);

  VkRenderPass render_pass = createRenderPass(device->device, colorFormat, depth.format);

  VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;

  uint32_t presentModeCount;
  vkGetPhysicalDeviceSurfacePresentModesKHR(device->gpu, surface, &presentModeCount, NULL);
  assert(presentModeCount > 0);

  VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(sizeof(VkPresentModeKHR) * presentModeCount);
  vkGetPhysicalDeviceSurfacePresentModesKHR(device->gpu, surface, &presentModeCount, presentModes);

  for (size_t i = 0; i < presentModeCount; i++) {
    if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
      swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
      break;
    }
    if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR) && (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)) {
      swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
    }
  }
  free(presentModes);

  VkSurfaceTransformFlagBitsKHR preTransform;
  if (surfCapabilities.supportedTransforms &
      VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
    preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
  } else {
    preTransform = surfCapabilities.currentTransform;
  }

  // Determine the number of VkImage's to use in the swap chain (we desire to
  // own only 1 image at a time, besides the images being displayed and
  // queued for display):
  uint32_t desiredNumberOfSwapchainImages =
      fmax(surfCapabilities.minImageCount+1, 3);
  if ((surfCapabilities.maxImageCount > 0) &&
      (desiredNumberOfSwapchainImages > surfCapabilities.maxImageCount)) {
    // Application must settle for fewer images than desired:
    desiredNumberOfSwapchainImages = surfCapabilities.maxImageCount;
  }

  VkSwapchainCreateInfoKHR swapchainInfo = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
  swapchainInfo.surface = surface;
  swapchainInfo.minImageCount = desiredNumberOfSwapchainImages;
  swapchainInfo.imageFormat = colorFormat;
  swapchainInfo.imageColorSpace = colorSpace;
  swapchainInfo.imageExtent = buffer_size;
  swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
  swapchainInfo.preTransform = preTransform;
  swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  swapchainInfo.imageArrayLayers = 1;
  swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
  swapchainInfo.presentMode = swapchainPresentMode;
  swapchainInfo.oldSwapchain = oldSwapchain;
  swapchainInfo.clipped = true;

  VkSwapchainKHR swap_chain;
  res = vkCreateSwapchainKHR(device->device, &swapchainInfo, NULL,
                             &swap_chain);
  assert(res == VK_SUCCESS);

  if (oldSwapchain != VK_NULL_HANDLE) {
    vkDestroySwapchainKHR(device->device, oldSwapchain, NULL);
  }

  uint32_t swapchain_image_count;
  res = vkGetSwapchainImagesKHR(device->device, swap_chain,
                                &swapchain_image_count, NULL);
  assert(res == VK_SUCCESS);

  VkImage *swapchainImages =
      (VkImage *)malloc(swapchain_image_count * sizeof(VkImage));

  assert(swapchainImages);

  res = vkGetSwapchainImagesKHR(device->device, swap_chain,
                                &swapchain_image_count,
                                swapchainImages);
  assert(res == VK_SUCCESS);

  SwapchainBuffers *swap_chain_buffers = (SwapchainBuffers *)malloc(swapchain_image_count * sizeof(SwapchainBuffers));
  for (uint32_t i = 0; i < swapchain_image_count; i++) {
    swap_chain_buffers[i] = createSwapchainBuffers(device, colorFormat, setup_cmd_buffer[0], swapchainImages[i]);
  }
  free(swapchainImages);

  VkImageView attachments[2];
  attachments[1] = depth.view;

  VkFramebufferCreateInfo fb_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
  fb_info.renderPass = render_pass;
  fb_info.attachmentCount = 2;
  fb_info.pAttachments = attachments;
  fb_info.width = buffer_size.width;
  fb_info.height = buffer_size.height;
  fb_info.layers = 1;
  uint32_t i;

  VkFramebuffer *framebuffers = (VkFramebuffer *)malloc(swapchain_image_count *
                                                        sizeof(VkFramebuffer));
  assert(framebuffers);

  for (i = 0; i < swapchain_image_count; i++) {
    attachments[0] = swap_chain_buffers[i].view;
    res = vkCreateFramebuffer(device->device, &fb_info, NULL,
                              &framebuffers[i]);
    assert(res == VK_SUCCESS);
  }

  vkEndCommandBuffer(setup_cmd_buffer[0]);
  VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
  submitInfo.commandBufferCount = 1;
  submitInfo.pCommandBuffers = setup_cmd_buffer;

  vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
  vkQueueWaitIdle(queue);

  vkFreeCommandBuffers(device->device, device->commandPool, 1, setup_cmd_buffer);
  free(setup_cmd_buffer);

  FrameBuffers buffer = {0};
  buffer.swap_chain = swap_chain;
  buffer.swap_chain_buffers = swap_chain_buffers;
  buffer.swapchain_image_count = swapchain_image_count;
  buffer.framebuffers = framebuffers;
  buffer.current_buffer = 0;
  buffer.format = colorFormat;
  buffer.buffer_size = buffer_size;
  buffer.render_pass = render_pass;
  buffer.depth = depth;
  buffer.present_complete_semaphore = (VkSemaphore *)calloc(swapchain_image_count, sizeof(VkSemaphore));
  buffer.render_complete_semaphore = (VkSemaphore *)calloc(swapchain_image_count, sizeof(VkSemaphore));
  buffer.flight_fence = (VkFence *)calloc(swapchain_image_count, sizeof(VkFence));

  VkSemaphoreCreateInfo presentCompleteSemaphoreCreateInfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
  VkFenceCreateInfo fenceCreateInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
  for (i = 0; i < swapchain_image_count; i++) {
    res = vkCreateSemaphore(device->device, &presentCompleteSemaphoreCreateInfo, NULL, &buffer.present_complete_semaphore[i]);
    assert(res == VK_SUCCESS);

    res = vkCreateSemaphore(device->device, &presentCompleteSemaphoreCreateInfo, NULL, &buffer.render_complete_semaphore[i]);
    assert(res == VK_SUCCESS);

    res = vkCreateFence(device->device, &fenceCreateInfo, NULL, &buffer.flight_fence[i]);
    assert(res == VK_SUCCESS);
  }

  return buffer;
}
void destroyFrameBuffers(const VulkanDevice *device, FrameBuffers *buffer, VkQueue queue) {
  VkResult res = vkQueueWaitIdle(queue);
  assert(res == VK_SUCCESS);

  for (uint32_t i = 0; i < buffer->swapchain_image_count; ++i) {
    if (buffer->present_complete_semaphore[i] != VK_NULL_HANDLE) {
      vkDestroySemaphore(device->device, buffer->present_complete_semaphore[i], NULL);
    }
    if (buffer->render_complete_semaphore[i] != VK_NULL_HANDLE) {
      vkDestroySemaphore(device->device, buffer->render_complete_semaphore[i], NULL);
    }
    if (buffer->flight_fence[i] != VK_NULL_HANDLE) {
      vkDestroyFence(device->device, buffer->flight_fence[i], NULL);
    }
  }

  for (uint32_t i = 0; i < buffer->swapchain_image_count; ++i) {
    vkDestroyImageView(device->device, buffer->swap_chain_buffers[i].view, 0);
    vkDestroyFramebuffer(device->device, buffer->framebuffers[i], 0);
  }

  vkDestroyImageView(device->device, buffer->depth.view, 0);
  vkDestroyImage(device->device, buffer->depth.image, 0);
  vkFreeMemory(device->device, buffer->depth.mem, 0);

  vkDestroyRenderPass(device->device, buffer->render_pass, 0);
  vkDestroySwapchainKHR(device->device, buffer->swap_chain, 0);

  free(buffer->framebuffers);
  free(buffer->swap_chain_buffers);
  free(buffer->present_complete_semaphore);
  free(buffer->render_complete_semaphore);
  free(buffer->flight_fence);
}
