Index: vulkan-game.cpp
===================================================================
--- vulkan-game.cpp	(revision c7fb8836bdb0128dbe9dff186dd0f4e8141fbb1a)
+++ vulkan-game.cpp	(revision 88ebdc83080ccaca68fabaf59ab879ce5e2eb2f0)
@@ -16,4 +16,7 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/matrix_transform.hpp>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
 
 #include <iostream>
@@ -189,4 +192,7 @@
       vector<VkSemaphore> renderFinishedSemaphores;
       vector<VkFence> inFlightFences;
+
+      VkImage textureImage;
+      VkDeviceMemory textureImageMemory;
 
       size_t currentFrame = 0;
@@ -223,4 +229,5 @@
          createFramebuffers();
          createCommandPool();
+         createTextureImage();
          createVertexBuffer();
          createIndexBuffer();
@@ -919,4 +926,152 @@
       }
 
+      void createTextureImage() {
+         int texWidth, texHeight, texChannels;
+
+         stbi_uc* pixels = stbi_load("textures/texture.jpg", &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(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(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, textureImage, textureImageMemory);
+
+         transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+         copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
+         transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+         vkDestroyBuffer(device, stagingBuffer, nullptr);
+         vkDestroyMemory(device, stagingBufferMemory, nullptr);
+      }
+
+      void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
+            VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
+         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) != VK_SUCCESS) {
+            throw runtime_error("failed to create image!");
+         }
+
+         VkMemoryRequirements memRequirements;
+         vkGetImageMemoryRequirements(device, image, &memRequirements);
+
+         VkMemoryAllocateInfo allocInfo;
+         allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+         allocInfo.allocationSize = memRequirements.size;
+         allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
+
+         if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) {
+            throw runtime_error("failed to allocate image memory!");
+         }
+
+         vkBindImageMemory(device, image, imageMemory, 0);
+      }
+
+      void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {
+         VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+         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;
+         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 sourceFlags;
+         VkPipelineStageFlags destinationFlags;
+
+         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 {
+            throw invalid_argument("unsupported layout transition!");
+         }
+
+         vkCmdPipelineBarrier(
+            commandBuffer,
+            sourceFlags, destinationFlags,
+            0,
+            0, nullptr,
+            0, nullptr,
+            1, &barrier
+         );
+
+         endSingleTimeCommands(commandBuffer);
+      }
+
+      void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {
+         VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+         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(commandBuffer);
+      }
+
       void createVertexBuffer() {
          VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
@@ -993,4 +1148,14 @@
 
       void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
+         VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+         VkBufferCopy copyRegion = {};
+         copyRegion.size = size;
+         vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
+
+         endSingleTimeCommands(commandBuffer);
+      }
+
+      VkCommandBuffer beginSingleTimeCommands() {
          VkCommandBufferAllocateInfo allocInfo = {};
          allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
@@ -1008,11 +1173,8 @@
          vkBeginCommandBuffer(commandBuffer, &beginInfo);
 
-         VkBufferCopy copyRegion = {};
-         copyRegion.srcOffset = 0;
-         copyRegion.dstOffset = 0;
-         copyRegion.size = size;
-   
-         vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
-
+         return commandBuffer;
+      }
+
+      void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
          vkEndCommandBuffer(commandBuffer);
 
@@ -1296,4 +1458,7 @@
          cleanupSwapChain();
 
+         vkDestroyImage(device, textureImage, nullptr);
+         vkDestroyMemory(device, textureImageMemory, nullptr);
+
          vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
 
