summaryrefslogblamecommitdiffstats
path: root/src/render.c
blob: b6383c42fac39b73e9859c279278f8e644851d59 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                   



                            
                               



                               


                            




                                      


                                

                              
 




                        


                          
                                         

                               
                                                                        
 

                                         
  

                                                                      


















                                              
         
           
              





                                                                 
 


                                                                
 











                                                                







                                                                                     
                                                                                   



                                                 
                                             


                                               














                                                       







                                                                 
















                                                                








                                                                                 




                                                                                               




                                                                                    

                                                 










                                  
























                                                                                                                                                                                 
 

                                                                                                               
                                                           




                                                  































































                                                                                                                             
                                                                                       












































































                                                                                                                     
                                                                                    




















                                                                                                          





                                                                        



























































                                                                                                                       
                                             
               















                                                                              
                                                 











                                                                                            















                                                                  
                                                    
          


                                




                                             


                             
   

 
    
                                           
 



                                                                               






                                                                               






                                                                              
 












                                                                                               





                                                                                          
                                                   























































                                                                                                                         
                                                                                                      

                                                                                     
                                                                                                                        






                                                      




















                                                                                 



                                                            
                                                                                                                                           







































































                                                                                                  

























































































































































































                                                                                                                                   
    
                                                           
 
                                                                                                                
                     
                                                   


                                       
   

                                                                                                          
                                                                                                                  
                     
                                                     


                                       
   

                                                                                                          


























                                                                                              


                                       



                                                                            
                                                               






                                                             
                                                                   

                                        
                                                               






                                                               




                                                               



                                                                                    
                                                                                        






























                                                                                    
                                        

                                          

                                                           






















                                                                                                                                                  


                                                                                 
                                        


















                                                                                                            












                                                                                  



                                                                    

                                                 




                                                                       
                                                       




                                                    
                                                  


                                                 
                                           







                                                                                                                              





                                          





                                                             





                                                                                           
                                               


                                                                   
                                                                                  




                              






                                                                     
 
                                                                                                










                                                                       
                                                            

                                             
 




                                                                                                 
 
                                                  




                                                                         









                                                                                 


                                                         

                                                 


                                                     
                                                    

                                                     
 

                                                                   

















                                                                      




                                                                              
 
  
                                                            

                                   






                                                                      
 
                                              












                                                                


                                                                                                           


   











                                                            
                                                                   








                                                                                         

                                                                          
 
                                                                       

 

                                                                             




                                                                         
                                       

 











                                                                                          
 
             
                                                                                   







                                                                                    
                                                             
















                                                                                          
 
             
                                                                                   







                                                                                   
                                                            











                                                                      

                                                                                            

                                                         








                                                                                      

                                                                         

                                                   
 
                                                                                                     










                                                                                            
                                                                                        
 
                                                                                                                                      





                               




                                                                


                                                                 

                                                   

                                          
                                                                                        










                                                            






                                                                     
 
                                                                                                



                                                     
                                                      


                                                   






















                                                                                                   



    

                  



                                                               














                                                                          
                                  


    












                                                                             












                                     


                                                          

                  






                                                              
              










                                                              

                                    





                                                       















































                                             











































































































                                                                                                                                                                              





                                                            



                                                                        





                                    
                                        
                                                  
                               



                                     









                                  




                                                     


                                                                                 





                                                             





                                                                 
                                                     

                                                                          


                                                                              
 






                                                               






                                                                                   






                                               






                                        
 







                                               
                                                  

                                                          
 


                                          
                              
 
                                



                                                    
                               
 
                
                                                                        

                               
                                   
 





                                                                                                     
                                                                                     
                                                                            
 


                                    
                                                                             



              
                                                                                                

                      
                                                                                                                                                                   









                                                                   
                                                                         
 

                                                                                                    

                                                                            
 


                                                   
                                                                                    




                                                                                      
                                                                         
 
                                                                                      


                                                  
                                                                                                                 














                                                                

                                                                                                  



















                                                              
 
                 


















                                                                           

                
                        


                          
                      








                                 
 
 
#include <stddef.h>
#include <stdlib.h>
#include <time.h>

#include <shaderc/shaderc.h>
#include <vulkan/vulkan_core.h>

#define SDL_MAIN_HANDLED
#define VK_USE_PLATFORM_XCB_KHR
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>

#include <vulkan/vulkan.h>

#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "vk_mem_alloc.h"

#define STB_IMAGE_IMPLEMENTATION
#include "../lib/stb_image.h"

#define VKSETUP_IMPLEMENTATION
#include "vksetup.h"

//#include "cplusplus.h"
#include "vkutil.h"
#include "state.h"

// embedded clgm library
uint32_t currentFrame = 0;
state_t s;

const char *const validation_layers[] = {
  "VK_LAYER_KHRONOS_validation"
};
const uint32_t validation_layer_count = VK_ARRAY_LEN(validation_layers);

const char *const device_extensions[] = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
const uint32_t deviceExtensionCount = VK_ARRAY_LEN(device_extensions);


#ifdef VKDEBUG
    const bool enableValidationLayers = true;
#else
    const bool enableValidationLayers = false;
#endif

typedef struct {
  float x;
  float y;
} V2;

typedef struct {
  float x;
  float y;
  float z;
} V3;

typedef struct {
  V3 pos;
  V3 color;
  V2 texCoord;
} Vertex;

/* Vertex vertices[] = { */
/*   (Vertex) { (V2) {-0.2f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */
/*   (Vertex) { (V2) {0.5f,  0.3f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/*   (Vertex) { (V2) {-0.5f, 0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */

/*   (Vertex) { (V2) {0.2f, -0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/*   (Vertex) { (V2) {0.5f,  0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */
/*   (Vertex) { (V2) {-0.5f, 0.3f}, (V3) {0.0f, 1.0f, 0.0f}}, */

/*   (Vertex) { (V2) {0.0f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}}, */
/*   (Vertex) { (V2) {0.5f,  0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */
/*   (Vertex) { (V2) {-0.5f, 0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/* }; */
/* const int VERTICES_SIZE = VK_ARRAY_LEN(vertices); */

/* const uint16_t indices[] = { */
/*   0, 1, 2, 3, 4, 5, 6, 7, 8, */
/* }; */
/* const int INDICES_SIZE = VK_ARRAY_LEN(indices); */

Vertex vertices[] = {
  (Vertex) { (V3) {-0.5f, -0.5f, 0.0f}, (V3) {1.0f, 0.0f, 0.0f}, (V2) {0.0f, 0.0f}},
  (Vertex) { (V3) {0.5f,  -0.5f, 0.0f}, (V3) {0.0f, 1.0f, 0.0f}, (V2) {1.0f, 0.0f}},
  (Vertex) { (V3) {0.5f,   0.5f, 0.0f}, (V3) {0.0f, 0.0f, 1.0f}, (V2) {1.0f, 1.0f}},
  (Vertex) { (V3) {-0.5f,  0.5f, 0.0f}, (V3) {1.0f, 1.0f, 1.0f}, (V2) {0.0f, 1.0f}},
  (Vertex) { (V3) {-0.5f, -0.5f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}, (V2) {0.0f, 0.0f}},
  (Vertex) { (V3) {0.5f,  -0.5f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}, (V2) {1.0f, 0.0f}},
  (Vertex) { (V3) {0.5f,   0.5f, -0.5f}, (V3) {0.0f, 0.0f, 1.0f}, (V2) {1.0f, 1.0f}},
  (Vertex) { (V3) {-0.5f,  0.5f, -0.5f}, (V3) {1.0f, 1.0f, 1.0f}, (V2) {0.0f, 1.0f}},
  (Vertex) { (V3) {0.0f,  0.0f, 0.5f}, (V3) {1.0f, 1.0f, 1.0f}, (V2) {0.0f, 0.0f}},
};
const int VERTICES_SIZE = VK_ARRAY_LEN(vertices);

const uint16_t indices[] = {
  0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 5, 4
};
const int INDICES_SIZE = VK_ARRAY_LEN(indices);

/* Vertex vertices[] = { */
/*   (Vertex) { (V3) {0}, (V3) {0}, (V2) {0}}, */
/*   (Vertex) { (V3) {1}, (V3) {0}, (V2) {0}}, */
/*   (Vertex) { (V3) {2}, (V3) {0}, (V2) {0}}, */
/*   (Vertex) { (V3) {3}, (V3) {0}, (V2) {0}}, */
/*   (Vertex) { (V3) {4}, (V3) {0}, (V2) {0}}, */
/*   (Vertex) { (V3) {5}, (V3) {0}, (V2) {0}}, */
/* }; */
/* const int VERTICES_SIZE = VK_ARRAY_LEN(vertices); */

/* const uint16_t indices[] = { */
/*   0,0,0,0,0,0 */
/* }; */
/* const int INDICES_SIZE = VK_ARRAY_LEN(indices); */

static int resizing_event_watcher(void* data, SDL_Event* event) {
  if (event->type == SDL_WINDOWEVENT &&
      event->window.event == SDL_WINDOWEVENT_RESIZED) {
    s.sdl_window_resized = 1;
  }
  return 0;
}

void move_towards(vec3 position, vec3 front, float step) {
  // Calculate the direction vector from position to front
  float direction[3] = {
    front[0] - position[0],
    front[1] - position[1],
    front[2] - position[2]
  };

  // Normalize the direction vector
  glm_normalize(direction);

  // Move position along the direction vector by the step amount
  position[0] += direction[0] * step;
  position[1] += direction[1] * step;
  position[2] += direction[2] * step;
}

bool
init()
{
  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
    vk_log(VK_INFO, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
    return false;
  }

  s.sdl_window = SDL_CreateWindow("Vulkanizater",
                                  SDL_WINDOWPOS_UNDEFINED,
                                  SDL_WINDOWPOS_UNDEFINED,
                                  s.window_w,
                                  s.window_h,
                                  SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN);
  if (s.sdl_window == NULL) {
    vk_log(VK_INFO, "Window could not be created! SDL_Error: %s\n", SDL_GetError());
    return false;
  }

  SDL_AddEventWatch(resizing_event_watcher,NULL);

  return true;
}

void
closeSDL()
{
  SDL_DestroyWindow(s.sdl_window);
  s.sdl_window = NULL;
  SDL_Quit();
}

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

void
vulkan_setup_debug_messenger()
{
  if (!enableValidationLayers) return;
  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_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;
  createInfo.pUserData = NULL; // Optional


  // TODO: func pointers returned are NULL
  s.pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkCreateDebugUtilsMessengerEXT");
  s.pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkDestroyDebugUtilsMessengerEXT");

  VkResult result = s.pfnCreateDebugUtilsMessengerEXT(s.vk_instance, &createInfo, NULL, &s.vk_debug_messenger);
  if (result != VK_SUCCESS) {
    vk_log(VK_WARN, "failed to set up debug messenger!\n");
  } else {
    vk_log(VK_INFO, "Debug messanger created!\n");
  }
}

VkExtent2D
chooseSwapExtent(const VkSurfaceCapabilitiesKHR * capabilities)
{
  if (capabilities->currentExtent.width != UINT32_MAX) {
    return capabilities->currentExtent;
  } else {
    int width, height;
    SDL_GetWindowSize(s.sdl_window, &width, &height);

    VkExtent2D actualExtent;
    actualExtent.width = (uint32_t) width;
    actualExtent.height = (uint32_t) height;

    // Manual implementation of std::clamp since it is not available in C
    actualExtent.width = (actualExtent.width < capabilities->minImageExtent.width) ? capabilities->minImageExtent.width :
                         (actualExtent.width > capabilities->maxImageExtent.width) ? capabilities->maxImageExtent.width :
                         actualExtent.width;

    actualExtent.height = (actualExtent.height < capabilities->minImageExtent.height) ? capabilities->minImageExtent.height :
                          (actualExtent.height > capabilities->maxImageExtent.height) ? capabilities->maxImageExtent.height :
                          actualExtent.height;

    return actualExtent;
  }
}

VkSurfaceFormatKHR
chooseSwapSurfaceFormat(const VkSurfaceFormatKHR * availableFormats, uint32_t formatCount)
{
  for (uint32_t i = 0 ; i < formatCount; i ++) {
    if (availableFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
        availableFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
      return availableFormats[i];
    }
  }

  // if it fails pick the first one
  return availableFormats[0];
}

VkPresentModeKHR
chooseSwapPresentMode(const VkPresentModeKHR * presentModes, uint32_t presentModeCount)
{
  for (uint32_t i = 0 ; i < presentModeCount; i ++) {
    if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
      return presentModes[i];
    }
  }

  // if it fails pick the the FIFO one
  return VK_PRESENT_MODE_FIFO_KHR;
}

typedef struct SwapChainSupportDetails {
  VkSurfaceCapabilitiesKHR capabilities;
  VkSurfaceFormatKHR formats[100];
  uint32_t formatCount;
  VkPresentModeKHR presentModes[100];
  uint32_t presentModeCount;
} SwapChainSupportDetails;

SwapChainSupportDetails
querySwapChainSupport(VkPhysicalDevice device)
{
  // TODO Make SwapChainSupportDetails malloc it;s arrays and free it after it is used.
  SwapChainSupportDetails details;

  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, s.vk_surface, &details.capabilities);

  vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, NULL);

  if (details.formatCount != 0) {
    // todo alloc format arrray
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, details.formats);
  }

  vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, NULL);

  if (details.presentModeCount != 0) {
    // todo alloc presentModes array
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, details.presentModes);
  }

  return details;
}

typedef struct QueueFamilyIndices {
  uint32_t graphicsFamily;
  bool graphicsFlag;
  uint32_t presentFamily;
  bool presentFlag;
} QueueFamilyIndices;

bool
vulkan_queue_family_check_flags(QueueFamilyIndices x)
{
  return x.graphicsFlag && x.presentFlag;
}

QueueFamilyIndices vulkan_find_queue_families(VkPhysicalDevice device) {
  QueueFamilyIndices indices;
  indices.graphicsFlag = false;
  indices.presentFlag = false;

  uint32_t queueFamilyCount = 0;
  vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL);

  VkQueueFamilyProperties queueFamilies[queueFamilyCount];
  vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies);

  for (uint32_t i = 0; i < queueFamilyCount; i++) {
    if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
      indices.graphicsFamily = i;
      indices.graphicsFlag = true;
    }

    VkBool32 presentSupport = false;
    vkGetPhysicalDeviceSurfaceSupportKHR(device, i, s.vk_surface, &presentSupport);
    if (presentSupport) {
      indices.presentFamily = i;
      indices.presentFlag = true;
    }

    if (vulkan_queue_family_check_flags(indices)) break;
  }

  return indices;
}

bool
vulkan_check_device_extension_support(VkPhysicalDevice device)
{
  uint32_t extensionCount;
  vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, NULL);

  VkExtensionProperties availableExtensions[extensionCount];
  vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, availableExtensions);

  uint32_t flag = 0;

  for (uint32_t i = 0; i < deviceExtensionCount; i++) {
    for (uint32_t j = 0; j < extensionCount; j++) {
      if (strcmp(device_extensions[i], availableExtensions[j].extensionName) == 0) {
        flag++;
        break;
      }
    }
  }

  return flag == deviceExtensionCount;
}

bool
vulkan_is_device_suitable(VkPhysicalDevice device)
{
  QueueFamilyIndices indices = vulkan_find_queue_families(device);
  bool extensionsSupported = vulkan_check_device_extension_support(device);

  bool swapChainAdequate = false;
  if (extensionsSupported) {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    swapChainAdequate = !(swapChainSupport.formatCount == 0) && !(swapChainSupport.presentModeCount == 0);
  }

  
  VkPhysicalDeviceFeatures supportedFeatures;
  vkGetPhysicalDeviceFeatures(device, &supportedFeatures);

  return vulkan_queue_family_check_flags(indices) && extensionsSupported
         && swapChainAdequate && supportedFeatures.samplerAnisotropy;
}

void
vulkan_pick_physical_device()
{
  uint32_t deviceCount = 0;
  vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, NULL);
  if (deviceCount == 0) {
    vk_log(VK_INFO, "failed to find GPUs with Vulkan support!\n");
  }

  VkPhysicalDevice devices[deviceCount];
  vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, devices);

  for (uint32_t i = 0; i < deviceCount; i++) {
    if (vulkan_is_device_suitable(devices[i])) {
      s.vk_physical_device = devices[i];
      break;
    }
  }

  if (s.vk_physical_device == VK_NULL_HANDLE) {
    vk_log(VK_ERROR, "failed to find a suitable GPU!\n");
  }

  VkPhysicalDeviceProperties deviceProperties;
  vkGetPhysicalDeviceProperties(s.vk_physical_device, &deviceProperties);
  vk_log(VK_INFO, "Picked [%s] physical device.\n", deviceProperties.deviceName);

  uint32_t extensionCount = 0;
  vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
  VkExtensionProperties extensions[extensionCount];
  VkResult result = vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions);

  vk_log(VK_INFO, "Vulkan enabled extensions: %s\n", string_VkResult(result));
  for (uint32_t i = 0; i < extensionCount; i++) {
    vk_log(VK_INFO, "\t%s\n", extensions[i].extensionName);
  }

}

void
vulkan_create_logical_device()
{
  QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device);

  // TODO CREATE MULPILE QUEUES
  // https://vulkan-tutorial.com/en/Drawing_a_triangle/Presentation/Window_surface#page_Creating-the-presentation-queue

  VkDeviceQueueCreateInfo queueCreateInfo = {
    .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
    .queueFamilyIndex = indices.graphicsFamily,
    .queueCount = 1,
  };

  float queuePriority = 1.0f;
  queueCreateInfo.pQueuePriorities = &queuePriority;

  VkPhysicalDeviceFeatures deviceFeatures = {0};
  vkGetPhysicalDeviceFeatures(s.vk_physical_device, &deviceFeatures);
  deviceFeatures.samplerAnisotropy = VK_TRUE;
#ifndef VKDEBUG
  /* Disable robust buffer access when building without debug */
  deviceFeatures.robustBufferAccess = VK_FALSE;
#endif

  VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamic_rendering_feature = {
    .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
    .dynamicRendering = VK_TRUE,
  };

  VkDeviceCreateInfo createInfo = {
    .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
    .pQueueCreateInfos = &queueCreateInfo,
    .queueCreateInfoCount = 1,
    .pNext = &dynamic_rendering_feature,
    .pEnabledFeatures = &deviceFeatures,
    .enabledExtensionCount = deviceExtensionCount,
    .ppEnabledExtensionNames = device_extensions,
    .enabledLayerCount = 0,
  };

  if (vkCreateDevice(s.vk_physical_device, &createInfo, NULL, &s.vk_device) != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to create logical device!\n");
  }
  vk_log(VK_INFO, "Vulkan logical device created\n");

  vkGetDeviceQueue(s.vk_device, indices.graphicsFamily, 0, &s.vk_graphics_queue);
  vkGetDeviceQueue(s.vk_device, indices.presentFamily, 0, &s.vk_present_queue);
}

void move_relative(vec3 position, vec3 front, float step, int x) {
  // Calculate the direction vector from position to front
  float direction_vec[3] = {
    front[0] - position[0],
    front[1] - position[1],
    front[2] - position[2]
  };

  // Normalize the direction vector
  glm_normalize(direction_vec);

  // Calculate the right vector
  vec3 right;
  glm_vec3_cross(GLM_ZUP, direction_vec, right);
  glm_normalize(right);

  // Move front according to the specified direction
  if (x) {
    front[0] += right[0] * step;
    front[1] += right[1] * step;
    front[2] += right[2] * step;
  } else {
    // Calculate the up vector
    vec3 up;
    glm_vec3_cross(right, direction_vec, up);
    glm_normalize(up);
    front[0] += up[0] * step;
    front[1] += up[1] * step;
    front[2] += up[2] * step;
  }
}

void
update_camera(float xoffset, float yoffset)
{
  s.camera.yaw += xoffset;
  s.camera.pitch += yoffset;

  // Make sure that when pitch is out of bounds, the screen doesn't get flipped
  /* if (s.camera.pitch > 89.0f) */
  /*   s.camera.pitch = 89.0f; */
  /* if (s.camera.pitch < -89.0f) */
  /*   s.camera.pitch = -89.0f; */

  move_relative(s.camera.pos, s.camera.front, xoffset / 100.0, 1 /* x axis */);
  move_relative(s.camera.pos, s.camera.front, yoffset / 100.0, 0 /* y axis */);

  /* vec3 front; */
  /* front[0] = -sin(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */
  /* front[1] = sin(glm_rad(s.camera.pitch)); */
  /* front[2] = -cos(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */
  /* glm_normalize_to(front, s.camera.front); */
}

void
mouseCallback(SDL_Event *event)
{
  if (event->type != SDL_MOUSEMOTION) return;
  if (!s.mouse_pressed) return;
  float xoffset = event->motion.xrel;
  float yoffset = -event->motion.yrel; // Reversed since y-coordinates range from bottom to top

  float sensitivity = 0.1f; // Change this value to your liking
  xoffset *= sensitivity;
  yoffset *= sensitivity;

  update_camera(xoffset, yoffset);
}

void
vulkan_create_surface()
{
  if (SDL_Vulkan_CreateSurface(s.sdl_window, s.vk_instance, &s.vk_surface) == SDL_FALSE) {
    vk_log(VK_ERROR, "Failed to create surface\n");
  } else {
    vk_log(VK_INFO, "Vulkan surface created\n");
  }
}

void
vulkan_create_swap_chain()
{
  SwapChainSupportDetails swapChainSupport = querySwapChainSupport(s.vk_physical_device);

  VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats, swapChainSupport.formatCount);
  VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes, swapChainSupport.presentModeCount);
  VkExtent2D extent = chooseSwapExtent(&swapChainSupport.capabilities);

  uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;

  if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
  }

  VkSwapchainCreateInfoKHR createInfo = {
    .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
    .surface = s.vk_surface,
    .minImageCount = imageCount,
    .imageFormat = surfaceFormat.format,
    .imageColorSpace = surfaceFormat.colorSpace,
    .imageExtent = extent,
    .imageArrayLayers = 1,
    .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
  };

  QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device);

  uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily};

  if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
  } else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = NULL; // Optional
  }

  createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
  createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  createInfo.presentMode = presentMode;
  createInfo.clipped = VK_TRUE;
  createInfo.oldSwapchain = VK_NULL_HANDLE;

  VK_CHECK(vkCreateSwapchainKHR(s.vk_device, &createInfo, NULL, &s.vk_swap_chain));
  /* if (result != VK_SUCCESS) { */
  /*   vk_log(VK_ERROR, "ERROR: failed to create swap chain!  %s\n", string_VkResult(result)); */
  /* } */

  VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, NULL));
  //vk_log(VK_INFO, "vk_swap_chain_images count: %d\n", s.vk_swap_chain_image_count);
  // todo alloc space for images
  VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, s.vk_swap_chain_images));

  s.vk_swap_chain_image_format = surfaceFormat.format;
  s.vk_swap_chain_extent = extent;

  vk_log(VK_INFO, "Vulkan swapchain created!\n");
}


VkImageView
create_image_view(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
{
  VkImageViewCreateInfo viewInfo = {0};
  viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
  viewInfo.image = image;
  viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
  viewInfo.format = format;
  viewInfo.subresourceRange.aspectMask = aspectFlags;
  viewInfo.subresourceRange.baseMipLevel = 0;
  viewInfo.subresourceRange.levelCount = 1;
  viewInfo.subresourceRange.baseArrayLayer = 0;
  viewInfo.subresourceRange.layerCount = 1;

  VkImageView imageView;
  VK_CHECK(vkCreateImageView(s.vk_device, &viewInfo, NULL, &imageView));
  
  return imageView;
}

void
vulkan_create_image_views()
{
  for (size_t i = 0; i < s.vk_swap_chain_image_count; i++) {
    s.vk_swap_chain_image_views[i] = create_image_view(s.vk_swap_chain_images[i], s.vk_swap_chain_image_format, VK_IMAGE_ASPECT_COLOR_BIT);
  }
  vk_log(VK_INFO, "Vulkan image views created!\n");
}

VkShaderModule
createShaderModule(const char * code, long size)
{
  VkShaderModuleCreateInfo createInfo = {0};
  createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
  createInfo.codeSize = size;
  createInfo.pCode = (const uint32_t *)code;

  VkShaderModule shaderModule;
  if (vkCreateShaderModule(s.vk_device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to create shader module!\n");
  }

  return shaderModule;
}

shaderc_compilation_result_t
load_compile_shader_data(const char * path, shaderc_shader_kind shader_kind)
{
  FILE* file = fopen(path, "r");
  if (!file) {
    vk_log(VK_ERROR, "SHADER COMPILATION: Failed to open file: %s\n", path);
    return NULL;
  }

  fseek(file, 0, SEEK_END);
  long glsl_length = ftell(file);
  fseek(file, 0, SEEK_SET);

  char* glsl_src = (char*)malloc(glsl_length + 1);
  if (!glsl_src) {
    vk_log(VK_ERROR, "SHADER COMPILATION: Failed to allocate memory\n");
    fclose(file);
    return NULL;
  }

  fread(glsl_src, 1, glsl_length, file);
  glsl_src[glsl_length] = '\0';
  fclose(file);

  shaderc_compiler_t compiler = shaderc_compiler_initialize();
  if (!compiler) {
    vk_log(VK_ERROR, "SHADER COMPILATION: Failed to initialize shader compiler\n");
    free(glsl_src);
    return NULL;
  }

  shaderc_compile_options_t options = shaderc_compile_options_initialize();
  shaderc_compilation_result_t result = shaderc_compile_into_spv(compiler,
                                                                 glsl_src,
                                                                 glsl_length,
                                                                 shader_kind,
                                                                 path, "main", options);

  if (shaderc_result_get_compilation_status(result) != shaderc_compilation_status_success) {
    vk_log(VK_ERROR, "SHADER COMPILATION: error: %s\n", shaderc_result_get_error_message(result));
    shaderc_result_release(result);
    shaderc_compiler_release(compiler);
    free(glsl_src);
    return NULL;
  }

  shaderc_compiler_release(compiler);
  free(glsl_src);

  return result;
}

uint32_t
findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
{
  VkPhysicalDeviceMemoryProperties memProperties;
  vkGetPhysicalDeviceMemoryProperties(s.vk_physical_device, &memProperties);

  for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
    if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
        return i;
    }
  }

  vk_log(VK_ERROR, "failed to find suitable memory type!\n");
  return 9999;
}

void
createImage(uint32_t width, uint32_t height, VkFormat format,
            VkImageTiling tiling, VkImageUsageFlags usage,
            VkMemoryPropertyFlags properties, VkImage *image,
            VkDeviceMemory *imageMemory)
{
  VkImageCreateInfo imageInfo = {0};
  imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
  imageInfo.imageType = VK_IMAGE_TYPE_2D;
  imageInfo.extent.width = width;
  imageInfo.extent.height = height;
  imageInfo.extent.depth = 1;
  imageInfo.mipLevels = 1;
  imageInfo.arrayLayers = 1;
  imageInfo.format = format;
  imageInfo.tiling = tiling;
  imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  imageInfo.usage = usage;
  imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
  imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

  VK_CHECK(vkCreateImage(s.vk_device, &imageInfo, NULL, image));

  VkMemoryRequirements memRequirements;
  vkGetImageMemoryRequirements(s.vk_device, *image, &memRequirements);

  VkMemoryAllocateInfo allocInfo = {0};
  allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  allocInfo.allocationSize = memRequirements.size;
  allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);

  VK_CHECK(vkAllocateMemory(s.vk_device, &allocInfo, NULL, imageMemory));

  vkBindImageMemory(s.vk_device, *image, *imageMemory , 0);
}

VkCommandBuffer
beginSingleTimeCommands()
{
  VkCommandBufferAllocateInfo allocInfo = {0};
  allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  allocInfo.commandPool = s.vk_command_pool;
  allocInfo.commandBufferCount = 1;

  VkCommandBuffer commandBuffer;
  VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &commandBuffer));

  VkCommandBufferBeginInfo beginInfo = {0};
  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

  VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo));

  return commandBuffer;
}

void
endSingleTimeCommands(VkCommandBuffer commandBuffer)
{
  VK_CHECK(vkEndCommandBuffer(commandBuffer));

  VkSubmitInfo submitInfo = {0};
  submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  submitInfo.commandBufferCount = 1;
  submitInfo.pCommandBuffers = &commandBuffer;

  VK_CHECK(vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, VK_NULL_HANDLE));
  VK_CHECK(vkQueueWaitIdle(s.vk_graphics_queue));

  vkFreeCommandBuffers(s.vk_device, s.vk_command_pool, 1, &commandBuffer);
}

int
hasStencilComponent(VkFormat format)
{
  return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
}

void
transitionImageLayout(VkImage image, VkFormat format,
                      VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask,
                      VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
                      VkImageLayout oldLayout, VkImageLayout newLayout)
{
  VkCommandBuffer commandBuffer = beginSingleTimeCommands();

  VkImageMemoryBarrier barrier = {0};
  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
  barrier.srcAccessMask = srcAccessMask;
  barrier.dstAccessMask = dstAccessMask;
  barrier.oldLayout = oldLayout;
  barrier.newLayout = newLayout;
  barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  barrier.image = image;

  // barrier.subresourceRange.aspectMask = 
  if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;

    if (hasStencilComponent(format)) {
      barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
    }
  } else {
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  }

  barrier.subresourceRange.baseMipLevel = 0;
  barrier.subresourceRange.levelCount = 1;
  barrier.subresourceRange.baseArrayLayer = 0;
  barrier.subresourceRange.layerCount = 1;

  vkCmdPipelineBarrier(commandBuffer,
                       srcStageMask, dstStageMask,
                       0,
                       0, NULL,
                       0, NULL,
                       1, &barrier);
  endSingleTimeCommands(commandBuffer);
}

VkFormat
findSupportedFormat(VkFormat *candidates, size_t n,
                    VkImageTiling tiling,
                    VkFormatFeatureFlags features)
{
  for (size_t i = 0; i < n; i++) {
    VkFormat format = candidates[i];
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(s.vk_physical_device, format, &props);

    if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
      return format;
    } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
      return format;
    }
  }

  vk_log(VK_ERROR, "failed to find supported format!\n");
  abort();
}

VkFormat
findDepthFormat()
{
  VkFormat formats[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT};
  return findSupportedFormat(formats, VK_ARRAY_LEN(formats),
                             VK_IMAGE_TILING_OPTIMAL,
                             VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
}

void
vulkan_create_depth_resources()
{
  VkFormat depthFormat = findDepthFormat();
  createImage(s.vk_swap_chain_extent.width, s.vk_swap_chain_extent.height,
              depthFormat, VK_IMAGE_TILING_OPTIMAL,
              VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
              VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
              &s.vk_depth_image, &s.vk_depth_image_memory);
  s.vk_depth_image_view = create_image_view(s.vk_depth_image, depthFormat,
                                            VK_IMAGE_ASPECT_DEPTH_BIT);

  transitionImageLayout(s.vk_depth_image, depthFormat,
                        VK_ACCESS_NONE, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
                        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
                        VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
}

void
vulkan_create_graphics_pipeline(VkPolygonMode polygon_mode)
{
  shaderc_compilation_result_t vert_result = load_compile_shader_data("src/shader.vert", shaderc_vertex_shader);
  if (!vert_result) {
    vk_log(VK_ERROR, "Can't load vertex shader\n");
    if (s.prev_vert_result) {
      vert_result = s.prev_vert_result;
    }
  }
  if (s.prev_vert_result && vert_result != s.prev_vert_result) shaderc_result_release(s.prev_vert_result);
  s.prev_vert_result = vert_result;
  shaderc_compilation_result_t frag_result = load_compile_shader_data("src/shader.frag", shaderc_fragment_shader);
  if (!frag_result) {
    vk_log(VK_ERROR, "Can't load fragment shader\n");
    if (s.prev_frag_result) {
      frag_result = s.prev_frag_result;
    }
  }
  if (s.prev_frag_result && frag_result != s.prev_frag_result) shaderc_result_release(s.prev_frag_result);
  s.prev_frag_result = frag_result;

  const char * vert_data = shaderc_result_get_bytes(vert_result);
  long vert_size = shaderc_result_get_length(vert_result);
  const char * frag_data = shaderc_result_get_bytes(frag_result);
  long frag_size = shaderc_result_get_length(frag_result);

  vk_log(VK_INFO, "Shaders loaded\n");

  VkShaderModule vertShaderModule = createShaderModule(vert_data, vert_size);
  VkShaderModule fragShaderModule = createShaderModule(frag_data, frag_size);

  VkPipelineShaderStageCreateInfo vertShaderStageInfo = {0};
  vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
  vertShaderStageInfo.module = vertShaderModule;
  vertShaderStageInfo.pName = "main";

  VkPipelineShaderStageCreateInfo fragShaderStageInfo = {0};
  fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
  fragShaderStageInfo.module = fragShaderModule;
  fragShaderStageInfo.pName = "main";

  VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};

  VkDynamicState dynamicStates[] = {
    VK_DYNAMIC_STATE_VIEWPORT,
    VK_DYNAMIC_STATE_SCISSOR,
    VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE,
    VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE
  };

  VkPipelineDynamicStateCreateInfo dynamicState = {0};
  dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
  dynamicState.dynamicStateCount = VK_ARRAY_LEN(dynamicStates);
  dynamicState.pDynamicStates = dynamicStates;

  VkVertexInputBindingDescription bindingDescription = {0};
  bindingDescription.binding = 0;
  bindingDescription.stride = sizeof(Vertex);
  bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

  VkVertexInputAttributeDescription attributeDescriptions[3] = {0};
  attributeDescriptions[0].binding = 0;
  attributeDescriptions[0].location = 0;
  attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
  attributeDescriptions[0].offset = offsetof(Vertex, pos);

  attributeDescriptions[1].binding = 0;
  attributeDescriptions[1].location = 1;
  attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
  attributeDescriptions[1].offset = offsetof(Vertex, color);

  attributeDescriptions[2].binding = 0;
  attributeDescriptions[2].location = 2;
  attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
  attributeDescriptions[2].offset = offsetof(Vertex, texCoord);

  VkPipelineVertexInputStateCreateInfo vertexInputInfo = {0};
  vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  vertexInputInfo.vertexBindingDescriptionCount = 1;
  vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
  vertexInputInfo.vertexAttributeDescriptionCount = VK_ARRAY_LEN(attributeDescriptions);
  vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;

  VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
  inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
  inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
  inputAssembly.primitiveRestartEnable = VK_FALSE;

  VkViewport viewport = {0};
  viewport.x = 0.0f;
  viewport.y = 0.0f;
  viewport.width = (float) s.vk_swap_chain_extent.width;
  viewport.height = (float) s.vk_swap_chain_extent.height;
  viewport.minDepth = 0.0f;
  viewport.maxDepth = 1.0f;

  VkRect2D scissor = {0};
  scissor.offset = (VkOffset2D){0, 0};
  scissor.extent = s.vk_swap_chain_extent;

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

  VkPipelineRasterizationStateCreateInfo rasterizer = {0};
  rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  rasterizer.depthClampEnable = VK_FALSE;
  rasterizer.rasterizerDiscardEnable = VK_FALSE;
  rasterizer.polygonMode = polygon_mode;
  rasterizer.lineWidth = 1.0f;
  rasterizer.cullMode = VK_CULL_MODE_NONE;
  //rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
  //rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
  rasterizer.depthBiasEnable = VK_FALSE;
  rasterizer.depthBiasConstantFactor = 0.0f; // Optional
  rasterizer.depthBiasClamp = 0.0f; // Optional
  rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

  VkPipelineMultisampleStateCreateInfo multisampling = {0};
  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; // Optional
  multisampling.pSampleMask = NULL; // Optional
  multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
  multisampling.alphaToOneEnable = VK_FALSE; // Optional

  VkPipelineColorBlendAttachmentState colorBlendAttachment = {0};
  colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
  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;
  colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
  colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
  colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

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

  VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0};
  pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  pipelineLayoutInfo.setLayoutCount = 1;
  pipelineLayoutInfo.pSetLayouts = &s.vk_descriptor_set_layout;
  pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
  pipelineLayoutInfo.pPushConstantRanges = NULL; // Optional

  if (vkCreatePipelineLayout(s.vk_device, &pipelineLayoutInfo, NULL, &s.vk_pipeline_layout) != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to create pipeline layout!\n");
  }

  VkPipelineDepthStencilStateCreateInfo depthStencil = {0};
  depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
  depthStencil.depthTestEnable = VK_TRUE;
  depthStencil.depthWriteEnable = VK_TRUE;
  depthStencil.depthBoundsTestEnable = VK_FALSE;
  depthStencil.stencilTestEnable = VK_FALSE;
  depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
  depthStencil.minDepthBounds = 0.0f; // Optional
  depthStencil.maxDepthBounds = 1.0f; // Optional
  depthStencil.front = (VkStencilOpState){0}; // Optional
  depthStencil.back = (VkStencilOpState){0}; // Optional

  // TODO depthAttachment
  VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR,
      .colorAttachmentCount = 1,
      .pColorAttachmentFormats = &s.vk_swap_chain_image_format,
      .depthAttachmentFormat = findDepthFormat(),
    };
  VkGraphicsPipelineCreateInfo pipelineInfo = {0};
  pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
  pipelineInfo.stageCount = 2;
  pipelineInfo.pStages = shaderStages;

  pipelineInfo.pNext = &pipeline_rendering_create_info;
  pipelineInfo.pVertexInputState = &vertexInputInfo;
  pipelineInfo.pInputAssemblyState = &inputAssembly;
  pipelineInfo.pViewportState = &viewportState;
  pipelineInfo.pRasterizationState = &rasterizer;
  pipelineInfo.pMultisampleState = &multisampling;
  pipelineInfo.pDepthStencilState = &depthStencil;
  pipelineInfo.pColorBlendState = &colorBlending;
  pipelineInfo.pDynamicState = &dynamicState;
  pipelineInfo.layout = s.vk_pipeline_layout;
  pipelineInfo.renderPass = VK_NULL_HANDLE;
  pipelineInfo.subpass = 0;
  pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
  pipelineInfo.basePipelineIndex = -1; // Optional

  if (vkCreateGraphicsPipelines(s.vk_device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &s.vk_graphics_pipeline) != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to create graphics pipeline!\n");
  }

  if (s.prev_vert_result != vert_result) {
    shaderc_result_release(vert_result);
  }
  if (s.prev_frag_result != frag_result) {
    shaderc_result_release(frag_result);
  }

  vkDestroyShaderModule(s.vk_device, fragShaderModule, NULL);
  vkDestroyShaderModule(s.vk_device, vertShaderModule, NULL);
}

void
vulkan_create_command_pool()
{
  QueueFamilyIndices queueFamilyIndices = vulkan_find_queue_families(s.vk_physical_device);

  VkCommandPoolCreateInfo poolInfo = {0};
  poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  // AMD doesn't like this flag for some reason
  poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;

  VK_CHECK(vkCreateCommandPool(s.vk_device, &poolInfo, NULL, &s.vk_command_pool));
}

void
vulkan_create_command_buffer()
{
  // TODO Find a way to group allocation
  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    VkCommandBufferAllocateInfo allocInfo = {0};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.commandPool = s.vk_command_pool;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandBufferCount = 1;

    VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &s.frames[i].vk_command_buffer));
  }
}

void
recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex)
{
  VkCommandBufferBeginInfo beginInfo = {0};
  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  beginInfo.flags = 0; // Optional
  beginInfo.pInheritanceInfo = NULL; // Optional

  VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo));
  vkCmdSetDepthTestEnable(commandBuffer, 1);
  vkCmdSetDepthWriteEnable(commandBuffer, 1);

  transitionImageLayout(
                        s.vk_swap_chain_images[imageIndex], VK_FORMAT_R8G8B8A8_SRGB, 0,
                        VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
                        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

  VkRenderingAttachmentInfo colorAttachment = {0};
  colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
  colorAttachment.imageView = s.vk_swap_chain_image_views[imageIndex];
  colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
  colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
  colorAttachment.clearValue.color =
    (VkClearColorValue){0.0f, 0.0f, 0.0f, 1.0f};

  VkRenderingAttachmentInfo depthAttachment = {0};
  depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
  depthAttachment.imageView = s.vk_depth_image_view;
  depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
  depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
  depthAttachment.clearValue.depthStencil = (VkClearDepthStencilValue){1.0f, 0u};

  VkRenderingInfo renderingInfo = {};
  renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
  renderingInfo.renderArea =
      (VkRect2D){{0, 0}, s.vk_swap_chain_extent};
  renderingInfo.layerCount = 1;
  renderingInfo.colorAttachmentCount = 1;
  renderingInfo.pColorAttachments = &colorAttachment;
  renderingInfo.pDepthAttachment = &depthAttachment;

  vkCmdBeginRendering(commandBuffer, &renderingInfo);

  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                    s.vk_graphics_pipeline);

  VkViewport viewport = {0};
  viewport.x = 0.0f;
  viewport.y = 0.0f;
  viewport.width = (float)(s.vk_swap_chain_extent.width);
  viewport.height = (float)(s.vk_swap_chain_extent.height);
  viewport.minDepth = 0.0f;
  viewport.maxDepth = 1.0f;
  vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

  VkRect2D scissor = {0};
  scissor.offset = (VkOffset2D){0, 0};
  scissor.extent = s.vk_swap_chain_extent;
  vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

  VkBuffer vertexBuffers[] = {s.vk_vertex_buffer};
  VkDeviceSize offsets[] = {0};
  vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
  vkCmdBindIndexBuffer(commandBuffer, s.vk_index_buffer, 0,
                       VK_INDEX_TYPE_UINT16);
  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                          s.vk_pipeline_layout, 0, 1,
                          &s.frames[currentFrame].vk_descriptor_set, 0, NULL);

  
  vkCmdDrawIndexed(commandBuffer, INDICES_SIZE, 1, 0, 0, 0);
  vkCmdEndRendering(commandBuffer);

  transitionImageLayout(s.vk_swap_chain_images[imageIndex],
                        VK_FORMAT_R8G8B8A8_SRGB,
                        VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0,
                        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
                        VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
                        VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);

  VK_CHECK(vkEndCommandBuffer(commandBuffer));
}

void
vulkan_create_sync_objects()
{
  VkSemaphoreCreateInfo semaphoreInfo = {0};
  semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

  VkFenceCreateInfo fenceInfo = {0};
  fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
  fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].image_available_semaphore));
    VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].render_finished_semaphore));
    VK_CHECK(vkCreateFence(s.vk_device, &fenceInfo, NULL, &s.frames[i].in_flight_fence));
  }
}

void
createBuffer(VkDeviceSize size,
             VkBufferUsageFlags usage,
             VkMemoryPropertyFlags properties,
             VkBuffer *buffer, VkDeviceMemory *bufferMemory)
{
  VkBufferCreateInfo bufferInfo = {0};
  bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  bufferInfo.size = size;
  bufferInfo.usage = usage;
  bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

  VK_CHECK(vkCreateBuffer(s.vk_device, &bufferInfo, NULL, buffer));

  VkMemoryRequirements memRequirements;
  vkGetBufferMemoryRequirements(s.vk_device, *buffer, &memRequirements);

  VkMemoryAllocateInfo allocInfo = {0};
  allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  allocInfo.allocationSize = memRequirements.size;
  allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);

  // TODO: group allocations etc... (allocations limited by hardware)
  VK_CHECK(vkAllocateMemory(s.vk_device, &allocInfo, NULL, bufferMemory));

  VK_CHECK(vkBindBufferMemory(s.vk_device, *buffer, *bufferMemory, 0));
}

void copy_buffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
  VkCommandBuffer commandBuffer = beginSingleTimeCommands();
    VkBufferCopy copyRegion = {0};
    copyRegion.srcOffset = 0; // Optional
    copyRegion.dstOffset = 0; // Optional
    copyRegion.size = size;
    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
  endSingleTimeCommands(commandBuffer);
}

void
vulkan_create_vertex_buffer()
{
  VkDeviceSize bufferSize = sizeof(vertices[0]) * VERTICES_SIZE;

  VkBuffer stagingBuffer;
  VkDeviceMemory stagingBufferMemory;

  createBuffer(bufferSize,
               VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
               VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
               &stagingBuffer, &stagingBufferMemory);

  void* data;
  VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data));
  memcpy(data, vertices, (size_t) bufferSize);
  vkUnmapMemory(s.vk_device, stagingBufferMemory);

  createBuffer(bufferSize,
               VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
               VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
               &s.vk_vertex_buffer, &s.vk_vertex_buffer_memory);

  copy_buffer(stagingBuffer, s.vk_vertex_buffer, bufferSize);

  vkDestroyBuffer(s.vk_device, stagingBuffer, NULL);
  vkFreeMemory(s.vk_device, stagingBufferMemory, NULL);
}

void
vulkan_create_index_buffer()
{
  VkDeviceSize bufferSize = sizeof(indices[0]) * INDICES_SIZE;

  VkBuffer stagingBuffer;
  VkDeviceMemory stagingBufferMemory;

  createBuffer(bufferSize,
               VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
               VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
               &stagingBuffer, &stagingBufferMemory);

  void* data;
  VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data));
  memcpy(data, indices, (size_t) bufferSize);
  vkUnmapMemory(s.vk_device, stagingBufferMemory);

  createBuffer(bufferSize,
               VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
               VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
               &s.vk_index_buffer, &s.vk_index_buffer_memory);

  copy_buffer(stagingBuffer, s.vk_index_buffer, bufferSize);

  vkDestroyBuffer(s.vk_device, stagingBuffer, NULL);
  vkFreeMemory(s.vk_device, stagingBufferMemory, NULL);
}

void
vulkan_create_descriptor_set_layout()
{
  VkDescriptorSetLayoutBinding uboLayoutBinding = {0};
  uboLayoutBinding.binding = 0;
  uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  uboLayoutBinding.descriptorCount = 1;
  uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL;
  //uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
  uboLayoutBinding.pImmutableSamplers = NULL; // optional

  VkDescriptorSetLayoutBinding samplerLayoutBinding = {0};
  samplerLayoutBinding.binding = 1;
  samplerLayoutBinding.descriptorCount = 1;
  samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
  samplerLayoutBinding.pImmutableSamplers = NULL;
  samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

  VkDescriptorSetLayoutBinding bindings[2] = {uboLayoutBinding, samplerLayoutBinding};
  
  VkDescriptorSetLayoutCreateInfo layoutInfo = {0};
  layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layoutInfo.bindingCount = VK_ARRAY_LEN(bindings);
  layoutInfo.pBindings = bindings;

  VK_CHECK(vkCreateDescriptorSetLayout(s.vk_device, &layoutInfo, NULL, &s.vk_descriptor_set_layout));
}

void
vulkan_create_uniform_buffers()
{
  VkDeviceSize bufferSize = sizeof(UniformBufferObject);

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    createBuffer(bufferSize,
                 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
                 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                 &s.frames[i].vk_uniform_buffer, &s.frames[i].vk_uniform_buffer_memory);

    VK_CHECK(vkMapMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, 0, bufferSize, 0, &s.frames[i].vk_uniform_buffer_mapped));
  }
}

void
vulkan_create_descriptor_pool()
{
  VkDescriptorPoolSize poolSizes[2] = {0};
  poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  poolSizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT;
  poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
  poolSizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT;

  VkDescriptorPoolCreateInfo poolInfo = {0};
  poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
  poolInfo.poolSizeCount = VK_ARRAY_LEN(poolSizes);
  poolInfo.pPoolSizes = poolSizes;
  poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;

  VK_CHECK(vkCreateDescriptorPool(s.vk_device, &poolInfo, NULL, &s.vk_descriptor_pool));
}

void
vulkan_create_descriptor_sets()
{
  VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT] = {0};

  for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    layouts[i] = s.vk_descriptor_set_layout;
  }

  // TODO Find a way to group allocation
  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    VkDescriptorSetAllocateInfo allocInfo = {0};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = s.vk_descriptor_pool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = layouts;

    VK_CHECK(vkAllocateDescriptorSets(s.vk_device, &allocInfo, &s.frames[i].vk_descriptor_set));
  }

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    VkDescriptorBufferInfo bufferInfo = {0};
    bufferInfo.buffer = s.frames[i].vk_uniform_buffer;
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(UniformBufferObject);

    VkDescriptorImageInfo imageInfo = {0};
    imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageInfo.imageView = s.vk_texture_image_view;
    imageInfo.sampler = s.vk_texture_sampler;

    VkWriteDescriptorSet descriptorWrites[2] = {0};
    descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrites[0].dstSet = s.frames[i].vk_descriptor_set;
    descriptorWrites[0].dstBinding = 0;
    descriptorWrites[0].dstArrayElement = 0;
    descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    descriptorWrites[0].descriptorCount = 1;
    descriptorWrites[0].pBufferInfo = &bufferInfo;

    descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrites[1].dstSet = s.frames[i].vk_descriptor_set;
    descriptorWrites[1].dstBinding = 1;
    descriptorWrites[1].dstArrayElement = 0;
    descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    descriptorWrites[1].descriptorCount = 1;
    descriptorWrites[1].pImageInfo = &imageInfo;

    vkUpdateDescriptorSets(s.vk_device, VK_ARRAY_LEN(descriptorWrites), descriptorWrites, 0, NULL);
  }
}

void
cleanupSwapChain()
{
  vkDestroyImageView(s.vk_device, s.vk_depth_image_view, NULL);
  vkDestroyImage(s.vk_device, s.vk_depth_image, NULL);
  vkFreeMemory(s.vk_device, s.vk_depth_image_memory, NULL);

  for (uint32_t i = 0; i < s.vk_swap_chain_image_count; i++) {
    vkDestroyImageView(s.vk_device, s.vk_swap_chain_image_views[i], NULL);
  }
  vkDestroySwapchainKHR(s.vk_device, s.vk_swap_chain, NULL);
}

void
recreateSwapChain()
{
  vkDeviceWaitIdle(s.vk_device);

  cleanupSwapChain();

  vulkan_create_swap_chain();
  vulkan_create_image_views();
  vulkan_create_depth_resources();
}

void
recreate_graphics_pipeline()
{
  recreateSwapChain();
  vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL);
  vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL);
  vulkan_create_graphics_pipeline(s.polygon_mode);
}

int polygon_mode_n = 3;
VkPolygonMode polygon_modes[3] = {VK_POLYGON_MODE_FILL, VK_POLYGON_MODE_LINE,
                                  VK_POLYGON_MODE_POINT};

void
handle_input(bool * quit)
{
  SDL_Event e;

  while (SDL_PollEvent(&e) != 0) {
    // User requests quit
    if (e.type == SDL_QUIT) {
      *quit = true;
    }
      // User presses a key
    else if (e.type == SDL_KEYDOWN) {
      switch (e.key.keysym.sym) {
      case SDLK_w:
        // s.camera.pos[0] += 0.1f;
        move_towards(s.camera.front, s.camera.pos, -0.1f);
        move_towards(s.camera.pos, s.camera.front, 0.1f);
        break;
      case SDLK_s:
        //s.camera.pos[0] -= 0.1f;
        move_towards(s.camera.front, s.camera.pos, 0.1f);
        move_towards(s.camera.pos, s.camera.front, -0.1f);
        break;
      case SDLK_a:
        move_relative(s.camera.front, s.camera.pos, -0.1f, 1);
        move_relative(s.camera.pos, s.camera.front, 0.1f, 1);
        break;
      case SDLK_d:
        move_relative(s.camera.front, s.camera.pos, 0.1f, 1);
        move_relative(s.camera.pos, s.camera.front, -0.1f, 1);
        break;
      case SDLK_q:
        move_relative(s.camera.front, s.camera.pos, 0.1f, 0);
        move_relative(s.camera.pos, s.camera.front, 0.1f, 0);
        break;
      case SDLK_e:
        move_relative(s.camera.front, s.camera.pos, -0.1f, 0);
        move_relative(s.camera.pos, s.camera.front, -0.1f, 0);
        break;
      case SDLK_g: // reload shaders
        recreate_graphics_pipeline();
        break;
      case SDLK_z: // toggle polygon mode
        polygon_mode_n = (polygon_mode_n + 1) % 3;
        s.polygon_mode = polygon_modes[polygon_mode_n];
        recreate_graphics_pipeline();
        break;
      case SDLK_l:
        s.rotate = s.rotate ? 0 : 1;
        break;
      case SDLK_r:
        s.camera.pos[0] = 1.0f;
        s.camera.pos[1] = 1.0f;
        s.camera.pos[2] = 1.0f;

        s.camera.front[0] = 0.0f;
        s.camera.front[1] = 0.0f;
        s.camera.front[2] = 0.0f;

        s.camera.up[0] = 0.0f;
        s.camera.up[1] = 0.0f;
        s.camera.up[2] = 1.0f;

        s.camera.yaw = 0.0f;
        s.camera.pitch = 90.0f;
        s.camera.lastX = 400.0f;
        s.camera.lastY = 300.0f;
        s.camera.fov = 45.0f;
        update_camera(0,0);
        break;
      }
    }
    else if (e.type == SDL_MOUSEWHEEL) {
      if(e.wheel.y > 0) // scroll up
        {
          s.zoom += 1;
        }
      else if(e.wheel.y < 0) // scroll down
        {
          s.zoom -= 1;
          if (s.zoom == -100) s.zoom = 1;
        }
    }
    else if (e.type == SDL_MOUSEBUTTONDOWN) {
      s.mouse_pressed = 1;
    }
    else if (e.type == SDL_MOUSEBUTTONUP) {
      s.mouse_pressed = 0;
    }
    mouseCallback(&e);
  }
}

void
copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width,
                  uint32_t height)
{
  VkCommandBuffer commandBuffer = beginSingleTimeCommands();

  VkBufferImageCopy region = {0};
  region.bufferOffset = 0;
  region.bufferRowLength = 0;
  region.bufferImageHeight = 0;

  region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  region.imageSubresource.mipLevel = 0;
  region.imageSubresource.baseArrayLayer = 0;
  region.imageSubresource.layerCount = 1;

  region.imageOffset = (VkOffset3D){0, 0, 0};
  region.imageExtent = (VkExtent3D){width, height, 1};

  vkCmdCopyBufferToImage(commandBuffer,
                         buffer,
                         image,
                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                         1,
                         &region
                         );

  endSingleTimeCommands(commandBuffer);
}

void
vulkan_create_texture_image()
{
  int texWidth, texHeight, texChannels;
  stbi_uc* pixels = stbi_load("assets/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
  VkDeviceSize imageSize = texWidth * texHeight * 4;

  if (!pixels) {
    vk_log(VK_ERROR, "failed to load texture image!\n");
  }

  VkBuffer stagingBuffer;
  VkDeviceMemory stagingBufferMemory;

  createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory);
  void* data;
  vkMapMemory(s.vk_device, stagingBufferMemory, 0, imageSize, 0, &data);
  memcpy(data, pixels, (size_t)imageSize);
  vkUnmapMemory(s.vk_device, stagingBufferMemory);

  stbi_image_free(pixels);

  createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB,
              VK_IMAGE_TILING_OPTIMAL,
              VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
              VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &s.vk_texture_image,
              &s.vk_texture_image_memory);

  transitionImageLayout(
                        s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB, 0,
                        VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
                        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
                        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
  copyBufferToImage(stagingBuffer, s.vk_texture_image, (uint32_t)texWidth,
                    (uint32_t)texHeight);
  transitionImageLayout(
                        s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB,
                        VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
                        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
                        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

  vkDestroyBuffer(s.vk_device, stagingBuffer, NULL);
  vkFreeMemory(s.vk_device, stagingBufferMemory, NULL);
}

void
vulkan_create_texture_image_view()
{
  s.vk_texture_image_view = create_image_view(s.vk_texture_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT);
}

void vulkan_create_texture_sampler() {
  VkSamplerCreateInfo samplerInfo = {0};
  samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
  samplerInfo.magFilter = VK_FILTER_LINEAR;
  samplerInfo.minFilter = VK_FILTER_LINEAR;
  samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
  samplerInfo.anisotropyEnable = VK_TRUE;

  VkPhysicalDeviceProperties properties = {0};
  vkGetPhysicalDeviceProperties(s.vk_physical_device, &properties);

  samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
  samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
  samplerInfo.unnormalizedCoordinates = VK_FALSE;
  samplerInfo.compareEnable = VK_FALSE;
  samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
  samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
  samplerInfo.mipLodBias = 0.0f;
  samplerInfo.minLod = 0.0f;
  samplerInfo.maxLod = 0.0f;

  VK_CHECK(vkCreateSampler(s.vk_device, &samplerInfo, NULL, &s.vk_texture_sampler));
}

void
init_vulkan()
{
  vk_log(VK_WARN, "====================================\n");
  vk_log(VK_WARN, "              DEBUG ON              \n");
  vk_log(VK_WARN, "====================================\n");

  //vulkan_create_instance();
  s.vk_instance =
      vksetup_create_instance(enableValidationLayers, validation_layers,
                              validation_layer_count, s.sdl_window);
  // vulkan_setup_debug_messenger();
  vulkan_create_surface();
  vulkan_pick_physical_device();
  vulkan_create_logical_device();
  vulkan_create_swap_chain();
  vulkan_create_image_views();
  vulkan_create_descriptor_set_layout();
  vulkan_create_graphics_pipeline(s.polygon_mode);
  vulkan_create_command_pool();
  vulkan_create_depth_resources();
  vulkan_create_texture_image();
  vulkan_create_texture_image_view();
  vulkan_create_texture_sampler();
  vulkan_create_vertex_buffer();
  vulkan_create_index_buffer();
  vulkan_create_uniform_buffers();
  vulkan_create_descriptor_pool();
  vulkan_create_descriptor_sets();
  vulkan_create_command_buffer();
  vulkan_create_sync_objects();
}

void
close_vulkan()
{
  vkDeviceWaitIdle(s.vk_device);
  // Cleanup
  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    vkDestroySemaphore(s.vk_device, s.frames[i].image_available_semaphore, NULL);
    vkDestroySemaphore(s.vk_device, s.frames[i].render_finished_semaphore, NULL);
    vkDestroyFence(s.vk_device, s.frames[i].in_flight_fence, NULL);
  }

  vkDestroyCommandPool(s.vk_device, s.vk_command_pool, NULL);

  cleanupSwapChain();

  vkDestroySampler(s.vk_device, s.vk_texture_sampler, NULL);
  vkDestroyImageView(s.vk_device, s.vk_texture_image_view, NULL);

  vkDestroyImage(s.vk_device, s.vk_texture_image, NULL);
  vkFreeMemory(s.vk_device, s.vk_texture_image_memory, NULL);

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    vkDestroyBuffer(s.vk_device, s.frames[i].vk_uniform_buffer, NULL);
    vkFreeMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, NULL);
  }
  vkDestroyDescriptorPool(s.vk_device, s.vk_descriptor_pool, NULL);
  vkDestroyDescriptorSetLayout(s.vk_device, s.vk_descriptor_set_layout, NULL);

  vkDestroyBuffer(s.vk_device, s.vk_vertex_buffer, NULL);
  vkFreeMemory(s.vk_device, s.vk_vertex_buffer_memory, NULL);

  vkDestroyBuffer(s.vk_device, s.vk_index_buffer, NULL);
  vkFreeMemory(s.vk_device, s.vk_index_buffer_memory, NULL);

  vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL);
  vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL);
  vkDestroyDevice(s.vk_device, NULL);
  vkDestroySurfaceKHR(s.vk_instance, s.vk_surface, NULL);
  /* if (enableValidationLayers) { */
  /*   DestroyDebugUtilsMessengerEXT(s.vk_instance, s.vk_debug_messenger, NULL); */
  /* } */
  vkDestroyInstance(s.vk_instance, NULL);

  if (s.prev_vert_result) {
    shaderc_result_release(s.prev_vert_result);
  }
  if (s.prev_frag_result) {
    shaderc_result_release(s.prev_frag_result);
  }
}

float
current_time()
{
  static struct timespec startTime;
  static int isStartTimeInitialized = 0;

  if (!isStartTimeInitialized) {
    clock_gettime(CLOCK_MONOTONIC, &startTime);
    isStartTimeInitialized = 1;
  }

  struct timespec currentTime;
  clock_gettime(CLOCK_MONOTONIC, &currentTime);

  return (currentTime.tv_sec - startTime.tv_sec) +
         (currentTime.tv_nsec - startTime.tv_nsec) / 1e9f;
}

void
updateUniformBuffer(uint32_t currentImage)
{
  float time = current_time();

  UniformBufferObject ubo = {0};

  ubo.resolution[0] = s.vk_swap_chain_extent.width;
  ubo.resolution[1] = s.vk_swap_chain_extent.height;
  
  glm_mat4_identity(ubo.model);

  if (!s.rotate)
    glm_rotate(ubo.model, glm_rad(50 * time * glm_rad(90.0f)), GLM_ZUP);

  vec3 eye = GLM_VEC3_ONE_INIT;
  vec3 center = GLM_VEC3_ZERO_INIT;

//  vk_log(VK_INFO, "{%.2f, %.2f, %.2f}\n", s.camera.front[0], s.camera.front[1], s.camera.front[2]);
  /* glm_vec3_add(s.camera.pos, s.camera.front, center); */
  glm_lookat(s.camera.pos, s.camera.front, s.camera.up, ubo.view);

  /* glm_lookat(eye, center, GLM_ZUP, ubo.view); */
  
  float aspect = s.vk_swap_chain_extent.width / (float)s.vk_swap_chain_extent.height;
  glm_perspective(glm_rad(45.0f + s.zoom ), aspect, 0.1f, 100.0f, ubo.proj);

  // Inverting the Y axis for Vulkan
  ubo.proj[1][1] *= -1;

  memcpy(s.frames[currentImage].vk_uniform_buffer_mapped, &ubo, sizeof(ubo));
}

void
draw_frame() {
  vkWaitForFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence, VK_TRUE, UINT64_MAX);

  uint32_t imageIndex;
  VkResult result = vkAcquireNextImageKHR(s.vk_device, s.vk_swap_chain, UINT64_MAX, s.frames[currentFrame].image_available_semaphore, VK_NULL_HANDLE, &imageIndex);

  if (result == VK_ERROR_OUT_OF_DATE_KHR) {
    recreateSwapChain();
    return;
  } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
    vk_log(VK_ERROR, "failed to acquire swap chain image!\n");
  }

  updateUniformBuffer(currentFrame);

  vkResetFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence);

  // both could work
  //vkResetCommandPool(s.vk_device, s.vk_command_pool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT);
  vkResetCommandBuffer(s.frames[currentFrame].vk_command_buffer, 0);
  recordCommandBuffer(s.frames[currentFrame].vk_command_buffer, imageIndex);

  VkSubmitInfo submitInfo = {0};
  submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

  VkSemaphore waitSemaphores[] = {s.frames[currentFrame].image_available_semaphore};
  VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
  submitInfo.waitSemaphoreCount = 1;
  submitInfo.pWaitSemaphores = waitSemaphores;
  submitInfo.pWaitDstStageMask = waitStages;
  submitInfo.commandBufferCount = 1;
  submitInfo.pCommandBuffers = &s.frames[currentFrame].vk_command_buffer;

  VkSemaphore signalSemaphores[] = {s.frames[currentFrame].render_finished_semaphore};
  submitInfo.signalSemaphoreCount = 1;
  submitInfo.pSignalSemaphores = signalSemaphores;

  if (vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, s.frames[currentFrame].in_flight_fence) != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to submit draw command buffer!\n");
  }

  VkSwapchainKHR swapChains[] = {s.vk_swap_chain};
  VkPresentInfoKHR presentInfo = {0};
  presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  presentInfo.waitSemaphoreCount = 1;
  presentInfo.pWaitSemaphores = signalSemaphores;
  presentInfo.swapchainCount = 1;
  presentInfo.pSwapchains = swapChains;
  presentInfo.pImageIndices = &imageIndex;
  presentInfo.pResults = NULL;

  result = vkQueuePresentKHR(s.vk_present_queue, &presentInfo);

  if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || s.sdl_window_resized) {
    s.sdl_window_resized = 0;
    recreateSwapChain();
  } else if (result != VK_SUCCESS) {
    vk_log(VK_ERROR, "failed to present swap chain image!\n");
  }

  currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

int
main(int argc, char* args[])
{
  init_state(&s);
  if (!init()) {
    vk_log(VK_INFO, "Failed to initialize!\n");
  }
  else {
    init_vulkan();

    bool quit = false;


    /* VMA POC */
    VmaVulkanFunctions vulkanFunctions = {0};
    vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
    vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;

    VmaAllocatorCreateInfo allocatorCreateInfo = {0};
    allocatorCreateInfo.flags = VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
    allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2;
    allocatorCreateInfo.physicalDevice = s.vk_physical_device;
    allocatorCreateInfo.device = s.vk_device;
    allocatorCreateInfo.instance = s.vk_instance;
    allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;

    VmaAllocator allocator;
    vmaCreateAllocator(&allocatorCreateInfo, &allocator);

    // Entire program...

    // At the end, don't forget to:
    vmaDestroyAllocator(allocator);

    // Game loop
    update_camera(0, 0);
    while (!quit) {
      handle_input(&quit);
      draw_frame();
      //SDL_Delay(16);
    }

    close_vulkan();
  }

  // Free resources and close SDL
  closeSDL();

  return 0;

}