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



                            
                               



                               


                            




                                      


                                





                               
                        
                     

                  


                                             

                           
                      
                       
                        


                          



                                                                 
 


                                                                
 











                                                                







                                                                                     
                                                                                   


                                                 
                            
                                             


                                               














                                                       

                                                                 






                                                       


                                                   















                                                                







                                                                                 
                                                




                                                                                               
                            



                                                                                    

                                                 





              

                                 


             
                                     

                                                                          
                                                                        
                                                                    
 


                         






                                                                      
                                        








                                                                                                                                                                                 

                                                                                                                                                    
 
                                                                                                               
                             
                                                           




                                                  
                                   
                                    






















                                                                                     

                                                                                                                        



                                    
                                                                                   










                                                        















                                                                  
                                                    
          


                                




                                             


                             
   

 
    
                                           
 



                                                                               






                                                                               






                                                                              
 



                                             






                                                                                               













                                                                        

 








                                                                 
                                                                                          

























































                                                                                                  







                                                   
                                                                              























                                                                                                           
                                            
                                                                                      
                                                         
                                                               
                                                                          
                                   

                                                                                      
 















                                                                                

 
                                      
                                                     
 



                                                                                      
                                                                          
                                   
                                                                                                                    

 
    
                                                           
 
                                                                                                                
                     
                                                   


                                       
   

                                                                                                          
                                                                                                                  
                     
                                                     


                                       
   

                                                                                                          




                                                                 




















                                                                                              

                                       


                                        



                                                                            
                                                               






                                                             
                                                                   

                                        
                                                               




                                                               
                                                             
 




                                                               



                                                                                    
                                                                                        









                                                                                    

                                                         




                                      
                                         
 





                                                                              




                                                                                
                                        

                                          
                                                
                                                         







                                                                                 
                                                         













                                                                                                                                                  


                                                                                 
                                        














                                                                           
                                                                                                                  


                                                            











                                                                                  


                                                                    
                                                              

                                                 




                                                                       
                                                       




                                                    
                                                  

                                                 
                                                   
                                           



                                                               
                                                                                                                                  


                                                              





                                          
 

                                                             


    

                            
                                                                                           


                                                              
                                               
                                                                   
                                                                          
 
                                                                                  




                              






                                                                     
 
                                                                                                










                                                                       
                                                            

                                             

                                                          
















                                                                               
 
                                                  
                                                                      
                                                 
                                                                         
                                                            
                                                                            
                                                                                

                                                         
                                    
                                                  


                                                                      
                                                 



                                                                                 


                                                         
                            
                                                


                                                     
                                                    

                                                     
 
                                                                   
                                                



                            

                                                          





                                                   
                                         

                                                 
                                                      

                                                                      
                                                               
                                             
                                                                         
                                                           
                                                                              
 

                                                               

                                   







                                                                                
 
                                              












                                                                


                                                                                                           


   


                             

                                                                      
 
                                 
 



                                                                                               
 

                                                                                    

                                                 
                                                   
 



                                                                                         
 

                                                                                         
 

                                                           




                            

                                                                    
 
                                 
 



                                                                                               
 
             
                                                                                    

                                                   
                                                   
 



                                                                                        
 

                                                                                         
 

                                                           








                                                                      

                                                                                            

                                                         







                                                                                      
 

                                                                         

                                                   
 
                                                                                                     







                                                        



                                                                                                 
 
                                                                                                                                





                               
                                          



                                                                

                                                                      


                                                                 

                                                   

                                          
                                                                                        










                                                            






                                                                     
 
                                                                                                



                                                     
                                                          


                                                   








                                                                           




                                                                     







                                                                                   



























                                                                                                                



    

                  


                                                            
 


                                                            
 

                                                                         
   
                                                                  




                   
                                


                     
                                   
                                  
                                  


    


                            

                                                                         






                                                                             

               
    












                                     


                                                          

                  






                                                              
              










                                                              

                                    





                                                       



                                    
                  
                                      

                         







                                                                       
                                                         



                           
                            
         

                                                                   
 

                                                                  


                                      

















                                 



                                       






                                        
                      


                                           
                      



                                             




                                 
 

                                           




                                 





                      


                                       
                                                                                                 

                                                    

                                                                         



                                                        
                                 
 


                                                                                                                                                                   
                                          
                                                   


                          



                                                                                                                  
 

































                                                                                                                         
 

                                                           




                                  
                                                                                                                                                  












                                                            
                                                                   








                                                                     
                                         
 
                                                                                    


    




















                                                                        
                                                                  







                                                                
                                                            










                                                                                                
 
                          










                                                                             


                                                                  
           


                                              
         
            


       


                                                                                                                  




                                               
 




                                                                
 



                                                      
   
 

                                     
 



                               
 



                                                             
 









































                                                                                                           
 
                   
 
 



























                                                                                                                 
 



































                                                                                                                                                                    
                                                                                         


                                                                                                                                                                                                                            
                                                                                                           



























                                                                                                             





                                                                                                                  
 





                                                                                                          
 




                                                             
 

 
    





                                                            

                                                                                          
                                        
                                                  
                                  
                               
                                  
                                  


                                     

                      









                                  

              
                                

                                                     


                                                                                 

   
                                                             


                     

                                                              
 

                                                            
 
                                                     

                                                                          
   

                                                                              
 

                                                             
 

                                                            
 



                                                                         
                                       
                                                                                   
         
                                         






                                               


                   






                                        
 







                                               
                                                  

                                                          
 
    
                                                    
 
                                
 


                            

                                                   
 




                                                                      
 
                                       
 

                                         
 
                                                                                                       



                                                                  
 
                                                                                   
                                                                            
 


                                    
                                                                          

 
                    
    

            


                              
 
 
                                                                                                

                      
                                                                                                                                                                         







                                                                   
                                        
 
                                                                         
 
                    
                                                                                                    
                                                                    
 
                                                                            
 

                                                                                      

                                                   
                                    
                                                                                 

                                            
                                                                         
                                      
                                                                                   
 
                                                                                                                   
 
                                                        


                                                         
                                                                                  






                                                               

                                                                                                  





                                                              
 
                   


   
                            
 


              








                                               
 
                 






                                                                           


                                                              








                                                            

                
                        


                          
                      








                                 
 
 
#include <stddef.h>
#include <stdint.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 FAST_OBJ_IMPLEMENTATION
#include "../lib/fast_obj.h"

#define CGLTF_IMPLEMENTATION
#include "../lib/cgltf.h"

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

#define MODEL_PATH "assets/human.obj"
#define TEXTURE_PATH "assets/viking_room.png"

#define PARTICLE_COUNT 2048

void load_model_obj();
void load_model_gltf();
// embedded clgm library
uint32_t currentFrame = 0;
state_t s;

/* 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 uint32_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) {
  (void) data;
  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.vk.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.vk.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.vk.window);
  s.vk.window = NULL;
  SDL_Quit();
}

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

void
vulkan_setup_debug_messenger()
{
  if (!enable_validation_layers) 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");
  }
}

typedef struct QueueFamilyIndices {
  uint32_t graphicsAndComputeFamily;
  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) && (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT)) {
      indices.graphicsAndComputeFamily = 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;
}

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;
  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;

  if (s.mouse_pressed) {
    update_camera(xoffset, yoffset);
  } else if (s.middle_click) {
    vec4 _up = {0, 1, 0, 0};
    vec4 _right = {1, 0, 0, 0};

    vec4 up, right;
    glm_mat4_mulv(s.ubo.model, _up, up);
    glm_mat4_mulv(s.ubo.model, _right, right);

    glm_rotate(s.ubo.model, glm_rad(xoffset * glm_rad(90.0f)), GLM_ZUP);
    glm_rotate(s.ubo.model, glm_rad(yoffset * glm_rad(90.0f)), GLM_XUP);
  }
  return;
}

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;
}

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 depth_format = findDepthFormat();
  vks_create_image(s.vk, s.vk.swapchain.extent.width, s.vk.swapchain.extent.height, 1,
                   depth_format, VK_IMAGE_TILING_OPTIMAL,
                   VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
                   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, s.vk.msaa_samples,
                   &s.depth_image);
  s.depth_image.view = vks_create_image_view(s.vk, s.depth_image.handle, depth_format,
                                             VK_IMAGE_ASPECT_DEPTH_BIT, 1);

  vks_transition_image_layout_info transition_info = { 0 };
  transition_info.cmd_info.vk                      = s.vk;
  transition_info.cmd_info.pool                    = s.vk_command_pool;
  transition_info.cmd_info.queue = s.vk_graphics_and_compute_queue;
  transition_info.image          = s.depth_image.handle;
  transition_info.format         = depth_format;
  transition_info.srcAccessMask  = VK_ACCESS_NONE;
  transition_info.dstAccessMask  = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
                                  VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
  transition_info.srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  transition_info.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
  transition_info.oldLayout    = VK_IMAGE_LAYOUT_UNDEFINED;
  transition_info.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
  transition_info.mipLevels = 1;

  vks_transition_image_layout(&transition_info);
}

void vulkan_create_color_resources() {
  VkFormat colorFormat = s.vk.swapchain.image_format;

  vks_create_image(s.vk, s.vk.swapchain.extent.width, s.vk.swapchain.extent.height, 1,
                   colorFormat, VK_IMAGE_TILING_OPTIMAL,
                   VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |
                   VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
                   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, s.vk.msaa_samples,
                   &s.color_image);
  s.color_image.view = vks_create_image_view(s.vk, s.color_image.handle, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
}

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,
    // TODO Make polygon mode dynamic
    //VK_DYNAMIC_STATE_POLYGON_MODE_EXT
  };

  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, normal);

  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.swapchain.extent.width;
  viewport.height = (float) s.vk.swapchain.extent.height;
  viewport.minDepth = 0.0f;
  viewport.maxDepth = 1.0f;

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

  VkPipelineViewportStateCreateInfo viewportState = {0};
  viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
  viewportState.viewportCount = 1;
  viewportState.pViewports = &viewport;
  viewportState.scissorCount = 1;
  viewportState.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 = s.vk.msaa_samples;
  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.graphics_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

  VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR,
      .colorAttachmentCount = 1,
      .pColorAttachmentFormats = &s.vk.swapchain.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.graphics_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.graphics_pipeline.handle) != 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.graphicsAndComputeFamily;

  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);
  // TODO Make polygon mode dynamic
  //vkCmdSetPolygonModeEXT(commandBuffer, s.polygon_mode);

  vks_transition_image_layout_info transition_info = { 0 };

  transition_info.cmd_info.vk    = s.vk;
  transition_info.cmd_info.pool  = s.vk_command_pool;
  transition_info.cmd_info.queue = s.vk_graphics_and_compute_queue;
  transition_info.image          = s.vk.swapchain.images[imageIndex];
  transition_info.format         = VK_FORMAT_R8G8B8A8_SRGB;
  transition_info.srcAccessMask  = 0;
  transition_info.dstAccessMask  = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
  transition_info.srcStageMask   = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  transition_info.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
  transition_info.oldLayout    = VK_IMAGE_LAYOUT_UNDEFINED;
  transition_info.newLayout    = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
  transition_info.mipLevels    = 1;

  vks_transition_image_layout(&transition_info);

  VkRenderingAttachmentInfo colorAttachment = {0};
  colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
  colorAttachment.imageView = s.color_image.view;
  colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
  colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT;
  colorAttachment.resolveImageView = s.vk.swapchain.image_views[imageIndex];
  colorAttachment.resolveImageLayout = 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.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.swapchain.extent};
  renderingInfo.layerCount = 1;
  renderingInfo.colorAttachmentCount = 1;
  renderingInfo.pColorAttachments = &colorAttachment;
  renderingInfo.pDepthAttachment = &depthAttachment;

  vkCmdBeginRendering(commandBuffer, &renderingInfo);

  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                    s.graphics_pipeline.handle);

  VkViewport viewport = {0};
  viewport.x = 0.0f;
  viewport.y = 0.0f;
  viewport.width = (float)(s.vk.swapchain.extent.width);
  viewport.height = (float)(s.vk.swapchain.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.swapchain.extent;
  vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

  VkBuffer vertexBuffers[] = {s.vertex_buffer.handle};
  VkDeviceSize offsets[] = {0};
  vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
  vkCmdBindIndexBuffer(commandBuffer, s.index_buffer.handle, 0,
                       VK_INDEX_TYPE_UINT32);
  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
                          s.graphics_pipeline.layout, 0, 1,
                          &s.frames[currentFrame].vk_descriptor_set, 0, NULL);


  vkCmdDrawIndexed(commandBuffer, s.indices_count, 1, 0, 0, 0);
  vkCmdEndRendering(commandBuffer);

  transition_info.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
  transition_info.dstAccessMask = 0;
  transition_info.srcStageMask  = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
  transition_info.dstStageMask  = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
  transition_info.oldLayout     = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
  transition_info.newLayout     = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

  vks_transition_image_layout(&transition_info);

  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
vulkan_create_vertex_buffer()
{
  VkDeviceSize bufferSize = sizeof(s.vertices[0]) * s.vertices_count;
  /* VkDeviceSize bufferSize = sizeof(vertices[0]) * VERTICES_SIZE; */

  vks_buffer stagingBuffer = {0};

  vks_create_buffer(s.vk, bufferSize,
                    VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                    &stagingBuffer);

  void *data;
  VK_CHECK(vkMapMemory(s.vk.device, stagingBuffer.memory, 0, bufferSize, 0, &data));
  // memcpy(data, vertices, (size_t) bufferSize);
  memcpy(data, s.vertices, (size_t) bufferSize);
  vkUnmapMemory(s.vk.device, stagingBuffer.memory);

  vks_create_buffer(s.vk, bufferSize,
                    VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
                    &s.vertex_buffer);

  vks_command_info cmd_info = {s.vk, s.vk_command_pool, s.vk_graphics_and_compute_queue};
  vks_copy_buffer(&cmd_info, stagingBuffer.handle, s.vertex_buffer.handle, bufferSize);

  vkDestroyBuffer(s.vk.device, stagingBuffer.handle, NULL);
  vkFreeMemory(s.vk.device, stagingBuffer.memory, NULL);
}

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

  vks_buffer stagingBuffer = {0};

  vks_create_buffer(s.vk, bufferSize,
                    VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
                    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                    &stagingBuffer);

  void* data;
  VK_CHECK(vkMapMemory(s.vk.device, stagingBuffer.memory, 0, bufferSize, 0, &data));
  memcpy(data, s.indices, (size_t) bufferSize);
  /* memcpy(data, indices, (size_t) bufferSize); */
  vkUnmapMemory(s.vk.device, stagingBuffer.memory);

  vks_create_buffer(s.vk, bufferSize,
                    VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
                    &s.index_buffer);

  vks_command_info cmd_info = {s.vk, s.vk_command_pool, s.vk_graphics_and_compute_queue};
  vks_copy_buffer(&cmd_info, stagingBuffer.handle, s.index_buffer.handle, bufferSize);

  vkDestroyBuffer(s.vk.device, stagingBuffer.handle, NULL);
  vkFreeMemory(s.vk.device, stagingBuffer.memory, 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++) {
    vks_create_buffer(s.vk, bufferSize,
                      VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
                      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                      &s.frames[i].uniform_buffer);

    VK_CHECK(vkMapMemory(s.vk.device, s.frames[i].uniform_buffer.memory, 0, bufferSize, 0, &s.frames[i].uniform_buffer_mapped));
  }
}

void
vulkan_create_descriptor_pool()
{
  VkDescriptorPoolSize poolSizes[3] = {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;
  poolSizes[2].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
  poolSizes[2].descriptorCount = (uint32_t)(MAX_FRAMES_IN_FLIGHT) * 2;

  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].uniform_buffer.handle;
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(UniformBufferObject);

    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;

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

    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;

    /* VkDescriptorBufferInfo storageBufferInfoLastFrame = {0}; */
    /* storageBufferInfoLastFrame.buffer = s.frames[(i - 1) % MAX_FRAMES_IN_FLIGHT].vk_shader_storage_buffer; */
    /* storageBufferInfoLastFrame.offset = 0; */
    /* storageBufferInfoLastFrame.range = sizeof(Particle) * PARTICLE_COUNT; */

    /* descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; */
    /* descriptorWrites[2].dstSet = s.frames[i].vk_compute_descriptor_set; */
    /* descriptorWrites[2].dstBinding = 1; */
    /* descriptorWrites[2].dstArrayElement = 0; */
    /* descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; */
    /* descriptorWrites[2].descriptorCount = 1; */
    /* descriptorWrites[2].pBufferInfo = &storageBufferInfoLastFrame; */

    /* VkDescriptorBufferInfo storageBufferInfoCurrentFrame = {0}; */
    /* storageBufferInfoCurrentFrame.buffer = s.frames[i].vk_shader_storage_buffer; */
    /* storageBufferInfoCurrentFrame.offset = 0; */
    /* storageBufferInfoCurrentFrame.range = sizeof(Particle) * PARTICLE_COUNT; */

    /* descriptorWrites[3].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; */
    /* descriptorWrites[3].dstSet = s.frames[i].vk_compute_descriptor_set; */
    /* descriptorWrites[3].dstBinding = 2; */
    /* descriptorWrites[3].dstArrayElement = 0; */
    /* descriptorWrites[3].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; */
    /* descriptorWrites[3].descriptorCount = 1; */
    /* descriptorWrites[3].pBufferInfo = &storageBufferInfoCurrentFrame; */


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

void
cleanupSwapChain()
{
  vkDestroyImageView(s.vk.device, s.color_image.view, NULL);
  vkDestroyImage(s.vk.device, s.color_image.handle, NULL);
  vkFreeMemory(s.vk.device, s.color_image.memory, NULL);

  vkDestroyImageView(s.vk.device, s.depth_image.view, NULL);
  vkDestroyImage(s.vk.device, s.depth_image.handle, NULL);
  vkFreeMemory(s.vk.device, s.depth_image.memory, NULL);

  for (uint32_t i = 0; i < s.vk.swapchain.image_count; i++) {
    vkDestroyImageView(s.vk.device, s.vk.swapchain.image_views[i], NULL);
  }
  vkDestroySwapchainKHR(s.vk.device, s.vk.swapchain.handle, NULL);
}

void
recreateSwapChain()
{
  vkDeviceWaitIdle(s.vk.device);

  cleanupSwapChain();

  _vulkan_create_swap_chain(&s.vk);
  vulkan_create_color_resources();
  vulkan_create_depth_resources();
}

void
recreate_graphics_pipeline()
{
  recreateSwapChain();
  vkDestroyPipeline(s.vk.device, s.graphics_pipeline.handle, NULL);
  vkDestroyPipelineLayout(s.vk.device, s.graphics_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};

int toggle = 0;

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_m:
        vkDeviceWaitIdle(s.vk.device);
        free(s.vertices);
        free(s.indices);
        /* if (!strcmp(s.model_path, "assets/human.obj")) */
        /*   strcpy(s.model_path, "assets/viking_room.obj"); */
        /* else if (!strcmp(s.model_path, "assets/viking_room.obj")) */
        /*   strcpy(s.model_path, "assets/monkey.obj"); */
        /* else */
        /*   strcpy(s.model_path, "assets/human.obj"); */

        if (toggle) {
          strcpy(s.model_path, "assets/viking_room.obj");
          load_model_obj();
          toggle = 0;
        } else {
          toggle = 1;
          load_model_gltf();
        }
        vkDestroyBuffer(s.vk.device, s.vertex_buffer.handle, NULL);
        vkFreeMemory(s.vk.device, s.vertex_buffer.memory, NULL);

        vkDestroyBuffer(s.vk.device, s.index_buffer.handle, NULL);
        vkFreeMemory(s.vk.device, s.index_buffer.memory, NULL);
        vulkan_create_vertex_buffer();
        vulkan_create_index_buffer();
        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;

        glm_mat4_identity(s.ubo.model);

        s.zoom = 10;
        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) {
      if (e.button.button != 2) {
        s.mouse_pressed = 1;
      } else {
        s.middle_click = 1;
      }

    }
    else if (e.type == SDL_MOUSEBUTTONUP) {
      if (e.button.button != 2) {
        s.mouse_pressed = 0;
      } else {
        s.middle_click = 0;
      }
    }
    mouseCallback(&e);
  }
}

void
vulkan_create_texture_image()
{
  int texWidth, texHeight, texChannels;
  stbi_uc* pixels = stbi_load(TEXTURE_PATH, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
  VkDeviceSize imageSize = texWidth * texHeight * 4;

  s.vk_mip_levels = (uint32_t)floor(log2(fmax(texWidth, texHeight))) + 1;

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

  vks_buffer stagingBuffer = {0};

  vks_create_buffer(s.vk, imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer);
  void *data;
  vkMapMemory(s.vk.device, stagingBuffer.memory, 0, imageSize, 0, &data);
  memcpy(data, pixels, (size_t)imageSize);
  vkUnmapMemory(s.vk.device, stagingBuffer.memory);

  stbi_image_free(pixels);

  vks_create_image(s.vk, texWidth, texHeight, s.vk_mip_levels, VK_FORMAT_R8G8B8A8_SRGB,
                   VK_IMAGE_TILING_OPTIMAL,
                   VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
                   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_SAMPLE_COUNT_1_BIT, &s.texture_image);

  vks_command_info cmd_info = {0};
  cmd_info.vk    = s.vk;
  cmd_info.pool  = s.vk_command_pool;
  cmd_info.queue = s.vk_graphics_and_compute_queue;

  vks_transition_image_layout_info transition_info = { 0 };
  transition_info.cmd_info       = cmd_info;
  transition_info.image          = s.texture_image.handle;
  transition_info.format         = VK_FORMAT_R8G8B8A8_SRGB;
  transition_info.srcAccessMask  = 0;
  transition_info.dstAccessMask  = VK_ACCESS_TRANSFER_WRITE_BIT;
  transition_info.srcStageMask   = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
  transition_info.dstStageMask   = VK_PIPELINE_STAGE_TRANSFER_BIT;
  transition_info.oldLayout      = VK_IMAGE_LAYOUT_UNDEFINED;
  transition_info.newLayout      = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
  transition_info.mipLevels      = s.vk_mip_levels;

  vks_transition_image_layout(&transition_info);

  vks_copy_buffer_to_image(&cmd_info,
                           stagingBuffer.handle,
                           s.texture_image.handle,
                           (uint32_t)texWidth,
                           (uint32_t)texHeight);
  vks_generate_mipmaps(&cmd_info, s.texture_image.handle, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, s.vk_mip_levels);

  transition_info.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
  transition_info.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
  transition_info.srcStageMask  = VK_PIPELINE_STAGE_TRANSFER_BIT;
  transition_info.dstStageMask  = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
  transition_info.oldLayout     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
  transition_info.newLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

  vks_transition_image_layout(&transition_info);

  vkDestroyBuffer(s.vk.device, stagingBuffer.handle, NULL);
  vkFreeMemory(s.vk.device, stagingBuffer.memory, NULL);
}

void
vulkan_create_texture_image_view()
{
  s.texture_image.view = vks_create_image_view(s.vk, s.texture_image.handle, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, s.vk_mip_levels);
}

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 = VK_LOD_CLAMP_NONE;

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

void
load_model_obj()
{
  fastObjMesh *m = fast_obj_read(s.model_path);

  if (!m) {
    vk_log(VK_ERROR, "Can't load object\n");
  }

  s.indices_count = m->index_count;
  s.indices = (uint32_t *)malloc(s.indices_count * sizeof(uint32_t));
  s.vertices_count = 0;

  if (m->group_count > 1) {
    vk_log(VK_WARN, "fast_obj Mesh with groups > 1 not supported.");
  }

  /*
    Since blender exports objs indexed we use those same indices for our
    indexed draw call.
   */

  s.vertices = (Vertex *)malloc(s.indices_count * sizeof(Vertex));
  /* Count indexs and vertices */
  size_t c = 0;
  for (size_t ii = 0; ii < m->group_count; ii++) {
    const fastObjGroup *grp = &m->groups[ii];
    for (unsigned int jj = 0; jj < grp->face_count; jj++) {
      unsigned int fv = m->face_vertices[grp->face_offset + jj];
      for (unsigned int kk = 0; kk < fv; kk++) {
        /* position index */
        fastObjIndex mi = m->indices[grp->index_offset + c];
        uint32_t mip = m->indices[grp->index_offset + c].p - 1; /* make index start from zero */

        /* flag if we've seen the index*/
        int index_seen = 0;
        for (size_t i = 0; i < c; i++) {
          if (mip == s.indices[i]) {
            index_seen = 1;
            break;
          }
        }
        s.indices[c] = mip;

        if (!index_seen) {
          int index = mi.p - 1; /* zero indexed */
          if (mi.p) {
            s.vertices[index].pos.x = m->positions[3 * mi.p + 0];
            s.vertices[index].pos.y = m->positions[3 * mi.p + 1];
            s.vertices[index].pos.z = m->positions[3 * mi.p + 2];
          }
          if (mi.t) {
            s.vertices[index].texCoord.x = m->texcoords[2 * mi.t + 0];
            s.vertices[index].texCoord.y = 1.0f - m->texcoords[2 * mi.t + 1];
          }
          if (mi.n) {
            s.vertices[index].normal.x = m->normals[3 * mi.n + 0];
            s.vertices[index].normal.y = m->normals[3 * mi.n + 1];
            s.vertices[index].normal.z = m->normals[3 * mi.n + 2];
          }

          /* If not seen, incremet vertices */
          s.vertices_count++;
        }
        c++;
      }
    }
  }

  vk_log(VK_INFO, "[obj] %s: Loaded %ld vertices %ld indices\n", s.model_path, s.vertices_count, s.indices_count);

  fast_obj_destroy(m);
}

void load_model_gltf() {
  // TODO maybe copy the raylib implemenetation

  char * path = "assets/monkey.glb";
  cgltf_options options;
  memset(&options, 0, sizeof(cgltf_options));
  cgltf_data* data = NULL;
  cgltf_result result = cgltf_parse_file(&options, path, &data);

  if (result == cgltf_result_success)
    result = cgltf_load_buffers(&options, data, path);
  else {
    vk_log(VK_ERROR, "Can't load %s\n", path);
  }

  if (result == cgltf_result_success)
    result = cgltf_validate(data);

  if (data->meshes_count < 1) {
    cgltf_free(data);
    return;
  }

  for (cgltf_size i = 0; i < data->meshes_count; ++i) {
    cgltf_mesh *mesh = &data->meshes[i];
    for (cgltf_size j = 0; j < mesh->primitives_count; ++j) {
      cgltf_primitive* primitive = &mesh->primitives[j];

      cgltf_accessor* index_accessor = primitive->indices;

      if (index_accessor) {
        s.indices_count = index_accessor->count;
        s.indices = (uint32_t *)malloc(s.indices_count * sizeof(uint32_t));
        // Read indices
        for (cgltf_size k = 0; k < index_accessor->count; ++k) {
          s.indices[k] = cgltf_accessor_read_index(index_accessor, k);
        }
      }

      // Find the position attribute accessor
      for (cgltf_size k = 0; k < primitive->attributes_count; ++k) {
        if (primitive->attributes[k].type == cgltf_attribute_type_position) {
          cgltf_accessor* position_accessor = primitive->attributes[k].data;
          s.vertices_count = position_accessor->count;
          s.vertices = (Vertex *)malloc(s.vertices_count * sizeof(Vertex));
          // Read vertex positions
          for (cgltf_size k = 0; k < position_accessor->count; ++k) {
            float vertex[3];
            cgltf_accessor_read_float(position_accessor, k, (cgltf_float *)(&s.vertices[k].pos), 3);
          }
        }
        if (primitive->attributes[k].type == cgltf_attribute_type_texcoord) {
          cgltf_accessor* texcoord_accessor = primitive->attributes[k].data;
          // Read texture coordinates
          for (cgltf_size k = 0; k < texcoord_accessor->count; ++k) {
            cgltf_accessor_read_float(texcoord_accessor, k, (cgltf_float *)(&s.vertices[k].texCoord), 2);
          }
        }
        if (primitive->attributes[k].type == cgltf_attribute_type_normal) {
          cgltf_accessor* color_accessor = primitive->attributes[k].data;
          // Read texture coordinates
          for (cgltf_size k = 0; k < color_accessor->count; ++k) {
            cgltf_accessor_read_float(color_accessor, k, (cgltf_float *)(&s.vertices[k].normal), 3);
          }
        }
      }
    }
  }

  vk_log(VK_INFO, "[glTF] %s: Loaded %ld vertices %ld indices\n", path, s.vertices_count, s.indices_count);

  cgltf_free(data);
}

float rand_float() {
  return rand() / (float)RAND_MAX;
}

void vulkan_create_compute_stuff()
{
  shaderc_compilation_result_t comp_result = load_compile_shader_data("src/shader.comp", shaderc_compute_shader);
  if (!comp_result) {
    vk_log(VK_ERROR, "Can't load compupte shader\n");
    if (s.prev_comp_result) {
      comp_result = s.prev_comp_result;
    }
  }
  if (s.prev_comp_result && comp_result != s.prev_comp_result) shaderc_result_release(s.prev_comp_result);
  s.prev_comp_result = comp_result;

  const char * comp_data = shaderc_result_get_bytes(comp_result);
  long comp_size = shaderc_result_get_length(comp_result);

  VkShaderModule compShaderModule = createShaderModule(comp_data, comp_size);
  VkPipelineShaderStageCreateInfo computeShaderStageInfo = {0};
  computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
  computeShaderStageInfo.module = compShaderModule;
  computeShaderStageInfo.pName = "main";


  srand((unsigned int)time(NULL));

  Particle particles[PARTICLE_COUNT];
  int width, height;
  SDL_GetWindowSize(s.vk.window, &width, &height);

  for (int i = 0; i < PARTICLE_COUNT; i++) {
    float r = 0.25f * sqrtf(rand_float());
    float theta = rand_float() * 2 * 3.14159265358979323846;
    float x = r * cosf(theta) * height / width;
    float y = r * sinf(theta);

    particles[i].position[0] = x;
    particles[i].position[1] = y;

    vec2 norm;
    glm_vec2_copy((vec2){x, y}, norm);
    glm_vec2_normalize(norm);

    particles[i].velocity[0] = norm[0] * 0.00025f;
    particles[i].velocity[1] = norm[1] * 0.00025f;

    particles[i].color[0] = rand_float();
    particles[i].color[1] = rand_float();
    particles[i].color[2] = rand_float();
    particles[i].color[3] = 1.0f;
  }

  VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT;

  vks_buffer stagingBuffer = {0};
  vks_create_buffer(s.vk, bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer);

  void* data;
  vkMapMemory(s.vk.device, stagingBuffer.memory, 0, bufferSize, 0, &data);
  memcpy(data, particles, (size_t)bufferSize);
  vkUnmapMemory(s.vk.device, stagingBuffer.memory);

  vks_command_info cmd_info = {s.vk, s.vk_command_pool, s.vk_graphics_and_compute_queue};
  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    vks_create_buffer(s.vk, bufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &s.frames[i].shader_storage_buffer);
    // Copy data from the staging buffer (host) to the shader storage buffer (GPU)
    vks_copy_buffer(&cmd_info, stagingBuffer.handle, s.frames[i].shader_storage_buffer.handle, bufferSize);
  }

  VkDescriptorSetLayoutBinding layoutBindings[3];
  layoutBindings[0].binding = 0;
  layoutBindings[0].descriptorCount = 1;
  layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  layoutBindings[0].pImmutableSamplers = NULL;
  layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;

  layoutBindings[1].binding = 1;
  layoutBindings[1].descriptorCount = 1;
  layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
  layoutBindings[1].pImmutableSamplers = NULL;
  layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;

  layoutBindings[2].binding = 2;
  layoutBindings[2].descriptorCount = 1;
  layoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
  layoutBindings[2].pImmutableSamplers = NULL;
  layoutBindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;

  VkDescriptorSetLayoutCreateInfo layoutInfo = {0};
  layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layoutInfo.bindingCount = 3;
  layoutInfo.pBindings = layoutBindings;

  VK_CHECK(vkCreateDescriptorSetLayout(s.vk.device, &layoutInfo, NULL, &s.vk_compute_descriptor_set_layout));

  VkComputePipelineCreateInfo pipelineInfo = {0};
  pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
  pipelineInfo.layout = s.vk_compute_pipeline_layout;
  pipelineInfo.stage = computeShaderStageInfo;

  VK_CHECK(vkCreateComputePipelines(s.vk.device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &s.vk_compute_pipeline));

  VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0};
  pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  pipelineLayoutInfo.setLayoutCount = 1;
  pipelineLayoutInfo.pSetLayouts = &s.vk_compute_descriptor_set_layout;

  VK_CHECK(vkCreatePipelineLayout(s.vk.device, &pipelineLayoutInfo, NULL, &s.vk_compute_pipeline_layout));

  if (s.prev_comp_result != comp_result) {
    shaderc_result_release(comp_result);
  }

  vkDestroyShaderModule(s.vk.device, compShaderModule, NULL);

}

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

  vks_create_vulkan_context(&s.vk, &s.vk_graphics_and_compute_queue, &s.vk_present_queue);

  vulkan_create_descriptor_set_layout();
  vulkan_create_graphics_pipeline(s.polygon_mode);
  //vulkan_create_compute_stuff();
  vulkan_create_command_pool();
  vulkan_create_depth_resources();
  vulkan_create_color_resources();
  vulkan_create_texture_image();
  vulkan_create_texture_image_view();
  vulkan_create_texture_sampler();
  load_model_obj();
  //load_model_gltf();
  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.texture_image.view, NULL);

  vkDestroyImage(s.vk.device, s.texture_image.handle, NULL);
  vkFreeMemory(s.vk.device, s.texture_image.memory, NULL);

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    vkDestroyBuffer(s.vk.device, s.frames[i].uniform_buffer.handle, NULL);
    vkFreeMemory(s.vk.device, s.frames[i].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.vertex_buffer.handle, NULL);
  vkFreeMemory(s.vk.device, s.vertex_buffer.memory, NULL);

  vkDestroyBuffer(s.vk.device, s.index_buffer.handle, NULL);
  vkFreeMemory(s.vk.device, s.index_buffer.memory, NULL);

  vkDestroyPipeline(s.vk.device, s.graphics_pipeline.handle, NULL);
  vkDestroyPipelineLayout(s.vk.device, s.graphics_pipeline.layout, NULL);
  vkDestroyDevice(s.vk.device, NULL);
  vkDestroySurfaceKHR(s.vk.instance, s.vk.surface, NULL);
  /* if (enable_validation_layers) { */
  /*   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);
  }

  free(s.vertices);
  free(s.indices);
}

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 dt)
{
  UniformBufferObject ubo = {0};

  ubo.time = current_time();
  ubo.dt = dt;

  ubo.resolution[0] = s.vk.swapchain.extent.width;
  ubo.resolution[1] = s.vk.swapchain.extent.height;

  //glm_mat4_identity(ubo.model);
  glm_mat4_dup(s.ubo.model, ubo.model);

  if (s.rotate)
    glm_rotate(ubo.model, glm_rad(30 * dt * glm_rad(90.0f)), GLM_ZUP);

  glm_mat4_dup(ubo.model, s.ubo.model);

  /* 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.swapchain.extent.width / (float)s.vk.swapchain.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].uniform_buffer_mapped, &ubo, sizeof(ubo));
}

float prev_time = 0;
void
draw_frame()
{
  float time = current_time();

  float dt = time - prev_time;


  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.swapchain.handle, 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, dt);

  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);

  VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};

  VkSubmitInfo submitInfo = {0};
  submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  submitInfo.waitSemaphoreCount = 1;
  submitInfo.pWaitSemaphores = &s.frames[currentFrame].image_available_semaphore;
  submitInfo.pWaitDstStageMask = waitStages;
  submitInfo.commandBufferCount = 1;
  submitInfo.pCommandBuffers = &s.frames[currentFrame].vk_command_buffer;
  submitInfo.signalSemaphoreCount = 1;
  submitInfo.pSignalSemaphores = &s.frames[currentFrame].render_finished_semaphore;

  VK_CHECK(vkQueueSubmit(s.vk_graphics_and_compute_queue, 1, &submitInfo, s.frames[currentFrame].in_flight_fence));

  VkSwapchainKHR swapChains[] = {s.vk.swapchain.handle};
  VkPresentInfoKHR presentInfo = {0};
  presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  presentInfo.waitSemaphoreCount = 1;
  presentInfo.pWaitSemaphores = &s.frames[currentFrame].render_finished_semaphore;
  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;

  prev_time = time;
}

int
main(int argc, char* argv[])
{
  (void) argc;
  (void)argv;

  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;

}