summaryrefslogtreecommitdiff
path: root/examples/vulkan/Vulkan.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/vulkan/Vulkan.cpp')
-rw-r--r--examples/vulkan/Vulkan.cpp2559
1 files changed, 2559 insertions, 0 deletions
diff --git a/examples/vulkan/Vulkan.cpp b/examples/vulkan/Vulkan.cpp
new file mode 100644
index 0000000..ade2d14
--- /dev/null
+++ b/examples/vulkan/Vulkan.cpp
@@ -0,0 +1,2559 @@
+
+////////////////////////////////////////////////////////////
+// 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 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, static_cast<sf::Uint32>(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 = static_cast<sf::Uint32>(validationLayers.size());
+ instanceCreateInfo.ppEnabledLayerNames = &validationLayers[0];
+ instanceCreateInfo.enabledExtensionCount = static_cast<sf::Uint32>(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 = static_cast<sf::Uint32>(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, static_cast<sf::Uint32>(i), surface, &surfaceSupported);
+
+ if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && (surfaceSupported == VK_TRUE))
+ {
+ queueFamilyIndex = static_cast<int>(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 = static_cast<uint32_t>(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, static_cast<uint32_t>(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<uint32_t> buffer(static_cast<std::size_t>(file.getSize()) / sizeof(uint32_t));
+
+ if (file.read(&buffer[0], file.getSize()) != file.getSize())
+ {
+ vulkanAvailable = false;
+ return;
+ }
+
+ shaderModuleCreateInfo.codeSize = buffer.size() * sizeof(uint32_t);
+ shaderModuleCreateInfo.pCode = &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<uint32_t> buffer(static_cast<std::size_t>(file.getSize()) / sizeof(uint32_t));
+
+ if (file.read(&buffer[0], file.getSize()) != file.getSize())
+ {
+ vulkanAvailable = false;
+ return;
+ }
+
+ shaderModuleCreateInfo.codeSize = buffer.size() * sizeof(uint32_t);
+ shaderModuleCreateInfo.pCode = &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 = static_cast<uint32_t>(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 & static_cast<unsigned int>(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 & static_cast<unsigned int>(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<std::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 (std::size_t 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 (std::size_t 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 (std::size_t 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
+ sf::Vector2f mousePosition = sf::Vector2f(sf::Mouse::getPosition(window));
+ sf::Vector2f windowSize = sf::Vector2f(window.getSize());
+ float x = clamp( mousePosition.x * 2.f / windowSize.x - 1.f, -1.0f, 1.0f) * 2.0f;
+ float y = clamp(-mousePosition.y * 2.f / windowSize.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 unsigned int maxFramesInFlight;
+ unsigned 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;
+}