summaryrefslogblamecommitdiffstats
path: root/src/render.c
blob: cb4203b13e8246e9c83d416c4154cabbaf8c186b (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 <stb_image.h>

#define FAST_OBJ_IMPLEMENTATION
#include <fast_obj.h>

#define CGLTF_IMPLEMENTATION
#include <cgltf.h>

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

#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "vk_mem_alloc.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;
}

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(s.vk),
  };
  
  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_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.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(s.vk, &transition_info);

  VkRenderingAttachmentInfo colorAttachment = {0};
  colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
  colorAttachment.imageView = s.vk.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.vk.depth_image.view;
  depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
  depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
  depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
  depthAttachment.clearValue.depthStencil = (VkClearDepthStencilValue){1.0f, 0u};

  VkRenderingInfo renderingInfo = {};
  renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
  renderingInfo.renderArea =
      (VkRect2D){{0, 0}, s.vk.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);

  imgui_draw(); // does this need to be here?
  imgui_draw_cmd(commandBuffer);
  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(s.vk, &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; */

  VkBufferCreateInfo bufferInfo = { 0 };
  bufferInfo.sType              = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  bufferInfo.size               = bufferSize;
  bufferInfo.usage              = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
  bufferInfo.sharingMode        = VK_SHARING_MODE_EXCLUSIVE;

  VmaAllocationCreateInfo allocCreateInfo = {0};
  allocCreateInfo.usage                   = VMA_MEMORY_USAGE_AUTO;
  allocCreateInfo.flags                   = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
  
  vmaCreateBuffer(s.vk.allocator,
                  &bufferInfo,
                  &allocCreateInfo,
                  &s.vertex_buffer.handle,
                  &s.vertex_buffer.allocation,
                  NULL);

  vmaCopyMemoryToAllocation(s.vk.allocator, s.vertices, s.vertex_buffer.allocation, 0, (size_t)bufferSize);
}

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

  VkBufferCreateInfo bufferInfo = { 0 };
  bufferInfo.sType              = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  bufferInfo.size               = bufferSize;
  bufferInfo.usage              = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
  bufferInfo.sharingMode        = VK_SHARING_MODE_EXCLUSIVE;

  VmaAllocationCreateInfo allocCreateInfo = {0};
  allocCreateInfo.usage                   = VMA_MEMORY_USAGE_AUTO;
  allocCreateInfo.flags                   = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
  
  vmaCreateBuffer(s.vk.allocator,
                  &bufferInfo,
                  &allocCreateInfo,
                  &s.index_buffer.handle,
                  &s.index_buffer.allocation,
                  NULL);

  vmaCopyMemoryToAllocation(s.vk.allocator, s.indices, s.index_buffer.allocation, 0, (size_t)bufferSize);
}

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++) {

    VkBufferCreateInfo bufferInfo = { 0 };
    bufferInfo.sType              = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size               = bufferSize;
    bufferInfo.usage              = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
    bufferInfo.sharingMode        = VK_SHARING_MODE_EXCLUSIVE;

   
    VmaAllocationCreateInfo allocCreateInfo = {0};
    allocCreateInfo.usage                   = VMA_MEMORY_USAGE_AUTO;
    allocCreateInfo.flags                   = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
  
    //if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)

    VmaAllocationInfo allocInfo;

    vmaCreateBuffer(s.vk.allocator,
                    &bufferInfo,
                    &allocCreateInfo,
                    &s.frames[i].uniform_buffer.handle,
                    &s.frames[i].uniform_buffer.allocation,
                    &allocInfo);

    s.frames[i].uniform_buffer_mapped = allocInfo.pMappedData;
  }
}

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
recreate_graphics_pipeline()
{
  vks_recreate_swapchain(&s.vk);
  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) {
    imgui_proc_event(&e);

    // 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");
  }

  VkBufferCreateInfo bufferInfo = { 0 };
  bufferInfo.sType              = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  bufferInfo.size               = imageSize;
  bufferInfo.usage              = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
  bufferInfo.sharingMode        = VK_SHARING_MODE_EXCLUSIVE;

  VmaAllocationCreateInfo allocCreateInfo = {0};
  allocCreateInfo.usage                   = VMA_MEMORY_USAGE_AUTO;
  allocCreateInfo.flags                   = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;

  vks_buffer stagingBuffer = {0};
  vmaCreateBuffer(s.vk.allocator,
                  &bufferInfo,
                  &allocCreateInfo,
                  &stagingBuffer.handle,
                  &stagingBuffer.allocation,
                  NULL);

  vmaCopyMemoryToAllocation(s.vk.allocator, pixels, stagingBuffer.allocation, 0, (size_t)imageSize);

  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_transition_image_layout_info transition_info = { 0 };
  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(s.vk, &transition_info);

  vks_copy_buffer_to_image(s.vk,
                           stagingBuffer.handle,
                           s.texture_image.handle,
                           (uint32_t)texWidth,
                           (uint32_t)texHeight);
  vks_generate_mipmaps(s.vk, 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(s.vk, &transition_info);

  vmaDestroyBuffer(s.vk.allocator, stagingBuffer.handle, stagingBuffer.allocation);

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

  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(s.vk, 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);
  
  vulkan_create_descriptor_set_layout();

  vulkan_create_graphics_pipeline(s.polygon_mode);
  //vulkan_create_compute_stuff();

  vulkan_create_texture_image();
  vulkan_create_texture_sampler();

  init_imgui(&s.vk);
  
  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);

  vks_cleanup_swapchain(s.vk);

  imgui_destroy(s.vk);

  vkDestroySampler(s.vk.device, s.vk_texture_sampler, NULL);
  vkDestroyImageView(s.vk.device, s.texture_image.view, NULL);

  vmaDestroyImage(s.vk.allocator, s.texture_image.handle, s.texture_image.allocation);

  for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    vmaDestroyBuffer(s.vk.allocator, s.frames[i].uniform_buffer.handle, s.frames[i].uniform_buffer.allocation);
  }
  vkDestroyDescriptorPool(s.vk.device, s.vk_descriptor_pool, NULL);
  vkDestroyDescriptorSetLayout(s.vk.device, s.vk_descriptor_set_layout, NULL);

  vmaDestroyBuffer(s.vk.allocator, s.vertex_buffer.handle, s.vertex_buffer.allocation);
  vmaDestroyBuffer(s.vk.allocator, s.index_buffer.handle, s.index_buffer.allocation);

  vkDestroyPipeline(s.vk.device, s.graphics_pipeline.handle, NULL);
  vkDestroyPipelineLayout(s.vk.device, s.graphics_pipeline.layout, NULL);

  vmaDestroyAllocator(s.vk.allocator);
  
  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) {
    vks_recreate_swapchain(&s.vk);
    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;
    vks_recreate_swapchain(&s.vk);
  } 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");
    abort();
  }

  init_vulkan();

  bool quit = false;

  // At the end, don't forget to:

  update_camera(0, 0);

  // Game loop
  while (!quit) {
    handle_input(&quit);
    imgui_new_frame(s.vk);
    draw_frame();
    //SDL_Delay(16);
  }

  close_vulkan();
  closeSDL();

  return 0;

}