Index: Vulkan.cpp
===================================================================
--- Vulkan.cpp	(revision 57d43d021f973604403220624950c82a9c67ae6e)
+++ Vulkan.cpp	(revision 57d43d021f973604403220624950c82a9c67ae6e)
@@ -0,0 +1,2557 @@
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#define GLAD_VULKAN_IMPLEMENTATION
+#include "vulkan.h"
+
+// Include graphics because we use sf::Image for loading images
+#include <SFML/Graphics.hpp>
+
+#include <SFML/Window.hpp>
+#include <vector>
+#include <limits>
+#include <cstring>
+#include <cmath>
+
+
+////////////////////////////////////////////////////////////
+// Helper functions
+////////////////////////////////////////////////////////////
+namespace
+{
+   typedef float Vec3[3];
+   typedef float Matrix[4][4];
+
+   // Multiply 2 matrices
+   void matrixMultiply(Matrix& result, const Matrix& left, const Matrix& right)
+   {
+      Matrix temp;
+
+      for (int i = 0; i < 4; i++)
+      {
+         for (int j = 0; j < 4; j++)
+            temp[i][j] = left[0][j] * right[i][0] + left[1][j] * right[i][1] + left[2][j] * right[i][2] + left[3][j] * right[i][3];
+      }
+
+      std::memcpy(result, temp, sizeof(Matrix));
+   }
+
+   // Rotate a matrix around the x-axis
+   void matrixRotateX(Matrix& result, float angle)
+   {
+      Matrix matrix = {
+          {1.f,   0.f,             0.f,             0.f},
+          {0.f,   std::cos(angle), std::sin(angle), 0.f},
+          {0.f,  -std::sin(angle), std::cos(angle), 0.f},
+          {0.f,   0.f,             0.f,             1.f}
+      };
+
+      matrixMultiply(result, result, matrix);
+   }
+
+   // Rotate a matrix around the y-axis
+   void matrixRotateY(Matrix& result, float angle)
+   {
+      Matrix matrix = {
+          { std::cos(angle), 0.f, std::sin(angle), 0.f},
+          { 0.f,             1.f, 0.f,             0.f},
+          {-std::sin(angle), 0.f, std::cos(angle), 0.f},
+          { 0.f,             0.f, 0.f,             1.f}
+      };
+
+      matrixMultiply(result, result, matrix);
+   }
+
+   // Rotate a matrix around the z-axis
+   void matrixRotateZ(Matrix& result, float angle)
+   {
+      Matrix matrix = {
+          { std::cos(angle), std::sin(angle), 0.f, 0.f},
+          {-std::sin(angle), std::cos(angle), 0.f, 0.f},
+          { 0.f,             0.f,             1.f, 0.f},
+          { 0.f,             0.f,             0.f, 1.f}
+      };
+
+      matrixMultiply(result, result, matrix);
+   }
+
+   // Construct a lookat view matrix
+   void matrixLookAt(Matrix& result, const Vec3& eye, const Vec3& center, const Vec3& up)
+   {
+      // Forward-looking vector
+      Vec3 forward = {
+          center[0] - eye[0],
+          center[1] - eye[1],
+          center[2] - eye[2]
+      };
+
+      // Normalize
+      float factor = 1.0f / std::sqrt(forward[0] * forward[0] + forward[1] * forward[1] + forward[2] * forward[2]);
+
+      for (int i = 0; i < 3; i++)
+         forward[i] = forward[i] * factor;
+
+      // Side vector (Forward cross product Up)
+      Vec3 side = {
+          forward[1] * up[2] - forward[2] * up[1],
+          forward[2] * up[0] - forward[0] * up[2],
+          forward[0] * up[1] - forward[1] * up[0]
+      };
+
+      // Normalize
+      factor = 1.0f / std::sqrt(side[0] * side[0] + side[1] * side[1] + side[2] * side[2]);
+
+      for (int i = 0; i < 3; i++)
+         side[i] = side[i] * factor;
+
+      result[0][0] = side[0];
+      result[0][1] = side[1] * forward[2] - side[2] * forward[1];
+      result[0][2] = -forward[0];
+      result[0][3] = 0.f;
+
+      result[1][0] = side[1];
+      result[1][1] = side[2] * forward[0] - side[0] * forward[2];
+      result[1][2] = -forward[1];
+      result[1][3] = 0.f;
+
+      result[2][0] = side[2];
+      result[2][1] = side[0] * forward[1] - side[1] * forward[0];
+      result[2][2] = -forward[2];
+      result[2][3] = 0.f;
+
+      result[3][0] = (-eye[0]) * result[0][0] + (-eye[1]) * result[1][0] + (-eye[2]) * result[2][0];
+      result[3][1] = (-eye[0]) * result[0][1] + (-eye[1]) * result[1][1] + (-eye[2]) * result[2][1];
+      result[3][2] = (-eye[0]) * result[0][2] + (-eye[1]) * result[1][2] + (-eye[2]) * result[2][2];
+      result[3][3] = (-eye[0]) * result[0][3] + (-eye[1]) * result[1][3] + (-eye[2]) * result[2][3] + 1.0f;
+   }
+
+   // Construct a perspective projection matrix
+   void matrixPerspective(Matrix& result, float fov, float aspect, float nearPlane, float farPlane)
+   {
+      const float a = 1.f / std::tan(fov / 2.f);
+
+      result[0][0] = a / aspect;
+      result[0][1] = 0.f;
+      result[0][2] = 0.f;
+      result[0][3] = 0.f;
+
+      result[1][0] = 0.f;
+      result[1][1] = -a;
+      result[1][2] = 0.f;
+      result[1][3] = 0.f;
+
+      result[2][0] = 0.f;
+      result[2][1] = 0.f;
+      result[2][2] = -((farPlane + nearPlane) / (farPlane - nearPlane));
+      result[2][3] = -1.f;
+
+      result[3][0] = 0.f;
+      result[3][1] = 0.f;
+      result[3][2] = -((2.f * farPlane * nearPlane) / (farPlane - nearPlane));
+      result[3][3] = 0.f;
+   }
+
+   // Clamp a value between low and high values
+   template<typename T>
+   T clamp(T value, T low, T high)
+   {
+      return (value <= low) ? low : ((value >= high) ? high : value);
+   }
+
+   // Helper function we pass to GLAD to load Vulkan functions via SFML
+   GLADapiproc getVulkanFunction(const char* name)
+   {
+      return reinterpret_cast<GLADapiproc>(sf::Vulkan::getFunction(name));
+   }
+
+   // Debug we pass to Vulkan to call when it detects warnings or errors
+   VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT, VkDebugReportObjectTypeEXT, uint64_t, size_t, int32_t, const char*, const char* pMessage, void*)
+   {
+      sf::err() << pMessage << std::endl;
+
+      return VK_FALSE;
+   }
+}
+
+
+////////////////////////////////////////////////////////////
+// VulkanExample class
+////////////////////////////////////////////////////////////
+class VulkanExample
+{
+public:
+   // Constructor
+   VulkanExample() :
+      window(sf::VideoMode(800, 600), "SFML window with Vulkan", sf::Style::Default),
+      vulkanAvailable(sf::Vulkan::isAvailable()),
+      maxFramesInFlight(2),
+      currentFrame(0),
+      swapchainOutOfDate(false),
+      instance(0),
+      debugReportCallback(0),
+      surface(0),
+      gpu(0),
+      queueFamilyIndex(-1),
+      device(0),
+      queue(0),
+      swapchainFormat(),
+      swapchainExtent(),
+      swapchain(0),
+      depthFormat(VK_FORMAT_UNDEFINED),
+      depthImage(0),
+      depthImageMemory(0),
+      depthImageView(0),
+      vertexShaderModule(0),
+      fragmentShaderModule(0),
+      descriptorSetLayout(0),
+      pipelineLayout(0),
+      renderPass(0),
+      graphicsPipeline(0),
+      commandPool(0),
+      vertexBuffer(0),
+      vertexBufferMemory(0),
+      indexBuffer(0),
+      indexBufferMemory(0),
+      textureImage(0),
+      textureImageMemory(0),
+      textureImageView(0),
+      textureSampler(0),
+      descriptorPool(0)
+   {
+      // Vulkan setup procedure
+      if (vulkanAvailable) setupInstance();
+      if (vulkanAvailable) setupDebugReportCallback();
+      if (vulkanAvailable) setupSurface();
+      if (vulkanAvailable) setupPhysicalDevice();
+      if (vulkanAvailable) setupLogicalDevice();
+      if (vulkanAvailable) setupSwapchain();
+      if (vulkanAvailable) setupSwapchainImages();
+      if (vulkanAvailable) setupShaders();
+      if (vulkanAvailable) setupRenderpass();
+      if (vulkanAvailable) setupDescriptorSetLayout();
+      if (vulkanAvailable) setupPipelineLayout();
+      if (vulkanAvailable) setupPipeline();
+      if (vulkanAvailable) setupCommandPool();
+      if (vulkanAvailable) setupVertexBuffer();
+      if (vulkanAvailable) setupIndexBuffer();
+      if (vulkanAvailable) setupUniformBuffers();
+      if (vulkanAvailable) setupDepthImage();
+      if (vulkanAvailable) setupDepthImageView();
+      if (vulkanAvailable) setupTextureImage();
+      if (vulkanAvailable) setupTextureImageView();
+      if (vulkanAvailable) setupTextureSampler();
+      if (vulkanAvailable) setupFramebuffers();
+      if (vulkanAvailable) setupDescriptorPool();
+      if (vulkanAvailable) setupDescriptorSets();
+      if (vulkanAvailable) setupCommandBuffers();
+      if (vulkanAvailable) setupDraw();
+      if (vulkanAvailable) setupSemaphores();
+      if (vulkanAvailable) setupFences();
+
+      // If something went wrong, notify the user by setting the window title
+      if (!vulkanAvailable)
+         window.setTitle("SFML window with Vulkan (Vulkan not available)");
+   }
+
+
+   // Destructor
+   ~VulkanExample()
+   {
+      // Wait until there are no pending frames
+      if (device)
+         vkDeviceWaitIdle(device);
+
+      // Teardown swapchain
+      cleanupSwapchain();
+
+      // Vulkan teardown procedure
+      for (std::size_t i = 0; i < fences.size(); i++)
+         vkDestroyFence(device, fences[i], 0);
+
+      for (std::size_t i = 0; i < renderFinishedSemaphores.size(); i++)
+         vkDestroySemaphore(device, renderFinishedSemaphores[i], 0);
+
+      for (std::size_t i = 0; i < imageAvailableSemaphores.size(); i++)
+         vkDestroySemaphore(device, imageAvailableSemaphores[i], 0);
+
+      if (descriptorPool)
+         vkDestroyDescriptorPool(device, descriptorPool, 0);
+
+      for (std::size_t i = 0; i < uniformBuffersMemory.size(); i++)
+         vkFreeMemory(device, uniformBuffersMemory[i], 0);
+
+      for (std::size_t i = 0; i < uniformBuffers.size(); i++)
+         vkDestroyBuffer(device, uniformBuffers[i], 0);
+
+      if (textureSampler)
+         vkDestroySampler(device, textureSampler, 0);
+
+      if (textureImageView)
+         vkDestroyImageView(device, textureImageView, 0);
+
+      if (textureImageMemory)
+         vkFreeMemory(device, textureImageMemory, 0);
+
+      if (textureImage)
+         vkDestroyImage(device, textureImage, 0);
+
+      if (indexBufferMemory)
+         vkFreeMemory(device, indexBufferMemory, 0);
+
+      if (indexBuffer)
+         vkDestroyBuffer(device, indexBuffer, 0);
+
+      if (vertexBufferMemory)
+         vkFreeMemory(device, vertexBufferMemory, 0);
+
+      if (vertexBuffer)
+         vkDestroyBuffer(device, vertexBuffer, 0);
+
+      if (commandPool)
+         vkDestroyCommandPool(device, commandPool, 0);
+
+      if (descriptorSetLayout)
+         vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 0);
+
+      if (fragmentShaderModule)
+         vkDestroyShaderModule(device, fragmentShaderModule, 0);
+
+      if (vertexShaderModule)
+         vkDestroyShaderModule(device, vertexShaderModule, 0);
+
+      if (device)
+         vkDestroyDevice(device, 0);
+
+      if (surface)
+         vkDestroySurfaceKHR(instance, surface, 0);
+
+      if (debugReportCallback)
+         vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, 0);
+
+      if (instance)
+         vkDestroyInstance(instance, 0);
+   }
+
+   // Cleanup swapchain
+   void cleanupSwapchain()
+   {
+      // Swapchain teardown procedure
+      for (std::size_t i = 0; i < fences.size(); i++)
+         vkWaitForFences(device, 1, &fences[i], VK_TRUE, std::numeric_limits<uint64_t>::max());
+
+      if (commandBuffers.size())
+         vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), &commandBuffers[0]);
+
+      commandBuffers.clear();
+
+      for (std::size_t i = 0; i < swapchainFramebuffers.size(); i++)
+         vkDestroyFramebuffer(device, swapchainFramebuffers[i], 0);
+
+      swapchainFramebuffers.clear();
+
+      if (graphicsPipeline)
+         vkDestroyPipeline(device, graphicsPipeline, 0);
+
+      if (renderPass)
+         vkDestroyRenderPass(device, renderPass, 0);
+
+      if (pipelineLayout)
+         vkDestroyPipelineLayout(device, pipelineLayout, 0);
+
+      if (depthImageView)
+         vkDestroyImageView(device, depthImageView, 0);
+
+      if (depthImageMemory)
+         vkFreeMemory(device, depthImageMemory, 0);
+
+      if (depthImage)
+         vkDestroyImage(device, depthImage, 0);
+
+      for (std::size_t i = 0; i < swapchainImageViews.size(); i++)
+         vkDestroyImageView(device, swapchainImageViews[i], 0);
+
+      swapchainImageViews.clear();
+
+      if (swapchain)
+         vkDestroySwapchainKHR(device, swapchain, 0);
+   }
+
+   // Cleanup and recreate swapchain
+   void recreateSwapchain()
+   {
+      // Wait until there are no pending frames
+      vkDeviceWaitIdle(device);
+
+      // Cleanup swapchain
+      cleanupSwapchain();
+
+      // Swapchain setup procedure
+      if (vulkanAvailable) setupSwapchain();
+      if (vulkanAvailable) setupSwapchainImages();
+      if (vulkanAvailable) setupPipelineLayout();
+      if (vulkanAvailable) setupRenderpass();
+      if (vulkanAvailable) setupPipeline();
+      if (vulkanAvailable) setupDepthImage();
+      if (vulkanAvailable) setupDepthImageView();
+      if (vulkanAvailable) setupFramebuffers();
+      if (vulkanAvailable) setupCommandBuffers();
+      if (vulkanAvailable) setupDraw();
+   }
+
+   // Setup Vulkan instance
+   void setupInstance()
+   {
+      // Load bootstrap entry points
+      gladLoadVulkan(0, getVulkanFunction);
+
+      if (!vkCreateInstance)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Retrieve the available instance layers
+      uint32_t objectCount = 0;
+
+      std::vector<VkLayerProperties> layers;
+
+      if (vkEnumerateInstanceLayerProperties(&objectCount, 0) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      layers.resize(objectCount);
+
+      if (vkEnumerateInstanceLayerProperties(&objectCount, &layers[0]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Activate the layers we are interested in
+      std::vector<const char*> validationLayers;
+
+      for (std::size_t i = 0; i < layers.size(); i++)
+      {
+         // VK_LAYER_LUNARG_standard_validation, meta-layer for the following layers:
+         // -- VK_LAYER_GOOGLE_threading
+         // -- VK_LAYER_LUNARG_parameter_validation
+         // -- VK_LAYER_LUNARG_device_limits
+         // -- VK_LAYER_LUNARG_object_tracker
+         // -- VK_LAYER_LUNARG_image
+         // -- VK_LAYER_LUNARG_core_validation
+         // -- VK_LAYER_LUNARG_swapchain
+         // -- VK_LAYER_GOOGLE_unique_objects
+         // These layers perform error checking and warn about bad or sub-optimal Vulkan API usage
+         // VK_LAYER_LUNARG_monitor appends an FPS counter to the window title
+         if (!std::strcmp(layers[i].layerName, "VK_LAYER_LUNARG_standard_validation"))
+         {
+            validationLayers.push_back("VK_LAYER_LUNARG_standard_validation");
+         }
+         else if (!std::strcmp(layers[i].layerName, "VK_LAYER_LUNARG_monitor"))
+         {
+            validationLayers.push_back("VK_LAYER_LUNARG_monitor");
+         }
+      }
+
+      // Retrieve the extensions we need to enable in order to use Vulkan with SFML
+      std::vector<const char*> requiredExtentions = sf::Vulkan::getGraphicsRequiredInstanceExtensions();
+      requiredExtentions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
+
+      // Register our application information
+      VkApplicationInfo applicationInfo = VkApplicationInfo();
+      applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+      applicationInfo.pApplicationName = "SFML Vulkan Test";
+      applicationInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+      applicationInfo.pEngineName = "SFML Vulkan Test Engine";
+      applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+      applicationInfo.apiVersion = VK_API_VERSION_1_0;
+
+      VkInstanceCreateInfo instanceCreateInfo = VkInstanceCreateInfo();
+      instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+      instanceCreateInfo.pApplicationInfo = &applicationInfo;
+      instanceCreateInfo.enabledLayerCount = validationLayers.size();
+      instanceCreateInfo.ppEnabledLayerNames = &validationLayers[0];
+      instanceCreateInfo.enabledExtensionCount = requiredExtentions.size();
+      instanceCreateInfo.ppEnabledExtensionNames = &requiredExtentions[0];
+
+      // Try to create a Vulkan instance with debug report enabled
+      VkResult result = vkCreateInstance(&instanceCreateInfo, 0, &instance);
+
+      // If an extension is missing, try disabling debug report
+      if (result == VK_ERROR_EXTENSION_NOT_PRESENT)
+      {
+         requiredExtentions.pop_back();
+
+         instanceCreateInfo.enabledExtensionCount = requiredExtentions.size();
+         instanceCreateInfo.ppEnabledExtensionNames = &requiredExtentions[0];
+
+         result = vkCreateInstance(&instanceCreateInfo, 0, &instance);
+      }
+
+      // If instance creation still fails, give up
+      if (result != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Load instance entry points
+      gladLoadVulkan(0, getVulkanFunction);
+   }
+
+   // Setup our debug callback function to be called by Vulkan
+   void setupDebugReportCallback()
+   {
+      // Don't try to register the callback if the extension is not available
+      if (!vkCreateDebugReportCallbackEXT)
+         return;
+
+      // Register for warnings and errors
+      VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfo = VkDebugReportCallbackCreateInfoEXT();
+      debugReportCallbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+      debugReportCallbackCreateInfo.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT;
+      debugReportCallbackCreateInfo.pfnCallback = debugCallback;
+
+      // Create the debug callback
+      if (vkCreateDebugReportCallbackEXT(instance, &debugReportCallbackCreateInfo, 0, &debugReportCallback) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Setup the SFML window Vulkan rendering surface
+   void setupSurface()
+   {
+      if (!window.createVulkanSurface(instance, surface))
+         vulkanAvailable = false;
+   }
+
+   // Select a GPU to use and query its capabilities
+   void setupPhysicalDevice()
+   {
+      // Last sanity check
+      if (!vkEnumeratePhysicalDevices || !vkCreateDevice || !vkGetPhysicalDeviceProperties)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Retrieve list of GPUs
+      uint32_t objectCount = 0;
+
+      std::vector<VkPhysicalDevice> devices;
+
+      if (vkEnumeratePhysicalDevices(instance, &objectCount, 0) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      devices.resize(objectCount);
+
+      if (vkEnumeratePhysicalDevices(instance, &objectCount, &devices[0]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Look for a GPU that supports swapchains
+      for (std::size_t i = 0; i < devices.size(); i++)
+      {
+         VkPhysicalDeviceProperties deviceProperties;
+         vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);
+
+         std::vector<VkExtensionProperties> extensions;
+
+         if (vkEnumerateDeviceExtensionProperties(devices[i], 0, &objectCount, 0) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         extensions.resize(objectCount);
+
+         if (vkEnumerateDeviceExtensionProperties(devices[i], 0, &objectCount, &extensions[0]) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         bool supportsSwapchain = false;
+
+         for (std::size_t j = 0; j < extensions.size(); j++)
+         {
+            if (!std::strcmp(extensions[j].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME))
+            {
+               supportsSwapchain = true;
+               break;
+            }
+         }
+
+         if (!supportsSwapchain)
+            continue;
+
+         // Prefer discrete over integrated GPUs if multiple are available
+         if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
+         {
+            gpu = devices[i];
+            break;
+         }
+         else if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
+         {
+            gpu = devices[i];
+         }
+      }
+
+      if (!gpu)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Load physical device entry points
+      gladLoadVulkan(gpu, getVulkanFunction);
+
+      // Check what depth formats are available and select one
+      VkFormatProperties formatProperties = VkFormatProperties();
+
+      vkGetPhysicalDeviceFormatProperties(gpu, VK_FORMAT_D24_UNORM_S8_UINT, &formatProperties);
+
+      if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+         depthFormat = VK_FORMAT_D24_UNORM_S8_UINT;
+      }
+      else
+      {
+         vkGetPhysicalDeviceFormatProperties(gpu, VK_FORMAT_D32_SFLOAT_S8_UINT, &formatProperties);
+
+         if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+            depthFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
+         }
+         else
+         {
+            vkGetPhysicalDeviceFormatProperties(gpu, VK_FORMAT_D32_SFLOAT, &formatProperties);
+
+            if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+               depthFormat = VK_FORMAT_D32_SFLOAT;
+            }
+            else
+            {
+               vulkanAvailable = false;
+               return;
+            }
+         }
+      }
+   }
+
+   // Setup logical device and device queue
+   void setupLogicalDevice()
+   {
+      // Select a queue family that supports graphics operations and surface presentation
+      uint32_t objectCount = 0;
+
+      std::vector<VkQueueFamilyProperties> queueFamilyProperties;
+
+      vkGetPhysicalDeviceQueueFamilyProperties(gpu, &objectCount, 0);
+
+      queueFamilyProperties.resize(objectCount);
+
+      vkGetPhysicalDeviceQueueFamilyProperties(gpu, &objectCount, &queueFamilyProperties[0]);
+
+      for (std::size_t i = 0; i < queueFamilyProperties.size(); i++)
+      {
+         VkBool32 surfaceSupported = VK_FALSE;
+
+         vkGetPhysicalDeviceSurfaceSupportKHR(gpu, i, surface, &surfaceSupported);
+
+         if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && (surfaceSupported == VK_TRUE))
+         {
+            queueFamilyIndex = i;
+            break;
+         }
+      }
+
+      if (queueFamilyIndex < 0)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      float queuePriority = 1.0f;
+
+      VkDeviceQueueCreateInfo deviceQueueCreateInfo = VkDeviceQueueCreateInfo();
+      deviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+      deviceQueueCreateInfo.queueCount = 1;
+      deviceQueueCreateInfo.queueFamilyIndex = queueFamilyIndex;
+      deviceQueueCreateInfo.pQueuePriorities = &queuePriority;
+
+      // Enable the swapchain extension
+      const char* extentions[1] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+
+      // Enable anisotropic filtering
+      VkPhysicalDeviceFeatures physicalDeviceFeatures = VkPhysicalDeviceFeatures();
+      physicalDeviceFeatures.samplerAnisotropy = VK_TRUE;
+
+      VkDeviceCreateInfo deviceCreateInfo = VkDeviceCreateInfo();
+      deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+      deviceCreateInfo.enabledExtensionCount = 1;
+      deviceCreateInfo.ppEnabledExtensionNames = extentions;
+      deviceCreateInfo.queueCreateInfoCount = 1;
+      deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo;
+      deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeatures;
+
+      // Create our logical device
+      if (vkCreateDevice(gpu, &deviceCreateInfo, 0, &device) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Retrieve a handle to the logical device command queue
+      vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
+   }
+
+   // Query surface formats and set up swapchain
+   void setupSwapchain()
+   {
+      // Select a surface format that supports RGBA color format
+      uint32_t objectCount = 0;
+
+      std::vector<VkSurfaceFormatKHR> surfaceFormats;
+
+      if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &objectCount, 0) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      surfaceFormats.resize(objectCount);
+
+      if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &objectCount, &surfaceFormats[0]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      if ((surfaceFormats.size() == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
+      {
+         swapchainFormat.format = VK_FORMAT_B8G8R8A8_UNORM;
+         swapchainFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+      }
+      else if (!surfaceFormats.empty())
+      {
+         for (std::size_t i = 0; i < surfaceFormats.size(); i++)
+         {
+            if ((surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_UNORM) && (surfaceFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR))
+            {
+               swapchainFormat.format = VK_FORMAT_B8G8R8A8_UNORM;
+               swapchainFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+
+               break;
+            }
+         }
+
+         if (swapchainFormat.format == VK_FORMAT_UNDEFINED)
+            swapchainFormat = surfaceFormats[0];
+      }
+      else
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Select a swapchain present mode
+      std::vector<VkPresentModeKHR> presentModes;
+
+      if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &objectCount, 0) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      presentModes.resize(objectCount);
+
+      if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &objectCount, &presentModes[0]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Prefer mailbox over FIFO if it is available
+      VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
+
+      for (std::size_t i = 0; i < presentModes.size(); i++)
+      {
+         if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
+         {
+            presentMode = presentModes[i];
+            break;
+         }
+      }
+
+      // Determine size and count of swapchain images
+      VkSurfaceCapabilitiesKHR surfaceCapabilities;
+
+      if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &surfaceCapabilities) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      swapchainExtent.width = clamp<uint32_t>(window.getSize().x, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width);
+      swapchainExtent.height = clamp<uint32_t>(window.getSize().y, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height);
+
+      uint32_t imageCount = clamp<uint32_t>(2, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount);
+
+      VkSwapchainCreateInfoKHR swapchainCreateInfo = VkSwapchainCreateInfoKHR();
+      swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+      swapchainCreateInfo.surface = surface;
+      swapchainCreateInfo.minImageCount = imageCount;
+      swapchainCreateInfo.imageFormat = swapchainFormat.format;
+      swapchainCreateInfo.imageColorSpace = swapchainFormat.colorSpace;
+      swapchainCreateInfo.imageExtent = swapchainExtent;
+      swapchainCreateInfo.imageArrayLayers = 1;
+      swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+      swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+      swapchainCreateInfo.preTransform = surfaceCapabilities.currentTransform;
+      swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+      swapchainCreateInfo.presentMode = presentMode;
+      swapchainCreateInfo.clipped = VK_TRUE;
+      swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE;
+
+      // Create the swapchain
+      if (vkCreateSwapchainKHR(device, &swapchainCreateInfo, 0, &swapchain) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Retrieve the swapchain images and create image views for them
+   void setupSwapchainImages()
+   {
+      // Retrieve swapchain images
+      uint32_t objectCount = 0;
+
+      if (vkGetSwapchainImagesKHR(device, swapchain, &objectCount, 0) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      swapchainImages.resize(objectCount);
+      swapchainImageViews.resize(objectCount);
+
+      if (vkGetSwapchainImagesKHR(device, swapchain, &objectCount, &swapchainImages[0]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      VkImageViewCreateInfo imageViewCreateInfo = VkImageViewCreateInfo();
+      imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+      imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+      imageViewCreateInfo.format = swapchainFormat.format;
+      imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+      imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+      imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+      imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+      imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+      imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
+      imageViewCreateInfo.subresourceRange.levelCount = 1;
+      imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
+      imageViewCreateInfo.subresourceRange.layerCount = 1;
+
+      // Create an image view for each swapchain image
+      for (std::size_t i = 0; i < swapchainImages.size(); i++)
+      {
+         imageViewCreateInfo.image = swapchainImages[i];
+
+         if (vkCreateImageView(device, &imageViewCreateInfo, 0, &swapchainImageViews[i]) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Load vertex and fragment shader modules
+   void setupShaders()
+   {
+      VkShaderModuleCreateInfo shaderModuleCreateInfo = VkShaderModuleCreateInfo();
+      shaderModuleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+
+      // Use the vertex shader SPIR-V code to create a vertex shader module
+      {
+         sf::FileInputStream file;
+
+         if (!file.open("resources/shader.vert.spv"))
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         std::vector<char> buffer(static_cast<std::size_t>(file.getSize()));
+
+         if (file.read(&buffer[0], file.getSize()) != file.getSize())
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         shaderModuleCreateInfo.codeSize = buffer.size();
+         shaderModuleCreateInfo.pCode = reinterpret_cast<const uint32_t*>(&buffer[0]);
+
+         if (vkCreateShaderModule(device, &shaderModuleCreateInfo, 0, &vertexShaderModule) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+
+      // Use the fragment shader SPIR-V code to create a fragment shader module
+      {
+         sf::FileInputStream file;
+
+         if (!file.open("resources/shader.frag.spv"))
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         std::vector<char> buffer(static_cast<std::size_t>(file.getSize()));
+
+         if (file.read(&buffer[0], file.getSize()) != file.getSize())
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         shaderModuleCreateInfo.codeSize = buffer.size();
+         shaderModuleCreateInfo.pCode = reinterpret_cast<const uint32_t*>(&buffer[0]);
+
+         if (vkCreateShaderModule(device, &shaderModuleCreateInfo, 0, &fragmentShaderModule) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+
+      // Prepare the shader stage information for later pipeline creation
+      shaderStages[0] = VkPipelineShaderStageCreateInfo();
+      shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+      shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+      shaderStages[0].module = vertexShaderModule;
+      shaderStages[0].pName = "main";
+
+      shaderStages[1] = VkPipelineShaderStageCreateInfo();
+      shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+      shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+      shaderStages[1].module = fragmentShaderModule;
+      shaderStages[1].pName = "main";
+   }
+
+   // Setup renderpass and its subpass dependencies
+   void setupRenderpass()
+   {
+      VkAttachmentDescription attachmentDescriptions[2];
+
+      // Color attachment
+      attachmentDescriptions[0] = VkAttachmentDescription();
+      attachmentDescriptions[0].format = swapchainFormat.format;
+      attachmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+      attachmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+      attachmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+      attachmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+      attachmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+      attachmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      attachmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+      // Depth attachment
+      attachmentDescriptions[1] = VkAttachmentDescription();
+      attachmentDescriptions[1].format = depthFormat;
+      attachmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+      attachmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+      attachmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+      attachmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+      attachmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+      attachmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      attachmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+      VkAttachmentReference attachmentReferences[2];
+
+      attachmentReferences[0] = VkAttachmentReference();
+      attachmentReferences[0].attachment = 0;
+      attachmentReferences[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+      attachmentReferences[1] = VkAttachmentReference();
+      attachmentReferences[1].attachment = 1;
+      attachmentReferences[1].layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+      // Set up the renderpass to depend on commands that execute before the renderpass begins
+      VkSubpassDescription subpassDescription = VkSubpassDescription();
+      subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+      subpassDescription.colorAttachmentCount = 1;
+      subpassDescription.pColorAttachments = &attachmentReferences[0];
+      subpassDescription.pDepthStencilAttachment = &attachmentReferences[1];
+
+      VkSubpassDependency subpassDependency = VkSubpassDependency();
+      subpassDependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+      subpassDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+      subpassDependency.srcAccessMask = 0;
+      subpassDependency.dstSubpass = 0;
+      subpassDependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+      subpassDependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+      VkRenderPassCreateInfo renderPassCreateInfo = VkRenderPassCreateInfo();
+      renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+      renderPassCreateInfo.attachmentCount = 2;
+      renderPassCreateInfo.pAttachments = attachmentDescriptions;
+      renderPassCreateInfo.subpassCount = 1;
+      renderPassCreateInfo.pSubpasses = &subpassDescription;
+      renderPassCreateInfo.dependencyCount = 1;
+      renderPassCreateInfo.pDependencies = &subpassDependency;
+
+      // Create the renderpass
+      if (vkCreateRenderPass(device, &renderPassCreateInfo, 0, &renderPass) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up uniform buffer and texture sampler descriptor set layouts
+   void setupDescriptorSetLayout()
+   {
+      VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[2];
+
+      // Layout binding for uniform buffer
+      descriptorSetLayoutBindings[0] = VkDescriptorSetLayoutBinding();
+      descriptorSetLayoutBindings[0].binding = 0;
+      descriptorSetLayoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+      descriptorSetLayoutBindings[0].descriptorCount = 1;
+      descriptorSetLayoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+
+      // Layout binding for texture sampler
+      descriptorSetLayoutBindings[1] = VkDescriptorSetLayoutBinding();
+      descriptorSetLayoutBindings[1].binding = 1;
+      descriptorSetLayoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+      descriptorSetLayoutBindings[1].descriptorCount = 1;
+      descriptorSetLayoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+      VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = VkDescriptorSetLayoutCreateInfo();
+      descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+      descriptorSetLayoutCreateInfo.bindingCount = 2;
+      descriptorSetLayoutCreateInfo.pBindings = descriptorSetLayoutBindings;
+
+      // Create descriptor set layout
+      if (vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, 0, &descriptorSetLayout) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up pipeline layout
+   void setupPipelineLayout()
+   {
+      VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = VkPipelineLayoutCreateInfo();
+      pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+      pipelineLayoutCreateInfo.setLayoutCount = 1;
+      pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout;
+
+      // Create pipeline layout
+      if (vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 0, &pipelineLayout) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up rendering pipeline
+   void setupPipeline()
+   {
+      // Set up how the vertex shader pulls data out of our vertex buffer
+      VkVertexInputBindingDescription vertexInputBindingDescription = VkVertexInputBindingDescription();
+      vertexInputBindingDescription.binding = 0;
+      vertexInputBindingDescription.stride = sizeof(float) * 9;
+      vertexInputBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+      // Set up how the vertex buffer data is interpreted as attributes by the vertex shader
+      VkVertexInputAttributeDescription vertexInputAttributeDescriptions[3];
+
+      // Position attribute
+      vertexInputAttributeDescriptions[0] = VkVertexInputAttributeDescription();
+      vertexInputAttributeDescriptions[0].binding = 0;
+      vertexInputAttributeDescriptions[0].location = 0;
+      vertexInputAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
+      vertexInputAttributeDescriptions[0].offset = sizeof(float) * 0;
+
+      // Color attribute
+      vertexInputAttributeDescriptions[1] = VkVertexInputAttributeDescription();
+      vertexInputAttributeDescriptions[1].binding = 0;
+      vertexInputAttributeDescriptions[1].location = 1;
+      vertexInputAttributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
+      vertexInputAttributeDescriptions[1].offset = sizeof(float) * 3;
+
+      // Texture coordinate attribute
+      vertexInputAttributeDescriptions[2] = VkVertexInputAttributeDescription();
+      vertexInputAttributeDescriptions[2].binding = 0;
+      vertexInputAttributeDescriptions[2].location = 2;
+      vertexInputAttributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
+      vertexInputAttributeDescriptions[2].offset = sizeof(float) * 7;
+
+      VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = VkPipelineVertexInputStateCreateInfo();
+      vertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+      vertexInputStateCreateInfo.vertexBindingDescriptionCount = 1;
+      vertexInputStateCreateInfo.pVertexBindingDescriptions = &vertexInputBindingDescription;
+      vertexInputStateCreateInfo.vertexAttributeDescriptionCount = 3;
+      vertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexInputAttributeDescriptions;
+
+      // We want to generate a triangle list with our vertex data
+      VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = VkPipelineInputAssemblyStateCreateInfo();
+      inputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+      inputAssemblyStateCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+      inputAssemblyStateCreateInfo.primitiveRestartEnable = VK_FALSE;
+
+      // Set up the viewport
+      VkViewport viewport = VkViewport();
+      viewport.x = 0.0f;
+      viewport.y = 0.0f;
+      viewport.width = static_cast<float>(swapchainExtent.width);
+      viewport.height = static_cast<float>(swapchainExtent.height);
+      viewport.minDepth = 0.0f;
+      viewport.maxDepth = 1.f;
+
+      // Set up the scissor region
+      VkRect2D scissor = VkRect2D();
+      scissor.offset.x = 0;
+      scissor.offset.y = 0;
+      scissor.extent = swapchainExtent;
+
+      VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = VkPipelineViewportStateCreateInfo();
+      pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+      pipelineViewportStateCreateInfo.viewportCount = 1;
+      pipelineViewportStateCreateInfo.pViewports = &viewport;
+      pipelineViewportStateCreateInfo.scissorCount = 1;
+      pipelineViewportStateCreateInfo.pScissors = &scissor;
+
+      // Set up rasterization parameters: fill polygons, no backface culling, front face is counter-clockwise
+      VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo = VkPipelineRasterizationStateCreateInfo();
+      pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+      pipelineRasterizationStateCreateInfo.depthClampEnable = VK_FALSE;
+      pipelineRasterizationStateCreateInfo.rasterizerDiscardEnable = VK_FALSE;
+      pipelineRasterizationStateCreateInfo.polygonMode = VK_POLYGON_MODE_FILL;
+      pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
+      pipelineRasterizationStateCreateInfo.cullMode = VK_CULL_MODE_NONE;
+      pipelineRasterizationStateCreateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+      pipelineRasterizationStateCreateInfo.depthBiasEnable = VK_FALSE;
+
+      // Enable depth testing and disable scissor testing
+      VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = VkPipelineDepthStencilStateCreateInfo();
+      pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+      pipelineDepthStencilStateCreateInfo.depthTestEnable = VK_TRUE;
+      pipelineDepthStencilStateCreateInfo.depthWriteEnable = VK_TRUE;
+      pipelineDepthStencilStateCreateInfo.depthCompareOp = VK_COMPARE_OP_LESS;
+      pipelineDepthStencilStateCreateInfo.depthBoundsTestEnable = VK_FALSE;
+      pipelineDepthStencilStateCreateInfo.stencilTestEnable = VK_FALSE;
+
+      // Enable multi-sampling
+      VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = VkPipelineMultisampleStateCreateInfo();
+      pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+      pipelineMultisampleStateCreateInfo.sampleShadingEnable = VK_FALSE;
+      pipelineMultisampleStateCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+      // Set up blending parameters
+      VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = VkPipelineColorBlendAttachmentState();
+      pipelineColorBlendAttachmentState.blendEnable = VK_TRUE;
+      pipelineColorBlendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+      pipelineColorBlendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+      pipelineColorBlendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+      pipelineColorBlendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+      pipelineColorBlendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+      pipelineColorBlendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+      pipelineColorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+      VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = VkPipelineColorBlendStateCreateInfo();
+      pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+      pipelineColorBlendStateCreateInfo.logicOpEnable = VK_FALSE;
+      pipelineColorBlendStateCreateInfo.attachmentCount = 1;
+      pipelineColorBlendStateCreateInfo.pAttachments = &pipelineColorBlendAttachmentState;
+
+      VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo = VkGraphicsPipelineCreateInfo();
+      graphicsPipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+      graphicsPipelineCreateInfo.stageCount = 2;
+      graphicsPipelineCreateInfo.pStages = shaderStages;
+      graphicsPipelineCreateInfo.pVertexInputState = &vertexInputStateCreateInfo;
+      graphicsPipelineCreateInfo.pInputAssemblyState = &inputAssemblyStateCreateInfo;
+      graphicsPipelineCreateInfo.pViewportState = &pipelineViewportStateCreateInfo;
+      graphicsPipelineCreateInfo.pRasterizationState = &pipelineRasterizationStateCreateInfo;
+      graphicsPipelineCreateInfo.pDepthStencilState = &pipelineDepthStencilStateCreateInfo;
+      graphicsPipelineCreateInfo.pMultisampleState = &pipelineMultisampleStateCreateInfo;
+      graphicsPipelineCreateInfo.pColorBlendState = &pipelineColorBlendStateCreateInfo;
+      graphicsPipelineCreateInfo.layout = pipelineLayout;
+      graphicsPipelineCreateInfo.renderPass = renderPass;
+      graphicsPipelineCreateInfo.subpass = 0;
+
+      // Create our graphics pipeline
+      if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &graphicsPipelineCreateInfo, 0, &graphicsPipeline) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Use our renderpass and swapchain images to create the corresponding framebuffers
+   void setupFramebuffers()
+   {
+      swapchainFramebuffers.resize(swapchainImageViews.size());
+
+      VkFramebufferCreateInfo framebufferCreateInfo = VkFramebufferCreateInfo();
+      framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+      framebufferCreateInfo.renderPass = renderPass;
+      framebufferCreateInfo.attachmentCount = 2;
+      framebufferCreateInfo.width = swapchainExtent.width;
+      framebufferCreateInfo.height = swapchainExtent.height;
+      framebufferCreateInfo.layers = 1;
+
+      for (std::size_t i = 0; i < swapchainFramebuffers.size(); i++)
+      {
+         // Each framebuffer consists of a corresponding swapchain image and the shared depth image
+         VkImageView attachments[] = { swapchainImageViews[i], depthImageView };
+
+         framebufferCreateInfo.pAttachments = attachments;
+
+         // Create the framebuffer
+         if (vkCreateFramebuffer(device, &framebufferCreateInfo, 0, &swapchainFramebuffers[i]) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Set up our command pool
+   void setupCommandPool()
+   {
+      // We want to be able to reset command buffers after submitting them
+      VkCommandPoolCreateInfo commandPoolCreateInfo = VkCommandPoolCreateInfo();
+      commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+      commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex;
+      commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+
+      // Create our command pool
+      if (vkCreateCommandPool(device, &commandPoolCreateInfo, 0, &commandPool) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Helper to create a generic buffer with the specified size, usage and memory flags
+   bool createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& memory)
+   {
+      // We only have a single queue so we can request exclusive access
+      VkBufferCreateInfo bufferCreateInfo = VkBufferCreateInfo();
+      bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+      bufferCreateInfo.size = size;
+      bufferCreateInfo.usage = usage;
+      bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+      // Create the buffer, this does not allocate any memory for it yet
+      if (vkCreateBuffer(device, &bufferCreateInfo, 0, &buffer) != VK_SUCCESS)
+         return false;
+
+      // Check what kind of memory we need to request from the GPU
+      VkMemoryRequirements memoryRequirements = VkMemoryRequirements();
+      vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements);
+
+      // Check what GPU memory type is available for us to allocate out of
+      VkPhysicalDeviceMemoryProperties memoryProperties = VkPhysicalDeviceMemoryProperties();
+      vkGetPhysicalDeviceMemoryProperties(gpu, &memoryProperties);
+
+      uint32_t memoryType = 0;
+
+      for (; memoryType < memoryProperties.memoryTypeCount; memoryType++)
+      {
+         if ((memoryRequirements.memoryTypeBits & (1 << memoryType)) &&
+            ((memoryProperties.memoryTypes[memoryType].propertyFlags & properties) == properties))
+            break;
+      }
+
+      if (memoryType == memoryProperties.memoryTypeCount)
+         return false;
+
+      VkMemoryAllocateInfo memoryAllocateInfo = VkMemoryAllocateInfo();
+      memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+      memoryAllocateInfo.allocationSize = memoryRequirements.size;
+      memoryAllocateInfo.memoryTypeIndex = memoryType;
+
+      // Allocate the memory out of the GPU pool for the required memory type
+      if (vkAllocateMemory(device, &memoryAllocateInfo, 0, &memory) != VK_SUCCESS)
+         return false;
+
+      // Bind the allocated memory to our buffer object
+      if (vkBindBufferMemory(device, buffer, memory, 0) != VK_SUCCESS)
+         return false;
+
+      return true;
+   }
+
+   // Helper to copy the contents of one buffer to another buffer
+   bool copyBuffer(VkBuffer dst, VkBuffer src, VkDeviceSize size)
+   {
+      // Allocate a primary command buffer out of our command pool
+      VkCommandBufferAllocateInfo commandBufferAllocateInfo = VkCommandBufferAllocateInfo();
+      commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+      commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+      commandBufferAllocateInfo.commandPool = commandPool;
+      commandBufferAllocateInfo.commandBufferCount = 1;
+
+      VkCommandBuffer commandBuffer;
+
+      if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer) != VK_SUCCESS)
+         return false;
+
+      // Begin the command buffer
+      VkCommandBufferBeginInfo commandBufferBeginInfo = VkCommandBufferBeginInfo();
+      commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+      commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+      if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         return false;
+      }
+
+      // Add our buffer copy command
+      VkBufferCopy bufferCopy = VkBufferCopy();
+      bufferCopy.srcOffset = 0;
+      bufferCopy.dstOffset = 0;
+      bufferCopy.size = size;
+
+      vkCmdCopyBuffer(commandBuffer, src, dst, 1, &bufferCopy);
+
+      // End and submit the command buffer
+      vkEndCommandBuffer(commandBuffer);
+
+      VkSubmitInfo submitInfo = VkSubmitInfo();
+      submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+      submitInfo.commandBufferCount = 1;
+      submitInfo.pCommandBuffers = &commandBuffer;
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         return false;
+      }
+
+      // Ensure the command buffer has been processed
+      if (vkQueueWaitIdle(queue) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         return false;
+      }
+
+      // Free the command buffer
+      vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+      return true;
+   }
+
+   // Create our vertex buffer and upload its data
+   void setupVertexBuffer()
+   {
+      float vertexData[] = {
+         // X      Y      Z     R     G     B     A     U     V
+         -0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
+          0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+          0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+         -0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
+
+         -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+          0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+          0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,
+         -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
+
+          0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
+          0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+          0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+          0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,
+
+         -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+         -0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+         -0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+         -0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
+
+         -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+          0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+          0.5f, -0.5f,  0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
+         -0.5f, -0.5f,  0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+
+         -0.5f,  0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
+          0.5f,  0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+          0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
+         -0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f
+      };
+
+      // Create a staging buffer that is writable by the CPU
+      VkBuffer stagingBuffer = 0;
+      VkDeviceMemory stagingBufferMemory = 0;
+
+      if (!createBuffer(
+         sizeof(vertexData),
+         VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+         VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+         stagingBuffer,
+         stagingBufferMemory
+      ))
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      void* ptr;
+
+      // Map the buffer into our address space
+      if (vkMapMemory(device, stagingBufferMemory, 0, sizeof(vertexData), 0, &ptr) != VK_SUCCESS)
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the vertex data into the buffer
+      std::memcpy(ptr, vertexData, sizeof(vertexData));
+
+      // Unmap the buffer
+      vkUnmapMemory(device, stagingBufferMemory);
+
+      // Create the GPU local vertex buffer
+      if (!createBuffer(
+         sizeof(vertexData),
+         VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+         VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+         vertexBuffer,
+         vertexBufferMemory
+      ))
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the contents of the staging buffer into the GPU vertex buffer
+      vulkanAvailable = copyBuffer(vertexBuffer, stagingBuffer, sizeof(vertexData));
+
+      // Free the staging buffer and its memory
+      vkFreeMemory(device, stagingBufferMemory, 0);
+      vkDestroyBuffer(device, stagingBuffer, 0);
+   }
+
+   // Create our index buffer and upload its data
+   void setupIndexBuffer()
+   {
+      uint16_t indexData[] = {
+          0,  1,  2,
+          2,  3,  0,
+
+          4,  5,  6,
+          6,  7,  4,
+
+          8,  9,  10,
+          10, 11, 8,
+
+          12, 13, 14,
+          14, 15, 12,
+
+          16, 17, 18,
+          18, 19, 16,
+
+          20, 21, 22,
+          22, 23, 20
+      };
+
+      // Create a staging buffer that is writable by the CPU
+      VkBuffer stagingBuffer = 0;
+      VkDeviceMemory stagingBufferMemory = 0;
+
+      if (!createBuffer(
+         sizeof(indexData),
+         VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+         VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+         stagingBuffer,
+         stagingBufferMemory
+      ))
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      void* ptr;
+
+      // Map the buffer into our address space
+      if (vkMapMemory(device, stagingBufferMemory, 0, sizeof(indexData), 0, &ptr) != VK_SUCCESS)
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the index data into the buffer
+      std::memcpy(ptr, indexData, sizeof(indexData));
+
+      // Unmap the buffer
+      vkUnmapMemory(device, stagingBufferMemory);
+
+      // Create the GPU local index buffer
+      if (!createBuffer(
+         sizeof(indexData),
+         VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+         VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+         indexBuffer,
+         indexBufferMemory
+      ))
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the contents of the staging buffer into the GPU index buffer
+      vulkanAvailable = copyBuffer(indexBuffer, stagingBuffer, sizeof(indexData));
+
+      // Free the staging buffer and its memory
+      vkFreeMemory(device, stagingBufferMemory, 0);
+      vkDestroyBuffer(device, stagingBuffer, 0);
+   }
+
+   // Create our uniform buffer but don't upload any data yet
+   void setupUniformBuffers()
+   {
+      // Create a uniform buffer for every frame that might be in flight to prevent clobbering
+      for (size_t i = 0; i < swapchainImages.size(); i++)
+      {
+         uniformBuffers.push_back(0);
+         uniformBuffersMemory.push_back(0);
+
+         // The uniform buffer will be host visible and coherent since we use it for streaming data every frame
+         if (!createBuffer(
+            sizeof(Matrix) * 3,
+            VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+            uniformBuffers[i],
+            uniformBuffersMemory[i]
+         ))
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Helper to create a generic image with the specified size, format, usage and memory flags
+   bool createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory)
+   {
+      // We only have a single queue so we can request exclusive access
+      VkImageCreateInfo imageCreateInfo = VkImageCreateInfo();
+      imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+      imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+      imageCreateInfo.extent.width = width;
+      imageCreateInfo.extent.height = height;
+      imageCreateInfo.extent.depth = 1;
+      imageCreateInfo.mipLevels = 1;
+      imageCreateInfo.arrayLayers = 1;
+      imageCreateInfo.format = format;
+      imageCreateInfo.tiling = tiling;
+      imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      imageCreateInfo.usage = usage;
+      imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+      imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+      // Create the image, this does not allocate any memory for it yet
+      if (vkCreateImage(device, &imageCreateInfo, 0, &image) != VK_SUCCESS)
+         return false;
+
+      // Check what kind of memory we need to request from the GPU
+      VkMemoryRequirements memoryRequirements = VkMemoryRequirements();
+      vkGetImageMemoryRequirements(device, image, &memoryRequirements);
+
+      // Check what GPU memory type is available for us to allocate out of
+      VkPhysicalDeviceMemoryProperties memoryProperties = VkPhysicalDeviceMemoryProperties();
+      vkGetPhysicalDeviceMemoryProperties(gpu, &memoryProperties);
+
+      uint32_t memoryType = 0;
+
+      for (; memoryType < memoryProperties.memoryTypeCount; memoryType++)
+      {
+         if ((memoryRequirements.memoryTypeBits & (1 << memoryType)) &&
+            ((memoryProperties.memoryTypes[memoryType].propertyFlags & properties) == properties))
+            break;
+      }
+
+      if (memoryType == memoryProperties.memoryTypeCount)
+         return false;
+
+      VkMemoryAllocateInfo memoryAllocateInfo = VkMemoryAllocateInfo();
+      memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+      memoryAllocateInfo.allocationSize = memoryRequirements.size;
+      memoryAllocateInfo.memoryTypeIndex = memoryType;
+
+      // Allocate the memory out of the GPU pool for the required memory type
+      if (vkAllocateMemory(device, &memoryAllocateInfo, 0, &imageMemory) != VK_SUCCESS)
+         return false;
+
+      // Bind the allocated memory to our image object
+      if (vkBindImageMemory(device, image, imageMemory, 0) != VK_SUCCESS)
+         return false;
+
+      return true;
+   }
+
+   // Create our depth image and transition it into the proper layout
+   void setupDepthImage()
+   {
+      // Create our depth image
+      if (!createImage(
+         swapchainExtent.width,
+         swapchainExtent.height,
+         depthFormat,
+         VK_IMAGE_TILING_OPTIMAL,
+         VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+         VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+         depthImage,
+         depthImageMemory
+      ))
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Allocate a command buffer
+      VkCommandBufferAllocateInfo commandBufferAllocateInfo = VkCommandBufferAllocateInfo();
+      commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+      commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+      commandBufferAllocateInfo.commandPool = commandPool;
+      commandBufferAllocateInfo.commandBufferCount = 1;
+
+      VkCommandBuffer commandBuffer;
+
+      if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Begin the command buffer
+      VkCommandBufferBeginInfo commandBufferBeginInfo = VkCommandBufferBeginInfo();
+      commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+      commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+      VkSubmitInfo submitInfo = VkSubmitInfo();
+      submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+      submitInfo.commandBufferCount = 1;
+      submitInfo.pCommandBuffers = &commandBuffer;
+
+      if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Submit a barrier to transition the image layout to depth stencil optimal
+      VkImageMemoryBarrier barrier = VkImageMemoryBarrier();
+      barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+      barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+      barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+      barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+      barrier.image = depthImage;
+      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | ((depthFormat == VK_FORMAT_D32_SFLOAT) ? 0 : VK_IMAGE_ASPECT_STENCIL_BIT);
+      barrier.subresourceRange.baseMipLevel = 0;
+      barrier.subresourceRange.levelCount = 1;
+      barrier.subresourceRange.baseArrayLayer = 0;
+      barrier.subresourceRange.layerCount = 1;
+      barrier.srcAccessMask = 0;
+      barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+
+      vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, 0, 0, 0, 0, 0, 1, &barrier);
+
+      // End and submit the command buffer
+      if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Ensure the command buffer has been processed
+      if (vkQueueWaitIdle(queue) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Free the command buffer
+      vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+   }
+
+   // Create an image view for our depth image
+   void setupDepthImageView()
+   {
+      VkImageViewCreateInfo imageViewCreateInfo = VkImageViewCreateInfo();
+      imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+      imageViewCreateInfo.image = depthImage;
+      imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+      imageViewCreateInfo.format = depthFormat;
+      imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | ((depthFormat == VK_FORMAT_D32_SFLOAT) ? 0 : VK_IMAGE_ASPECT_STENCIL_BIT);
+      imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
+      imageViewCreateInfo.subresourceRange.levelCount = 1;
+      imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
+      imageViewCreateInfo.subresourceRange.layerCount = 1;
+
+      // Create the depth image view
+      if (vkCreateImageView(device, &imageViewCreateInfo, 0, &depthImageView) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Create an image for our texture data
+   void setupTextureImage()
+   {
+      // Load the image data
+      sf::Image imageData;
+
+      if (!imageData.loadFromFile("resources/logo.png"))
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Create a staging buffer to transfer the data with
+      VkDeviceSize imageSize = imageData.getSize().x * imageData.getSize().y * 4;
+
+      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* ptr;
+
+      // Map the buffer into our address space
+      if (vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &ptr) != VK_SUCCESS)
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the image data into the buffer
+      std::memcpy(ptr, imageData.getPixelsPtr(), static_cast<size_t>(imageSize));
+
+      // Unmap the buffer
+      vkUnmapMemory(device, stagingBufferMemory);
+
+      // Create a GPU local image
+      if (!createImage(
+         imageData.getSize().x,
+         imageData.getSize().y,
+         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
+      ))
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Create a command buffer
+      VkCommandBufferAllocateInfo commandBufferAllocateInfo = VkCommandBufferAllocateInfo();
+      commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+      commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+      commandBufferAllocateInfo.commandPool = commandPool;
+      commandBufferAllocateInfo.commandBufferCount = 1;
+
+      VkCommandBuffer commandBuffer;
+
+      if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer) != VK_SUCCESS)
+      {
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Begin the command buffer
+      VkCommandBufferBeginInfo commandBufferBeginInfo = VkCommandBufferBeginInfo();
+      commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+      commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+      VkSubmitInfo submitInfo = VkSubmitInfo();
+      submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+      submitInfo.commandBufferCount = 1;
+      submitInfo.pCommandBuffers = &commandBuffer;
+
+      if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Submit a barrier to transition the image layout to transfer destionation optimal
+      VkImageMemoryBarrier barrier = VkImageMemoryBarrier();
+      barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+      barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+      barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+      barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+      barrier.image = textureImage;
+      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+      barrier.subresourceRange.baseMipLevel = 0;
+      barrier.subresourceRange.levelCount = 1;
+      barrier.subresourceRange.baseArrayLayer = 0;
+      barrier.subresourceRange.layerCount = 1;
+      barrier.srcAccessMask = 0;
+      barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+      vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, 0, 0, 0, 1, &barrier);
+
+      if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Ensure the command buffer has been processed
+      if (vkQueueWaitIdle(queue) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Begin the command buffer
+      if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the staging buffer contents into the image
+      VkBufferImageCopy bufferImageCopy = VkBufferImageCopy();
+      bufferImageCopy.bufferOffset = 0;
+      bufferImageCopy.bufferRowLength = 0;
+      bufferImageCopy.bufferImageHeight = 0;
+      bufferImageCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+      bufferImageCopy.imageSubresource.mipLevel = 0;
+      bufferImageCopy.imageSubresource.baseArrayLayer = 0;
+      bufferImageCopy.imageSubresource.layerCount = 1;
+      bufferImageCopy.imageOffset.x = 0;
+      bufferImageCopy.imageOffset.y = 0;
+      bufferImageCopy.imageOffset.z = 0;
+      bufferImageCopy.imageExtent.width = imageData.getSize().x;
+      bufferImageCopy.imageExtent.height = imageData.getSize().y;
+      bufferImageCopy.imageExtent.depth = 1;
+
+      vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferImageCopy);
+
+      // End and submit the command buffer
+      if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Ensure the command buffer has been processed
+      if (vkQueueWaitIdle(queue) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Begin the command buffer
+      if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Submit a barrier to transition the image layout from transfer destionation optimal to shader read-only optimal
+      barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+      barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+      barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+      barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+      vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, 0, 0, 0, 1, &barrier);
+
+      // End and submit the command buffer
+      if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Ensure the command buffer has been processed
+      if (vkQueueWaitIdle(queue) != VK_SUCCESS)
+      {
+         vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+         vkFreeMemory(device, stagingBufferMemory, 0);
+         vkDestroyBuffer(device, stagingBuffer, 0);
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Free the command buffer
+      vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+
+      vkFreeMemory(device, stagingBufferMemory, 0);
+      vkDestroyBuffer(device, stagingBuffer, 0);
+   }
+
+   // Create an image view for our texture
+   void setupTextureImageView()
+   {
+      VkImageViewCreateInfo imageViewCreateInfo = VkImageViewCreateInfo();
+      imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+      imageViewCreateInfo.image = textureImage;
+      imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+      imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+      imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+      imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
+      imageViewCreateInfo.subresourceRange.levelCount = 1;
+      imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
+      imageViewCreateInfo.subresourceRange.layerCount = 1;
+
+      // Create our texture image view
+      if (vkCreateImageView(device, &imageViewCreateInfo, 0, &textureImageView) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Create a sampler for our texture
+   void setupTextureSampler()
+   {
+      // Sampler parameters: linear min/mag filtering, 4x anisotropic
+      VkSamplerCreateInfo samplerCreateInfo = VkSamplerCreateInfo();
+      samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+      samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+      samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+      samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+      samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+      samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+      samplerCreateInfo.anisotropyEnable = VK_TRUE;
+      samplerCreateInfo.maxAnisotropy = 4;
+      samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+      samplerCreateInfo.unnormalizedCoordinates = VK_FALSE;
+      samplerCreateInfo.compareEnable = VK_FALSE;
+      samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+      samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+      samplerCreateInfo.mipLodBias = 0.0f;
+      samplerCreateInfo.minLod = 0.0f;
+      samplerCreateInfo.maxLod = 0.0f;
+
+      // Create our sampler
+      if (vkCreateSampler(device, &samplerCreateInfo, 0, &textureSampler) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up our descriptor pool
+   void setupDescriptorPool()
+   {
+      // We need to allocate as many descriptor sets as we have frames in flight
+      VkDescriptorPoolSize descriptorPoolSizes[2];
+
+      descriptorPoolSizes[0] = VkDescriptorPoolSize();
+      descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+      descriptorPoolSizes[0].descriptorCount = static_cast<uint32_t>(swapchainImages.size());
+
+      descriptorPoolSizes[1] = VkDescriptorPoolSize();
+      descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+      descriptorPoolSizes[1].descriptorCount = static_cast<uint32_t>(swapchainImages.size());
+
+      VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = VkDescriptorPoolCreateInfo();
+      descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+      descriptorPoolCreateInfo.poolSizeCount = 2;
+      descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes;
+      descriptorPoolCreateInfo.maxSets = static_cast<uint32_t>(swapchainImages.size());
+
+      // Create the descriptor pool
+      if (vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, 0, &descriptorPool) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up our descriptor sets
+   void setupDescriptorSets()
+   {
+      // Allocate a descriptor set for each frame in flight
+      std::vector<VkDescriptorSetLayout> descriptorSetLayouts(swapchainImages.size(), descriptorSetLayout);
+
+      VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo();
+      descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+      descriptorSetAllocateInfo.descriptorPool = descriptorPool;
+      descriptorSetAllocateInfo.descriptorSetCount = static_cast<uint32_t>(swapchainImages.size());
+      descriptorSetAllocateInfo.pSetLayouts = &descriptorSetLayouts[0];
+
+      descriptorSets.resize(swapchainImages.size());
+
+      if (vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSets[0]) != VK_SUCCESS)
+      {
+         descriptorSets.clear();
+
+         vulkanAvailable = false;
+         return;
+      }
+
+      // For every descriptor set, set up the bindings to our uniform buffer and texture sampler
+      for (std::size_t i = 0; i < descriptorSets.size(); i++)
+      {
+         VkWriteDescriptorSet writeDescriptorSets[2];
+
+         // Uniform buffer binding information
+         VkDescriptorBufferInfo descriptorBufferInfo = VkDescriptorBufferInfo();
+         descriptorBufferInfo.buffer = uniformBuffers[i];
+         descriptorBufferInfo.offset = 0;
+         descriptorBufferInfo.range = sizeof(Matrix) * 3;
+
+         writeDescriptorSets[0] = VkWriteDescriptorSet();
+         writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+         writeDescriptorSets[0].dstSet = descriptorSets[i];
+         writeDescriptorSets[0].dstBinding = 0;
+         writeDescriptorSets[0].dstArrayElement = 0;
+         writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+         writeDescriptorSets[0].descriptorCount = 1;
+         writeDescriptorSets[0].pBufferInfo = &descriptorBufferInfo;
+
+         // Texture sampler binding information
+         VkDescriptorImageInfo descriptorImageInfo = VkDescriptorImageInfo();
+         descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+         descriptorImageInfo.imageView = textureImageView;
+         descriptorImageInfo.sampler = textureSampler;
+
+         writeDescriptorSets[1] = VkWriteDescriptorSet();
+         writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+         writeDescriptorSets[1].dstSet = descriptorSets[i];
+         writeDescriptorSets[1].dstBinding = 1;
+         writeDescriptorSets[1].dstArrayElement = 0;
+         writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+         writeDescriptorSets[1].descriptorCount = 1;
+         writeDescriptorSets[1].pImageInfo = &descriptorImageInfo;
+
+         // Update the desciptor set
+         vkUpdateDescriptorSets(device, 2, writeDescriptorSets, 0, 0);
+      }
+   }
+
+   // Set up the command buffers we use for drawing each frame
+   void setupCommandBuffers()
+   {
+      // We need a command buffer for every frame in flight
+      commandBuffers.resize(swapchainFramebuffers.size());
+
+      // These are primary command buffers
+      VkCommandBufferAllocateInfo commandBufferAllocateInfo = VkCommandBufferAllocateInfo();
+      commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+      commandBufferAllocateInfo.commandPool = commandPool;
+      commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+      commandBufferAllocateInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
+
+      // Allocate the command buffers from our command pool
+      if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffers[0]) != VK_SUCCESS)
+      {
+         commandBuffers.clear();
+         vulkanAvailable = false;
+         return;
+      }
+   }
+
+   // Set up the commands we need to issue to draw a frame
+   void setupDraw()
+   {
+      // Set up our clear colors
+      VkClearValue clearColors[2];
+
+      // Clear color buffer to opaque black
+      clearColors[0] = VkClearValue();
+      clearColors[0].color.float32[0] = 0.0f;
+      clearColors[0].color.float32[1] = 0.0f;
+      clearColors[0].color.float32[2] = 0.0f;
+      clearColors[0].color.float32[3] = 0.0f;
+
+      // Clear depth to 1.0f
+      clearColors[1] = VkClearValue();
+      clearColors[1].depthStencil.depth = 1.0f;
+      clearColors[1].depthStencil.stencil = 0;
+
+      VkRenderPassBeginInfo renderPassBeginInfo = VkRenderPassBeginInfo();
+      renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+      renderPassBeginInfo.renderPass = renderPass;
+      renderPassBeginInfo.renderArea.offset.x = 0;
+      renderPassBeginInfo.renderArea.offset.y = 0;
+      renderPassBeginInfo.renderArea.extent = swapchainExtent;
+      renderPassBeginInfo.clearValueCount = 2;
+      renderPassBeginInfo.pClearValues = clearColors;
+
+      // Simultaneous use: this command buffer can be resubmitted to a queue before a previous submission is completed
+      VkCommandBufferBeginInfo commandBufferBeginInfo = VkCommandBufferBeginInfo();
+      commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+      commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+
+      // Set up the command buffers for each frame in flight
+      for (std::size_t i = 0; i < commandBuffers.size(); i++)
+      {
+         // Begin the command buffer
+         if (vkBeginCommandBuffer(commandBuffers[i], &commandBufferBeginInfo) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+
+         // Begin the renderpass
+         renderPassBeginInfo.framebuffer = swapchainFramebuffers[i];
+
+         vkCmdBeginRenderPass(commandBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+         // Bind our graphics pipeline
+         vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+
+         // Bind our vertex buffer
+         VkDeviceSize offset = 0;
+
+         vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexBuffer, &offset);
+
+         // Bind our index buffer
+         vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16);
+
+         // Bind our descriptor sets
+         vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, 0);
+
+         // Draw our primitives
+         vkCmdDrawIndexed(commandBuffers[i], 36, 1, 0, 0, 0);
+
+         // End the renderpass
+         vkCmdEndRenderPass(commandBuffers[i]);
+
+         // End the command buffer
+         if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Set up the semaphores we use to synchronize frames among each other
+   void setupSemaphores()
+   {
+      VkSemaphoreCreateInfo semaphoreCreateInfo = VkSemaphoreCreateInfo();
+      semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+      // Create a semaphore to track when an swapchain image is available for each frame in flight
+      for (int i = 0; i < maxFramesInFlight; i++)
+      {
+         imageAvailableSemaphores.push_back(0);
+
+         if (vkCreateSemaphore(device, &semaphoreCreateInfo, 0, &imageAvailableSemaphores[i]) != VK_SUCCESS)
+         {
+            imageAvailableSemaphores.pop_back();
+            vulkanAvailable = false;
+            return;
+         }
+      }
+
+      // Create a semaphore to track when rendering is complete for each frame in flight
+      for (int i = 0; i < maxFramesInFlight; i++)
+      {
+         renderFinishedSemaphores.push_back(0);
+
+         if (vkCreateSemaphore(device, &semaphoreCreateInfo, 0, &renderFinishedSemaphores[i]) != VK_SUCCESS)
+         {
+            renderFinishedSemaphores.pop_back();
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Set up the fences we use to synchronize frames among each other
+   void setupFences()
+   {
+      // Create the fences in the signaled state
+      VkFenceCreateInfo fenceCreateInfo = VkFenceCreateInfo();
+      fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+      fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+      // Create a fence to track when queue submission is complete for each frame in flight
+      for (int i = 0; i < maxFramesInFlight; i++)
+      {
+         fences.push_back(0);
+
+         if (vkCreateFence(device, &fenceCreateInfo, 0, &fences[i]) != VK_SUCCESS)
+         {
+            fences.pop_back();
+            vulkanAvailable = false;
+            return;
+         }
+      }
+   }
+
+   // Update the matrices in our uniform buffer every frame
+   void updateUniformBuffer(float elapsed)
+   {
+      const float pi = 3.14159265359f;
+
+      // Construct the model matrix
+      Matrix model = {
+          1.0f, 0.0f, 0.0f, 0.0f,
+          0.0f, 1.0f, 0.0f, 0.0f,
+          0.0f, 0.0f, 1.0f, 0.0f,
+          0.0f, 0.0f, 0.0f, 1.0f
+      };
+
+      matrixRotateX(model, elapsed * 59.0f * pi / 180.f);
+      matrixRotateY(model, elapsed * 83.0f * pi / 180.f);
+      matrixRotateZ(model, elapsed * 109.0f * pi / 180.f);
+
+      // Translate the model based on the mouse position
+      float x = clamp(sf::Mouse::getPosition(window).x * 2.f / window.getSize().x - 1.f, -1.0f, 1.0f) * 2.0f;
+      float y = clamp(-sf::Mouse::getPosition(window).y * 2.f / window.getSize().y + 1.f, -1.0f, 1.0f) * 1.5f;
+
+      model[3][0] -= x;
+      model[3][2] += y;
+
+      // Construct the view matrix
+      const Vec3 eye = { 0.0f, 4.0f, 0.0f };
+      const Vec3 center = { 0.0f, 0.0f, 0.0f };
+      const Vec3 up = { 0.0f, 0.0f, 1.0f };
+
+      Matrix view;
+
+      matrixLookAt(view, eye, center, up);
+
+      // Construct the projection matrix
+      const float fov = 45.0f;
+      const float aspect = static_cast<float>(swapchainExtent.width) / static_cast<float>(swapchainExtent.height);
+      const float nearPlane = 0.1f;
+      const float farPlane = 10.0f;
+
+      Matrix projection;
+
+      matrixPerspective(projection, fov * pi / 180.f, aspect, nearPlane, farPlane);
+
+      char* ptr;
+
+      // Map the current frame's uniform buffer into our address space
+      if (vkMapMemory(device, uniformBuffersMemory[currentFrame], 0, sizeof(Matrix) * 3, 0, reinterpret_cast<void**>(&ptr)) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Copy the matrix data into the current frame's uniform buffer
+      std::memcpy(ptr + sizeof(Matrix) * 0, model, sizeof(Matrix));
+      std::memcpy(ptr + sizeof(Matrix) * 1, view, sizeof(Matrix));
+      std::memcpy(ptr + sizeof(Matrix) * 2, projection, sizeof(Matrix));
+
+      // Unmap the buffer
+      vkUnmapMemory(device, uniformBuffersMemory[currentFrame]);
+   }
+
+   void draw()
+   {
+      uint32_t imageIndex = 0;
+
+      // If the objects we need to submit this frame are still pending, wait here
+      vkWaitForFences(device, 1, &fences[currentFrame], VK_TRUE, std::numeric_limits<uint64_t>::max());
+
+      {
+         // Get the next image in the swapchain
+         VkResult result = vkAcquireNextImageKHR(device, swapchain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+
+         // Check if we need to re-create the swapchain (e.g. if the window was resized)
+         if (result == VK_ERROR_OUT_OF_DATE_KHR)
+         {
+            recreateSwapchain();
+            swapchainOutOfDate = false;
+            return;
+         }
+
+         if ((result != VK_SUCCESS) && (result != VK_TIMEOUT) && (result != VK_NOT_READY) && (result != VK_SUBOPTIMAL_KHR))
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+
+      // Wait for the swapchain image to be available in the color attachment stage before submitting the queue
+      VkPipelineStageFlags waitStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+
+      // Signal the render finished semaphore once the queue has been processed
+      VkSubmitInfo submitInfo = VkSubmitInfo();
+      submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+      submitInfo.waitSemaphoreCount = 1;
+      submitInfo.pWaitSemaphores = &imageAvailableSemaphores[currentFrame];
+      submitInfo.pWaitDstStageMask = &waitStages;
+      submitInfo.commandBufferCount = 1;
+      submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
+      submitInfo.signalSemaphoreCount = 1;
+      submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame];
+
+      vkResetFences(device, 1, &fences[currentFrame]);
+
+      if (vkQueueSubmit(queue, 1, &submitInfo, fences[currentFrame]) != VK_SUCCESS)
+      {
+         vulkanAvailable = false;
+         return;
+      }
+
+      // Wait for rendering to complete before presenting
+      VkPresentInfoKHR presentInfo = VkPresentInfoKHR();
+      presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+      presentInfo.waitSemaphoreCount = 1;
+      presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame];
+      presentInfo.swapchainCount = 1;
+      presentInfo.pSwapchains = &swapchain;
+      presentInfo.pImageIndices = &imageIndex;
+
+      {
+         // Queue presentation
+         VkResult result = vkQueuePresentKHR(queue, &presentInfo);
+
+         // Check if we need to re-create the swapchain (e.g. if the window was resized)
+         if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR) || swapchainOutOfDate)
+         {
+            recreateSwapchain();
+            swapchainOutOfDate = false;
+         }
+         else if (result != VK_SUCCESS)
+         {
+            vulkanAvailable = false;
+            return;
+         }
+      }
+
+      // Make sure to use the next frame's objects next frame
+      currentFrame = (currentFrame + 1) % maxFramesInFlight;
+   }
+
+   void run()
+   {
+      sf::Clock clock;
+
+      // Start game loop
+      while (window.isOpen())
+      {
+         // Process events
+         sf::Event event;
+         while (window.pollEvent(event))
+         {
+            // Close window: exit
+            if (event.type == sf::Event::Closed)
+               window.close();
+
+            // Escape key: exit
+            if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))
+               window.close();
+
+            // Re-create the swapchain when the window is resized
+            if (event.type == sf::Event::Resized)
+               swapchainOutOfDate = true;
+         }
+
+         if (vulkanAvailable)
+         {
+            // Update the uniform buffer (matrices)
+            updateUniformBuffer(clock.getElapsedTime().asSeconds());
+
+            // Render the frame
+            draw();
+         }
+      }
+   }
+
+private:
+   sf::WindowBase window;
+
+   bool vulkanAvailable;
+
+   const int maxFramesInFlight;
+   int currentFrame;
+   bool swapchainOutOfDate;
+
+   VkInstance instance;
+   VkDebugReportCallbackEXT debugReportCallback;
+   VkSurfaceKHR surface;
+   VkPhysicalDevice gpu;
+   int queueFamilyIndex;
+   VkDevice device;
+   VkQueue queue;
+   VkSurfaceFormatKHR swapchainFormat;
+   VkExtent2D swapchainExtent;
+   VkSwapchainKHR swapchain;
+   std::vector<VkImage> swapchainImages;
+   std::vector<VkImageView> swapchainImageViews;
+   VkFormat depthFormat;
+   VkImage depthImage;
+   VkDeviceMemory depthImageMemory;
+   VkImageView depthImageView;
+   VkShaderModule vertexShaderModule;
+   VkShaderModule fragmentShaderModule;
+   VkPipelineShaderStageCreateInfo shaderStages[2];
+   VkDescriptorSetLayout descriptorSetLayout;
+   VkPipelineLayout pipelineLayout;
+   VkRenderPass renderPass;
+   VkPipeline graphicsPipeline;
+   std::vector<VkFramebuffer> swapchainFramebuffers;
+   VkCommandPool commandPool;
+   VkBuffer vertexBuffer;
+   VkDeviceMemory vertexBufferMemory;
+   VkBuffer indexBuffer;
+   VkDeviceMemory indexBufferMemory;
+   std::vector<VkBuffer> uniformBuffers;
+   std::vector<VkDeviceMemory> uniformBuffersMemory;
+   VkImage textureImage;
+   VkDeviceMemory textureImageMemory;
+   VkImageView textureImageView;
+   VkSampler textureSampler;
+   VkDescriptorPool descriptorPool;
+   std::vector<VkDescriptorSet> descriptorSets;
+   std::vector<VkCommandBuffer> commandBuffers;
+   std::vector<VkSemaphore> imageAvailableSemaphores;
+   std::vector<VkSemaphore> renderFinishedSemaphores;
+   std::vector<VkFence> fences;
+};
+
+
+////////////////////////////////////////////////////////////
+/// Entry point of application
+///
+/// \return Application exit code
+///
+////////////////////////////////////////////////////////////
+int main()
+{
+   VulkanExample example;
+
+   example.run();
+
+   return EXIT_SUCCESS;
+}
