//////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// #define GLAD_VULKAN_IMPLEMENTATION #include // Include graphics because we use sf::Image for loading images #include #include #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////// // Helper functions //////////////////////////////////////////////////////////// namespace { using Matrix = std::array, 4>; // Multiply 2 matrices Matrix matrixMultiply(const Matrix& left, const Matrix& right) { Matrix matrix; for (std::size_t i = 0; i < matrix.size(); ++i) { for (std::size_t j = 0; j < matrix[0].size(); ++j) matrix[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]; } return matrix; } // Rotate a matrix around the x-axis void matrixRotateX(Matrix& result, sf::Angle angle) { const float rad = angle.asRadians(); // clang-format off const Matrix matrix = {{ {1.f, 0.f, 0.f, 0.f}, {0.f, std::cos(rad), std::sin(rad), 0.f}, {0.f, -std::sin(rad), std::cos(rad), 0.f}, {0.f, 0.f, 0.f, 1.f} }}; // clang-format on result = matrixMultiply(result, matrix); } // Rotate a matrix around the y-axis void matrixRotateY(Matrix& result, sf::Angle angle) { const float rad = angle.asRadians(); // clang-format off const Matrix matrix = {{ { std::cos(rad), 0.f, std::sin(rad), 0.f}, { 0.f, 1.f, 0.f, 0.f}, {-std::sin(rad), 0.f, std::cos(rad), 0.f}, { 0.f, 0.f, 0.f, 1.f} }}; // clang-format on result = matrixMultiply(result, matrix); } // Rotate a matrix around the z-axis void matrixRotateZ(Matrix& result, sf::Angle angle) { const float rad = angle.asRadians(); // clang-format off const Matrix matrix = {{ { std::cos(rad), std::sin(rad), 0.f, 0.f}, {-std::sin(rad), std::cos(rad), 0.f, 0.f}, { 0.f, 0.f, 1.f, 0.f}, { 0.f, 0.f, 0.f, 1.f} }}; // clang-format on result = matrixMultiply(result, matrix); } // Construct a lookat view matrix Matrix matrixLookAt(const sf::Vector3f& eye, const sf::Vector3f& center, const sf::Vector3f& up) { // Forward-looking vector const sf::Vector3f forward = (center - eye).normalized(); // Side vector (Forward cross product Up) const sf::Vector3f side = forward.cross(up).normalized(); Matrix result; result[0][0] = side.x; result[0][1] = side.y * forward.z - side.z * forward.y; result[0][2] = -forward.x; result[0][3] = 0.f; result[1][0] = side.y; result[1][1] = side.z * forward.x - side.x * forward.z; result[1][2] = -forward.y; result[1][3] = 0.f; result[2][0] = side.z; result[2][1] = side.x * forward.y - side.y * forward.x; result[2][2] = -forward.z; result[2][3] = 0.f; result[3][0] = (-eye.x) * result[0][0] + (-eye.y) * result[1][0] + (-eye.z) * result[2][0]; result[3][1] = (-eye.x) * result[0][1] + (-eye.y) * result[1][1] + (-eye.z) * result[2][1]; result[3][2] = (-eye.x) * result[0][2] + (-eye.y) * result[1][2] + (-eye.z) * result[2][2]; result[3][3] = (-eye.x) * result[0][3] + (-eye.y) * result[1][3] + (-eye.z) * result[2][3] + 1.0f; return result; } // Construct a perspective projection matrix Matrix matrixPerspective(sf::Angle fov, float aspect, float nearPlane, float farPlane) { const float a = 1.f / std::tan(fov.asRadians() / 2.f); Matrix result; 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; return result; } // 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, std::uint64_t, std::size_t, std::int32_t, const char*, const char* pMessage, void*) { std::cerr << pMessage << std::endl; return VK_FALSE; } } // namespace //////////////////////////////////////////////////////////// // VulkanExample class //////////////////////////////////////////////////////////// class VulkanExample { public: // Constructor VulkanExample() { // 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 (VkFence fence : fences) vkDestroyFence(device, fence, nullptr); for (VkSemaphore renderFinishedSemaphore : renderFinishedSemaphores) vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); for (VkSemaphore imageAvailableSemaphore : imageAvailableSemaphores) vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); if (descriptorPool) vkDestroyDescriptorPool(device, descriptorPool, nullptr); for (VkDeviceMemory i : uniformBuffersMemory) vkFreeMemory(device, i, nullptr); for (VkBuffer uniformBuffer : uniformBuffers) vkDestroyBuffer(device, uniformBuffer, nullptr); if (textureSampler) vkDestroySampler(device, textureSampler, nullptr); if (textureImageView) vkDestroyImageView(device, textureImageView, nullptr); if (textureImageMemory) vkFreeMemory(device, textureImageMemory, nullptr); if (textureImage) vkDestroyImage(device, textureImage, nullptr); if (indexBufferMemory) vkFreeMemory(device, indexBufferMemory, nullptr); if (indexBuffer) vkDestroyBuffer(device, indexBuffer, nullptr); if (vertexBufferMemory) vkFreeMemory(device, vertexBufferMemory, nullptr); if (vertexBuffer) vkDestroyBuffer(device, vertexBuffer, nullptr); if (commandPool) vkDestroyCommandPool(device, commandPool, nullptr); if (descriptorSetLayout) vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); if (fragmentShaderModule) vkDestroyShaderModule(device, fragmentShaderModule, nullptr); if (vertexShaderModule) vkDestroyShaderModule(device, vertexShaderModule, nullptr); if (device) vkDestroyDevice(device, nullptr); if (surface) vkDestroySurfaceKHR(instance, surface, nullptr); if (debugReportCallback) vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, nullptr); if (instance) vkDestroyInstance(instance, nullptr); } // Cleanup swapchain void cleanupSwapchain() { // Swapchain teardown procedure for (VkFence fence : fences) vkWaitForFences(device, 1, &fence, VK_TRUE, std::numeric_limits::max()); if (!commandBuffers.empty()) vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); commandBuffers.clear(); for (VkFramebuffer swapchainFramebuffer : swapchainFramebuffers) vkDestroyFramebuffer(device, swapchainFramebuffer, nullptr); swapchainFramebuffers.clear(); if (graphicsPipeline) vkDestroyPipeline(device, graphicsPipeline, nullptr); if (renderPass) vkDestroyRenderPass(device, renderPass, nullptr); if (pipelineLayout) vkDestroyPipelineLayout(device, pipelineLayout, nullptr); if (depthImageView) vkDestroyImageView(device, depthImageView, nullptr); if (depthImageMemory) vkFreeMemory(device, depthImageMemory, nullptr); if (depthImage) vkDestroyImage(device, depthImage, nullptr); for (VkImageView swapchainImageView : swapchainImageViews) vkDestroyImageView(device, swapchainImageView, nullptr); swapchainImageViews.clear(); if (swapchain) vkDestroySwapchainKHR(device, swapchain, nullptr); } // 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({}, getVulkanFunction); if (!vkCreateInstance) { vulkanAvailable = false; return; } // Retrieve the available instance layers std::uint32_t objectCount = 0; std::vector layers; if (vkEnumerateInstanceLayerProperties(&objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } layers.resize(objectCount); if (vkEnumerateInstanceLayerProperties(&objectCount, layers.data()) != VK_SUCCESS) { vulkanAvailable = false; return; } // Activate the layers we are interested in std::vector validationLayers; for (VkLayerProperties& layer : layers) { // 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::string_view(layer.layerName) == "VK_LAYER_LUNARG_standard_validation") { validationLayers.push_back("VK_LAYER_LUNARG_standard_validation"); } else if (std::string_view(layer.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 requiredExtensions = sf::Vulkan::getGraphicsRequiredInstanceExtensions(); requiredExtensions.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(validationLayers.size()); instanceCreateInfo.ppEnabledLayerNames = validationLayers.data(); instanceCreateInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); instanceCreateInfo.ppEnabledExtensionNames = requiredExtensions.data(); // Try to create a Vulkan instance with debug report enabled VkResult result = vkCreateInstance(&instanceCreateInfo, nullptr, &instance); // If an extension is missing, try disabling debug report if (result == VK_ERROR_EXTENSION_NOT_PRESENT) { requiredExtensions.pop_back(); instanceCreateInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); instanceCreateInfo.ppEnabledExtensionNames = requiredExtensions.data(); result = vkCreateInstance(&instanceCreateInfo, nullptr, &instance); } // If instance creation still fails, give up if (result != VK_SUCCESS) { vulkanAvailable = false; return; } // Load instance entry points gladLoadVulkan({}, 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, nullptr, &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 std::uint32_t objectCount = 0; std::vector devices; if (vkEnumeratePhysicalDevices(instance, &objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } devices.resize(objectCount); if (vkEnumeratePhysicalDevices(instance, &objectCount, devices.data()) != VK_SUCCESS) { vulkanAvailable = false; return; } VkPhysicalDeviceType deviceType = VK_PHYSICAL_DEVICE_TYPE_OTHER; // Look for a GPU that supports swapchains for (VkPhysicalDevice dev : devices) { VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(dev, &deviceProperties); std::vector extensions; if (vkEnumerateDeviceExtensionProperties(dev, nullptr, &objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } extensions.resize(objectCount); if (vkEnumerateDeviceExtensionProperties(dev, nullptr, &objectCount, extensions.data()) != VK_SUCCESS) { vulkanAvailable = false; return; } bool supportsSwapchain = false; for (VkExtensionProperties& extension : extensions) { if (std::string_view(extension.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 = dev; break; } if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { gpu = dev; deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; } else if ((deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) && (deviceType != VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)) { gpu = dev; deviceType = VK_PHYSICAL_DEVICE_TYPE_CPU; } } 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 std::uint32_t objectCount = 0; std::vector queueFamilyProperties; vkGetPhysicalDeviceQueueFamilyProperties(gpu, &objectCount, nullptr); queueFamilyProperties.resize(objectCount); vkGetPhysicalDeviceQueueFamilyProperties(gpu, &objectCount, queueFamilyProperties.data()); for (std::size_t i = 0; i < queueFamilyProperties.size(); ++i) { VkBool32 surfaceSupported = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(gpu, static_cast(i), surface, &surfaceSupported); if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && (surfaceSupported == VK_TRUE)) { queueFamilyIndex = static_cast(i); break; } } if (!queueFamilyIndex.has_value()) { vulkanAvailable = false; return; } const float queuePriority = 1.0f; VkDeviceQueueCreateInfo deviceQueueCreateInfo = VkDeviceQueueCreateInfo(); deviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; deviceQueueCreateInfo.queueCount = 1; deviceQueueCreateInfo.queueFamilyIndex = *queueFamilyIndex; deviceQueueCreateInfo.pQueuePriorities = &queuePriority; // Enable the swapchain extension static constexpr std::array extensions = {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 = static_cast(extensions.size()); deviceCreateInfo.ppEnabledExtensionNames = extensions.data(); deviceCreateInfo.queueCreateInfoCount = 1; deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo; deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeatures; // Create our logical device if (vkCreateDevice(gpu, &deviceCreateInfo, nullptr, &device) != VK_SUCCESS) { vulkanAvailable = false; return; } // Retrieve a handle to the logical device command queue vkGetDeviceQueue(device, *queueFamilyIndex, 0, &queue); } // Query surface formats and set up swapchain void setupSwapchain() { // Select a surface format that supports RGBA color format std::uint32_t objectCount = 0; std::vector surfaceFormats; if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } surfaceFormats.resize(objectCount); if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &objectCount, surfaceFormats.data()) != 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 (const VkSurfaceFormatKHR& surfaceFormat : surfaceFormats) { if ((surfaceFormat.format == VK_FORMAT_B8G8R8A8_UNORM) && (surfaceFormat.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 presentModes; if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } presentModes.resize(objectCount); if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &objectCount, presentModes.data()) != VK_SUCCESS) { vulkanAvailable = false; return; } // Prefer mailbox over FIFO if it is available VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; for (const VkPresentModeKHR& i : presentModes) { if (i == VK_PRESENT_MODE_MAILBOX_KHR) { presentMode = i; break; } } // Determine size and count of swapchain images VkSurfaceCapabilitiesKHR surfaceCapabilities; if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &surfaceCapabilities) != VK_SUCCESS) { vulkanAvailable = false; return; } swapchainExtent.width = std::clamp(window.getSize().x, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); swapchainExtent.height = std::clamp(window.getSize().y, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); const auto imageCount = std::clamp(2u, 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, nullptr, &swapchain) != VK_SUCCESS) { vulkanAvailable = false; return; } } // Retrieve the swapchain images and create image views for them void setupSwapchainImages() { // Retrieve swapchain images std::uint32_t objectCount = 0; if (vkGetSwapchainImagesKHR(device, swapchain, &objectCount, nullptr) != VK_SUCCESS) { vulkanAvailable = false; return; } swapchainImages.resize(objectCount); swapchainImageViews.resize(objectCount); if (vkGetSwapchainImagesKHR(device, swapchain, &objectCount, swapchainImages.data()) != 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, nullptr, &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; } const auto fileSize = file.getSize().value(); std::vector buffer(fileSize / sizeof(std::uint32_t)); if (file.read(buffer.data(), fileSize) != file.getSize()) { vulkanAvailable = false; return; } shaderModuleCreateInfo.codeSize = buffer.size() * sizeof(std::uint32_t); shaderModuleCreateInfo.pCode = buffer.data(); if (vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &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; } const auto fileSize = file.getSize().value(); std::vector buffer(fileSize / sizeof(std::uint32_t)); if (file.read(buffer.data(), fileSize) != file.getSize()) { vulkanAvailable = false; return; } shaderModuleCreateInfo.codeSize = buffer.size() * sizeof(std::uint32_t); shaderModuleCreateInfo.pCode = buffer.data(); if (vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &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() { std::array attachmentDescriptions{}; // 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 colorAttachmentReference = {}; colorAttachmentReference.attachment = 0; colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depthStencilAttachmentReference = {}; depthStencilAttachmentReference.attachment = 1; depthStencilAttachmentReference.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 = &colorAttachmentReference; subpassDescription.pDepthStencilAttachment = &depthStencilAttachmentReference; 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 = static_cast(attachmentDescriptions.size()); renderPassCreateInfo.pAttachments = attachmentDescriptions.data(); renderPassCreateInfo.subpassCount = 1; renderPassCreateInfo.pSubpasses = &subpassDescription; renderPassCreateInfo.dependencyCount = 1; renderPassCreateInfo.pDependencies = &subpassDependency; // Create the renderpass if (vkCreateRenderPass(device, &renderPassCreateInfo, nullptr, &renderPass) != VK_SUCCESS) { vulkanAvailable = false; return; } } // Set up uniform buffer and texture sampler descriptor set layouts void setupDescriptorSetLayout() { std::array descriptorSetLayoutBindings{}; // 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 = static_cast(descriptorSetLayoutBindings.size()); descriptorSetLayoutCreateInfo.pBindings = descriptorSetLayoutBindings.data(); // Create descriptor set layout if (vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &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, nullptr, &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 std::array vertexInputAttributeDescriptions{}; // 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 = static_cast( vertexInputAttributeDescriptions.size()); vertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data(); // 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(swapchainExtent.width); viewport.height = static_cast(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 = static_cast(shaderStages.size()); graphicsPipelineCreateInfo.pStages = shaderStages.data(); 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, nullptr, &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 const std::array attachments = {swapchainImageViews[i], depthImageView}; framebufferCreateInfo.pAttachments = attachments.data(); // Create the framebuffer if (vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &swapchainFramebuffers[i]) != VK_SUCCESS) { vulkanAvailable = false; return; } } } // Set up our command pool void setupCommandPool() { // We want to be able to reset command buffers after submitting them VkCommandPoolCreateInfo commandPoolCreateInfo = VkCommandPoolCreateInfo(); commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; commandPoolCreateInfo.queueFamilyIndex = *queueFamilyIndex; commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // Create our command pool if (vkCreateCommandPool(device, &commandPoolCreateInfo, nullptr, &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, nullptr, &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); std::uint32_t memoryType = 0; for (; memoryType < memoryProperties.memoryTypeCount; ++memoryType) { if ((memoryRequirements.memoryTypeBits & static_cast(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, nullptr, &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 = nullptr; 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() { // clang-format off constexpr std::array 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 }; // clang-format on // Create a staging buffer that is writable by the CPU VkBuffer stagingBuffer = {}; VkDeviceMemory stagingBufferMemory = {}; 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 = nullptr; // Map the buffer into our address space if (vkMapMemory(device, stagingBufferMemory, 0, sizeof(vertexData), 0, &ptr) != VK_SUCCESS) { vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Copy the vertex data into the buffer std::memcpy(ptr, vertexData.data(), 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); } // Create our index buffer and upload its data void setupIndexBuffer() { // clang-format off constexpr std::array 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 }; // clang-format on // Create a staging buffer that is writable by the CPU VkBuffer stagingBuffer = {}; VkDeviceMemory stagingBufferMemory = {}; 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 = nullptr; // Map the buffer into our address space if (vkMapMemory(device, stagingBufferMemory, 0, sizeof(indexData), 0, &ptr) != VK_SUCCESS) { vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Copy the index data into the buffer std::memcpy(ptr, indexData.data(), 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); } // 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 (std::size_t i = 0; i < swapchainImages.size(); ++i) { uniformBuffers.push_back({}); uniformBuffersMemory.push_back({}); // 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(std::uint32_t width, std::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, nullptr, &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); std::uint32_t memoryType = 0; for (; memoryType < memoryProperties.memoryTypeCount; ++memoryType) { if ((memoryRequirements.memoryTypeBits & static_cast(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, nullptr, &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 = nullptr; 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, nullptr, 0, nullptr, 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, nullptr, &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 const 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 = nullptr; // Map the buffer into our address space if (vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &ptr) != VK_SUCCESS) { vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Copy the image data into the buffer std::memcpy(ptr, imageData.getPixelsPtr(), static_cast(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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); 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 = nullptr; if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer) != VK_SUCCESS) { vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Submit a barrier to transition the image layout to transfer destination 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, nullptr, 0, nullptr, 1, &barrier); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Ensure the command buffer has been processed if (vkQueueWaitIdle(queue) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Begin the command buffer if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); 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, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Ensure the command buffer has been processed if (vkQueueWaitIdle(queue) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Begin the command buffer if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Submit a barrier to transition the image layout from transfer destination 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, nullptr, 0, nullptr, 1, &barrier); // End and submit the command buffer if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } if (vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Ensure the command buffer has been processed if (vkQueueWaitIdle(queue) != VK_SUCCESS) { vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vulkanAvailable = false; return; } // Free the command buffer vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkFreeMemory(device, stagingBufferMemory, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); } // 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, nullptr, &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, nullptr, &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 std::array descriptorPoolSizes{}; descriptorPoolSizes[0] = VkDescriptorPoolSize(); descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorPoolSizes[0].descriptorCount = static_cast(swapchainImages.size()); descriptorPoolSizes[1] = VkDescriptorPoolSize(); descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorPoolSizes[1].descriptorCount = static_cast(swapchainImages.size()); VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = VkDescriptorPoolCreateInfo(); descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolCreateInfo.poolSizeCount = static_cast(descriptorPoolSizes.size()); descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes.data(); descriptorPoolCreateInfo.maxSets = static_cast(swapchainImages.size()); // Create the descriptor pool if (vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool) != VK_SUCCESS) { vulkanAvailable = false; return; } } // Set up our descriptor sets void setupDescriptorSets() { // Allocate a descriptor set for each frame in flight std::vector descriptorSetLayouts(swapchainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo(); descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; descriptorSetAllocateInfo.descriptorPool = descriptorPool; descriptorSetAllocateInfo.descriptorSetCount = static_cast(swapchainImages.size()); descriptorSetAllocateInfo.pSetLayouts = descriptorSetLayouts.data(); descriptorSets.resize(swapchainImages.size()); if (vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, descriptorSets.data()) != 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) { std::array writeDescriptorSets{}; // 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 descriptor set vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } } // 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(commandBuffers.size()); // Allocate the command buffers from our command pool if (vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, commandBuffers.data()) != 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 std::array clearColors{}; // 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 = static_cast(clearColors.size()); renderPassBeginInfo.pClearValues = clearColors.data(); // 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 const 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, nullptr); // 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({}); if (vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &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({}); if (vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &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({}); if (vkCreateFence(device, &fenceCreateInfo, nullptr, &fences[i]) != VK_SUCCESS) { fences.pop_back(); vulkanAvailable = false; return; } } } // Update the matrices in our uniform buffer every frame void updateUniformBuffer(float elapsed) { // Construct the model matrix // clang-format off 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} }}; // clang-format on matrixRotateX(model, sf::degrees(elapsed * 59.0f)); matrixRotateY(model, sf::degrees(elapsed * 83.0f)); matrixRotateZ(model, sf::degrees(elapsed * 109.0f)); // Translate the model based on the mouse position const sf::Vector2f mousePosition = sf::Vector2f(sf::Mouse::getPosition(window)); const sf::Vector2f windowSize = sf::Vector2f(window.getSize()); const float x = std::clamp(mousePosition.x * 2.f / windowSize.x - 1.f, -1.0f, 1.0f) * 2.0f; const float y = std::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 sf::Vector3f eye(0.0f, 4.0f, 0.0f); const sf::Vector3f center(0.0f, 0.0f, 0.0f); const sf::Vector3f up(0.0f, 0.0f, 1.0f); const Matrix view = matrixLookAt(eye, center, up); // Construct the projection matrix const sf::Angle fov = sf::degrees(45); const float aspect = static_cast(swapchainExtent.width) / static_cast(swapchainExtent.height); const float nearPlane = 0.1f; const float farPlane = 10.0f; const Matrix projection = matrixPerspective(fov, aspect, nearPlane, farPlane); char* ptr = nullptr; // Map the current frame's uniform buffer into our address space if (vkMapMemory(device, uniformBuffersMemory[currentFrame], 0, sizeof(Matrix) * 3, 0, reinterpret_cast(&ptr)) != VK_SUCCESS) { vulkanAvailable = false; return; } // Copy the matrix data into the current frame's uniform buffer std::memcpy(ptr + sizeof(Matrix) * 0, model.data(), sizeof(Matrix)); std::memcpy(ptr + sizeof(Matrix) * 1, view.data(), sizeof(Matrix)); std::memcpy(ptr + sizeof(Matrix) * 2, projection.data(), sizeof(Matrix)); // Unmap the buffer vkUnmapMemory(device, uniformBuffersMemory[currentFrame]); } void draw() { std::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::max()); { // Get the next image in the swapchain const VkResult result = vkAcquireNextImageKHR(device, swapchain, std::numeric_limits::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 const 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 const 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() { const sf::Clock clock; // Start game loop while (window.isOpen()) { // Process events while (const std::optional event = window.pollEvent()) { // Window closed or escape key pressed: exit if (event->is() || (event->is() && event->getIf()->code == sf::Keyboard::Key::Escape)) { window.close(); } // Re-create the swapchain when the window is resized if (event->is()) swapchainOutOfDate = true; } // Check that window was not closed before drawing to it if (vulkanAvailable && window.isOpen()) { // Update the uniform buffer (matrices) updateUniformBuffer(clock.getElapsedTime().asSeconds()); // Render the frame draw(); } } } private: // NOLINTBEGIN(readability-identifier-naming) sf::WindowBase window{sf::VideoMode({800, 600}), "SFML window with Vulkan", sf::Style::Default}; bool vulkanAvailable{sf::Vulkan::isAvailable()}; const unsigned int maxFramesInFlight{2}; unsigned int currentFrame{}; bool swapchainOutOfDate{}; VkInstance instance{}; VkDebugReportCallbackEXT debugReportCallback{}; VkSurfaceKHR surface{}; VkPhysicalDevice gpu{}; std::optional queueFamilyIndex; VkDevice device{}; VkQueue queue{}; VkSurfaceFormatKHR swapchainFormat{}; VkExtent2D swapchainExtent{}; VkSwapchainKHR swapchain{}; std::vector swapchainImages; std::vector swapchainImageViews; VkFormat depthFormat{VK_FORMAT_UNDEFINED}; VkImage depthImage{}; VkDeviceMemory depthImageMemory{}; VkImageView depthImageView{}; VkShaderModule vertexShaderModule{}; VkShaderModule fragmentShaderModule{}; std::array shaderStages{}; VkDescriptorSetLayout descriptorSetLayout{}; VkPipelineLayout pipelineLayout{}; VkRenderPass renderPass{}; VkPipeline graphicsPipeline{}; std::vector swapchainFramebuffers; VkCommandPool commandPool{}; VkBuffer vertexBuffer{}; VkDeviceMemory vertexBufferMemory{}; VkBuffer indexBuffer{}; VkDeviceMemory indexBufferMemory{}; std::vector uniformBuffers; std::vector uniformBuffersMemory; VkImage textureImage{}; VkDeviceMemory textureImageMemory{}; VkImageView textureImageView{}; VkSampler textureSampler{}; VkDescriptorPool descriptorPool{}; std::vector descriptorSets; std::vector commandBuffers; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; std::vector fences; // NOLINTEND(readability-identifier-naming) }; //////////////////////////////////////////////////////////// /// Entry point of application /// /// \return Application exit code /// //////////////////////////////////////////////////////////// int main() { VulkanExample example; example.run(); }