#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <sys/stat.h>

#include "volk/volk.h"
#include "vulkan/vk_enum_string_helper.h"

#include "collada/inputs.h"
#include "collada/scene/vulkan.h"

#include "minmax.h"
#include "vulkan_helper.h"
#include "dds/validate.h"
#include "dds/vulkan.h"
#include "tga/tga.h"

#include "check.h"
#include "new.h"
#include "file.h"

inline static uint32_t vulkan_semantic_location(char const * const semantic, int semantic_index)
{
  if (strcmp(semantic, "POSITION") == 0 && semantic_index == 0) {
    return 0;
  }
  if (strcmp(semantic, "NORMAL") == 0 && semantic_index == 0) {
    return 1;
  }
  if (strcmp(semantic, "TEXCOORD") == 0 && semantic_index == 0) {
    return 2;
  }
  if (strcmp(semantic, "BLENDINDICES") == 0 && semantic_index == 0) {
    return 3;
  }
  if (strcmp(semantic, "BLENDWEIGHT") == 0 && semantic_index == 0) {
    return 4;
  }
  fprintf(stderr, "unknown semantic %s index %d\n", semantic, semantic_index);
  assert(false);
}

inline static VkFormat vulkan_format(collada::types::input_format format)
{
  switch (format) {
  case collada::types::input_format::FLOAT1: return VK_FORMAT_R32_SFLOAT;
  case collada::types::input_format::FLOAT2: return VK_FORMAT_R32G32_SFLOAT;
  case collada::types::input_format::FLOAT3: return VK_FORMAT_R32G32B32_SFLOAT;
  case collada::types::input_format::FLOAT4: return VK_FORMAT_R32G32B32A32_SFLOAT;
  case collada::types::input_format::INT1: return VK_FORMAT_R32_SINT;
  case collada::types::input_format::INT2: return VK_FORMAT_R32G32_SINT;
  case collada::types::input_format::INT3: return VK_FORMAT_R32G32B32_SINT;
  case collada::types::input_format::INT4: return VK_FORMAT_R32G32B32A32_SINT;
  default: assert(false);
  }
}


inline static uint32_t vulkan_load_layout(collada::types::inputs const & inputs,
                                          uint32_t binding,
                                          uint32_t start_offset,
                                          VkVertexInputAttributeDescription * vertexAttributeDescriptions)
{
  uint32_t offset = start_offset;
  for (int i = 0; i < inputs.elements_count; i++) {
    uint32_t location = vulkan_semantic_location(inputs.elements[i].semantic, inputs.elements[i].semantic_index);
    VkFormat format = vulkan_format(inputs.elements[i].format);
    vertexAttributeDescriptions[i].location = location;
    vertexAttributeDescriptions[i].binding = binding;
    vertexAttributeDescriptions[i].format = format;
    vertexAttributeDescriptions[i].offset = offset;
    offset += collada::inputs::format_size(inputs.elements[i].format);
  }
  return offset;
}

inline static void vulkan_vertex_input_states(collada::types::descriptor const * const descriptor,
                                              VkPipelineVertexInputStateCreateInfo * vertexInputStates,
                                              VkVertexInputBindingDescription * vertexBindingDescriptions)
{
  for (int i = 0; i < descriptor->inputs_list_count; i++) {
    collada::types::inputs const & inputs = descriptor->inputs_list[1];
    VkVertexInputAttributeDescription * vertexAttributeDescriptions = NewM<VkVertexInputAttributeDescription>(inputs.elements_count + collada::inputs::skin_inputs.elements_count);
    uint32_t stride = vulkan_load_layout(inputs,
                                         0, // binding
                                         0, // start_offset
                                         vertexAttributeDescriptions);

    uint32_t vertexJointWeightStride = vulkan_load_layout(collada::inputs::skin_inputs,
                                                          1, // binding
                                                          0, // start_offset
                                                          &vertexAttributeDescriptions[inputs.elements_count]);

    vertexBindingDescriptions[i * 2 + 0] = {
      .binding = 0,
      .stride = stride,
      .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
    };

    vertexBindingDescriptions[i * 2 + 1] = {
      .binding = 1,
      .stride = vertexJointWeightStride,
      .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
    };

    // non-skinned
    vertexInputStates[i * 2 + 0] = {
      .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
      .vertexBindingDescriptionCount = 1,
      .pVertexBindingDescriptions = &vertexBindingDescriptions[i * 2 + 0],
      .vertexAttributeDescriptionCount = (uint32_t)inputs.elements_count,
      .pVertexAttributeDescriptions = vertexAttributeDescriptions,
    };

    // skinned
    vertexInputStates[i * 2 + 1] = {
      .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
      .vertexBindingDescriptionCount = 2,
      .pVertexBindingDescriptions = &vertexBindingDescriptions[i * 2 + 0],
      .vertexAttributeDescriptionCount = (uint32_t)(inputs.elements_count + collada::inputs::skin_inputs.elements_count),
      .pVertexAttributeDescriptions = vertexAttributeDescriptions,
    };
  }
}

namespace collada::scene {

  void vulkan::initial_state(VkInstance instance,
                             VkDevice device,
                             VkQueue queue,
                             VkCommandPool commandPool,
                             VkPhysicalDeviceProperties const & physicalDeviceProperties,
                             VkPhysicalDeviceMemoryProperties const & physicalDeviceMemoryProperties,
                             VkFormat colorFormat,
                             VkFormat depthFormat,
                             VkSampler linearSampler,
                             VkImageView shadowDepthImageView)
  {
    this->instance = instance;
    this->device = device;
    this->queue = queue;
    this->commandPool = commandPool;

    this->physicalDeviceProperties = physicalDeviceProperties;
    this->physicalDeviceMemoryProperties = physicalDeviceMemoryProperties;

    this->colorFormat = colorFormat;
    this->depthFormat = depthFormat;

    this->linearSampler = linearSampler;
    this->shadowDepthImageView = shadowDepthImageView;

    load_shader();
  }

  //////////////////////////////////////////////////////////////////////
  // vertex index buffer
  //////////////////////////////////////////////////////////////////////

  void vulkan::load_vertex_index_buffer(char const * vertex_filename,
                                        char const * vertex_joint_weight_filename,
                                        char const * index_filename)
  {
    uint32_t vertexSize;
    void const * vertexStart = file::open(vertex_filename, &vertexSize);
    uint32_t vertexJointWeightSize;
    void const * vertexJointWeightStart = file::open(vertex_joint_weight_filename, &vertexJointWeightSize);
    uint32_t indexSize;
    void const * indexStart = file::open(index_filename, &indexSize);

    vertexIndex.jointWeightOffset = vertexSize;
    vertexIndex.indexOffset = vertexSize + vertexJointWeightSize;

    // create buffer

    VkDeviceSize bufferSize{ vertexSize + vertexJointWeightSize + indexSize };
    VkBufferCreateInfo vertexIndexBufferCreateInfo{
      .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
      .size = bufferSize,
      .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
      .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VK_CHECK(vkCreateBuffer(device, &vertexIndexBufferCreateInfo, nullptr, &vertexIndex.buffer));

    // allocate memory

    VkMemoryRequirements memoryRequirements;
    vkGetBufferMemoryRequirements(device, vertexIndex.buffer, &memoryRequirements);
    VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT };
    VkMemoryAllocateFlags memoryAllocateFlags{};
    VkDeviceSize stride;
    allocateFromMemoryRequirements(device,
                                   physicalDeviceProperties.limits.nonCoherentAtomSize,
                                   physicalDeviceMemoryProperties,
                                   memoryRequirements,
                                   memoryPropertyFlags,
                                   memoryAllocateFlags,
                                   1,
                                   &vertexIndex.memory,
                                   &stride);

    VK_CHECK(vkBindBufferMemory(device, vertexIndex.buffer, vertexIndex.memory, 0));

    // copy data

    void * vertexIndexMappedData;
    VK_CHECK(vkMapMemory(device, vertexIndex.memory, 0, VK_WHOLE_SIZE, 0, &vertexIndexMappedData));
    memcpy((void *)(((ptrdiff_t)vertexIndexMappedData) + 0), vertexStart, vertexSize);
    memcpy((void *)(((ptrdiff_t)vertexIndexMappedData) + vertexSize), vertexJointWeightStart, vertexJointWeightSize);
    memcpy((void *)(((ptrdiff_t)vertexIndexMappedData) + vertexSize + vertexJointWeightSize), indexStart, indexSize);

    VkMappedMemoryRange mappedMemoryRange{
      .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
      .memory = vertexIndex.memory,
      .offset = 0,
      .size = VK_WHOLE_SIZE,
    };
    vkFlushMappedMemoryRanges(device, 1, &mappedMemoryRange);

    vkUnmapMemory(device, vertexIndex.memory);
  }


  //////////////////////////////////////////////////////////////////////
  // uniform and storage buffers
  //////////////////////////////////////////////////////////////////////

  void vulkan::create_uniform_buffers(collada::types::descriptor const * const descriptor)
  {
    VkMemoryRequirements memoryRequirements[uniformBufferDescriptorCount];
    VkDeviceSize offsets[uniformBufferDescriptorCount];

    shaderData.nodes = NewM<Node>(descriptor->nodes_count);
    shaderData.materialColorImages = NewM<MaterialColorImage>(descriptor->materials_count);

    uint32_t memoryRequirementsIndex = 0;
    // per-frame
    for (uint32_t i = 0; i < maxFrames; i++) {
      // scene buffer
      VkBufferCreateInfo sceneBufferCreateInfo{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = (sizeof (Scene)),
        .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
      };

      VK_CHECK(vkCreateBuffer(device, &sceneBufferCreateInfo, nullptr, &shaderDataDevice.frame[i].sceneBuffer));
      vkGetBufferMemoryRequirements(device, shaderDataDevice.frame[i].sceneBuffer, &memoryRequirements[memoryRequirementsIndex++]);

      // nodes buffer
      VkBufferCreateInfo nodesBufferCreateInfo{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = (sizeof (Node)) * descriptor->nodes_count,
        .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
      };
      VK_CHECK(vkCreateBuffer(device, &nodesBufferCreateInfo, nullptr, &shaderDataDevice.frame[i].nodesBuffer));
      vkGetBufferMemoryRequirements(device, shaderDataDevice.frame[i].nodesBuffer, &memoryRequirements[memoryRequirementsIndex++]);

      // joints buffer
      VkBufferCreateInfo jointsBufferCreateInfo{
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = (sizeof (Joint)) * maxJointsCount,
        .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE
      };
      VK_CHECK(vkCreateBuffer(device, &jointsBufferCreateInfo, nullptr, &shaderDataDevice.frame[i].jointsBuffer));
      vkGetBufferMemoryRequirements(device, shaderDataDevice.frame[i].jointsBuffer, &memoryRequirements[memoryRequirementsIndex++]);
    };

    // material color buffer
    VkBufferCreateInfo materialColorImagesBufferCreateInfo{
      .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
      .size = (sizeof (MaterialColorImage)) * descriptor->materials_count,
      .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
      .sharingMode = VK_SHARING_MODE_EXCLUSIVE
    };
    VK_CHECK(vkCreateBuffer(device, &materialColorImagesBufferCreateInfo, nullptr, &shaderDataDevice.constant.materialColorImagesBuffer));
    vkGetBufferMemoryRequirements(device, shaderDataDevice.constant.materialColorImagesBuffer, &memoryRequirements[memoryRequirementsIndex++]);

    assert(memoryRequirementsIndex == uniformBufferDescriptorCount);

    VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT };
    VkMemoryAllocateFlags memoryAllocateFlags{ };
    shaderDataDevice.memorySize = allocateFromMemoryRequirements2(device,
                                                                  physicalDeviceProperties.limits.nonCoherentAtomSize,
                                                                  physicalDeviceMemoryProperties,
                                                                  memoryPropertyFlags,
                                                                  memoryAllocateFlags,
                                                                  uniformBufferDescriptorCount,
                                                                  memoryRequirements,
                                                                  &shaderDataDevice.memory,
                                                                  offsets);

    VkDeviceSize offset{ 0 };
    VkDeviceSize size{ VK_WHOLE_SIZE };
    VkMemoryMapFlags flags{ 0 };
    VK_CHECK(vkMapMemory(device, shaderDataDevice.memory, offset, size, flags, &shaderDataDevice.mappedData));

    uint32_t offsetsIndex = 0;
    // this must match the same order as memoryRequirements
    for (uint32_t i = 0; i < maxFrames; i++) {
      shaderDataDevice.frame[i].sceneOffset = offsets[offsetsIndex];
      shaderDataDevice.frame[i].sceneSize = memoryRequirements[offsetsIndex++].size;
      shaderDataDevice.frame[i].sceneMapped = (void *)(((size_t)shaderDataDevice.mappedData) + shaderDataDevice.frame[i].sceneOffset);
      VK_CHECK(vkBindBufferMemory(device, shaderDataDevice.frame[i].sceneBuffer, shaderDataDevice.memory, shaderDataDevice.frame[i].sceneOffset));

      shaderDataDevice.frame[i].nodesOffset = offsets[offsetsIndex];
      shaderDataDevice.frame[i].nodesSize = memoryRequirements[offsetsIndex++].size;
      shaderDataDevice.frame[i].nodesMapped = (void *)(((size_t)shaderDataDevice.mappedData) + shaderDataDevice.frame[i].nodesOffset);
      VK_CHECK(vkBindBufferMemory(device, shaderDataDevice.frame[i].nodesBuffer, shaderDataDevice.memory, shaderDataDevice.frame[i].nodesOffset));

      shaderDataDevice.frame[i].jointsOffset = offsets[offsetsIndex];
      shaderDataDevice.frame[i].jointsSize = memoryRequirements[offsetsIndex++].size;
      shaderDataDevice.frame[i].jointsMapped = (void *)(((size_t)shaderDataDevice.mappedData) + shaderDataDevice.frame[i].jointsOffset);
      VK_CHECK(vkBindBufferMemory(device, shaderDataDevice.frame[i].jointsBuffer, shaderDataDevice.memory, shaderDataDevice.frame[i].jointsOffset));
    }
    shaderDataDevice.constant.materialColorImagesOffset = offsets[offsetsIndex];
    shaderDataDevice.constant.materialColorImagesSize = memoryRequirements[offsetsIndex++].size;
    shaderDataDevice.constant.materialColorImagesMapped = (void *)(((size_t)shaderDataDevice.mappedData) + shaderDataDevice.constant.materialColorImagesOffset);
    VK_CHECK(vkBindBufferMemory(device, shaderDataDevice.constant.materialColorImagesBuffer, shaderDataDevice.memory, shaderDataDevice.constant.materialColorImagesOffset));

    assert(offsetsIndex == uniformBufferDescriptorCount);
  }

  //////////////////////////////////////////////////////////////////////
  // descriptor sets
  //////////////////////////////////////////////////////////////////////

  void vulkan::create_descriptor_sets(collada::types::descriptor const * const descriptor)
  {
    //
    // pool
    //
    constexpr int descriptorPoolSizesCount = 4;
    VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{
      {
        .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
        .descriptorCount = (maxFrames * 1),
      },
      {
        .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
        .descriptorCount = (maxFrames * 2) + 1, // +1 for materialColorImages
      },
      {
        .type = VK_DESCRIPTOR_TYPE_SAMPLER,
        .descriptorCount = 1,
      },
      {
        .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
        .descriptorCount = (uint32_t)descriptor->images_count + 1, // +1 for shadow sampler
      },
    };
    VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{
      .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
      .maxSets = maxFrames + 1, // +1 for descriptorSet1
      .poolSizeCount = descriptorPoolSizesCount,
      .pPoolSizes = descriptorPoolSizes
    };
    VK_CHECK(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));

    //
    // uniform buffer descriptor set layout/allocation (set 0, per-frame)
    //
    {
      constexpr int bindingCount = 3;
      VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{
        {
          .binding = 0,
          .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT
        },
        {
          .binding = 1,
          .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT
        },
        {
          .binding = 2,
          .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
        }
      };

      VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = bindingCount,
        .pBindings = descriptorSetLayoutBindings
      };
      VK_CHECK(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts[0]));

      VkDescriptorSetLayout setLayouts[maxFrames];
      for (uint32_t i = 0; i < maxFrames; i++) {
        setLayouts[i] = descriptorSetLayouts[0];
      };

      VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = descriptorPool,
        .descriptorSetCount = maxFrames,
        .pSetLayouts = setLayouts
      };
      VK_CHECK(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, descriptorSets0));
    }

    //
    // uniform buffer descriptor set layout/allocation (set 1, constant)
    //
    {
      constexpr int bindingCount = 4;
      VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{
        {
          .binding = 0,
          .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        },
        {
          .binding = 1,
          .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        },
        { // shadow sampled image
          .binding = 2,
          .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
          .descriptorCount = 1,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        },
        { // scene images
          .binding = 3,
          .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
          .descriptorCount = (uint32_t)descriptor->images_count,
          .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
        }
      };

      VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = bindingCount,
        .pBindings = descriptorSetLayoutBindings
      };
      VK_CHECK(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts[1]));

      VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = descriptorPool,
        .descriptorSetCount = 1,
        .pSetLayouts = &descriptorSetLayouts[1]
      };
      VK_CHECK(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet1));
    }
  }

  //////////////////////////////////////////////////////////////////////
  // descriptor set writes
  //////////////////////////////////////////////////////////////////////

  void vulkan::write_descriptor_sets(collada::types::descriptor const * const descriptor)
  {
    VkWriteDescriptorSet writeDescriptorSets[bindingCount];
    uint32_t writeIndex = 0;

    VkDescriptorBufferInfo sceneDescriptorBufferInfos[maxFrames];
    VkDescriptorBufferInfo nodesDescriptorBufferInfos[maxFrames];
    VkDescriptorBufferInfo jointsDescriptorBufferInfos[maxFrames];

    for (uint32_t i = 0; i < maxFrames; i++) {
      sceneDescriptorBufferInfos[i] = {
        .buffer = shaderDataDevice.frame[i].sceneBuffer,
        .offset = 0,
        .range = shaderDataDevice.frame[i].sceneSize,
      };
      writeDescriptorSets[writeIndex++] = {
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = descriptorSets0[i],
        .dstBinding = 0,
        .descriptorCount = 1,
        .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
        .pBufferInfo = &sceneDescriptorBufferInfos[i]
      };
      nodesDescriptorBufferInfos[i] = {
        .buffer = shaderDataDevice.frame[i].nodesBuffer,
        .offset = 0,
        .range = shaderDataDevice.frame[i].nodesSize,
      };
      writeDescriptorSets[writeIndex++] = {
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = descriptorSets0[i],
        .dstBinding = 1,
        .descriptorCount = 1,
        .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
        .pBufferInfo = &nodesDescriptorBufferInfos[i]
      };
      jointsDescriptorBufferInfos[i] = {
        .buffer = shaderDataDevice.frame[i].jointsBuffer,
        .offset = 0,
        .range = shaderDataDevice.frame[i].jointsSize,
      };
      writeDescriptorSets[writeIndex++] = {
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = descriptorSets0[i],
        .dstBinding = 2,
        .descriptorCount = 1,
        .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
        .pBufferInfo = &jointsDescriptorBufferInfos[i]
      };
    }

    VkDescriptorBufferInfo materialColorImagesDescriptorBufferInfo{
      .buffer = shaderDataDevice.constant.materialColorImagesBuffer,
      .offset = 0,
      .range = shaderDataDevice.constant.materialColorImagesSize,
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet1,
      .dstBinding = 0,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
      .pBufferInfo = &materialColorImagesDescriptorBufferInfo
    };
    VkDescriptorImageInfo samplerDescriptorImageInfo = {
      .sampler = linearSampler,
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet1,
      .dstBinding = 1,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
      .pImageInfo = &samplerDescriptorImageInfo
    };
    VkDescriptorImageInfo sampledImageDescriptorImageInfo = {
      .imageView = shadowDepthImageView,
      .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
    };
    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet1,
      .dstBinding = 2,
      .descriptorCount = 1,
      .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
      .pImageInfo = &sampledImageDescriptorImageInfo
    };

    // scene images
    VkDescriptorImageInfo * sceneDescriptorImageInfos = NewM<VkDescriptorImageInfo>(descriptor->images_count);
    for (int i = 0; i < descriptor->images_count; i++) {
      sceneDescriptorImageInfos[i] = {
        .imageView = images[i].imageView,
        .imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
      };
    }

    writeDescriptorSets[writeIndex++] = {
      .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
      .dstSet = descriptorSet1,
      .dstBinding = 3,
      .descriptorCount = (uint32_t)descriptor->images_count,
      .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
      .pImageInfo = sceneDescriptorImageInfos
    };

    assert(writeIndex == bindingCount);
    vkUpdateDescriptorSets(device, writeIndex, writeDescriptorSets, 0, nullptr);
    free(sceneDescriptorImageInfos);
  }

  //////////////////////////////////////////////////////////////////////
  // material constants
  //////////////////////////////////////////////////////////////////////

  void vulkan::load_material_constants(collada::types::descriptor const * const descriptor)
  {
    constexpr collada::types::color_or_texture_type TEXTURE = collada::types::color_or_texture_type::TEXTURE;
    // store
    for (int i = 0; i < descriptor->materials_count; i++) {
      collada::types::effect const * const effect = descriptor->materials[i]->effect;
      MaterialColorImage & mci = shaderData.materialColorImages[i];
      switch (effect->type) {
      case collada::types::effect_type::BLINN:
        mci.color.emission = *(XMFLOAT4 *)(&effect->blinn.emission.color);
        mci.color.ambient = *(XMFLOAT4 *)(&effect->blinn.ambient.color);
        mci.color.diffuse = *(XMFLOAT4 *)(&effect->blinn.diffuse.color);
        mci.color.specular = *(XMFLOAT4 *)(&effect->blinn.specular.color);
        mci.image.emission = (effect->blinn.emission.type == TEXTURE) ? effect->blinn.emission.texture.image_index : -1;
        mci.image.ambient = (effect->blinn.ambient.type == TEXTURE) ? effect->blinn.ambient.texture.image_index : -1;
        mci.image.diffuse = (effect->blinn.diffuse.type == TEXTURE) ? effect->blinn.diffuse.texture.image_index : -1;
        mci.image.specular = (effect->blinn.specular.type == TEXTURE) ? effect->blinn.specular.texture.image_index : -1;
        break;
      case collada::types::effect_type::LAMBERT:
        mci.color.emission = *(XMFLOAT4 *)(&effect->lambert.emission.color);
        mci.color.ambient = *(XMFLOAT4 *)(&effect->lambert.ambient.color);
        mci.color.diffuse = *(XMFLOAT4 *)(&effect->lambert.diffuse.color);
        mci.color.specular = XMFLOAT4{0, 0, 0, 0};
        mci.image.emission = (effect->lambert.emission.type == TEXTURE) ? effect->lambert.emission.texture.image_index : -1;
        mci.image.ambient = (effect->lambert.ambient.type == TEXTURE) ? effect->lambert.ambient.texture.image_index : -1;
        mci.image.diffuse = (effect->lambert.diffuse.type == TEXTURE) ? effect->lambert.diffuse.texture.image_index : -1;
        mci.image.specular = -1;
        break;
      case collada::types::effect_type::PHONG:
        mci.color.emission = *(XMFLOAT4 *)(&effect->phong.emission.color);
        mci.color.ambient = *(XMFLOAT4 *)(&effect->phong.ambient.color);
        mci.color.diffuse = *(XMFLOAT4 *)(&effect->phong.diffuse.color);
        mci.color.specular = *(XMFLOAT4 *)(&effect->phong.specular.color);
        mci.image.emission = (effect->phong.emission.type == TEXTURE) ? effect->phong.emission.texture.image_index : -1;
        mci.image.ambient = (effect->phong.ambient.type == TEXTURE) ? effect->phong.ambient.texture.image_index : -1;
        mci.image.diffuse = (effect->phong.diffuse.type == TEXTURE) ? effect->phong.diffuse.texture.image_index : -1;
        mci.image.specular = (effect->phong.specular.type == TEXTURE) ? effect->phong.specular.texture.image_index : -1;
        break;
      case collada::types::effect_type::CONSTANT:
        mci.color.emission = *(XMFLOAT4 *)(&effect->constant.color);
        mci.color.ambient = XMFLOAT4{0, 0, 0, 0};
        mci.color.diffuse = XMFLOAT4{0, 0, 0, 0};
        mci.color.specular = XMFLOAT4{0, 0, 0, 0};
        mci.image.emission = -1;
        mci.image.ambient = -1;
        mci.image.diffuse = -1;
        mci.image.specular = -1;
        break;
      default:
        assert(false);
        break;
      }
    }

    // copy
    memcpy(shaderDataDevice.constant.materialColorImagesMapped, &shaderData.materialColorImages[0], (sizeof (MaterialColorImage)) * descriptor->materials_count);

    // flush

    VkMappedMemoryRange mappedMemoryRanges[1]{
      {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = shaderDataDevice.memory,
        .offset = shaderDataDevice.constant.materialColorImagesOffset,
        .size = shaderDataDevice.constant.materialColorImagesSize,
      },
    };
    alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize,
                            shaderDataDevice.memorySize,
                            1, mappedMemoryRanges);
    vkFlushMappedMemoryRanges(device, 1, mappedMemoryRanges);
  }

  //////////////////////////////////////////////////////////////////////
  // material textures
  //////////////////////////////////////////////////////////////////////

  void vulkan::load_image_inner(VkCommandBuffer commandBuffer, VkFence fence, int i, char const * filename)
  {
    size_t length = strlen(filename);
    if (dds::isDDSExtension(filename, length)) {
      createImageFromFilenameDDS(device,
                                 queue,
                                 commandBuffer,
                                 fence,
                                 physicalDeviceProperties.limits.nonCoherentAtomSize,
                                 physicalDeviceMemoryProperties,
                                 filename,
                                 &images[i].image,
                                 &images[i].memory,
                                 &images[i].imageView);
    } else if (tga::isTGAExtension(filename, length)) {
      createImageFromFilenameTGA(device,
                                 queue,
                                 commandBuffer,
                                 fence,
                                 physicalDeviceProperties.limits.nonCoherentAtomSize,
                                 physicalDeviceMemoryProperties,
                                 filename,
                                 &images[i].image,
                                 &images[i].memory,
                                 &images[i].imageView);
    } else {
      fprintf(stderr, "filename: %s\n", filename);
      ASSERT(false, "invalid image filename extension");
    }
  }

  void vulkan::load_image(int i, char const * filename)
  {
    VkCommandBuffer commandBuffer{};
    VkCommandBufferAllocateInfo commandBufferAllocateInfo{
      .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
      .commandPool = commandPool,
      .commandBufferCount = 1
    };
    VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));

    VkFenceCreateInfo fenceCreateInfo{
      .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
    };
    VkFence fence{};
    VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));

    // load

    load_image_inner(commandBuffer, fence, i, filename);

    // cleanup

    vkDestroyFence(device, fence, nullptr);
    vkFreeCommandBuffers(device,
                         commandPool,
                         1,
                         &commandBuffer);
  }

  void vulkan::load_images(collada::types::descriptor const * const descriptor)
  {
    VkCommandBuffer commandBuffer{};
    VkCommandBufferAllocateInfo commandBufferAllocateInfo{
      .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
      .commandPool = commandPool,
      .commandBufferCount = 1
    };
    VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));

    VkFenceCreateInfo fenceCreateInfo{
      .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
    };
    VkFence fence{};
    VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));

    // images
    images = NewM<Image>(descriptor->images_count);

    for (int i = 0; i < descriptor->images_count; i++) {
      char const * filename = descriptor->images[i]->uri;
      load_image_inner(commandBuffer, fence, i, filename);
    }

    // cleanup

    vkDestroyFence(device, fence, nullptr);
    vkFreeCommandBuffers(device,
                         commandPool,
                         1,
                         &commandBuffer);
  }

  //////////////////////////////////////////////////////////////////////
  // shader
  //////////////////////////////////////////////////////////////////////

  void vulkan::load_shader()
  {
    uint32_t shaderSize;
    void const * shaderStart = file::open("shader/collada.spv", &shaderSize);

    VkShaderModuleCreateInfo shaderModuleCreateInfo{
      .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
      .codeSize = shaderSize,
      .pCode = (uint32_t *)shaderStart
    };
    VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule));
  }

  //////////////////////////////////////////////////////////////////////
  // pipeline
  //////////////////////////////////////////////////////////////////////

  void vulkan::create_pipelines(collada::types::descriptor const * const descriptor)
  {
    VkPushConstantRange pushConstantRanges[1]{
      {
        .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_GEOMETRY_BIT,
        .offset = 0,
        .size = (sizeof (PushConstant))
      }
    };

    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
      .setLayoutCount = 2,
      .pSetLayouts = descriptorSetLayouts,
      .pushConstantRangeCount = 1,
      .pPushConstantRanges = pushConstantRanges
    };
    VK_CHECK(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));

    VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
      .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
    };

    VkPipelineShaderStageCreateInfo shaderStages[2]{
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_VERTEX_BIT,
        .module = shaderModule,
        .pName = "VSMain"
      },
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
        .module = shaderModule,
        .pName = "PSMain"
      }
    };

    VkPipelineShaderStageCreateInfo geometryShaderStages[3]{
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_VERTEX_BIT,
        .module = shaderModule,
        .pName = "VSGeometryMain"
      },
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_GEOMETRY_BIT,
        .module = shaderModule,
        .pName = "GSGeometryMain"
      },
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
        .module = shaderModule,
        .pName = "PSGeometryMain"
      }
    };

    VkPipelineShaderStageCreateInfo shadowShaderStages[2]{
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_VERTEX_BIT,
        .module = shaderModule,
        .pName = "VSShadowMain"
      },
      {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
        .module = shaderModule,
        .pName = "PSShadowMain"
      }
    };

    VkPipelineViewportStateCreateInfo viewportState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
      .viewportCount = 1,
      .scissorCount = 1
    };

    constexpr uint32_t dynamicStateCount = 2;
    VkDynamicState dynamicStates[dynamicStateCount]{
      VK_DYNAMIC_STATE_VIEWPORT,
      VK_DYNAMIC_STATE_SCISSOR,
    };
    VkPipelineDynamicStateCreateInfo dynamicState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
      .dynamicStateCount = dynamicStateCount,
      .pDynamicStates = dynamicStates
    };

    VkPipelineDepthStencilStateCreateInfo depthStencilState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
      .depthTestEnable = VK_TRUE,
      .depthWriteEnable = VK_TRUE,
      .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
      .stencilTestEnable = VK_TRUE,
      .front = {
        .failOp = VK_STENCIL_OP_REPLACE,
        .passOp = VK_STENCIL_OP_REPLACE,
        .depthFailOp = VK_STENCIL_OP_REPLACE,
        .compareOp = VK_COMPARE_OP_ALWAYS,
        .compareMask = 0x01,
        .writeMask = 0x01,
        .reference = 1,
      },
      .back = {
        .failOp = VK_STENCIL_OP_REPLACE,
        .passOp = VK_STENCIL_OP_REPLACE,
        .depthFailOp = VK_STENCIL_OP_REPLACE,
        .compareOp = VK_COMPARE_OP_ALWAYS,
        .compareMask = 0x01,
        .writeMask = 0x01,
        .reference = 1,
      },
    };

    VkPipelineRenderingCreateInfo renderingCreateInfo{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
      .colorAttachmentCount = 1,
      .pColorAttachmentFormats = &colorFormat,
      .depthAttachmentFormat = depthFormat,
      .stencilAttachmentFormat = depthFormat
    };

    VkPipelineRenderingCreateInfo shadowRenderingCreateInfo{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
      //.colorAttachmentCount = 1,
      //.pColorAttachmentFormats = &colorFormat,
      .depthAttachmentFormat = depthFormat,
      .stencilAttachmentFormat = depthFormat
    };

    VkPipelineColorBlendAttachmentState blendAttachment{
      .colorWriteMask = 0xF
    };
    VkPipelineColorBlendStateCreateInfo colorBlendState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
      .attachmentCount = 1,
      .pAttachments = &blendAttachment
    };
    VkPipelineRasterizationStateCreateInfo rasterizationState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
      //.cullMode = VK_CULL_MODE_BACK_BIT,
      .cullMode = VK_CULL_MODE_NONE,
      .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
      .lineWidth = 1.0f
    };
    VkPipelineRasterizationStateCreateInfo shadowRasterizationState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
      //.cullMode = VK_CULL_MODE_BACK_BIT,
      .cullMode = VK_CULL_MODE_NONE,
      .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
      .lineWidth = 1.0f
    };
    VkPipelineMultisampleStateCreateInfo multisampleState{
      .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
      .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
    };

    VkPipelineVertexInputStateCreateInfo * vertexInputStates = NewM<VkPipelineVertexInputStateCreateInfo>(descriptor->inputs_list_count * 2);
    VkVertexInputBindingDescription * vertexBindingDescriptions = NewM<VkVertexInputBindingDescription>(descriptor->inputs_list_count * 2);
    vulkan_vertex_input_states(descriptor,
                               vertexInputStates,
                               vertexBindingDescriptions);

    int pipelineCount = descriptor->inputs_list_count * shaderVariantCount;
    VkGraphicsPipelineCreateInfo * pipelineCreateInfos = NewM<VkGraphicsPipelineCreateInfo>(pipelineCount);

    for (int i = 0; i < descriptor->inputs_list_count; i++) {
      // shadow
      pipelineCreateInfos[i * shaderVariantCount + 0] = {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .pNext = &shadowRenderingCreateInfo,
        .stageCount = 2,
        .pStages = shadowShaderStages,
        .pVertexInputState = &vertexInputStates[i * 2 + 1],
        .pInputAssemblyState = &inputAssemblyState,
        .pViewportState = &viewportState,
        .pRasterizationState = &shadowRasterizationState,
        .pMultisampleState = &multisampleState,
        .pDepthStencilState = &depthStencilState,
        .pColorBlendState = &colorBlendState,
        .pDynamicState = &dynamicState,
        .layout = pipelineLayout
      };

      // non-shadow
      pipelineCreateInfos[i * shaderVariantCount + 1] = {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .pNext = &renderingCreateInfo,
        .stageCount = 2,
        .pStages = shaderStages,
        .pVertexInputState = &vertexInputStates[i * 2 + 1],
        .pInputAssemblyState = &inputAssemblyState,
        .pViewportState = &viewportState,
        .pRasterizationState = &rasterizationState,
        .pMultisampleState = &multisampleState,
        .pDepthStencilState = &depthStencilState,
        .pColorBlendState = &colorBlendState,
        .pDynamicState = &dynamicState,
        .layout = pipelineLayout
      };

      // geometry
      pipelineCreateInfos[i * shaderVariantCount + 2] = {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .pNext = &renderingCreateInfo,
        .stageCount = 3,
        .pStages = geometryShaderStages,
        .pVertexInputState = &vertexInputStates[i * 2 + 1],
        .pInputAssemblyState = &inputAssemblyState,
        .pViewportState = &viewportState,
        .pRasterizationState = &rasterizationState,
        .pMultisampleState = &multisampleState,
        .pDepthStencilState = &depthStencilState,
        .pColorBlendState = &colorBlendState,
        .pDynamicState = &dynamicState,
        .layout = pipelineLayout
      };
    };

    pipelines = NewM<VkPipeline>(pipelineCount);
    VK_CHECK(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, pipelineCount, pipelineCreateInfos, nullptr, pipelines));

    free(pipelineCreateInfos);

    free(vertexBindingDescriptions);
    for (int i = 0; i < descriptor->inputs_list_count; i++) {
      free((void *)vertexInputStates[i * 2 + 0].pVertexAttributeDescriptions); // [i * 2 + 1] uses same pointer
    }
    free(vertexInputStates);
  }

  //////////////////////////////////////////////////////////////////////
  // draw
  //////////////////////////////////////////////////////////////////////

  void vulkan::draw_geometry(types::geometry const & geometry,
                             types::instance_material const * const instance_materials,
                             int const instance_materials_count)
  {
    assert(false);
    types::mesh const& mesh = geometry.mesh;

    vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset + mesh.index_buffer_offset, VK_INDEX_TYPE_UINT32);

    for (int j = 0; j < instance_materials_count; j++) {
      types::instance_material const& instance_material = instance_materials[j];
      int materialIndex = instance_material.material_index;
      if (materialIndex == excludeMaterialIndex) {
        continue;
      }
      types::triangles const& triangles = mesh.triangles[instance_material.element_index];

      VkShaderStageFlags stageFlags{ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_GEOMETRY_BIT };
      constexpr uint32_t offset{ (offsetof (PushConstant, materialIndex)) };
      vkCmdPushConstants(commandBuffer, pipelineLayout, stageFlags, offset, (sizeof (uint32_t)), &materialIndex);

      VkDeviceSize vertexOffset{ (VkDeviceSize)mesh.vertex_buffer_offset };
      vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexIndex.buffer, &vertexOffset);
      vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[triangles.inputs_index * shaderVariantCount + pipelineIndex]);

      uint32_t indexCount = triangles.count * 3;
      vkCmdDrawIndexed(commandBuffer, indexCount, 1, triangles.index_offset, 0, 0);
    }
  }

  void vulkan::draw_instance_geometries(types::instance_geometry const * const instance_geometries,
                                        int const instance_geometries_count)
  {
    for (int i = 0; i < instance_geometries_count; i++) {
      types::instance_geometry const &instance_geometry = instance_geometries[i];
      draw_geometry(*instance_geometry.geometry,
                    instance_geometry.instance_materials,
                    instance_geometry.instance_materials_count);
    }
  }

  void vulkan::draw_skin(types::skin const & skin,
                         types::instance_material const * const instance_materials,
                         int const instance_materials_count)
  {
    types::mesh const& mesh = skin.geometry->mesh;

    vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset + mesh.index_buffer_offset, VK_INDEX_TYPE_UINT32);

    for (int j = 0; j < instance_materials_count; j++) {
      types::instance_material const& instance_material = instance_materials[j];
      int materialIndex = instance_material.material_index;
      if (materialIndex == excludeMaterialIndex) {
        continue;
      }
      types::triangles const& triangles = mesh.triangles[instance_material.element_index];

      VkShaderStageFlags stageFlags{ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_GEOMETRY_BIT };
      constexpr uint32_t offset{ (offsetof (PushConstant, materialIndex)) };
      vkCmdPushConstants(commandBuffer, pipelineLayout, stageFlags, offset, (sizeof (uint32_t)), &materialIndex);

      VkBuffer buffers[2] = {
        vertexIndex.buffer,
        vertexIndex.buffer,
      };
      VkDeviceSize offsets[2] = {
        (VkDeviceSize)mesh.vertex_buffer_offset,
        vertexIndex.jointWeightOffset + (VkDeviceSize)skin.vertex_buffer_offset,
      };
      vkCmdBindVertexBuffers(commandBuffer, 0, 2, buffers, offsets);
      vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[triangles.inputs_index * shaderVariantCount + pipelineIndex]);

      uint32_t indexCount = triangles.count * 3;
      vkCmdDrawIndexed(commandBuffer, indexCount, 1, triangles.index_offset, 0, 0);
    }
  }

  void vulkan::draw_instance_controllers(types::instance_controller const * const instance_controllers,
                                         int const instance_controllers_count,
                                         instance_types::node const * const node_instances)
  {
    static Joint joints[maxJointsCount];

    for (int i = 0; i < instance_controllers_count; i++) {
      types::instance_controller const &instance_controller = instance_controllers[i];
      types::skin const &skin = instance_controller.controller->skin;

      XMMATRIX bsm = XMLoadFloat4x4((XMFLOAT4X4*)&skin.bind_shape_matrix);

      assert((uint32_t)instance_controller.joint_count <= maxJointsCount);
      for (int joint_index = 0; joint_index < instance_controller.joint_count; joint_index++) {
        XMMATRIX ibm = XMLoadFloat4x4((XMFLOAT4X4*)&skin.inverse_bind_matrices[joint_index]);
        int node_index = instance_controller.joint_node_indices[joint_index];
        instance_types::node const & node_instance = node_instances[node_index];

        XMStoreFloat4x4(&joints[joint_index].transform,
                        XMMatrixTranspose(bsm) * XMMatrixTranspose(ibm) * node_instance.world);

      }

      // copy
      memcpy(shaderDataDevice.frame[frameIndex].jointsMapped, &joints[0], (sizeof (Joint)) * instance_controller.joint_count);

      // flush

      VkMappedMemoryRange mappedMemoryRanges[1]{
        {
          .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
          .memory = shaderDataDevice.memory,
          .offset = shaderDataDevice.frame[frameIndex].jointsOffset,
          .size = shaderDataDevice.frame[frameIndex].jointsSize,
        },
      };

      alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize,
                              shaderDataDevice.memorySize,
                              1,
                              mappedMemoryRanges);
      vkFlushMappedMemoryRanges(device, 1, mappedMemoryRanges);

      /*
      int joints_size = (sizeof (XMFLOAT4X4)) * instance_controller.joint_count;
      glBindBuffer(GL_UNIFORM_BUFFER, joint_uniform_buffer);
      glBufferData(GL_UNIFORM_BUFFER, joints_size, (void *)&joints[0], GL_DYNAMIC_DRAW);
      glBindBuffer(GL_UNIFORM_BUFFER, 0);
      glBindBufferRange(GL_UNIFORM_BUFFER, layout.binding.joint, joint_uniform_buffer, 0, joints_size);
      */

      draw_skin(skin,
                instance_controller.instance_materials,
                instance_controller.instance_materials_count);
    }
  }

  void vulkan::transfer_transforms(XMMATRIX const & projection,
                                   XMMATRIX const & view,
                                   XMMATRIX const & shadowProjection,
                                   XMMATRIX const & shadowView,
                                   XMVECTOR const & light_position_world,
                                   int nodes_count,
                                   instance_types::node const * const node_instances)
  {
    // store
    XMStoreFloat4x4(&shaderData.scene.projection, projection);
    XMStoreFloat4x4(&shaderData.scene.view, view);
    XMStoreFloat4x4(&shaderData.scene.shadowProjection, shadowProjection);
    XMStoreFloat4x4(&shaderData.scene.shadowView, shadowView);
    XMVECTOR lightPositionView = XMVector3Transform(light_position_world, view);
    XMStoreFloat4(&shaderData.scene.lightPosition, lightPositionView);

    for (int i = 0; i < nodes_count; i++) {
      XMStoreFloat4x4(&shaderData.nodes[i].world, node_instances[i].world);
    }

    // copy
    memcpy(shaderDataDevice.frame[frameIndex].sceneMapped, &shaderData.scene, (sizeof (Scene)));
    memcpy(shaderDataDevice.frame[frameIndex].nodesMapped, &shaderData.nodes[0], (sizeof (Node)) * nodes_count);

    // flush

    VkMappedMemoryRange mappedMemoryRanges[2]{
      {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = shaderDataDevice.memory,
        .offset = shaderDataDevice.frame[frameIndex].sceneOffset,
        .size = shaderDataDevice.frame[frameIndex].sceneSize,
      },
      {
        .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
        .memory = shaderDataDevice.memory,
        .offset = shaderDataDevice.frame[frameIndex].nodesOffset,
        .size = shaderDataDevice.frame[frameIndex].nodesSize,
      }
    };
    alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize,
                            shaderDataDevice.memorySize,
                            2,
                            mappedMemoryRanges);
    vkFlushMappedMemoryRanges(device, 2, mappedMemoryRanges);
  }

  void vulkan::draw_node(int32_t node_index,
                         types::node const & node,
                         instance_types::node const & node_instance,
                         instance_types::node const * const node_instances)
  {
    VkShaderStageFlags stageFlags{ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_GEOMETRY_BIT };
    constexpr uint32_t offset{ (offsetof (PushConstant, nodeIndex)) };
    vkCmdPushConstants(commandBuffer, pipelineLayout, stageFlags, offset, (sizeof (uint32_t)), &node_index);

    draw_instance_geometries(node.instance_geometries, node.instance_geometries_count);
    draw_instance_controllers(node.instance_controllers, node.instance_controllers_count, node_instances);
  }

  void vulkan::change_frame(VkCommandBuffer commandBuffer, uint32_t frameIndex)
  {
    this->commandBuffer = commandBuffer;
    this->frameIndex = frameIndex;
    VkDescriptorSet descriptorSets[2] = {
      descriptorSets0[frameIndex],
      descriptorSet1,
    };
    vkCmdBindDescriptorSets(commandBuffer,
                            VK_PIPELINE_BIND_POINT_GRAPHICS,
                            pipelineLayout,
                            0, 2, descriptorSets,
                            0, nullptr);
  }

  void vulkan::destroy_image(int i)
  {
    vkDestroyImage(device, images[i].image, nullptr);
    vkDestroyImageView(device, images[i].imageView, nullptr);
    vkFreeMemory(device, images[i].memory, nullptr);
  }

  void vulkan::destroy_all(collada::types::descriptor const * const descriptor)
  {
    for (int i = 0; i < descriptor->images_count; i++) {
      destroy_image(i);
    }
    free(images);

    free(shaderData.nodes);
    free(shaderData.materialColorImages);

    vkDestroyBuffer(device, vertexIndex.buffer, nullptr);
    vkFreeMemory(device, vertexIndex.memory, nullptr);

    for (uint32_t i = 0; i < maxFrames; i++) {
      vkDestroyBuffer(device, shaderDataDevice.frame[i].sceneBuffer, nullptr);
      vkDestroyBuffer(device, shaderDataDevice.frame[i].nodesBuffer, nullptr);
      vkDestroyBuffer(device, shaderDataDevice.frame[i].jointsBuffer, nullptr);
    }
    vkDestroyBuffer(device, shaderDataDevice.constant.materialColorImagesBuffer, nullptr);
    vkFreeMemory(device, shaderDataDevice.memory, nullptr);

    vkDestroyDescriptorSetLayout(device, descriptorSetLayouts[0], nullptr);
    vkDestroyDescriptorSetLayout(device, descriptorSetLayouts[1], nullptr);
    vkDestroyDescriptorPool(device, descriptorPool, nullptr);
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
    int pipelineCount = descriptor->inputs_list_count * shaderVariantCount;
    for (int i = 0; i < pipelineCount; i++) {
      vkDestroyPipeline(device, pipelines[i], nullptr);
    }
    free(pipelines);
    vkDestroyShaderModule(device, shaderModule, nullptr);
  }
}
