#define VK_USE_PLATFORM_MACOS_MVK
#include "vulkan/vulkan_core.h"
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <cglm/types.h>
#include <cglm/mat4.h>
#include <cglm/vec3.h>
#include <cglm/affine.h>
#include <cglm/quat.h>
#include <cglm/cam.h>
#include <stdio.h>
#include <string.h>

typedef struct QueueIndicesStruct {
  uint32_t graphics_family;
  uint32_t graphics_index;

  uint32_t present_family;
  uint32_t present_index;

  uint32_t transfer_family;
  uint32_t transfer_index;
} QueueIndices;

typedef struct QueuesStruct {
  VkQueue graphics;
  VkQueue present;
  VkQueue transfer;
} Queues;

typedef struct SwapchainDetailsStruct {
  VkSurfaceCapabilitiesKHR capabilities;

  VkSurfaceFormatKHR* formats;
  uint32_t formats_count;

  VkPresentModeKHR* present_modes;
  uint32_t present_modes_count;
} SwapchainDetails;

typedef struct SwapchainImagesStruct {
  VkImage* images;
  uint32_t count;
} SwapchainImages;

typedef struct AllocatedBufferStruct {
  VkBuffer buffer;
  VkDeviceMemory memory;
} AllocatedBuffer;

typedef struct AllocatedImageStruct {
  VkImage image;
  VkDeviceMemory memory;
} AllocatedImage;

typedef struct TextureStruct {
  AllocatedImage image;
  VkImageView view;
  VkSampler sampler;
} Texture;

typedef struct EntryStruct {
  uint32_t key;
  void*    value;
} Entry;

typedef struct MapStruct {
  uint32_t          buckets_count;
  Entry**           buckets;
  uint32_t*         bucket_sizes;
  uint32_t*         bucket_usage;
} Map;

Map map_create(uint32_t buckets_count, uint32_t initial_bucket_size) {
  Map zero = {};

  Entry** buckets = malloc(sizeof(Entry*)*buckets_count);
  if(buckets == 0) {
    return zero;
  }

  uint32_t* bucket_sizes = malloc(sizeof(uint32_t)*buckets_count);
  if(bucket_sizes == 0) {
    free(buckets);
    return zero;
  }

  uint32_t* bucket_usage = malloc(sizeof(uint32_t)*buckets_count);
  if(bucket_usage == 0) {
    free(bucket_usage);
    free(buckets);
    return zero;
  }

  for(uint32_t i = 0; i < buckets_count; i++) {
    Entry* bucket = malloc(sizeof(Entry)*initial_bucket_size);
    if(bucket == 0) {
      for(uint32_t j = 0; j < i; j++) {
        free(buckets[j]);
      }
      free(bucket_sizes);
      free(buckets);
      return zero;
    }
    buckets[i] = bucket;
    bucket_sizes[i] = initial_bucket_size;
    bucket_usage[i] = 0;
  }

  Map ret = {
    .buckets_count = buckets_count,
    .bucket_sizes = bucket_sizes,
    .bucket_usage = bucket_usage,
    .buckets = buckets,
  };

  return ret;
}

typedef struct MaybeValueStruct {
  bool has_value;
  void* value;
} MaybeValue;

MaybeValue map_lookup(Map map, uint32_t key) {
  MaybeValue ret = {
    .has_value = false,
    .value = 0,
  };

  uint32_t bucket_index = key % map.buckets_count;
  for(uint32_t i = 0; i < map.bucket_usage[bucket_index]; i++) {
    if(map.buckets[bucket_index][i].key == key) {
      ret.has_value = true;
      ret.value = map.buckets[bucket_index][i].value;
    }
  }

  return ret;
}

bool map_add(Map* map, uint32_t key, void* value) {
  uint32_t bucket_index = key % map->buckets_count;

  for(uint32_t i = 0; i < map->bucket_usage[bucket_index]; i++) {
    if(map->buckets[bucket_index][i].key == key) {
      map->buckets[bucket_index][i].value = value;
      return true;
    }
  }

  if(map->bucket_usage[bucket_index] < map->bucket_sizes[bucket_index]) {
    map->buckets[bucket_index][map->bucket_usage[bucket_index]].key = key;
    map->buckets[bucket_index][map->bucket_usage[bucket_index]].value = value;
    map->bucket_usage[bucket_index] += 1;
    return true;
  }

  Entry* new_bucket = realloc(map->buckets[bucket_index], 2*map->bucket_sizes[bucket_index]);
  if(new_bucket == 0) {
    return false;
  }

  map->bucket_usage[bucket_index] += 1;
  map->bucket_sizes[bucket_index] *= 2;
  map->buckets[bucket_index] = new_bucket;
  map->buckets[bucket_index][map->bucket_usage[bucket_index]].key = key;
  map->buckets[bucket_index][map->bucket_usage[bucket_index]].value = value;

  return true;
}

MaybeValue map_del(Map* map, uint32_t key) {
  MaybeValue ret = {
    .has_value = false,
    .value = 0,
  };

  uint32_t bucket_index = key % map->buckets_count;
  for(uint32_t i = 0; i < map->bucket_usage[bucket_index]; i++) {
    if(map->buckets[bucket_index][i].key == key) {
      ret.value = map->buckets[bucket_index][i].value;
      ret.has_value = true;

      if(map->bucket_usage[bucket_index] > 1) {
        map->buckets[bucket_index][i] = map->buckets[bucket_index][map->bucket_usage[bucket_index]-1];
      }
      map->bucket_usage[bucket_index] -= 1;
      break;
    }
  }

  return ret;
}

void map_destroy(Map map) {
  for(uint32_t i = 0; i < map.buckets_count; i++) {
    free(map.buckets[i]);
  }
  free(map.buckets);
  free(map.bucket_sizes);
  free(map.bucket_usage);
}

typedef struct MapIteratorStruct {
  uint32_t  count;
  uint32_t* keys;
  void**    vals;
} MapIterator;

MapIterator map_iterator_create(Map map) {
  MapIterator iterator = {
    .keys  = 0,
    .vals  = 0,
    .count = 0,
  };

  uint32_t count = 0;
  for(uint32_t i = 0; i < map.buckets_count; i++) {
    count += map.bucket_usage[i];
  }

  uint32_t* keys = malloc(sizeof(uint32_t)*count);
  if(keys == 0) {
    return iterator;
  }

  void** vals = malloc(sizeof(void*)*count);
  if(vals == 0) {
    free(keys);
    return iterator;
  }

  uint32_t idx = 0;
  for(uint32_t i = 0; i < map.buckets_count; i++) {
    for(uint32_t j = 0; j < map.bucket_usage[i]; j++) {
      keys[idx] = map.buckets[i][j].key;
      vals[idx] = map.buckets[i][j].value;
      idx += 1;
    }
  }

  iterator.keys = keys;
  iterator.vals = vals;
  iterator.count = count;

  return iterator;
}

void map_iterator_free(MapIterator iterator) {
  if(iterator.count > 0) {
    free(iterator.keys);
    free(iterator.vals);
  }
}

typedef struct PositionStruct {
  vec3 position;
  vec3 scale;
  versor rotation;
} Position;

typedef void(*MappingFunc)(void*,void*);

void attribute_mapping_position_to_matrix(void* dest, void* source) {
  Position* position = source;
  glm_translate_make(dest, position->position);
  glm_quat_rotate(dest, position->rotation, dest);
  glm_scale(dest, position->scale);
}

#define MAPPING_POSITION_TO_MATRIX 0

MappingFunc mapping_functions[] = {
  attribute_mapping_position_to_matrix,
};

typedef struct MappingStruct {
  uint32_t mapping_type; // What function to use to map it
  uint32_t index; // Which index to use in the ATTRIBUTE_ID_DESCRIPTORS array
} Mapping;

#define ATTRIBUTE_ID_MESH            0x00000001 // Mesh*
#define ATTRIBUTE_ID_MATERIAL        0x00000002 // Material*
#define ATTRIBUTE_ID_DESCRIPTORS     0x00000003 // void***(array of array of data pointers)
#define ATTRIBUTE_ID_DESCRIPTOR_SETS 0x00000004 // VkDescriptorSet*
#define ATTRIBUTE_ID_POSITION        0x00000005 // Position*

typedef struct ObjectStruct {
  Map      attributes;
} Object;

// Defines how a mesh is read from a buffer into a graphics pipeline
typedef struct MeshTypeStruct {
  uint32_t                           bindings_count;
  VkVertexInputBindingDescription*   bindings;

  uint32_t                           attributes_count;
  VkVertexInputAttributeDescription* attributes;
} MeshType;

// Defines what descriptors are bound at two different upate rates for the pipeline
typedef struct PipelineLayoutStruct {
  uint32_t                      object_bindings_count;
  VkDescriptorSetLayoutBinding* object_bindings;

  uint32_t                      material_bindings_count;
  VkDescriptorSetLayoutBinding* material_bindings;
} PipelineLayout;

typedef struct MeshStruct {
  uint32_t        vertex_count;
  AllocatedBuffer vertex_buffer;

  uint32_t        index_count;
  AllocatedBuffer index_buffer;
} Mesh;

typedef struct MaterialStruct {
  VkDescriptorSetLayout material_set_layout;
  VkDescriptorSetLayout object_set_layout;

  VkPipelineLayout layout;
  VkPipeline       pipeline;

  VkDescriptorPool material_descriptor_pool;
  VkDescriptorSet* material_descriptors;
  uint32_t         material_descriptors_count;

  Map              object_descriptor_mappings;
} Material;

typedef struct VulkanContextStruct {
  VkInstance instance;
  VkDebugUtilsMessengerEXT debug_messenger;
  VkPhysicalDevice physical_device;

  QueueIndices queue_indices;
  VkDevice device;
  Queues queues;

  VkSurfaceKHR surface;

  SwapchainDetails swapchain_details;
  VkSwapchainKHR swapchain;

  VkSurfaceFormatKHR swapchain_format;
  VkPresentModeKHR swapchain_present_mode;
  VkExtent2D swapchain_extent;

  uint32_t swapchain_image_count;
  // Per image objects
  VkImage* swapchain_images;
  VkImageView* swapchain_image_views;
  VkFramebuffer* swapchain_framebuffers;

  AllocatedImage depth_image;
  VkFormat depth_format;
  VkImageView depth_image_view;

  uint32_t max_frames_in_flight;
  // Per frame objects
  VkCommandBuffer* swapchain_command_buffers;
  VkSemaphore* image_available_semaphores;
  VkSemaphore* render_finished_semaphores;
  VkFence* in_flight_fences;

  VkRenderPass render_pass;

  VkCommandPool graphics_command_pool;
  VkCommandPool transfer_command_pool;

  VkDescriptorPool      scene_pool;
  VkDescriptorSetLayout scene_descriptor_layout;
  VkDescriptorSet*      scene_descriptors;
  AllocatedBuffer*      scene_ubos;
  void**                scene_ubo_ptrs;

  Mesh triangle_mesh;
  Mesh triangle_mesh_textured;
  Material simple_mesh_material;
  Material texture_mesh_material;
  Object triangle_object;
  Object triangle_object_textured;

  uint32_t current_frame;
} VulkanContext;

struct TextureVertex {
  vec3 pos;
  vec3 color;
  vec2 tex;
};

struct Vertex {
  vec3 pos;
  vec3 color;
};

struct ModelUBO {
  mat4 model;
};

struct SceneUBO {
  mat4 view;
  mat4 proj;
};

const struct Vertex vertices[] = {
  {.pos = {-1.f, -1.f, 0.f}, .color = {1.0f, 0.0f, 0.0f}},
  {.pos = { 1.f, -1.f, 0.f}, .color = {0.0f, 1.0f, 0.0f}},
  {.pos = { 1.f,  1.f, 0.f}, .color = {0.0f, 0.0f, 1.0f}},
  {.pos = {-1.f,  1.f, 0.f}, .color = {1.0f, 1.0f, 1.0f}},
};

const struct TextureVertex texture_vertices[] = {
  {.pos = {-1.f, -1.f, 0.f}, .color = {1.0f, 0.0f, 0.0f}, .tex = {1.0f, 1.0f}},
  {.pos = { 1.f, -1.f, 0.f}, .color = {0.0f, 1.0f, 0.0f}, .tex = {0.0f, 1.0f}},
  {.pos = { 1.f,  1.f, 0.f}, .color = {0.0f, 0.0f, 1.0f}, .tex = {0.0f, 0.0f}},
  {.pos = {-1.f,  1.f, 0.f}, .color = {1.0f, 1.0f, 1.0f}, .tex = {1.0f, 0.0f}},
};

const uint16_t indices[] = {
  2, 1, 0, 0, 3, 2,
};

const char * validation_layers[] = {
  "VK_LAYER_KHRONOS_validation",
  //"VK_LAYER_LUNARG_api_dump",
  "VK_LAYER_KHRONOS_synchronization2",
  "VK_LAYER_KHRONOS_shader_object",
};
uint32_t validation_layer_count = sizeof(validation_layers) / sizeof(const char *);

const char * instance_extensions[] = {
  VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
  VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
  VK_MVK_MACOS_SURFACE_EXTENSION_NAME,
  VK_KHR_SURFACE_EXTENSION_NAME,
};
uint32_t instance_extension_count = sizeof(instance_extensions) / sizeof(const char *);

const char * device_extensions[] = {
  VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
uint32_t device_extension_count = sizeof(device_extensions) / sizeof(const char *);

void glfw_error(int error, const char* description) {
  fprintf(stderr, "GLFW_ERR: 0x%02x - %s\n", error, description);
}

GLFWwindow* init_window(int width, int height) {
  glfwInit();
  glfwSetErrorCallback(glfw_error);

  glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
  glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
  GLFWwindow* window = glfwCreateWindow(width, height, "Vulkan window", 0, 0);


  return window;
}

void object_update_mappings(Material material, Object object, uint32_t frame_num) {
  if(material.object_descriptor_mappings.buckets == 0) {
    return;
  }

  MaybeValue maybe_descriptors = map_lookup(object.attributes, ATTRIBUTE_ID_DESCRIPTORS);
  if(maybe_descriptors.has_value == false) {
    return;
  }

  void** descriptors = ((void***)(maybe_descriptors.value))[frame_num];

  MapIterator mapping_iterator = map_iterator_create(material.object_descriptor_mappings);
  for(uint32_t i = 0; i < mapping_iterator.count; i++) {
    MaybeValue maybe_attribute = map_lookup(object.attributes, mapping_iterator.keys[i]);
    if(maybe_attribute.has_value == true) {
      Mapping* mapping = mapping_iterator.vals[i];
      if(mapping->mapping_type > sizeof(mapping_functions)/sizeof(MappingFunc)) {
        fprintf(stderr, "material requested mapping_function 0x%02x which does not exist\n", mapping->mapping_type);
        continue;
      }
      mapping_functions[mapping->mapping_type](descriptors[mapping->index], maybe_attribute.value);
    } else {
      fprintf(stderr, "material requested attribute 0x%02x from object, but it does not have it\n", mapping_iterator.keys[i]);
    }
  }
  map_iterator_free(mapping_iterator);
}

VkFormat find_depth_format(VkPhysicalDevice physical_device, uint32_t num_requested, VkFormat* requested, VkImageTiling tiling, VkFormatFeatureFlags features) {
  for(uint32_t i = 0; i < num_requested; i++) {
    VkFormatProperties properties;
    vkGetPhysicalDeviceFormatProperties(physical_device, requested[i], &properties);

    if(tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) {
      return requested[i];
    } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) {
      return requested[i];
    }
  }
  return VK_FORMAT_MAX_ENUM;
}

bool check_validation_layers(const char ** layers, uint32_t num_layers) {
  uint32_t layer_count;
  VkResult result;

  result = vkEnumerateInstanceLayerProperties(&layer_count, 0);
  if(result != VK_SUCCESS) {
    return false;
  }

  VkLayerProperties* available_layers = malloc(sizeof(VkLayerProperties)*layer_count);

  result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers);

  for(uint32_t i = 0; i < num_layers; i++) {
    bool found = false;
    for(uint32_t j = 0; j < layer_count; j++) {
      if(strcmp(layers[i], available_layers[j].layerName) == 0) {
        found = true;
      }
    }
    if(found == false) {
      free(available_layers);
      return false;
    }
  }

  free(available_layers);
  return true;
}

static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(
  VkDebugUtilsMessageSeverityFlagBitsEXT severity,
  VkDebugUtilsMessageTypeFlagsEXT type,
  const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
  void* user_data) {

  (void)severity;
  (void)type;
  (void)user_data;

  fprintf(stderr, "Validation layer: %s\n", callback_data->pMessage);

  return VK_FALSE;
}

VkDescriptorSet* create_descriptor_sets(VkDevice device, VkDescriptorSetLayout layout, VkDescriptorPool pool, uint32_t count) {
  VkDescriptorSetLayout* layouts = malloc(sizeof(VkDescriptorSetLayout)*count);
  if(layouts == 0) {
    return 0;
  }

  VkDescriptorSet* sets = malloc(sizeof(VkDescriptorSet)*count);
  if(sets == 0) {
    free(layouts);
    return 0;
  }

  for(uint32_t i = 0; i < count; i++) {
    layouts[i] = layout;
  }

  VkDescriptorSetAllocateInfo alloc_info = {};
  alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
  alloc_info.descriptorPool = pool;
  alloc_info.descriptorSetCount = count;
  alloc_info.pSetLayouts = layouts;

  VkResult result = vkAllocateDescriptorSets(device, &alloc_info, sets);
  free(layouts);
  if(result != VK_SUCCESS) {
    free(sets);
    return 0;
  }


  return sets;
}

VkDescriptorSetLayout create_descriptor_set_layout(VkDevice device, VkDescriptorSetLayoutBinding* bindings, uint32_t bindings_count) {
  VkDescriptorSetLayoutCreateInfo layout_info = {};
  layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
  layout_info.bindingCount = bindings_count;
  layout_info.pBindings = bindings;

  VkDescriptorSetLayout layout;
  VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, 0, &layout);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return layout;
}

VkSurfaceKHR create_surface_khr(VkInstance instance, GLFWwindow* window) {
  VkSurfaceKHR surface;
  VkResult result = glfwCreateWindowSurface(instance, window, 0, &surface);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return surface;
}

VkPhysicalDevice get_best_physical_device(VkInstance instance) {
  VkPhysicalDevice device = VK_NULL_HANDLE;

  uint32_t device_count = 0;
  VkResult result;
  result = vkEnumeratePhysicalDevices(instance, &device_count, 0);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count);
  result = vkEnumeratePhysicalDevices(instance, &device_count, devices);
  if(result != VK_SUCCESS) {
    free(devices);
    return VK_NULL_HANDLE;
  }

  int top_score = -1;
  for(uint32_t i = 0; i < device_count; i++) {
    int score = 0;

    VkPhysicalDeviceProperties properties;
    vkGetPhysicalDeviceProperties(devices[i], &properties);

    VkPhysicalDeviceFeatures features;
    vkGetPhysicalDeviceFeatures(devices[i], &features);

    switch(properties.deviceType) {
    case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
      score += 100;
      break;
    case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
      score += 50;
      break;
    case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
      score += 25;
      break;
    case VK_PHYSICAL_DEVICE_TYPE_CPU:
      score += 0;
      break;
    default:
      continue;
    }

    if(score > top_score) {
      top_score = score;
      device = devices[i];
    }
  }

  free(devices);

  return device;
}

bool check_queue_indices(QueueIndices indices) {
  return ((indices.graphics_family != 0xFFFFFFFF)
      && (indices.present_family != 0xFFFFFFFF)
      && (indices.transfer_family != 0xFFFFFFFF));
}

QueueIndices get_queue_indices(VkPhysicalDevice physical_device, VkSurfaceKHR surface) {
  QueueIndices indices = {};
  indices.graphics_family = 0xFFFFFFFF;
  indices.graphics_index  = 0xFFFFFFFF;
  indices.present_family  = 0xFFFFFFFF;
  indices.present_index   = 0xFFFFFFFF;
  indices.transfer_family = 0xFFFFFFFF;
  indices.transfer_index  = 0xFFFFFFFF;

  uint32_t queue_family_count;
  vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, 0);

  VkQueueFamilyProperties* queue_families = malloc(sizeof(VkQueueFamilyProperties)*queue_family_count);

  vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families);

  for(uint32_t family_idx = 0; family_idx < queue_family_count; family_idx++) {
    VkBool32 present_support;
    vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, family_idx, surface, &present_support);
    for(uint32_t queue_idx = 0; queue_idx < queue_families[family_idx].queueCount; queue_idx++) {
      if(((indices.graphics_family == 0xFFFFFFFF)
            || (indices.present_family == 0xFFFFFFFF)
            || (indices.present_family != indices.graphics_family))
          && (queue_families[family_idx].queueFlags & VK_QUEUE_GRAPHICS_BIT)
          && (present_support == VK_TRUE)) {
        fprintf(stderr, "Selected %d:%d for graphics and present queues\n", family_idx, queue_idx);
        indices.graphics_family = family_idx;
        indices.graphics_index = queue_idx;

        indices.present_family = family_idx;
        indices.present_index = queue_idx;
      } else if((indices.graphics_family == 0xFFFFFFFF)
          && (queue_families[family_idx].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
        fprintf(stderr, "Selected %d:%d for graphics queue\n", family_idx, queue_idx);
        indices.graphics_family = family_idx;
        indices.graphics_index = queue_idx;
      } else if((indices.present_family == 0xFFFFFFFF)
          && (present_support == VK_TRUE)) {
        fprintf(stderr, "Selected %d:%d for present queue\n", family_idx, queue_idx);
        indices.present_family = family_idx;
        indices.present_index = queue_idx;
      } else if((indices.transfer_family == 0xFFFFFFFF)
          && (queue_families[family_idx].queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))) {
        fprintf(stderr, "Selected %d:%d for transfer queue\n", family_idx, queue_idx);
        indices.transfer_family = family_idx;
        indices.transfer_index = queue_idx;
      }
    }
  }

  free(queue_families);

  return indices;
}

VkDebugUtilsMessengerEXT create_debug_messenger(VkInstance instance) {
  VkDebugUtilsMessengerEXT debug_messenger;

  VkDebugUtilsMessengerCreateInfoEXT messenger_info = {};
  messenger_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
  messenger_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
  messenger_info.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 | VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT;
  messenger_info.pfnUserCallback = debug_callback;
  messenger_info.pUserData = 0;

  PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");

  VkResult result;
  result = func(instance, &messenger_info, 0, &debug_messenger);
  if(result != VK_SUCCESS) {
    fprintf(stderr, "failed to create debug messenger\n");
    return VK_NULL_HANDLE;
  }

  return debug_messenger;
}

VkDescriptorPool create_descriptor_pool(VkDevice device, VkDescriptorPoolSize* sizes, uint32_t num_sizes, uint32_t max_sets) {
  VkDescriptorPoolCreateInfo pool_info = {};
  pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
  pool_info.poolSizeCount = num_sizes;
  pool_info.pPoolSizes = sizes;
  pool_info.maxSets = max_sets;

  VkDescriptorPool pool;
  VkResult result = vkCreateDescriptorPool(device, &pool_info, 0, &pool);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return pool;
}

VkInstance create_instance() {
  VkInstance instance;

  if(check_validation_layers(validation_layers, validation_layer_count) == false) {
    fprintf(stderr, "requested validation layers not supported");
    return VK_NULL_HANDLE;
  }

  VkApplicationInfo app_info = {};
  app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  app_info.pApplicationName = "spacegame";
  app_info.applicationVersion = VK_MAKE_VERSION(0, 0, 1);
  app_info.pEngineName = "spacegame";
  app_info.engineVersion = VK_MAKE_VERSION(0, 0, 1);
  app_info.apiVersion = VK_API_VERSION_1_3;

  VkInstanceCreateInfo instance_info = {};
  instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  instance_info.pApplicationInfo = &app_info;

  instance_info.enabledLayerCount = validation_layer_count;
  instance_info.ppEnabledLayerNames = validation_layers;

  uint32_t glfwExtensionCount = 0;
  const char** glfwExtensions;

  glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

  const char** requested_extensions = malloc(sizeof(char*)*(glfwExtensionCount + instance_extension_count));
  for (uint32_t i = 0; i < glfwExtensionCount; i++) {
    requested_extensions[i] = glfwExtensions[i];
  }

  for (uint32_t i = 0; i < instance_extension_count; i++) {
    requested_extensions[glfwExtensionCount + i] = instance_extensions[i];
  }

  instance_info.enabledExtensionCount = glfwExtensionCount + instance_extension_count;
  instance_info.ppEnabledExtensionNames = requested_extensions;

  instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;

  VkResult result = vkCreateInstance(&instance_info, 0, &instance);
  if(result != VK_SUCCESS) {
    fprintf(stderr, "vkCreateInstance: 0x%02x\n", result);
    return VK_NULL_HANDLE;
  }

  free(requested_extensions);

  return instance;
}

VkDevice create_logical_device(VkPhysicalDevice physical_device, QueueIndices queue_indices) {
  VkDevice device;

  uint32_t unique_families[3] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
  uint32_t unique_family_queues[3] = {0, 0, 0};
  uint32_t unique_family_count = 0;

  uint32_t queue_family[] = {queue_indices.transfer_family, queue_indices.graphics_family, queue_indices.present_family};
  uint32_t unique_queue_count = 3;
  if((queue_indices.graphics_family == queue_indices.present_family)
      && (queue_indices.graphics_index == queue_indices.present_index)) {
    unique_queue_count = 2;
  }

  for(uint32_t queue_idx = 0; queue_idx < unique_queue_count; queue_idx++) {
    uint32_t idx = 0xFFFFFFFF;
    for(uint32_t check_idx = 0; check_idx < unique_family_count; check_idx++) {
      if(queue_family[queue_idx] == unique_families[check_idx]) {
        idx = check_idx;
        break;
      }
    }

    if(idx == 0xFFFFFFFF) {
      unique_families[unique_family_count] = queue_family[queue_idx];
      unique_family_queues[unique_family_count] += 1;
      unique_family_count += 1;
    } else {
      unique_family_queues[idx] += 1;
    }
  }

  VkDeviceQueueCreateInfo queue_create_info[3] = {};
  float default_queue_priority = 1.0f;
  for(uint32_t i = 0; i < unique_family_count; i++) {
    queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queue_create_info[i].queueFamilyIndex = unique_families[i];
    queue_create_info[i].queueCount = unique_family_queues[i];
    queue_create_info[i].pQueuePriorities = &default_queue_priority;
  }

  VkPhysicalDeviceFeatures device_features = {
    .samplerAnisotropy = VK_TRUE,
  };

  VkDeviceCreateInfo device_create_info = {};
  device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  device_create_info.pQueueCreateInfos = queue_create_info;
  device_create_info.queueCreateInfoCount = unique_family_count;
  device_create_info.pEnabledFeatures = &device_features;

  device_create_info.enabledExtensionCount = device_extension_count;
  device_create_info.ppEnabledExtensionNames = device_extensions;

  device_create_info.enabledLayerCount = validation_layer_count;
  device_create_info.ppEnabledLayerNames = validation_layers;

  VkResult result = vkCreateDevice(physical_device, &device_create_info, 0, &device);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return device;
}

SwapchainDetails get_swapchain_details(VkPhysicalDevice physical_device, VkSurfaceKHR surface) {
  SwapchainDetails details = {};
  details.formats = 0;
  details.present_modes = 0;

  VkResult result;

  result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &details.capabilities);
  if(result != VK_SUCCESS) {
    return details;
  }

  result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details.formats_count, 0);
  if(result != VK_SUCCESS) {
    return details;
  }
  details.formats = malloc(sizeof(VkSurfaceFormatKHR)*details.formats_count);
  result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details.formats_count, details.formats);
  if(result != VK_SUCCESS) {
    free(details.formats);
    details.formats = 0;
    return details;
  }

  result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details.present_modes_count, 0);
  if(result != VK_SUCCESS) {
    free(details.formats);
    details.formats = 0;
    return details;
  }
  details.present_modes = malloc(sizeof(VkPresentModeKHR)*details.present_modes_count);
  result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details.present_modes_count, details.present_modes);
  if(result != VK_SUCCESS) {
    free(details.formats);
    free(details.present_modes);
    details.formats = 0;
    details.present_modes = 0;
    return details;
  }

  return details;
}

VkSurfaceFormatKHR choose_swapchain_format(SwapchainDetails swapchain_details) {
  for(uint32_t i = 0; i < swapchain_details.formats_count; i++) {
    VkSurfaceFormatKHR format = swapchain_details.formats[i];
    if(format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
      return format;
    }
  }
  return swapchain_details.formats[0];
}

VkPresentModeKHR choose_present_mode(SwapchainDetails swapchain_details) {
  for(uint32_t i = 0; i < swapchain_details.present_modes_count; i++) {
    if(swapchain_details.present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
      return VK_PRESENT_MODE_MAILBOX_KHR;
    }
  }

  return VK_PRESENT_MODE_FIFO_KHR;
}

VkExtent2D choose_swapchain_extent(SwapchainDetails swapchain_details) {
  return swapchain_details.capabilities.currentExtent;
}

VkSwapchainKHR create_swapchain(VkDevice device, VkSurfaceFormatKHR format, VkPresentModeKHR present_mode, VkExtent2D extent, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR capabilities, QueueIndices indices, VkSwapchainKHR old_swapchain) {
  uint32_t image_count = capabilities.minImageCount + 1;
  uint32_t max_images = capabilities.maxImageCount;
  if((max_images > 0) && (image_count > max_images)) {
    image_count = max_images;
  }

  VkSwapchainCreateInfoKHR swapchain_info = {};
  swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
  swapchain_info.surface = surface;
  swapchain_info.minImageCount = image_count;
  swapchain_info.imageFormat = format.format;
  swapchain_info.imageColorSpace = format.colorSpace;
  swapchain_info.imageExtent = extent;
  swapchain_info.imageArrayLayers = 1;
  swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

  uint32_t queue_families[2] = {indices.graphics_family, indices.present_index};
  if(indices.graphics_family != indices.present_family) {
    swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    swapchain_info.queueFamilyIndexCount = 2;
    swapchain_info.pQueueFamilyIndices = queue_families;
  } else {
    swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    swapchain_info.queueFamilyIndexCount = 0;
    swapchain_info.pQueueFamilyIndices = 0;
  }

  swapchain_info.preTransform = capabilities.currentTransform;
  swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
  swapchain_info.presentMode = present_mode;
  swapchain_info.clipped = VK_TRUE;

  swapchain_info.oldSwapchain = old_swapchain;

  VkSwapchainKHR swapchain;
  VkResult result;
  result = vkCreateSwapchainKHR(device, &swapchain_info, 0, &swapchain);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return swapchain;
}

SwapchainImages get_swapchain_images(VkDevice device, VkSwapchainKHR swapchain) {
  SwapchainImages images;
  images.images = 0;
  images.count = 0;

  VkResult result;
  result = vkGetSwapchainImagesKHR(device, swapchain, &images.count, 0);
  if(result != VK_SUCCESS) {
    images.count = 0;
    return images;
  }

  images.images = malloc(sizeof(VkImage)*images.count);
  if(images.images == 0) {
    images.count = 0;
    return images;
  }

  result = vkGetSwapchainImagesKHR(device, swapchain, &images.count, images.images);
  if(result != VK_SUCCESS) {
    images.count = 0;
    return images;
  }

  return images;
}

VkImageView* create_image_views(VkDevice device, uint32_t image_count, VkImage* images, VkSurfaceFormatKHR format) {
  VkImageView* image_views = malloc(sizeof(VkImageView)*image_count);
  if(image_views == 0) {
    return 0;
  }

  for(uint32_t i = 0; i < image_count; i++) {
    VkImageViewCreateInfo view_info = {};
    view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    view_info.image = images[i];
    view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
    view_info.format = format.format;

    view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;

    view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    view_info.subresourceRange.baseMipLevel = 0;
    view_info.subresourceRange.levelCount = 1;
    view_info.subresourceRange.baseArrayLayer = 0;
    view_info.subresourceRange.layerCount = 1;

    VkResult result = vkCreateImageView(device, &view_info, 0, &image_views[i]);
    if(result != VK_SUCCESS) {
      free(image_views);
      return 0;
    }
  }

  return image_views;
}

VkFramebuffer* create_swapchain_framebuffers(VkDevice device, uint32_t image_count, VkImageView* image_views, VkImageView depth_image_view, VkRenderPass render_pass, VkExtent2D extent) {
  VkFramebuffer* framebuffers = malloc(sizeof(VkFramebuffer)*image_count);
  if(framebuffers == 0) {
    return 0;
  }

  for(uint32_t i = 0; i < image_count; i++) {
    VkImageView attachments[] = {
      image_views[i],
      depth_image_view,
    };

    VkFramebufferCreateInfo framebuffer_info = {};
    framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    framebuffer_info.renderPass = render_pass;
    framebuffer_info.attachmentCount = 2;
    framebuffer_info.pAttachments = attachments;
    framebuffer_info.width = extent.width;
    framebuffer_info.height = extent.height;
    framebuffer_info.layers = 1;

    VkResult result = vkCreateFramebuffer(device, &framebuffer_info, 0, &framebuffers[i]);
    if(result != VK_SUCCESS) {
      free(framebuffers);
      return 0;
    }
  }

  return framebuffers;
}

VkShaderModule create_shader_module(VkDevice device, const char * code, uint32_t code_size) {
  VkShaderModuleCreateInfo shader_info = {};
  shader_info.sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
  shader_info.codeSize = code_size;
  shader_info.pCode = (uint32_t*)code;

  VkShaderModule shader;
  VkResult result;
  result = vkCreateShaderModule(device, &shader_info, 0, &shader);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return shader;
}

VkShaderModule load_shader_file(uint32_t buffer_size, const char* path, VkDevice device) {
  FILE* file;
  file = fopen(path, "r");
  if(file == 0) {
    return VK_NULL_HANDLE;
  }

  char * buffer = malloc(buffer_size);
  if(buffer == 0) {
    return VK_NULL_HANDLE;
  }

  size_t read = fread(buffer, 1, buffer_size, file);

  VkShaderModule shader = create_shader_module(device, buffer, read);
  free(buffer);
  return shader;
}

VkRenderPass create_render_pass(VkDevice device, VkSurfaceFormatKHR format, VkFormat depth_format) {
  VkAttachmentDescription attachments[] = {
    {
      .format = format.format,
      .samples = VK_SAMPLE_COUNT_1_BIT,
      .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
      .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
      .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
      .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
      .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
      .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
    },
    {
      .format = depth_format,
      .samples = VK_SAMPLE_COUNT_1_BIT,
      .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
      .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
      .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
      .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
      .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
      .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
    },
  };

  VkAttachmentReference color_attachment_refs[] = {
    {
      .attachment = 0,
      .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
    },
  };

  VkAttachmentReference depth_attachment_ref = {
    .attachment = 1,
    .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
  };

  VkSubpassDescription subpass = {
    .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
    .colorAttachmentCount = sizeof(color_attachment_refs)/sizeof(VkAttachmentReference),
    .pColorAttachments = color_attachment_refs,
    .pDepthStencilAttachment = &depth_attachment_ref,
  };

  VkSubpassDependency dependency = {
    .srcSubpass = VK_SUBPASS_EXTERNAL,
    .dstSubpass = 0,
    .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
    .srcAccessMask = 0,
    .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
    .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
  };

  VkRenderPassCreateInfo render_info = {
    .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
    .attachmentCount = sizeof(attachments)/sizeof(VkAttachmentDescription),
    .pAttachments = attachments,
    .subpassCount = 1,
    .pSubpasses = &subpass,
    .dependencyCount = 1,
    .pDependencies = &dependency,
  };

  VkRenderPass render_pass;
  VkResult result = vkCreateRenderPass(device, &render_info, 0, &render_pass);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return render_pass;
}

uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties) {
  VkPhysicalDeviceMemoryProperties memory_properties;
  vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);

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

  return 0xFFFFFFFF;
}

AllocatedImage allocate_image(VkPhysicalDevice physical_device, VkDevice device, VkImageType type, VkFormat format, VkExtent3D size, VkImageUsageFlags usage, VkMemoryPropertyFlags properties) {
  VkImageCreateInfo image_info = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
    .imageType = type,
    .extent = size,
    .mipLevels = 1,
    .arrayLayers = 1,
    .format = format,
    .tiling = VK_IMAGE_TILING_OPTIMAL,
    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    .usage =  usage,
    .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
    .samples = VK_SAMPLE_COUNT_1_BIT,
    .flags = 0,
  };

  AllocatedImage allocated = {
    .memory = VK_NULL_HANDLE,
    .image = VK_NULL_HANDLE,
  };
  VkResult result = vkCreateImage(device, &image_info, 0, &allocated.image);
  if(result != VK_SUCCESS) {
    return allocated;
  }

  VkMemoryRequirements memory_requirements;
  vkGetImageMemoryRequirements(device, allocated.image, &memory_requirements);

  VkMemoryAllocateInfo memory_info = {
    .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
    .allocationSize = memory_requirements.size,
    .memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties),
  };

  result = vkAllocateMemory(device, &memory_info, 0, &allocated.memory);
  if(result != VK_SUCCESS) {
    vkDestroyImage(device, allocated.image, 0);
    allocated.image = VK_NULL_HANDLE;
    return allocated;
  }

  result = vkBindImageMemory(device, allocated.image, allocated.memory, 0);
  if(result != VK_SUCCESS) {
    vkFreeMemory(device, allocated.memory, 0);
    vkDestroyImage(device, allocated.image, 0);
    allocated.memory = VK_NULL_HANDLE;
    allocated.image = VK_NULL_HANDLE;

    return allocated;
  }

  return allocated;
}

AllocatedBuffer allocate_buffer(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) {
  AllocatedBuffer ret = {};
  ret.memory = VK_NULL_HANDLE;
  ret.buffer = VK_NULL_HANDLE;


  VkBufferCreateInfo buffer_info = {};
  buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  buffer_info.size = size;
  buffer_info.usage = usage;
  buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

  VkResult result = vkCreateBuffer(device, &buffer_info, 0, &ret.buffer);
  if(result != VK_SUCCESS) {
    ret.buffer = VK_NULL_HANDLE;
    ret.memory = VK_NULL_HANDLE;
    return ret;
  }

  VkMemoryRequirements memory_requirements;
  vkGetBufferMemoryRequirements(device, ret.buffer, &memory_requirements);

  VkMemoryAllocateInfo alloc_info = {};
  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  alloc_info.allocationSize = memory_requirements.size;
  alloc_info.memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties);

  result = vkAllocateMemory(device, &alloc_info, 0, &ret.memory);
  if(result != VK_SUCCESS) {
    vkDestroyBuffer(device, ret.buffer, 0);
    ret.buffer = VK_NULL_HANDLE;
    ret.memory = VK_NULL_HANDLE;
    return ret;
  }

  result = vkBindBufferMemory(device, ret.buffer, ret.memory, 0);
  if(result != VK_SUCCESS) {
    vkDestroyBuffer(device, ret.buffer, 0);
    ret.buffer = VK_NULL_HANDLE;
    ret.memory = VK_NULL_HANDLE;
    return ret;
  }

  return ret;
}

void deallocate_buffer(VkDevice device, AllocatedBuffer buffer) {
  vkDestroyBuffer(device, buffer.buffer, 0);
  vkFreeMemory(device, buffer.memory, 0);
};

void deallocate_image(VkDevice device, AllocatedImage image) {
  vkDestroyImage(device, image.image, 0);
  vkFreeMemory(device, image.memory, 0);
};

AllocatedBuffer* allocate_buffers(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, uint32_t count) {
  AllocatedBuffer* buffers = malloc(sizeof(AllocatedBuffer)*count);
  if(buffers == 0) {
    return 0;
  }

  for(uint32_t i = 0; i < count; i++) {
    buffers[i] = allocate_buffer(physical_device, device, size, usage, properties);
    if(buffers[i].memory == VK_NULL_HANDLE) {
      for(uint32_t j = 0; j < i; j++) {
        deallocate_buffer(device, buffers[i]);
      }

      free(buffers);
      return 0;
    }
  }

  return buffers;
}

VkCommandBuffer command_begin_single(VkDevice device, VkCommandPool transfer_pool) {
  VkCommandBufferAllocateInfo command_info = {};
  command_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  command_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  command_info.commandPool = transfer_pool;
  command_info.commandBufferCount = 1;

  VkCommandBuffer command_buffer;
  VkResult result = vkAllocateCommandBuffers(device, &command_info, &command_buffer);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  VkCommandBufferBeginInfo begin_info = {};
  begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

  result = vkBeginCommandBuffer(command_buffer, &begin_info);
  if(result != VK_SUCCESS) {
    vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer);
    return VK_NULL_HANDLE;
  }

  return command_buffer;
}

VkResult command_end_single(VkDevice device, VkCommandBuffer command_buffer, VkCommandPool transfer_pool, VkQueue transfer_queue) {
  VkSubmitInfo submit_info = {};
  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  submit_info.commandBufferCount = 1;
  submit_info.pCommandBuffers = &command_buffer;

  VkResult result = vkQueueSubmit(transfer_queue, 1, &submit_info, 0);
  if(result != VK_SUCCESS) {
    vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer);
    return result;
  }

  result = vkQueueWaitIdle(transfer_queue);
  vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer);
  return result;
}

VkResult command_copy_buffers(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkBuffer source, VkBuffer dest, VkDeviceSize size) {
  VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool);

  VkBufferCopy copy_region = {};
  copy_region.srcOffset = 0;
  copy_region.dstOffset = 0;
  copy_region.size = size;

  vkCmdCopyBuffer(command_buffer, source, dest, 1, &copy_region);
  VkResult result = vkEndCommandBuffer(command_buffer);
  if(result != VK_SUCCESS) {
    vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer);
    return result;
  }

  return command_end_single(device, command_buffer, transfer_pool, transfer_queue);
}

VkResult command_transition_image_layout(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkImageLayout old_layout, VkImageLayout new_layout, VkImage image) {
  VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool);

  VkImageMemoryBarrier barrier = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
    .oldLayout = old_layout,
    .newLayout = new_layout,
    .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .image = image,
    .subresourceRange = {
      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
      .levelCount = 1,
      .layerCount = 1,
      .baseMipLevel = 0,
      .baseArrayLayer = 0,
    },
    .srcAccessMask = 0,
    .dstAccessMask = 0,
  };
  vkCmdPipelineBarrier(command_buffer, 0, 0, 0, 0, 0, 0, 0, 1, &barrier);

  return command_end_single(device, command_buffer, transfer_pool, transfer_queue);
}

VkResult command_copy_buffer_to_image(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkExtent3D image_size, VkBuffer source, VkImage dest) {
  VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool);

  VkBufferImageCopy region = {
    .bufferOffset = 0,
    .bufferRowLength = 0,
    .bufferImageHeight = 0,
    .imageSubresource = {
      .baseArrayLayer = 0,
      .layerCount = 1,
      .mipLevel = 0,
      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
    },
    .imageOffset = {
      .x = 0,
      .y = 0,
      .z = 0,
    },
    .imageExtent = image_size,
  };

  vkCmdCopyBufferToImage(command_buffer, source, dest, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

  return command_end_single(device, command_buffer, transfer_pool, transfer_queue);
}

AllocatedBuffer create_populated_buffer(VkPhysicalDevice physical_device, VkDevice device, void* data, VkDeviceSize size, VkCommandPool transfer_pool, VkQueue transfer_queue, VkBufferUsageFlags usage) {
  AllocatedBuffer staging_buffer = {};
  AllocatedBuffer vertex_buffer = {}; 
  staging_buffer = allocate_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
  if(staging_buffer.memory == VK_NULL_HANDLE) {
    return vertex_buffer;
  }


  void* buffer_data;
  VkResult result = vkMapMemory(device, staging_buffer.memory, 0, size, 0, &buffer_data);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging_buffer);
    return vertex_buffer;
  }

  memcpy(buffer_data, data, size);

  vkUnmapMemory(device, staging_buffer.memory);

  vertex_buffer = allocate_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  if(vertex_buffer.memory == VK_NULL_HANDLE) {
    deallocate_buffer(device, staging_buffer);
    return vertex_buffer;
  }

  result = command_copy_buffers(device, transfer_pool, transfer_queue, staging_buffer.buffer, vertex_buffer.buffer, size);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging_buffer);
    deallocate_buffer(device, vertex_buffer);
    vertex_buffer.buffer = VK_NULL_HANDLE;
    vertex_buffer.memory = VK_NULL_HANDLE;
    return vertex_buffer;
  }

  return vertex_buffer;
}

Texture load_texture(VkPhysicalDevice physical_device, VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkExtent2D size, uint32_t stride, VkFormat format, void* image_data){
  Texture ret = {
    .image.image = VK_NULL_HANDLE,
    .image.memory = VK_NULL_HANDLE,
    .view = VK_NULL_HANDLE,
  };

  uint32_t image_size = size.width * size.height * stride;
  AllocatedBuffer staging = allocate_buffer(physical_device, device, image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
  if(staging.memory == VK_NULL_HANDLE) {
    return ret;
  }

  void* staging_ptr;
  VkResult result = vkMapMemory(device, staging.memory, 0, image_size, 0, &staging_ptr);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging);
    return ret;
  }

  memcpy(staging_ptr, image_data, image_size);

  vkUnmapMemory(device, staging.memory);

  VkExtent3D full_extent = {
    .width = size.width,
    .height = size.height,
    .depth = 1,
  };
  AllocatedImage image = allocate_image(physical_device, device, VK_IMAGE_TYPE_2D, format, full_extent, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  if(image.memory == VK_NULL_HANDLE) {
    deallocate_buffer(device, staging);
    deallocate_image(device, image);
    return ret;
  }

  result = command_transition_image_layout(device, transfer_pool, transfer_queue, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image.image);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging);
    deallocate_image(device, image);
    return ret;
  }

  result = command_copy_buffer_to_image(device, transfer_pool, transfer_queue, full_extent, staging.buffer, image.image);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging);
    deallocate_image(device, image);
    return ret;
  }

  VkImageView view;
  VkImageViewCreateInfo view_info = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    .image = image.image,
    .viewType = VK_IMAGE_VIEW_TYPE_2D,
    .components = {
      .a = VK_COMPONENT_SWIZZLE_IDENTITY,
      .b = VK_COMPONENT_SWIZZLE_IDENTITY,
      .g = VK_COMPONENT_SWIZZLE_IDENTITY,
      .r = VK_COMPONENT_SWIZZLE_IDENTITY,
    },
    .format = format,
    .subresourceRange = {
      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
      .layerCount = 1,
      .levelCount = 1,
      .baseArrayLayer = 0,
      .baseMipLevel = 0,
    },
  };

  result = vkCreateImageView(device, &view_info, 0, &view);
  if(result != VK_SUCCESS) {
    deallocate_buffer(device, staging);
    deallocate_image(device, image);
    return ret;
  }

  VkSampler sampler;
  VkSamplerCreateInfo sampler_info = {
    .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
    .magFilter = VK_FILTER_NEAREST,
    .minFilter = VK_FILTER_NEAREST,
    .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
    .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
    .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
    .anisotropyEnable = VK_FALSE,
    .maxAnisotropy = 2.0f,
    .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
    .unnormalizedCoordinates = VK_FALSE,
    .compareEnable = VK_FALSE,
    .compareOp = VK_COMPARE_OP_ALWAYS,
    .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
    .mipLodBias = 0.0f,
    .minLod = 0.0f,
    .maxLod = 0.0f,
  };

  result = vkCreateSampler(device, &sampler_info, 0, &sampler);
  deallocate_buffer(device, staging);
  if(result != VK_SUCCESS) {
    deallocate_image(device, image);
    return ret;
  }

  ret.image = image;
  ret.view = view;
  ret.sampler = sampler;
  return ret;
}

VkPipeline create_graphics_pipeline(
    VkDevice device,
    VkExtent2D extent,
    VkPipelineLayout layout,
    VkRenderPass render_pass,
    uint32_t shader_stage_count,
    VkPipelineShaderStageCreateInfo* shader_stages,
    MeshType mesh_type
    ) {
  VkDynamicState dynamic_states[] = {
    VK_DYNAMIC_STATE_VIEWPORT,
    VK_DYNAMIC_STATE_SCISSOR,
  };
  uint32_t dynamic_state_count = sizeof(dynamic_states)/sizeof(VkDynamicState);

  VkPipelineDynamicStateCreateInfo dynamic_info = {};
  dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
  dynamic_info.dynamicStateCount = dynamic_state_count;
  dynamic_info.pDynamicStates = dynamic_states;

  VkPipelineVertexInputStateCreateInfo vertex_input_info = {};
  vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  vertex_input_info.vertexBindingDescriptionCount = mesh_type.bindings_count;
  vertex_input_info.pVertexBindingDescriptions = mesh_type.bindings;
  vertex_input_info.vertexAttributeDescriptionCount = mesh_type.attributes_count;
  vertex_input_info.pVertexAttributeDescriptions = mesh_type.attributes;

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

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

  VkRect2D scissor = {};
  VkOffset2D scissor_offset = {.x = 0, .y = 0};
  scissor.offset = scissor_offset;
  scissor.extent = extent;

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

  VkPipelineRasterizationStateCreateInfo raster_info = {};
  raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  raster_info.depthClampEnable = VK_FALSE;
  raster_info.rasterizerDiscardEnable = VK_FALSE;
  raster_info.polygonMode = VK_POLYGON_MODE_FILL;
  raster_info.lineWidth = 1.0f;
  raster_info.cullMode = VK_CULL_MODE_BACK_BIT;
  raster_info.frontFace = VK_FRONT_FACE_CLOCKWISE;
  raster_info.depthBiasEnable = VK_FALSE;
  raster_info.depthBiasConstantFactor = 0.0f;
  raster_info.depthBiasClamp = 0.0f;
  raster_info.depthBiasSlopeFactor = 0.0f;

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

  VkPipelineDepthStencilStateCreateInfo depth_info = {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
    .depthTestEnable = VK_TRUE,
    .depthWriteEnable = VK_TRUE,
    .depthCompareOp = VK_COMPARE_OP_LESS,
    .depthBoundsTestEnable = VK_FALSE,
    .maxDepthBounds = 1.0f,
    .minDepthBounds = 0.0f,
    .stencilTestEnable = VK_FALSE,
    .front = {},
    .back = {},
  };

  VkPipelineColorBlendAttachmentState color_blend_attachment = {};
  color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
  color_blend_attachment.blendEnable = VK_TRUE;
  color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
  color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
  color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
  color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
  color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
  color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;

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

  VkGraphicsPipelineCreateInfo pipeline_info = {};
  pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
  pipeline_info.stageCount = shader_stage_count;
  pipeline_info.pStages = shader_stages;
  pipeline_info.pVertexInputState = &vertex_input_info;
  pipeline_info.pInputAssemblyState = &input_assembly_info;
  pipeline_info.pViewportState = &viewport_state;
  pipeline_info.pRasterizationState = &raster_info;
  pipeline_info.pDepthStencilState = 0;
  pipeline_info.pColorBlendState = &color_blend_info;
  pipeline_info.pDynamicState = &dynamic_info;
  pipeline_info.pDepthStencilState = &depth_info;
  pipeline_info.layout = layout;
  pipeline_info.renderPass = render_pass;
  pipeline_info.subpass = 0;

  pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
  pipeline_info.basePipelineIndex = -1;

  VkPipeline pipeline;
  VkResult result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, 0, &pipeline);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return pipeline;
}

VkCommandPool create_command_pool(VkDevice device, uint32_t queue_family) {
  VkCommandPoolCreateInfo pool_info = {};
  pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  pool_info.queueFamilyIndex = queue_family;

  VkCommandPool command_pool;
  VkResult result = vkCreateCommandPool(device, &pool_info, 0, &command_pool);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return command_pool;
}

VkResult recreate_swap_chain(VulkanContext* context) {
  for(uint32_t i = 0; i < context->swapchain_image_count; i++) {
    vkDestroyFramebuffer(context->device, context->swapchain_framebuffers[i], 0);
    vkDestroyImageView(context->device, context->swapchain_image_views[i], 0);
  }

  vkDestroySwapchainKHR(context->device, context->swapchain, 0);
  free(context->swapchain_images);
  free(context->swapchain_image_views);
  free(context->swapchain_framebuffers);
  free(context->swapchain_details.formats);
  free(context->swapchain_details.present_modes);

  vkDestroyImageView(context->device, context->depth_image_view, 0);
  deallocate_image(context->device, context->depth_image);

  SwapchainDetails swapchain_details = get_swapchain_details(context->physical_device, context->surface);
  if(swapchain_details.formats == 0) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->swapchain_details = swapchain_details;
  }

  context->swapchain_format = choose_swapchain_format(context->swapchain_details);
  context->swapchain_present_mode = choose_present_mode(context->swapchain_details);
  context->swapchain_extent = choose_swapchain_extent(context->swapchain_details);

  VkExtent3D depth_extent = {
    .width = context->swapchain_extent.width,
    .height = context->swapchain_extent.height,
    .depth = 1,
  };

  AllocatedImage depth_image = allocate_image(context->physical_device, context->device, VK_IMAGE_TYPE_2D, context->depth_format, depth_extent, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  if(depth_image.memory == VK_NULL_HANDLE) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->depth_image = depth_image;
  }

  VkImageViewCreateInfo depth_view_info = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    .image = depth_image.image,
    .viewType = VK_IMAGE_VIEW_TYPE_2D,
    .format = context->depth_format,
    .components = {
      .r = VK_COMPONENT_SWIZZLE_IDENTITY,
      .g = VK_COMPONENT_SWIZZLE_IDENTITY,
      .b = VK_COMPONENT_SWIZZLE_IDENTITY,
      .a = VK_COMPONENT_SWIZZLE_IDENTITY,
    },
    .subresourceRange = {
      .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
      .baseMipLevel = 0,
      .levelCount = 1,
      .baseArrayLayer = 0,
      .layerCount = 1,
    },
  };

  VkImageView depth_image_view;
  VkResult result = vkCreateImageView(context->device, &depth_view_info, 0, &depth_image_view);
  if(result != VK_SUCCESS) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->depth_image_view = depth_image_view;
  }

  VkSwapchainKHR swapchain = create_swapchain(context->device, context->swapchain_format, context->swapchain_present_mode, context->swapchain_extent, context->surface, context->swapchain_details.capabilities, context->queue_indices, context->swapchain);
  if(swapchain == VK_NULL_HANDLE) {
    context->swapchain = VK_NULL_HANDLE;
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->swapchain = swapchain;
  }

  SwapchainImages swapchain_images = get_swapchain_images(context->device, context->swapchain);
  if(swapchain_images.count == 0) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->swapchain_images = swapchain_images.images;
    context->swapchain_image_count = swapchain_images.count;
  }

  VkImageView* image_views = create_image_views(context->device, context->swapchain_image_count, context->swapchain_images, context->swapchain_format);
  if(image_views == 0) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->swapchain_image_views = image_views;
  }

  VkFramebuffer* framebuffers = create_swapchain_framebuffers(context->device, context->swapchain_image_count, context->swapchain_image_views, context->depth_image_view, context->render_pass, context->swapchain_extent);
  if(framebuffers == 0) {
    return VK_ERROR_INITIALIZATION_FAILED;
  } else {
    context->swapchain_framebuffers = framebuffers;
  }

  return VK_SUCCESS;
}

void command_draw_object(Material material, Object object, uint32_t frame_num, VkCommandBuffer command_buffer) {
  MaybeValue maybe_mesh = map_lookup(object.attributes, ATTRIBUTE_ID_MESH);
  if(maybe_mesh.has_value == false) {
    return;
  }

  object_update_mappings(material, object, frame_num);

  Mesh* mesh = maybe_mesh.value;

  VkBuffer vertex_buffers[] = {mesh->vertex_buffer.buffer};
  VkDeviceSize offsets[] = {0};

  vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, offsets);
  vkCmdBindIndexBuffer(command_buffer, mesh->index_buffer.buffer, 0, VK_INDEX_TYPE_UINT16);

  if(material.object_set_layout != VK_NULL_HANDLE) {
    MaybeValue maybe_descriptors = map_lookup(object.attributes, ATTRIBUTE_ID_DESCRIPTOR_SETS);
    if(maybe_descriptors.has_value == false) {
      return;
    }

    VkDescriptorSet* descriptor_sets = maybe_descriptors.value;
    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.layout, 2, 1, &descriptor_sets[frame_num], 0, 0);
  }

  vkCmdDrawIndexed(command_buffer, mesh->index_count, 1, 0, 0, 0);
}

void command_draw_material(Material material, uint32_t object_count, Object* objects, uint32_t frame_num, VkDescriptorSet* scene_descriptors, VkCommandBuffer command_buffer) {
  vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.pipeline);
  vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.layout, 0, 1, &scene_descriptors[frame_num], 0, 0);
  if(material.material_descriptors != 0) {
    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.layout, 1, 1, &material.material_descriptors[frame_num], 0, 0);
  }
  for(uint32_t i = 0; i < object_count; i++) {
    command_draw_object(material, objects[i], frame_num, command_buffer);
  }
}

VkResult command_draw_scene(uint32_t materials_count, Material* materials, uint32_t* object_counts, Object** objects, uint32_t frame_num, VkDescriptorSet* scene_descriptors, VkCommandBuffer command_buffer, VkRenderPass render_pass, VkFramebuffer framebuffer, VkExtent2D extent) {
  VkCommandBufferBeginInfo begin_info = {};
  begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  begin_info.flags = 0;
  begin_info.pInheritanceInfo = 0;

  VkResult result = vkBeginCommandBuffer(command_buffer, &begin_info);
  if(result != VK_SUCCESS) {
    return result;
  }

  VkRenderPassBeginInfo render_pass_info = {};
  render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
  render_pass_info.renderPass = render_pass;
  render_pass_info.framebuffer = framebuffer;
  VkOffset2D render_offset = {.x = 0, .y = 0};
  render_pass_info.renderArea.offset = render_offset;
  render_pass_info.renderArea.extent = extent;
  VkClearValue clear_colors[] = {
    {
      .color = {
        {0.0f, 0.0f, 0.0f, 1.0f}
      }
    },
    {
      .depthStencil = {1.0f, 0.0f},
    },
  };
  render_pass_info.clearValueCount = sizeof(clear_colors)/sizeof(VkClearValue);
  render_pass_info.pClearValues = clear_colors;

  vkCmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);

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

  VkRect2D scissor = {};
  VkOffset2D scissor_offset = {.x = 0.0f, .y = 0.0f};
  scissor.offset = scissor_offset;
  scissor.extent = extent;
  vkCmdSetScissor(command_buffer, 0, 1, &scissor);

  for(uint i = 0; i < materials_count; i++) {
    command_draw_material(materials[i], object_counts[i], objects[i], frame_num, scene_descriptors, command_buffer);
  }

  vkCmdEndRenderPass(command_buffer);

  return vkEndCommandBuffer(command_buffer);
}

VkCommandBuffer* create_command_buffers(VkDevice device, VkCommandPool command_pool, uint32_t image_count)  {
  VkCommandBufferAllocateInfo alloc_info = {};
  alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  alloc_info.commandPool = command_pool;
  alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  alloc_info.commandBufferCount = image_count;

  VkCommandBuffer* command_buffers = malloc(sizeof(VkCommandBuffer)*image_count);
  if(command_buffers == 0) {
    return 0;
  }

  VkResult result = vkAllocateCommandBuffers(device, &alloc_info, command_buffers);
  if(result != VK_SUCCESS) {
    return VK_NULL_HANDLE;
  }

  return command_buffers;
}

VkSemaphore* create_semaphores(VkDevice device, VkSemaphoreCreateFlags flags, uint32_t count) {
  VkSemaphore* semaphores = malloc(sizeof(VkSemaphore)*count);
  if(semaphores == 0) {
    return 0;
  }

  VkSemaphoreCreateInfo semaphore_info = {};
  semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
  semaphore_info.flags = flags;

  for(uint32_t i = 0; i < count; i++) {
    VkResult result = vkCreateSemaphore(device, &semaphore_info, 0, &semaphores[i]);
    if(result != VK_SUCCESS) {
      free(semaphores);
      return 0;
    }
  }
  return semaphores;
}

VkFence* create_fences(VkDevice device, VkFenceCreateFlags flags, uint32_t count) {
  VkFence* fences = malloc(sizeof(VkFence)*count);
  if(fences == 0) {
    return 0;
  }

  VkFenceCreateInfo fence_info = {};
  fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
  fence_info.flags = flags;

  for(uint32_t i = 0; i < count; i++) {
    VkResult result = vkCreateFence(device, &fence_info, 0, &fences[i]);
    if(result != VK_SUCCESS) {
      free(fences);
      return 0;
    }
  }
  return fences;
}

Mesh load_texture_mesh(VkPhysicalDevice physical_device, VkDevice device, struct TextureVertex* vertices, uint32_t vertex_count, uint16_t* indices, uint32_t index_count, VkCommandPool transfer_pool, VkQueue transfer_queue) {
  Mesh mesh = {};
  mesh.vertex_buffer.buffer = VK_NULL_HANDLE;
  mesh.vertex_buffer.memory = VK_NULL_HANDLE;
  mesh.index_buffer.buffer = VK_NULL_HANDLE;
  mesh.index_buffer.memory = VK_NULL_HANDLE;

  AllocatedBuffer vertex_buffer = create_populated_buffer(physical_device, device, (void*)vertices, sizeof(struct TextureVertex) * vertex_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
  if(vertex_buffer.memory == VK_NULL_HANDLE) {
    return mesh;
  }

  mesh.vertex_buffer = vertex_buffer;
  mesh.vertex_count = vertex_count;

  AllocatedBuffer index_buffer = create_populated_buffer(physical_device, device, (void*)indices, sizeof(uint16_t) * index_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
  if(index_buffer.memory == VK_NULL_HANDLE) {
    deallocate_buffer(device, vertex_buffer);
    AllocatedBuffer tmp = { .memory = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE};
    mesh.vertex_buffer = tmp;
    return mesh;
  }

  mesh.index_buffer = index_buffer;
  mesh.index_count = index_count;

  return mesh;
}

Object create_object(uint32_t max_frames_in_flight, uint32_t descriptor_count) {
  Object ret = {
    .attributes = {
      .buckets = 0,
    },
  };

  Map attributes = map_create(8, 2);
  if(attributes.buckets == 0) {
    return ret;
  }

  if(descriptor_count > 0) {
    void*** descriptor_arrays = malloc(sizeof(void**)*max_frames_in_flight);
    if(descriptor_arrays == 0) {
      return ret;
    }
    for(uint32_t i = 0; i < max_frames_in_flight; i++) {
      descriptor_arrays[i] = malloc(sizeof(void*)*descriptor_count);

      if(descriptor_arrays[i] == 0) {
        for(uint32_t j = 0; j < i; j++) {
          free(descriptor_arrays[j]);
        }
        free(descriptor_arrays);
        map_destroy(attributes);
      }
    }

    bool result = map_add(&attributes, ATTRIBUTE_ID_DESCRIPTORS, descriptor_arrays);
    if(result == false) {
      for(uint32_t i = 0; i < max_frames_in_flight; i++) {
        free(descriptor_arrays[i]);
      }
      free(descriptor_arrays);
      map_destroy(attributes);
    }
  }

  ret.attributes = attributes;

  return ret;
}

Object create_renderable(Mesh* mesh, Material* material, VkDescriptorSet* descriptor_sets, uint32_t max_frames_in_flight, uint32_t descriptor_count) {
  Object zero = {
    .attributes = {
      .buckets = 0,
    },
  };

  Object object = create_object(max_frames_in_flight, descriptor_count);
  if(object.attributes.buckets == 0) {
    return zero;
  }

  if(mesh == 0 || material == 0) {
    return zero;
  }

  bool result = map_add(&object.attributes, ATTRIBUTE_ID_MESH, mesh);
  if(result == false) {
    map_destroy(object.attributes);
    return zero;
  }

  result = map_add(&object.attributes, ATTRIBUTE_ID_MATERIAL, material);
  if(result == false) {
    map_destroy(object.attributes);
    return zero;
  }

  result = map_add(&object.attributes, ATTRIBUTE_ID_DESCRIPTOR_SETS, descriptor_sets);
  if(result == false) {
    map_destroy(object.attributes);
    return zero;
  }
  return object;
}

Mesh load_simple_mesh(VkPhysicalDevice physical_device, VkDevice device, struct Vertex* vertices, uint32_t vertex_count, uint16_t* indices, uint32_t index_count, VkCommandPool transfer_pool, VkQueue transfer_queue) {
  Mesh mesh = {};
  mesh.vertex_buffer.buffer = VK_NULL_HANDLE;
  mesh.vertex_buffer.memory = VK_NULL_HANDLE;
  mesh.index_buffer.buffer = VK_NULL_HANDLE;
  mesh.index_buffer.memory = VK_NULL_HANDLE;

  AllocatedBuffer vertex_buffer = create_populated_buffer(physical_device, device, (void*)vertices, sizeof(struct Vertex) * vertex_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
  if(vertex_buffer.memory == VK_NULL_HANDLE) {
    return mesh;
  }

  mesh.vertex_buffer = vertex_buffer;
  mesh.vertex_count = vertex_count;

  AllocatedBuffer index_buffer = create_populated_buffer(physical_device, device, (void*)indices, sizeof(uint16_t) * index_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
  if(index_buffer.memory == VK_NULL_HANDLE) {
    deallocate_buffer(device, vertex_buffer);
    AllocatedBuffer tmp = { .memory = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE};
    mesh.vertex_buffer = tmp;
    return mesh;
  }

  mesh.index_buffer = index_buffer;
  mesh.index_count = index_count;

  return mesh;
}

Material create_material(
    VkDevice device,
    VkExtent2D extent,
    VkRenderPass render_pass,
    uint32_t shader_stage_count,
    VkPipelineShaderStageCreateInfo* shader_stages,
    VkDescriptorSetLayout scene_ubo_layout,
    PipelineLayout pipeline_layout,
    MeshType mesh_type,
    uint32_t max_frames_in_flight,
    Map object_descriptor_mappings
    ) {
  Material zero_material = {
    .pipeline = VK_NULL_HANDLE,
  };

  VkDescriptorSetLayout material_set_layout;
  VkDescriptorSetLayout object_set_layout;

  VkDescriptorPool material_descriptor_pool = VK_NULL_HANDLE;
  VkDescriptorSet* material_descriptors = 0;

  VkDescriptorSetLayoutCreateInfo material_layout_info = {
    .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
    .bindingCount = pipeline_layout.material_bindings_count,
    .pBindings = pipeline_layout.material_bindings,
  };

  VkResult result = vkCreateDescriptorSetLayout(device, &material_layout_info, 0, &material_set_layout);
  if(result != VK_SUCCESS) {
    return zero_material;
  }

  if(pipeline_layout.material_bindings_count > 0) {
    material_descriptors = malloc(sizeof(VkDescriptorSet)*max_frames_in_flight);
    if(material_descriptors == 0) {
      return zero_material;
    }

    VkDescriptorPoolSize* pool_sizes = malloc(sizeof(VkDescriptorPool)*pipeline_layout.material_bindings_count);
    if(pool_sizes == 0) {
      return zero_material;
    }

    for(uint32_t i = 0; i < pipeline_layout.material_bindings_count; i++) {
      VkDescriptorPoolSize pool_size = {
        .type = pipeline_layout.material_bindings[i].descriptorType,
        .descriptorCount = pipeline_layout.material_bindings[i].descriptorCount,
      };
      pool_sizes[i] = pool_size;
    }

    VkDescriptorPoolCreateInfo pool_info = {
      .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
      .poolSizeCount = pipeline_layout.material_bindings_count,
      .maxSets = max_frames_in_flight,
      .pPoolSizes = pool_sizes,
    };

    result = vkCreateDescriptorPool(device, &pool_info, 0, &material_descriptor_pool);
    free(pool_sizes);
    if(result != VK_SUCCESS) {
      return zero_material;
    }

    VkDescriptorSetLayout* set_layouts = malloc(sizeof(VkDescriptorSetLayout)*max_frames_in_flight);
    if(set_layouts == 0) {
      vkDestroyDescriptorPool(device, material_descriptor_pool, 0);
      return zero_material;
    }

    for(uint32_t i = 0; i < max_frames_in_flight; i++) {
      set_layouts[i] = material_set_layout;
    }

    VkDescriptorSetAllocateInfo alloc_info = {
      .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
      .descriptorSetCount = max_frames_in_flight,
      .descriptorPool = material_descriptor_pool,
      .pSetLayouts = set_layouts,
    };

    result = vkAllocateDescriptorSets(device, &alloc_info, material_descriptors);
    free(set_layouts);
    if(result != VK_SUCCESS) {
      vkDestroyDescriptorPool(device, material_descriptor_pool, 0);
      return zero_material;
    }
  }

  VkDescriptorSetLayoutCreateInfo mesh_layout_info = {
    .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
    .bindingCount = pipeline_layout.object_bindings_count,
    .pBindings = pipeline_layout.object_bindings,
  };

  result = vkCreateDescriptorSetLayout(device, &mesh_layout_info, 0, &object_set_layout);
  if(result != VK_SUCCESS) {
    return zero_material;
  }
  VkDescriptorSetLayout all_layouts[3] = {scene_ubo_layout, material_set_layout, object_set_layout};

  VkPipelineLayout layout;
  VkPipelineLayoutCreateInfo layout_info = {
    .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
    .setLayoutCount = 3,
    .pSetLayouts = all_layouts,
    .pushConstantRangeCount = 0,
    .pPushConstantRanges = 0,
  };

  result = vkCreatePipelineLayout(device, &layout_info, 0, &layout);
  if(result != VK_SUCCESS) {
    return zero_material;
  }

  VkPipeline pipeline = create_graphics_pipeline(device, extent, layout, render_pass, shader_stage_count, shader_stages, mesh_type);
  if(pipeline == VK_NULL_HANDLE) {
    return zero_material;
  }

  Material material = {
    .layout = layout,
    .pipeline = pipeline,

    .material_set_layout = material_set_layout,
    .object_set_layout = object_set_layout,

    .material_descriptors = material_descriptors,
    .material_descriptor_pool = material_descriptor_pool,
    .material_descriptors_count = max_frames_in_flight,
    .object_descriptor_mappings = object_descriptor_mappings,
  };

  return material;
}

Material create_simple_mesh_material(VkDevice device, VkExtent2D extent, VkRenderPass render_pass, VkDescriptorSetLayout scene_ubo_layout, uint32_t max_frames_in_flight) {
  VkShaderModule vert_shader = load_shader_file(2048, "shader_src/basic.vert.spv", device);
  VkShaderModule frag_shader = load_shader_file(2048, "shader_src/basic.frag.spv", device);
  VkPipelineShaderStageCreateInfo shader_stages[2] = {};
  shader_stages[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
  shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
  shader_stages[0].module = vert_shader;
  shader_stages[0].pName = "main";

  shader_stages[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
  shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
  shader_stages[1].module = frag_shader;
  shader_stages[1].pName = "main";

  VkVertexInputBindingDescription bindings[1] = {
    {
      .binding = 0, // Which buffer 'binding' to use
      .stride = sizeof(struct Vertex), // How many bytes to increase the index between instance
      .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, // Whether an instance is a vertex or an index
    },
  };

  VkVertexInputAttributeDescription attributes[2] = {
    {
      .binding = 0, // Which buffer 'binding' to use
      .location = 0, // Which 'location' to export as to shader
      .format = VK_FORMAT_R32G32B32_SFLOAT, // What format to interpret as for shader
      .offset = offsetof(struct Vertex, pos), // What offset from instance start
    },
    {
      .binding = 0,
      .location = 1,
      .format = VK_FORMAT_R32G32B32_SFLOAT,
      .offset = offsetof(struct Vertex, color),
    },
  };

  MeshType simple_mesh_type = {
    .bindings = bindings,
    .bindings_count = sizeof(bindings)/sizeof(VkVertexInputBindingDescription),
    .attributes = attributes,
    .attributes_count = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription),
  };

  PipelineLayout simple_layout = {

  };

  Map empty_map = {
    .buckets = 0,
    .buckets_count = 0,
  };

  return create_material(device, extent, render_pass, 2, shader_stages, scene_ubo_layout, simple_layout, simple_mesh_type, max_frames_in_flight, empty_map);
}

Material create_texture_mesh_material(VkDevice device, VkExtent2D extent, VkRenderPass render_pass, VkDescriptorSetLayout scene_ubo_layout, uint32_t max_frames_in_flight) {
  VkShaderModule vert_shader = load_shader_file(2048, "shader_src/texture.vert.spv", device);
  if(vert_shader == VK_NULL_HANDLE) {
    Material tmp = {};
    return tmp;
  }
  VkShaderModule frag_shader = load_shader_file(2048, "shader_src/texture.frag.spv", device);
  if(frag_shader == VK_NULL_HANDLE) {
    Material tmp = {};
    return tmp;
  }
  VkPipelineShaderStageCreateInfo shader_stages[2] = {};
  shader_stages[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
  shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
  shader_stages[0].module = vert_shader;
  shader_stages[0].pName = "main";

  shader_stages[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT;
  shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
  shader_stages[1].module = frag_shader;
  shader_stages[1].pName = "main";

  VkVertexInputBindingDescription bindings[] = {
    {
      .binding = 0,
      .stride = sizeof(struct TextureVertex),
      .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
    },
  };

  VkVertexInputAttributeDescription attributes[] = {
    {
      .binding = 0,
      .location = 0,
      .format = VK_FORMAT_R32G32B32_SFLOAT,
      .offset = offsetof(struct TextureVertex, pos),
    },
    {
      .binding = 0,
      .location = 1,
      .format = VK_FORMAT_R32G32B32_SFLOAT,
      .offset = offsetof(struct TextureVertex, color),
    },
    {
      .binding = 0,
      .location = 2,
      .format = VK_FORMAT_R32G32_SFLOAT,
      .offset = offsetof(struct TextureVertex, tex),
    },
  };

  VkDescriptorSetLayoutBinding mesh_set_bindings[] = {
    {
      .binding = 0,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
      .pImmutableSamplers = 0,
      .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
    },
    {
      .binding = 1,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
      .pImmutableSamplers = 0,
      .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
    },
  };

  MeshType textured_mesh_type = {
    .bindings = bindings,
    .bindings_count = sizeof(bindings)/sizeof(VkVertexInputBindingDescription),
    .attributes = attributes,
    .attributes_count = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription),
  };

  PipelineLayout texture_layout = {
    .object_bindings_count = sizeof(mesh_set_bindings)/sizeof(VkDescriptorSetLayoutBinding),
    .object_bindings = mesh_set_bindings,
  };

  Map object_descriptor_mappings = map_create(8, 2);
  if(object_descriptor_mappings.buckets == 0) {
    Material tmp = {};
    return tmp;
  }

  Mapping* position_mapping = malloc(sizeof(Mapping));
  if(position_mapping == 0) {
    map_destroy(object_descriptor_mappings);
    Material tmp = {};
    return tmp;
  }
  position_mapping->mapping_type = MAPPING_POSITION_TO_MATRIX;
  position_mapping->index = 0;

  bool map_result = map_add(&object_descriptor_mappings, ATTRIBUTE_ID_POSITION, position_mapping);
  if(map_result != true) {
    map_destroy(object_descriptor_mappings);
    free(position_mapping);
    Material tmp = {};
    return tmp;
  }

  return create_material(device, extent, render_pass, 2, shader_stages, scene_ubo_layout, texture_layout, textured_mesh_type, max_frames_in_flight, object_descriptor_mappings);
}

VulkanContext* init_vulkan(GLFWwindow* window, uint32_t max_frames_in_flight) {
  VulkanContext* context = (VulkanContext*)malloc(sizeof(VulkanContext));

  VkInstance instance = create_instance();
  if(instance == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to initialize vulkan instance\n");
    return 0;
  } else {
    context->instance = instance;
  }

  VkDebugUtilsMessengerEXT debug_messenger = create_debug_messenger(context->instance);
  if(debug_messenger == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to initialize vulkan debug messenger\n");
    return 0;
  } else {
    context->debug_messenger = debug_messenger;
  }

  VkPhysicalDevice physical_device = get_best_physical_device(context->instance);
  if(physical_device == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to pick vulkan physical device\n");
    return 0;
  } else {
    context->physical_device = physical_device;
  }

  VkSurfaceKHR surface = create_surface_khr(context->instance, window);
  if(surface == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan surface\n");
    return 0;
  } else {
    context->surface = surface;
  }

  QueueIndices queue_indices = get_queue_indices(context->physical_device, context->surface);
  if(check_queue_indices(queue_indices) == false) {
    fprintf(stderr, "failed to get vulkan queue indices\n");
    return 0;
  } else {
    context->queue_indices = queue_indices;
  }

  VkDevice device = create_logical_device(context->physical_device, context->queue_indices);
  if(device == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan logical device\n");
    return 0;
  } else {
    context->device = device;
  }
  vkGetDeviceQueue(device, context->queue_indices.graphics_family, context->queue_indices.graphics_index, &context->queues.graphics);
  vkGetDeviceQueue(device, context->queue_indices.present_family, context->queue_indices.present_index, &context->queues.present);
  vkGetDeviceQueue(device, context->queue_indices.transfer_family, context->queue_indices.transfer_index, &context->queues.transfer);

  SwapchainDetails swapchain_details = get_swapchain_details(context->physical_device, context->surface);
  if(swapchain_details.formats == 0) {
    fprintf(stderr, "failed to create vulkan logical device\n");
    return 0;
  } else {
    context->swapchain_details = swapchain_details;
  }

  context->swapchain_format = choose_swapchain_format(context->swapchain_details);
  context->swapchain_present_mode = choose_present_mode(context->swapchain_details);
  context->swapchain_extent = choose_swapchain_extent(context->swapchain_details);

  VkSwapchainKHR swapchain = create_swapchain(context->device, context->swapchain_format, context->swapchain_present_mode, context->swapchain_extent, context->surface, context->swapchain_details.capabilities, context->queue_indices, VK_NULL_HANDLE);
  if(swapchain == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan swapchain\n");
    return 0;
  } else {
    context->swapchain = swapchain;
  }

  SwapchainImages swapchain_images = get_swapchain_images(context->device, context->swapchain);
  if(swapchain_images.count == 0) {
    fprintf(stderr, "failed to get vulkan swapchain images\n");
    return 0;
  } else {
    context->swapchain_image_count = swapchain_images.count;
    context->swapchain_images = swapchain_images.images;
  }

  VkImageView* image_views = create_image_views(context->device, context->swapchain_image_count, context->swapchain_images, context->swapchain_format);
  if(image_views == 0) {
    fprintf(stderr, "failed to create vulkan image views\n");
    return 0;
  } else {
    context->swapchain_image_views = image_views;
  }

  VkFormat requested[] = {
    VK_FORMAT_D32_SFLOAT,
    VK_FORMAT_D32_SFLOAT_S8_UINT,
    VK_FORMAT_D24_UNORM_S8_UINT
  };
  VkFormat depth_format = find_depth_format(context->physical_device, 3, requested, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
  if(depth_format == VK_FORMAT_MAX_ENUM) {
    fprintf(stderr, "failed to find a suitable depth image format\n");
    return 0;
  } else {
    context->depth_format = depth_format;
  }

  VkRenderPass render_pass = create_render_pass(context->device, context->swapchain_format, context->depth_format);
  if(render_pass == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan render pass\n");
    return 0;
  } else {
    context->render_pass = render_pass;
  }

  VkExtent3D depth_extent = {
    .width = context->swapchain_extent.width,
    .height = context->swapchain_extent.height,
    .depth = 1,
  };
  AllocatedImage depth_image = allocate_image(context->physical_device, context->device, VK_IMAGE_TYPE_2D, depth_format, depth_extent, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  if(depth_image.memory == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan depth image\n");
    return 0;
  } else {
    context->depth_image = depth_image;
  }

  VkImageViewCreateInfo depth_view_info = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    .image = depth_image.image,
    .viewType = VK_IMAGE_VIEW_TYPE_2D,
    .format = depth_format,
    .components = {
      .r = VK_COMPONENT_SWIZZLE_IDENTITY,
      .g = VK_COMPONENT_SWIZZLE_IDENTITY,
      .b = VK_COMPONENT_SWIZZLE_IDENTITY,
      .a = VK_COMPONENT_SWIZZLE_IDENTITY,
    },
    .subresourceRange = {
      .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
      .baseMipLevel = 0,
      .levelCount = 1,
      .baseArrayLayer = 0,
      .layerCount = 1,
    },
  };

  VkImageView depth_image_view;
  VkResult result = vkCreateImageView(context->device, &depth_view_info, 0, &depth_image_view);
  if(result != VK_SUCCESS) {
    fprintf(stderr, "failed to create vulkan depth image view\n");
    return 0;
  } else {
    context->depth_image_view = depth_image_view;
  }

  VkFramebuffer* framebuffers = create_swapchain_framebuffers(context->device, context->swapchain_image_count, context->swapchain_image_views, context->depth_image_view, context->render_pass, context->swapchain_extent);
  if(framebuffers == 0) {
    fprintf(stderr, "failed to create vulkan framebuffers\n");
    return 0;
  } else {
    context->swapchain_framebuffers = framebuffers;
  }

  VkCommandPool graphics_command_pool = create_command_pool(context->device, context->queue_indices.graphics_family);
  if(graphics_command_pool == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan graphics command pool");
    return 0;
  } else {
    context->graphics_command_pool = graphics_command_pool;
  }

  VkCommandPool transfer_command_pool = create_command_pool(context->device, context->queue_indices.transfer_family);
  if(transfer_command_pool == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan transfer command pool");
    return 0;
  } else {
    context->transfer_command_pool = transfer_command_pool;
  }

  context->max_frames_in_flight = max_frames_in_flight;

  VkCommandBuffer* swapchain_command_buffers = create_command_buffers(context->device, context->graphics_command_pool, max_frames_in_flight);
  if(swapchain_command_buffers == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan swapchain command buffer\n");
    return 0;
  } else {
    context->swapchain_command_buffers = swapchain_command_buffers;
  }

  VkSemaphore* ia_semaphores = create_semaphores(context->device, 0, max_frames_in_flight);
  if(ia_semaphores == 0) {
    fprintf(stderr, "failed to create vulkan image available semaphores\n");
    return 0;
  } else {
    context->image_available_semaphores = ia_semaphores;
  }

  VkSemaphore* rf_semaphores = create_semaphores(context->device, 0, max_frames_in_flight);
  if(rf_semaphores == 0) {
    fprintf(stderr, "failed to create vulkan render finished semaphores\n");
    return 0;
  } else {
    context->render_finished_semaphores = rf_semaphores;
  }

  VkFence* if_fences = create_fences(context->device, VK_FENCE_CREATE_SIGNALED_BIT, max_frames_in_flight);
  if(if_fences == 0) {
    fprintf(stderr, "failed to create vulkan in flight fence\n");
    return 0;
  } else {
    context->in_flight_fences = if_fences;
  }

  VkDescriptorPoolSize scene_pool_sizes[] = {
    {
      .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
      .descriptorCount = max_frames_in_flight,
    }
  };
  VkDescriptorPool scene_pool = create_descriptor_pool(device, scene_pool_sizes, 1, max_frames_in_flight);
  if(scene_pool == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan scene descriptor pool\n");
    return 0;
  } else {
    context->scene_pool = scene_pool;
  }

  VkDescriptorSetLayoutBinding scene_ubo_layout_binding = {
    .binding = 0,
    .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
    .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
    .descriptorCount = 1,
    .pImmutableSamplers = 0,
  };

  VkDescriptorSetLayout scene_descriptor_layout = create_descriptor_set_layout(device, &scene_ubo_layout_binding, 1);
  if(scene_descriptor_layout == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create vulkan scene descriptor layout\n");
    return 0;
  } else {
    context->scene_descriptor_layout = scene_descriptor_layout;
  }

  VkDescriptorSet* scene_descriptors = create_descriptor_sets(context->device, context->scene_descriptor_layout, context->scene_pool, max_frames_in_flight);
  if(scene_descriptors == 0) {
    fprintf(stderr, "failed to create vulkan scene descriptore\n");
    return 0;
  } else {
    context->scene_descriptors = scene_descriptors;
  }

  AllocatedBuffer* scene_ubos = allocate_buffers(context->physical_device, context->device, sizeof(struct SceneUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, max_frames_in_flight);
  if(scene_ubos == 0) {
    fprintf(stderr, "failed to create vulkan scnene ubo buffers\n");
    return 0;
  } else {
    context->scene_ubos = scene_ubos;
  }

  void** scene_ubo_ptrs = malloc(sizeof(void*)*max_frames_in_flight);
  if(scene_ubo_ptrs == 0) {
    return 0;
  } else {
    context->scene_ubo_ptrs = scene_ubo_ptrs;
  }

  for(uint32_t i = 0; i < max_frames_in_flight; i++) {
    VkResult result = vkMapMemory(device, context->scene_ubos[i].memory, 0, sizeof(struct SceneUBO), 0, &context->scene_ubo_ptrs[i]);
    if(result != VK_SUCCESS) {
      for(uint32_t j = 0; j < i; j++) {
        vkUnmapMemory(device, context->scene_ubos[j].memory);
      }

      fprintf(stderr, "failed to map vulkan buffers to pointers for scene ubos\n");
      return 0;
    }
  }

  for(uint32_t i = 0; i < max_frames_in_flight; i++) {
    VkDescriptorBufferInfo buffer_info = {};
    buffer_info.buffer = context->scene_ubos[i].buffer;
    buffer_info.offset = 0;
    buffer_info.range = sizeof(struct SceneUBO);

    VkWriteDescriptorSet descriptor_write = {};
    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptor_write.dstSet = context->scene_descriptors[i];
    descriptor_write.dstBinding = 0;
    descriptor_write.dstArrayElement = 0;
    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    descriptor_write.descriptorCount = 1;
    descriptor_write.pBufferInfo = &buffer_info;
    descriptor_write.pImageInfo = 0;
    descriptor_write.pTexelBufferView = 0;

    vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, 0);
  }

  Material simple_mesh_material = create_simple_mesh_material(context->device, context->swapchain_extent, context->render_pass, context->scene_descriptor_layout, max_frames_in_flight);
  if(simple_mesh_material.pipeline == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create simple mesh material\n");
    return 0;
  } else {
    context->simple_mesh_material = simple_mesh_material;
  }

  Mesh triangle_mesh = load_simple_mesh(context->physical_device, context->device, (struct Vertex*)vertices, 4, (uint16_t*)indices, 6, context->transfer_command_pool, context->queues.transfer);
  if(triangle_mesh.vertex_buffer.buffer == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to load triangle mesh\n");
    return 0;
  } else {
    context->triangle_mesh = triangle_mesh;
  }

  Object triangle_object = create_renderable(&context->triangle_mesh, &context->simple_mesh_material, 0, 2, 0);
  if(triangle_object.attributes.buckets == 0) {
    fprintf(stderr, "failed to create renderable triangle object\n");
    return 0;
  } else {
    context->triangle_object = triangle_object;
  }

  Material texture_mesh_material = create_texture_mesh_material(context->device, context->swapchain_extent, context->render_pass, context->scene_descriptor_layout, max_frames_in_flight);
  if(texture_mesh_material.pipeline == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to create texture mesh material\n");
    return 0;
  } else {
    context->texture_mesh_material = texture_mesh_material;
  }

  Mesh triangle_mesh_textured = load_texture_mesh(context->physical_device, context->device, (struct TextureVertex*)texture_vertices, 4, (uint16_t*)indices, 6, context->transfer_command_pool, context->queues.transfer);
  if(triangle_mesh_textured.vertex_buffer.buffer == VK_NULL_HANDLE) {
    fprintf(stderr, "failed to load textured triangle mesh\n");
    return 0;
  } else {
    context->triangle_mesh_textured = triangle_mesh_textured;
  }

  VkDescriptorPoolSize TODO_sizes[] = {
    {
      .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
      .descriptorCount = max_frames_in_flight,
    },
    {
      .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 
      .descriptorCount = max_frames_in_flight,
    },
  };

  VkDescriptorPoolCreateInfo TODO_info = {
    .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
    .poolSizeCount = sizeof(TODO_sizes)/sizeof(VkDescriptorPoolSize),
    .maxSets = max_frames_in_flight,
    .pPoolSizes = TODO_sizes,
  };

  VkDescriptorPool TODO_pool;
  result = vkCreateDescriptorPool(context->device, &TODO_info, 0, &TODO_pool);
  if(result != VK_SUCCESS) {
    fprintf(stderr, "failed to create temporary descriptor pool\n");
    return 0;
  }

  VkDescriptorSetLayout* TODO_layouts = malloc(sizeof(VkDescriptorSetLayout)*max_frames_in_flight);
  if(TODO_layouts == 0) {
    fprintf(stderr, "failed to allocate temp buffer\n");
    return 0;
  }

  for(uint32_t i = 0; i < max_frames_in_flight; i++) {
    TODO_layouts[i] = texture_mesh_material.object_set_layout;
  }

  VkDescriptorSetAllocateInfo TODO_alloc = {
    .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
    .descriptorPool = TODO_pool,
    .descriptorSetCount = max_frames_in_flight,
    .pSetLayouts = TODO_layouts,
  };

  VkDescriptorSet* TODO_sets = malloc(sizeof(VkDescriptorSet)*max_frames_in_flight);
  if(TODO_sets == 0) {
    fprintf(stderr, "failed to allocate temp buffer\n");
    return 0;
  }

  result = vkAllocateDescriptorSets(device, &TODO_alloc, TODO_sets);
  if(result != VK_SUCCESS) {
    fprintf(stderr, "failed to allocate TODO descriptors\n");
    return 0;
  }

  Object triangle_object_textured = create_renderable(&context->triangle_mesh_textured, &context->texture_mesh_material, TODO_sets, max_frames_in_flight, 1);
  if(triangle_object_textured.attributes.buckets == 0) {
    fprintf(stderr, "failed to create renderable textured triangle object\n");
    return 0;
  } else {
    context->triangle_object_textured = triangle_object_textured;
  }

  Position* position = malloc(sizeof(Position));
  if(position == 0) {
    return 0;
  }
  glm_quat_identity(position->rotation);
  position->scale[0] = 0.1f;
  position->scale[1] = 0.1f;
  position->scale[2] = 0.1f;
  position->position[2] = 1.0f;
  bool map_result = map_add(&triangle_object_textured.attributes, ATTRIBUTE_ID_POSITION, position);
  if(map_result == 0) {
    return 0;
  }

  AllocatedBuffer* triangle_ubos = allocate_buffers(context->physical_device, context->device, sizeof(struct ModelUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, max_frames_in_flight);
  if(triangle_ubos == 0) {
    return 0;
  }

  MaybeValue maybe_descriptors = map_lookup(triangle_object_textured.attributes, ATTRIBUTE_ID_DESCRIPTORS);
  if(maybe_descriptors.has_value == false) {
    return 0;
  }

  void*** descriptors = maybe_descriptors.value;

  for(uint32_t i = 0; i < max_frames_in_flight; i++) {
    VkResult result = vkMapMemory(device, triangle_ubos[i].memory, 0, sizeof(Position), 0, &descriptors[i][0]);
    if(result != VK_SUCCESS) {
      return 0;
    }

    VkDescriptorBufferInfo buffer_info = {
      .buffer = triangle_ubos[i].buffer,
      .offset = 0,
      .range = sizeof(struct ModelUBO),
    };

    VkWriteDescriptorSet write_info = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = TODO_sets[i],
      .dstBinding = 1,
      .dstArrayElement = 0,
      .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
      .descriptorCount = 1,
      .pBufferInfo = &buffer_info,
    };

    vkUpdateDescriptorSets(context->device, 1, &write_info, 0, 0);
  }

  VkExtent2D texture_size = {
    .width = 10,
    .height = 10,
  };
  struct __attribute__((__packed__)) texel  {
    uint8_t r;
    uint8_t g;
    uint8_t b;
    uint8_t a;
  };

  struct texel WHT = {255, 255, 255, 255};
  struct texel BLK = {0, 0, 0, 255};
  struct texel RED = {255, 0, 0, 255};
  struct texel GRN = {0, 255, 0, 255};
  struct texel BLU = {0, 0, 255, 255};

  struct texel texture_data[100] = {
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, WHT,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, WHT,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, WHT,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, WHT,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
    RED, WHT, GRN, WHT, BLU, WHT, RED, WHT, GRN, BLK,
  };

  Texture test_texture = load_texture(context->physical_device, context->device, context->transfer_command_pool, context->queues.transfer, texture_size, 4, VK_FORMAT_R8G8B8A8_SRGB, texture_data);

  for(uint32_t i = 0; i < max_frames_in_flight; i++) {
    VkDescriptorImageInfo image_info = {
      .sampler = test_texture.sampler,
      .imageView = test_texture.view,
      .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
    };

    VkWriteDescriptorSet descriptor_write = {};
    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptor_write.dstSet = TODO_sets[i];
    descriptor_write.dstBinding = 0;
    descriptor_write.dstArrayElement = 0;
    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    descriptor_write.descriptorCount = 1;
    descriptor_write.pBufferInfo = 0;
    descriptor_write.pImageInfo = &image_info;
    descriptor_write.pTexelBufferView = 0;

    vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, 0);
  }

  return context;
}

struct {
  bool forward;
  bool backward;
  bool left;
  bool right;
  bool up;
  bool down;
  bool turn_left;
  bool turn_right;
  bool turn_up;
  bool turn_down;
  bool roll_left;
  bool roll_right;
} key_flags = {
  .forward = false,
  .backward = false,
  .left = false,
  .right = false,

  .turn_left = false,
  .turn_right = false,
  .turn_up = false,
  .turn_down = false,
  .roll_left = false,
  .roll_right = false,
};

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
  (void)scancode;
  (void)window;
  (void)mods;

  switch(key) {
  case GLFW_KEY_W:
    if(action == GLFW_PRESS) {
      key_flags.forward = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.forward = false;
    }
    break;

  case GLFW_KEY_A:
    if(action == GLFW_PRESS) {
      key_flags.left = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.left = false;
    }
    break;

  case GLFW_KEY_S:
    if(action == GLFW_PRESS) {
      key_flags.backward = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.backward = false;
    }
    break;

  case GLFW_KEY_D:
    if(action == GLFW_PRESS) {
      key_flags.right = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.right = false;
    }
    break;

  case GLFW_KEY_SPACE:
    if(action == GLFW_PRESS) {
      key_flags.up = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.up = false;
    }
    break;

  case GLFW_KEY_LEFT_SHIFT:
    if(action == GLFW_PRESS) {
      key_flags.down = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.down = false;
    }
    break;

  case GLFW_KEY_RIGHT:
    if(action == GLFW_PRESS) {
      key_flags.turn_right = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.turn_right = false;
    }
    break;

  case GLFW_KEY_LEFT:
    if(action == GLFW_PRESS) {
      key_flags.turn_left = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.turn_left = false;
    }
    break;

  case GLFW_KEY_UP:
    if(action == GLFW_PRESS) {
      key_flags.turn_up = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.turn_up = false;
    }
    break;

  case GLFW_KEY_DOWN:
    if(action == GLFW_PRESS) {
      key_flags.turn_down = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.turn_down = false;
    }
    break;

  case GLFW_KEY_Q:
    if(action == GLFW_PRESS) {
      key_flags.roll_left = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.roll_left = false;
    }
    break;

  case GLFW_KEY_E:
    if(action == GLFW_PRESS) {
      key_flags.roll_right = true;
    } else if(action == GLFW_RELEASE) {
      key_flags.roll_right = false;
    }
    break;
  }
}

vec3 world_position = {0.0f, 0.0f, 0.0f};
versor world_rotation = {-1.0f, 0.0f, 0.0f, 0.0f};

VkResult update_scene_descriptor(void** buffers, uint32_t frame_index, vec3 world_position, versor world_rotation, float aspect_ratio, float time_delta) {
  vec3 movement_sum = {0.0f, 0.0f, 0.0f};

  if(key_flags.forward) {
    movement_sum[2] -= 1 * time_delta;
  }

  if(key_flags.backward) {
    movement_sum[2] += 1 * time_delta;
  }

  if(key_flags.right) {
    movement_sum[0] += 1 * time_delta;
  }

  if(key_flags.left) {
    movement_sum[0] -= 1 * time_delta;
  }

  if(key_flags.up) {
    movement_sum[1] -= 1 * time_delta;
  }

  if(key_flags.down) {
    movement_sum[1] += 1 * time_delta;
  }


  vec3 eular_rotation = {0.0f, 0.0f, 0.0f};
  if(key_flags.turn_right) {
    eular_rotation[0] -= 1 * time_delta;
  }

  if(key_flags.turn_left) {
    eular_rotation[0] += 1 * time_delta;
  }

  if(key_flags.turn_up) {
    eular_rotation[1] -= 1 * time_delta;
  }

  if(key_flags.turn_down) {
    eular_rotation[1] += 1 * time_delta;
  }

  if(key_flags.roll_right) {
    eular_rotation[2] += 1 * time_delta;
  }

  if(key_flags.roll_left) {
    eular_rotation[2] -= 1 * time_delta;
  }

  vec3 right = {1.0f, 0.0f, 0.0f};
  vec3 up = {0.0f, 1.0f, 0.0f};
  vec3 forward = {0.0f, 0.0f, 1.0f};
  glm_quat_rotatev(world_rotation, right, right);
  glm_quat_rotatev(world_rotation, up, up);
  glm_quat_rotatev(world_rotation, forward, forward);

  versor relative_rotation_y;
  glm_quatv(relative_rotation_y, eular_rotation[1], right);

  versor relative_rotation_x;
  glm_quatv(relative_rotation_x, eular_rotation[0], up);

  versor relative_rotation_z;
  glm_quatv(relative_rotation_z, eular_rotation[2], forward);

  glm_quat_mul(relative_rotation_x, world_rotation, world_rotation);
  glm_quat_mul(relative_rotation_y, world_rotation, world_rotation);
  glm_quat_mul(relative_rotation_z, world_rotation, world_rotation);

  vec3 movement_rot;
  glm_quat_rotatev(world_rotation, movement_sum, movement_rot);
  glm_vec3_add(movement_rot, world_position, world_position);

  struct SceneUBO ubo = {};

  glm_perspective(1.5708f, aspect_ratio, 0.01, 1000, ubo.proj);
  glm_quat_look(world_position, world_rotation, ubo.view);

  memcpy(buffers[frame_index], (void*)&ubo, sizeof(ubo));

  return VK_SUCCESS;
}

VkResult draw_frame(VulkanContext* context) {
  update_scene_descriptor(context->scene_ubo_ptrs, context->current_frame, world_position, world_rotation, (float)context->swapchain_extent.width/(float)context->swapchain_extent.height, 0.01);

  VkResult result;
  result = vkWaitForFences(context->device, 1, &context->in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX);
  if(result != VK_SUCCESS) {
    return result;
  }

  uint32_t image_index;
  result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, context->image_available_semaphores[context->current_frame], VK_NULL_HANDLE, &image_index);
  if(result != VK_SUCCESS) {
    return result;
  }

  result = vkResetFences(context->device, 1, &context->in_flight_fences[context->current_frame]);
  if(result != VK_SUCCESS) {
    return result;
  }

  result = vkResetCommandBuffer(context->swapchain_command_buffers[context->current_frame], 0);
  if(result != VK_SUCCESS) {
    return result;
  }

  uint32_t mesh_counts[] = {1};
  Object* objects[] = {&context->triangle_object_textured};
  result = command_draw_scene(1, &context->texture_mesh_material, (uint32_t*)&mesh_counts, (Object**)objects, context->current_frame, context->scene_descriptors, context->swapchain_command_buffers[context->current_frame], context->render_pass, context->swapchain_framebuffers[image_index], context->swapchain_extent);
  if(result != VK_SUCCESS) {
    return result;
  }

  VkSubmitInfo submit_info = {};
  VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  submit_info.waitSemaphoreCount = 1;
  submit_info.pWaitSemaphores = &context->image_available_semaphores[context->current_frame];
  submit_info.pWaitDstStageMask = wait_stages;
  submit_info.commandBufferCount = 1;
  submit_info.pCommandBuffers = &context->swapchain_command_buffers[context->current_frame];
  submit_info.signalSemaphoreCount = 1;
  submit_info.pSignalSemaphores = &context->render_finished_semaphores[context->current_frame];

  result = vkQueueSubmit(context->queues.graphics, 1, &submit_info, context->in_flight_fences[context->current_frame]);
  if(result != VK_SUCCESS) {
    return result;
  }

  VkPresentInfoKHR present_info = {};
  present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  present_info.waitSemaphoreCount = 1;
  present_info.pWaitSemaphores = &context->render_finished_semaphores[context->current_frame];
  present_info.swapchainCount = 1;
  present_info.pSwapchains = &context->swapchain;
  present_info.pImageIndices = &image_index;
  present_info.pResults = 0;

  return vkQueuePresentKHR(context->queues.present, &present_info);
}

void main_loop(GLFWwindow* window, VulkanContext* context) {
  context->current_frame = 0;
  while(!glfwWindowShouldClose(window)) {
    glfwPollEvents();

    VkResult result = draw_frame(context);
    if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
      recreate_swap_chain(context);
    } else if(result != VK_SUCCESS) {
      fprintf(stderr, "draw_frame error %d\n", result);
      return;
    }

    context->current_frame += 1;
    if(context->current_frame >= context->max_frames_in_flight) {
      context->current_frame = 0;
    }
  }

  vkDeviceWaitIdle(context->device);
}

void cleanup(GLFWwindow* window, VulkanContext* context) {
  if(context != 0) {
    if(context->instance != VK_NULL_HANDLE) {
      if(context->swapchain != VK_NULL_HANDLE) {
        vkDestroySwapchainKHR(context->device, context->swapchain, 0);
      }

      if(context->surface != VK_NULL_HANDLE) {
        vkDestroySurfaceKHR(context->instance, context->surface, 0);
      }

      if(context->device != VK_NULL_HANDLE) {
        vkDestroyDevice(context->device, 0);
      }

      if(context->debug_messenger != VK_NULL_HANDLE) {
        PFN_vkDestroyDebugUtilsMessengerEXT destroy_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(context->instance, "vkDestroyDebugUtilsMessengerEXT");
        destroy_messenger(context->instance, context->debug_messenger, 0);
      }

      vkDestroyInstance(context->instance, 0);
    }
    free(context);
  }

  if(window != 0) {
    glfwDestroyWindow(window);
    glfwTerminate();
  }
}

int main() {
  GLFWwindow* window = init_window(800, 600);
  if(window == 0) {
    fprintf(stderr, "failed to initialize glfw window\n");
    return 1;
  }

  VulkanContext* context = init_vulkan(window, 2);
  if (context == 0) {
    fprintf(stderr, "failed to initialize vulkan context\n");
    return 2;
  }

  glfwSetKeyCallback(window, key_callback);
  main_loop(window, context);

  cleanup(window, context);

  return 0;
}