diff options
Diffstat (limited to 'examples/vulkan/Vulkan.cpp')
-rw-r--r-- | examples/vulkan/Vulkan.cpp | 2559 |
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; +} |