Index: vulkan-utils.cpp
===================================================================
--- vulkan-utils.cpp	(revision 6fc24c7297278ec4d1c89725b3f9a0f98a6adcca)
+++ vulkan-utils.cpp	(revision b79417880394af692ba165be4054f0843fdb1c91)
@@ -4,5 +4,9 @@
 #include <set>
 #include <stdexcept>
-#include <string>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h" // TODO: Probably switch to SDL_image
+
+// TODO: Remove all instances of auto
 
 bool VulkanUtils::checkValidationLayerSupport(const vector<const char*> &validationLayers) {
@@ -210,2 +214,263 @@
    throw runtime_error("failed to find supported format!");
 }
+
+void VulkanUtils::createBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize size, VkBufferUsageFlags usage,
+      VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
+   VkBufferCreateInfo bufferInfo = {};
+   bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+   bufferInfo.size = size;
+   bufferInfo.usage = usage;
+   bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+   if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
+      throw runtime_error("failed to create buffer!");
+   }
+
+   VkMemoryRequirements memRequirements;
+   vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
+
+   VkMemoryAllocateInfo allocInfo = {};
+   allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+   allocInfo.allocationSize = memRequirements.size;
+   allocInfo.memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);
+
+   if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
+      throw runtime_error("failed to allocate buffer memory!");
+   }
+
+   vkBindBufferMemory(device, buffer, bufferMemory, 0);
+}
+
+uint32_t VulkanUtils::findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties) {
+   VkPhysicalDeviceMemoryProperties memProperties;
+   vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
+
+   for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
+      if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
+         return i;
+      }
+   }
+
+   throw runtime_error("failed to find suitable memory type!");
+}
+
+void VulkanUtils::createVulkanImageFromFile(VkDevice device, VkPhysicalDevice physicalDevice,
+      VkCommandPool commandPool, string filename, VulkanImage& image, VkQueue graphicsQueue) {
+   int texWidth, texHeight, texChannels;
+
+   stbi_uc* pixels = stbi_load(filename.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
+   VkDeviceSize imageSize = texWidth * texHeight * 4;
+
+   if (!pixels) {
+      throw runtime_error("failed to load texture image!");
+   }
+
+   VkBuffer stagingBuffer;
+   VkDeviceMemory stagingBufferMemory;
+
+   createBuffer(device, physicalDevice, imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+      stagingBuffer, stagingBufferMemory);
+
+   void* data;
+
+   vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
+   memcpy(data, pixels, static_cast<size_t>(imageSize));
+   vkUnmapMemory(device, stagingBufferMemory);
+
+   stbi_image_free(pixels);
+
+   createImage(device, physicalDevice, texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
+      VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image);
+
+   transitionImageLayout(device, commandPool, image.image, VK_FORMAT_R8G8B8A8_UNORM,
+      VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, graphicsQueue);
+   copyBufferToImage(device, commandPool, stagingBuffer, image.image,
+      static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight), graphicsQueue);
+   transitionImageLayout(device, commandPool, image.image, VK_FORMAT_R8G8B8A8_UNORM,
+      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, graphicsQueue);
+
+   vkDestroyBuffer(device, stagingBuffer, nullptr);
+   vkFreeMemory(device, stagingBufferMemory, nullptr);
+
+   image.imageView = createImageView(device, image.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
+}
+
+void VulkanUtils::createVulkanImageFromSDLTexture(VkDevice device, VkPhysicalDevice physicalDevice,
+      SDL_Texture* texture, VulkanImage& image) {
+   int a, w, h;
+
+   // I only need this here for the width and height, which are constants, so just use those instead
+   SDL_QueryTexture(texture, nullptr, &a, &w, &h);
+
+   createImage(device, physicalDevice, w, h, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
+      VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image);
+
+   image.imageView = createImageView(device, image.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
+}
+
+void VulkanUtils::createImage(VkDevice device, VkPhysicalDevice physicalDevice, uint32_t width, uint32_t height,
+      VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
+      VulkanImage& image) {
+   VkImageCreateInfo imageInfo = {};
+   imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+   imageInfo.imageType = VK_IMAGE_TYPE_2D;
+   imageInfo.extent.width = width;
+   imageInfo.extent.height = height;
+   imageInfo.extent.depth = 1;
+   imageInfo.mipLevels = 1;
+   imageInfo.arrayLayers = 1;
+   imageInfo.format = format;
+   imageInfo.tiling = tiling;
+   imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+   imageInfo.usage = usage;
+   imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+   imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+   if (vkCreateImage(device, &imageInfo, nullptr, &image.image) != VK_SUCCESS) {
+      throw runtime_error("failed to create image!");
+   }
+
+   VkMemoryRequirements memRequirements;
+   vkGetImageMemoryRequirements(device, image.image, &memRequirements);
+
+   VkMemoryAllocateInfo allocInfo = {};
+   allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+   allocInfo.allocationSize = memRequirements.size;
+   allocInfo.memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);
+
+   if (vkAllocateMemory(device, &allocInfo, nullptr, &image.imageMemory) != VK_SUCCESS) {
+      throw runtime_error("failed to allocate image memory!");
+   }
+
+   vkBindImageMemory(device, image.image, image.imageMemory, 0);
+}
+
+void VulkanUtils::transitionImageLayout(VkDevice device, VkCommandPool commandPool, VkImage image,
+      VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkQueue graphicsQueue) {
+   VkCommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
+
+   VkImageMemoryBarrier barrier = {};
+   barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+   barrier.oldLayout = oldLayout;
+   barrier.newLayout = newLayout;
+   barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+   barrier.image = image;
+
+   if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
+      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+
+      if (hasStencilComponent(format)) {
+         barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+      }
+   } else {
+      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+   }
+
+   barrier.subresourceRange.baseMipLevel = 0;
+   barrier.subresourceRange.levelCount = 1;
+   barrier.subresourceRange.baseArrayLayer = 0;
+   barrier.subresourceRange.layerCount = 1;
+
+   VkPipelineStageFlags sourceStage;
+   VkPipelineStageFlags destinationStage;
+
+   if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+      barrier.srcAccessMask = 0;
+      barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+      sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+      destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+   } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+      barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+      barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+      sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+      destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+   } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
+      barrier.srcAccessMask = 0;
+      barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+
+      sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+      destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+   } else {
+      throw invalid_argument("unsupported layout transition!");
+   }
+
+   vkCmdPipelineBarrier(
+      commandBuffer,
+      sourceStage, destinationStage,
+      0,
+      0, nullptr,
+      0, nullptr,
+      1, &barrier
+   );
+
+   endSingleTimeCommands(device, commandPool, commandBuffer, graphicsQueue);
+}
+
+VkCommandBuffer VulkanUtils::beginSingleTimeCommands(VkDevice device, VkCommandPool commandPool) {
+   VkCommandBufferAllocateInfo allocInfo = {};
+   allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+   allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+   allocInfo.commandPool = commandPool;
+   allocInfo.commandBufferCount = 1;
+
+   VkCommandBuffer commandBuffer;
+   vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
+
+   VkCommandBufferBeginInfo beginInfo = {};
+   beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+   beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+   vkBeginCommandBuffer(commandBuffer, &beginInfo);
+
+   return commandBuffer;
+}
+
+void VulkanUtils::endSingleTimeCommands(VkDevice device, VkCommandPool commandPool,
+      VkCommandBuffer commandBuffer, VkQueue graphicsQueue) {
+   vkEndCommandBuffer(commandBuffer);
+
+   VkSubmitInfo submitInfo = {};
+   submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+   submitInfo.commandBufferCount = 1;
+   submitInfo.pCommandBuffers = &commandBuffer;
+
+   vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+   vkQueueWaitIdle(graphicsQueue);
+
+   vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+}
+
+void VulkanUtils::copyBufferToImage(VkDevice device, VkCommandPool commandPool, VkBuffer buffer,
+      VkImage image, uint32_t width, uint32_t height, VkQueue graphicsQueue) {
+   VkCommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
+
+   VkBufferImageCopy region = {};
+   region.bufferOffset = 0;
+   region.bufferRowLength = 0;
+   region.bufferImageHeight = 0;
+   region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+   region.imageSubresource.mipLevel = 0;
+   region.imageSubresource.baseArrayLayer = 0;
+   region.imageSubresource.layerCount = 1;
+   region.imageOffset = { 0, 0, 0 };
+   region.imageExtent = { width, height, 1 };
+
+   vkCmdCopyBufferToImage(
+      commandBuffer,
+      buffer,
+      image,
+      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+      1,
+      &region
+   );
+
+   endSingleTimeCommands(device, commandPool, commandBuffer, graphicsQueue);
+}
+
+bool VulkanUtils::hasStencilComponent(VkFormat format) {
+   return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
+}
