From 7efb320373186e68897c021c7be2feb93274d2cf Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Mon, 26 May 2025 08:38:29 +0200 Subject: [PATCH 01/47] Shader Objects 09 Replaced the old shader module system and replaced them with Shader Objects --- .gitignore | 4 +- code/09_shader_modules.cpp | 82 +++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index ae5f6c81..80afaa56 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ ads.txt build_ebook.log temp_ebook.md ebook/*.pdf -ebook/*.epub \ No newline at end of file +ebook/*.epub +/code/build +/build diff --git a/code/09_shader_modules.cpp b/code/09_shader_modules.cpp index 9de7078c..e3484825 100644 --- a/code/09_shader_modules.cpp +++ b/code/09_shader_modules.cpp @@ -1,3 +1,4 @@ +#include "volk.h" #define GLFW_INCLUDE_VULKAN #include @@ -21,7 +22,8 @@ const std::vector validationLayers = { }; const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME }; #ifdef NDEBUG @@ -64,6 +66,7 @@ struct SwapChainSupportDetails { class HelloTriangleApplication { public: void run() { + volkInitialize(); initWindow(); initVulkan(); mainLoop(); @@ -89,6 +92,9 @@ class HelloTriangleApplication { VkExtent2D swapChainExtent; std::vector swapChainImageViews; + VkShaderEXT vertShader; + VkShaderEXT fragShader; + void initWindow() { glfwInit(); @@ -120,6 +126,9 @@ class HelloTriangleApplication { vkDestroyImageView(device, imageView, nullptr); } + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroyDevice(device, nullptr); @@ -146,7 +155,7 @@ class HelloTriangleApplication { appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -172,6 +181,8 @@ class HelloTriangleApplication { if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } + + volkLoadInstance(instance); } void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { @@ -238,7 +249,14 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures{}; + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -246,7 +264,7 @@ class HelloTriangleApplication { createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.pNext = &deviceFeatures2; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); @@ -348,41 +366,33 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); + VkShaderCreateInfoEXT vertShaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + vertShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + vertShaderCreateInfo.pCode = reinterpret_cast(vertShaderCode.data()); + vertShaderCreateInfo.codeSize = vertShaderCode.size(); + vertShaderCreateInfo.pName = "main"; + + VkShaderCreateInfoEXT fragShaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + fragShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + fragShaderCreateInfo.pCode = reinterpret_cast(fragShaderCode.data()); + fragShaderCreateInfo.codeSize = fragShaderCode.size(); + fragShaderCreateInfo.pName = "main"; + + VkShaderEXT shaders[2]; + VkShaderCreateInfoEXT shaderCreateInfos[] = {vertShaderCreateInfo, fragShaderCreateInfo}; + if (vkCreateShadersEXT(device, 2, + shaderCreateInfos, + nullptr, shaders) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); } - - return shaderModule; + + vertShader = shaders[0]; + fragShader = shaders[1]; } + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { From b42e5bf7f73f93c97447cd7f705021aa271886c4 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Thu, 1 Jan 2026 18:07:32 +0100 Subject: [PATCH 02/47] - New Lessons. Deleted Old ones --- ...9_shader_base.frag => 08_shader_base.frag} | 18 +- ...9_shader_base.vert => 08_shader_base.vert} | 40 +-- ...ader_modules.cpp => 08_shader_objects.cpp} | 43 ++- ...cs_pipeline.cpp => 09_command_buffers.cpp} | 71 ++++- ...d_buffers.cpp => 10_dynamic_rendering.cpp} | 295 +++--------------- code/CMakeLists.txt | 3 +- windows.sh | 54 ++++ 7 files changed, 211 insertions(+), 313 deletions(-) rename code/{09_shader_base.frag => 08_shader_base.frag} (94%) rename code/{09_shader_base.vert => 08_shader_base.vert} (94%) rename code/{09_shader_modules.cpp => 08_shader_objects.cpp} (94%) rename code/{08_graphics_pipeline.cpp => 09_command_buffers.cpp} (88%) rename code/{14_command_buffers.cpp => 10_dynamic_rendering.cpp} (61%) create mode 100644 windows.sh diff --git a/code/09_shader_base.frag b/code/08_shader_base.frag similarity index 94% rename from code/09_shader_base.frag rename to code/08_shader_base.frag index 36176035..7c5b0e74 100644 --- a/code/09_shader_base.frag +++ b/code/08_shader_base.frag @@ -1,9 +1,9 @@ -#version 450 - -layout(location = 0) in vec3 fragColor; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = vec4(fragColor, 1.0); -} +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/09_shader_base.vert b/code/08_shader_base.vert similarity index 94% rename from code/09_shader_base.vert rename to code/08_shader_base.vert index 9bd71d4d..f5b2f8dc 100644 --- a/code/09_shader_base.vert +++ b/code/08_shader_base.vert @@ -1,20 +1,20 @@ -#version 450 - -layout(location = 0) out vec3 fragColor; - -vec2 positions[3] = vec2[]( - vec2(0.0, -0.5), - vec2(0.5, 0.5), - vec2(-0.5, 0.5) -); - -vec3 colors[3] = vec3[]( - vec3(1.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0) -); - -void main() { - gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); - fragColor = colors[gl_VertexIndex]; -} +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/code/09_shader_modules.cpp b/code/08_shader_objects.cpp similarity index 94% rename from code/09_shader_modules.cpp rename to code/08_shader_objects.cpp index e3484825..474480d4 100644 --- a/code/09_shader_modules.cpp +++ b/code/08_shader_objects.cpp @@ -366,30 +366,29 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VkShaderCreateInfoEXT vertShaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; - vertShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; - vertShaderCreateInfo.pCode = reinterpret_cast(vertShaderCode.data()); - vertShaderCreateInfo.codeSize = vertShaderCode.size(); - vertShaderCreateInfo.pName = "main"; - - VkShaderCreateInfoEXT fragShaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; - fragShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; - fragShaderCreateInfo.pCode = reinterpret_cast(fragShaderCode.data()); - fragShaderCreateInfo.codeSize = fragShaderCode.size(); - fragShaderCreateInfo.pName = "main"; - - VkShaderEXT shaders[2]; - VkShaderCreateInfoEXT shaderCreateInfos[] = {vertShaderCreateInfo, fragShaderCreateInfo}; - if (vkCreateShadersEXT(device, 2, - shaderCreateInfos, - nullptr, shaders) != VK_SUCCESS) { + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { throw std::runtime_error("failed to create shader objects!"); } - - vertShader = shaders[0]; - fragShader = shaders[1]; + + return shader; } diff --git a/code/08_graphics_pipeline.cpp b/code/09_command_buffers.cpp similarity index 88% rename from code/08_graphics_pipeline.cpp rename to code/09_command_buffers.cpp index 4c337f75..474480d4 100644 --- a/code/08_graphics_pipeline.cpp +++ b/code/09_command_buffers.cpp @@ -1,7 +1,9 @@ +#include "volk.h" #define GLFW_INCLUDE_VULKAN #include #include +#include #include #include #include @@ -20,7 +22,8 @@ const std::vector validationLayers = { }; const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME }; #ifdef NDEBUG @@ -63,6 +66,7 @@ struct SwapChainSupportDetails { class HelloTriangleApplication { public: void run() { + volkInitialize(); initWindow(); initVulkan(); mainLoop(); @@ -88,6 +92,9 @@ class HelloTriangleApplication { VkExtent2D swapChainExtent; std::vector swapChainImageViews; + VkShaderEXT vertShader; + VkShaderEXT fragShader; + void initWindow() { glfwInit(); @@ -119,6 +126,9 @@ class HelloTriangleApplication { vkDestroyImageView(device, imageView, nullptr); } + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroyDevice(device, nullptr); @@ -145,7 +155,7 @@ class HelloTriangleApplication { appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -171,6 +181,8 @@ class HelloTriangleApplication { if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } + + volkLoadInstance(instance); } void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { @@ -237,7 +249,14 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures{}; + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -245,7 +264,7 @@ class HelloTriangleApplication { createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.pNext = &deviceFeatures2; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); @@ -344,9 +363,35 @@ class HelloTriangleApplication { } void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { @@ -511,6 +556,24 @@ class HelloTriangleApplication { return true; } + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; diff --git a/code/14_command_buffers.cpp b/code/10_dynamic_rendering.cpp similarity index 61% rename from code/14_command_buffers.cpp rename to code/10_dynamic_rendering.cpp index 8332b5b1..e7929815 100644 --- a/code/14_command_buffers.cpp +++ b/code/10_dynamic_rendering.cpp @@ -1,3 +1,4 @@ +#include "volk.h" #define GLFW_INCLUDE_VULKAN #include @@ -21,7 +22,9 @@ const std::vector validationLayers = { }; const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, }; #ifdef NDEBUG @@ -64,6 +67,7 @@ struct SwapChainSupportDetails { class HelloTriangleApplication { public: void run() { + volkInitialize(); initWindow(); initVulkan(); mainLoop(); @@ -88,14 +92,9 @@ class HelloTriangleApplication { VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; std::vector swapChainImageViews; - std::vector swapChainFramebuffers; - VkRenderPass renderPass; - VkPipelineLayout pipelineLayout; - VkPipeline graphicsPipeline; - - VkCommandPool commandPool; - VkCommandBuffer commandBuffer; + VkShaderEXT vertShader; + VkShaderEXT fragShader; void initWindow() { glfwInit(); @@ -114,11 +113,7 @@ class HelloTriangleApplication { createLogicalDevice(); createSwapChain(); createImageViews(); - createRenderPass(); createGraphicsPipeline(); - createFramebuffers(); - createCommandPool(); - createCommandBuffer(); } void mainLoop() { @@ -128,20 +123,13 @@ class HelloTriangleApplication { } void cleanup() { - vkDestroyCommandPool(device, commandPool, nullptr); - - for (auto framebuffer : swapChainFramebuffers) { - vkDestroyFramebuffer(device, framebuffer, nullptr); - } - - vkDestroyPipeline(device, graphicsPipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyRenderPass(device, renderPass, nullptr); - for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroyDevice(device, nullptr); @@ -168,7 +156,7 @@ class HelloTriangleApplication { appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -194,6 +182,8 @@ class HelloTriangleApplication { if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } + + volkLoadInstance(instance); } void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { @@ -260,7 +250,15 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures{}; + VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES }; + dynamicRenderingFeature.dynamicRendering = VK_TRUE; + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeature { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT }; + shaderObjectFeature.pNext = &dynamicRenderingFeature; + shaderObjectFeature.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; + deviceFeatures2.pNext = &shaderObjectFeature; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -268,7 +266,7 @@ class HelloTriangleApplication { createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.pNext = &deviceFeatures2; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); @@ -366,253 +364,36 @@ class HelloTriangleApplication { } } - void createRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapChainImageFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } - } - void createGraphicsPipeline() { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { - throw std::runtime_error("failed to create graphics pipeline!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); - void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size()); - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - VkImageView attachments[] = { - swapChainImageViews[i] - }; - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = swapChainExtent.width; - framebufferInfo.height = swapChainExtent.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create framebuffer!"); - } - } } - void createCommandPool() { - QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; - if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { - throw std::runtime_error("failed to create command pool!"); + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); } - } - void createCommandBuffer() { - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; - - if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate command buffers!"); - } - } - - void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - - if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { - throw std::runtime_error("failed to begin recording command buffer!"); - } - - VkRenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = renderPass; - renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; - renderPassInfo.renderArea.offset = {0, 0}; - renderPassInfo.renderArea.extent = swapChainExtent; - - VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; - - vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = (float) swapChainExtent.width; - viewport.height = (float) swapChainExtent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = swapChainExtent; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - - vkCmdDraw(commandBuffer, 3, 1, 0, 0); - - vkCmdEndRenderPass(commandBuffer); - - if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + return shader; } - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { for (const auto& availableFormat : availableFormats) { diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4c409f92..7dbbd043 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -6,6 +6,7 @@ find_package (glfw3 REQUIRED) find_package (glm REQUIRED) find_package (Vulkan REQUIRED) find_package (tinyobjloader REQUIRED) +find_package (volk REQUIRED) find_package (PkgConfig) pkg_get_variable (STB_INCLUDEDIR stb includedir) @@ -54,7 +55,7 @@ function (add_chapter CHAPTER_NAME) set_target_properties (${CHAPTER_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) - target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw) + target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw volk::volk) target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) if (DEFINED CHAPTER_SHADER) diff --git a/windows.sh b/windows.sh new file mode 100644 index 00000000..8a786304 --- /dev/null +++ b/windows.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -e + +exit_on_error() +{ + errcode=$? + echo "❌ Error $errcode" + echo "⚠️ The command executing at the time of the error was:" + echo "$BASH_COMMAND" + echo "on line ${BASH_LINENO[0]}" + sleep 5 + exit $errcode +} +trap exit_on_error ERR + +pushd . + +# === vcpkg setup === +VCPKG_DIR="$HOME/dev/vcpkg" # You can customize this +VCPKG_TOOLCHAIN_FILE="$VCPKG_DIR/scripts/buildsystems/vcpkg.cmake" + +# Clone vcpkg if missing +if [ ! -d "$VCPKG_DIR" ]; then + echo "📦 Cloning vcpkg..." + git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR" +fi + +# Bootstrap vcpkg if needed +cd "$VCPKG_DIR" +echo "$VCPKG_DIR" +if [ ! -f "./vcpkg.exe" ]; then + echo "🔧 Bootstrapping vcpkg..." + ./bootstrap-vcpkg.bat +fi + +# Install required packages +echo "📥 Installing glfw3, glm, stb, volk, tinyobjloader..." +./vcpkg install glfw3 glm stb volk tinyobjloader --triplet x64-windows + +# === back to project and build === +cd "$OLDPWD" + +mkdir -p build/ +cd build + +# Configure with Visual Studio and vcpkg toolchain +echo "🛠️ Running CMake configuration with vcpkg toolchain..." +cmake -G "Visual Studio 17 2022" -A "x64" ../code \ + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_TOOLCHAIN_FILE" "$@" + +popd + +echo "✅ Build system is ready. You can now build the solution in Visual Studio or with cmake --build build" +sleep 5 From 322f5858ed67c545bb28fc3f77a073b4139cad5b Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Thu, 1 Jan 2026 19:23:40 +0100 Subject: [PATCH 03/47] - Made it all work --- code/CMakeLists.txt | 27 ++++++++++++++------------- code/create_patches.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 code/create_patches.sh diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 7dbbd043..be150a01 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -55,7 +55,7 @@ function (add_chapter CHAPTER_NAME) set_target_properties (${CHAPTER_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) - target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw volk::volk) + target_link_libraries (${CHAPTER_NAME} glfw volk::volk) target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) if (DEFINED CHAPTER_SHADER) @@ -91,34 +91,35 @@ add_chapter (06_swap_chain_creation) add_chapter (07_image_views) -add_chapter (08_graphics_pipeline) +add_chapter (08_shader_objects + SHADER 08_shader_base) -add_chapter (09_shader_modules - SHADER 09_shader_base) +add_chapter (09_command_buffers + SHADER 08_shader_base) -add_chapter (10_fixed_functions - SHADER 09_shader_base) +add_chapter (10_dynamic_rendering + SHADER 08_shader_base) add_chapter (11_render_passes - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (12_graphics_pipeline_complete - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (13_framebuffers - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (14_command_buffers - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (15_hello_triangle - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (16_frames_in_flight - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (17_swap_chain_recreation - SHADER 09_shader_base) + SHADER 08_shader_base) add_chapter (18_vertex_input SHADER 18_shader_vertexbuffer diff --git a/code/create_patches.sh b/code/create_patches.sh new file mode 100644 index 00000000..8f37cc3f --- /dev/null +++ b/code/create_patches.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# Check if at least two .cpp files exist +cpp_files=$(ls -Sr *.cpp 2>/dev/null) +file_count=$(echo "$cpp_files" | wc -l) + +if [ "$file_count" -lt 2 ]; then + echo "Need at least two .cpp files to generate patches." + exit 1 +fi + +# Initialize variables +prev_file="" +i=1 + +# Iterate through sorted .cpp files by size +for current_file in $cpp_files; do + if [ -n "$prev_file" ]; then + patch_name="patch_${i}_${prev_file}_to_${current_file}.diff" + echo "Generating patch: $patch_name" + diff -u "$prev_file" "$current_file" > "$patch_name" + i=$((i + 1)) + fi + prev_file="$current_file" +done + +echo "All patches generated." +exit 0 From b733ef202abb97c246b090264d42ae323d3f2718 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 2 Jan 2026 08:37:23 +0100 Subject: [PATCH 04/47] Update CMakeLists.txt - Added visual studio debugging working directory path --- code/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index be150a01..27124b18 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -58,6 +58,8 @@ function (add_chapter CHAPTER_NAME) target_link_libraries (${CHAPTER_NAME} glfw volk::volk) target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) + set_target_properties(${CHAPTER_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME} ) + if (DEFINED CHAPTER_SHADER) set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert ${CHAPTER_SHADER}.comp) From db5a585988eafe32b63872800a4f7464214602e2 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 24 Jan 2026 11:55:59 +0100 Subject: [PATCH 05/47] - formatting --- code/08_shader_objects.cpp | 30 ++++++++++++++++++------------ code/09_command_buffers.cpp | 30 ++++++++++++++++++------------ 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/code/08_shader_objects.cpp b/code/08_shader_objects.cpp index 474480d4..3707f82d 100644 --- a/code/08_shader_objects.cpp +++ b/code/08_shader_objects.cpp @@ -33,16 +33,17 @@ const bool enableValidationLayers = true; #endif VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { + } + else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } @@ -171,8 +172,9 @@ class HelloTriangleApplication { createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; @@ -237,7 +239,7 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { @@ -272,7 +274,8 @@ class HelloTriangleApplication { if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { + } + else { createInfo.enabledLayerCount = 0; } @@ -308,13 +311,14 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { + } + else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } @@ -415,7 +419,8 @@ class HelloTriangleApplication { VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; - } else { + } + else { int width, height; glfwGetFramebufferSize(window, &width, &height); @@ -563,7 +568,7 @@ class HelloTriangleApplication { throw std::runtime_error("failed to open file!"); } - size_t fileSize = (size_t) file.tellg(); + size_t fileSize = (size_t)file.tellg(); std::vector buffer(fileSize); file.seekg(0); @@ -586,7 +591,8 @@ int main() { try { app.run(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } diff --git a/code/09_command_buffers.cpp b/code/09_command_buffers.cpp index 474480d4..3707f82d 100644 --- a/code/09_command_buffers.cpp +++ b/code/09_command_buffers.cpp @@ -33,16 +33,17 @@ const bool enableValidationLayers = true; #endif VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { + } + else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } @@ -171,8 +172,9 @@ class HelloTriangleApplication { createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; @@ -237,7 +239,7 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { @@ -272,7 +274,8 @@ class HelloTriangleApplication { if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { + } + else { createInfo.enabledLayerCount = 0; } @@ -308,13 +311,14 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { + } + else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } @@ -415,7 +419,8 @@ class HelloTriangleApplication { VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; - } else { + } + else { int width, height; glfwGetFramebufferSize(window, &width, &height); @@ -563,7 +568,7 @@ class HelloTriangleApplication { throw std::runtime_error("failed to open file!"); } - size_t fileSize = (size_t) file.tellg(); + size_t fileSize = (size_t)file.tellg(); std::vector buffer(fileSize); file.seekg(0); @@ -586,7 +591,8 @@ int main() { try { app.run(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } From 0a43b4d80d1ab1713ddf9bba4f7d7f845590ed31 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 31 Jan 2026 23:39:37 +0100 Subject: [PATCH 06/47] - Command buffers --- code/09_command_buffers.cpp | 108 +++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/code/09_command_buffers.cpp b/code/09_command_buffers.cpp index 3707f82d..2c6730b1 100644 --- a/code/09_command_buffers.cpp +++ b/code/09_command_buffers.cpp @@ -23,7 +23,8 @@ const std::vector validationLayers = { const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, - VK_EXT_SHADER_OBJECT_EXTENSION_NAME + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME }; #ifdef NDEBUG @@ -96,6 +97,9 @@ class HelloTriangleApplication { VkShaderEXT vertShader; VkShaderEXT fragShader; + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + void initWindow() { glfwInit(); @@ -114,6 +118,8 @@ class HelloTriangleApplication { createSwapChain(); createImageViews(); createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); } void mainLoop() { @@ -123,6 +129,8 @@ class HelloTriangleApplication { } void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } @@ -251,9 +259,14 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } + VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeatures{}; + dynamicRenderingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + dynamicRenderingFeatures.pNext = nullptr; + dynamicRenderingFeatures.dynamicRendering = true; + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; - shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.pNext = &dynamicRenderingFeatures; shaderObjectFeatures.shaderObject = VK_TRUE; VkPhysicalDeviceFeatures2 deviceFeatures2{}; @@ -373,10 +386,101 @@ class HelloTriangleApplication { vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + /* + // Provide information for dynamic rendering + VkPipelineRenderingCreateInfoKHR pipeline_create{ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; + pipeline_create.pNext = VK_NULL_HANDLE; + pipeline_create.colorAttachmentCount = 1; + pipeline_create.pColorAttachmentFormats = &color_rendering_format; + pipeline_create.depthAttachmentFormat = depth_format; + pipeline_create.stencilAttachmentFormat = depth_format; + // Use the pNext to point to the rendering create struct + VkGraphicsPipelineCreateInfo graphics_create{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + graphics_create.pNext = &pipeline_create; // reference the new dynamic structure + graphics_create.renderPass = VK_NULL_HANDLE; // previously required non-null + */ } + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffer() + { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // swap chain color attachment? + //transitionToColorAttachment(commandBuffer, swapchainImages[imageIndex]); + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { 0.0f, 0.0f, 0.0f, 1.0f }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + // --- bind shader objects (NO pipeline) --- + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + } + vkCmdEndRendering(commandBuffer); + + + }; + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; From e55aec47f56f2790b935401286ccb387f9c0e32f Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 1 Feb 2026 22:18:46 +0100 Subject: [PATCH 07/47] Update 09_command_buffers.cpp - Added transition and scissors to record command functions --- code/09_command_buffers.cpp | 46 +++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/code/09_command_buffers.cpp b/code/09_command_buffers.cpp index 2c6730b1..e8c06a7c 100644 --- a/code/09_command_buffers.cpp +++ b/code/09_command_buffers.cpp @@ -441,8 +441,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to begin recording command buffer!"); } - // swap chain color attachment? - //transitionToColorAttachment(commandBuffer, swapchainImages[imageIndex]); + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + VkRenderingAttachmentInfo colorAttachment{}; colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; @@ -450,7 +470,7 @@ class HelloTriangleApplication { colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.clearValue = { 0.0f, 0.0f, 0.0f, 1.0f }; + colorAttachment.clearValue = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; VkRenderingInfo renderingInfo{}; renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; @@ -474,10 +494,28 @@ class HelloTriangleApplication { vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + } vkCmdEndRendering(commandBuffer); - + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } }; From 2da4dce31f52979814aa195eea3f5ea322c7e1fa Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 1 Feb 2026 22:26:49 +0100 Subject: [PATCH 08/47] Update 10_dynamic_rendering.cpp - Made it the same --- code/10_dynamic_rendering.cpp | 185 ++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 20 deletions(-) diff --git a/code/10_dynamic_rendering.cpp b/code/10_dynamic_rendering.cpp index e7929815..72dd1004 100644 --- a/code/10_dynamic_rendering.cpp +++ b/code/10_dynamic_rendering.cpp @@ -24,7 +24,7 @@ const std::vector validationLayers = { const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, - VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME }; #ifdef NDEBUG @@ -34,16 +34,17 @@ const bool enableValidationLayers = true; #endif VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { + } + else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } @@ -96,6 +97,9 @@ class HelloTriangleApplication { VkShaderEXT vertShader; VkShaderEXT fragShader; + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + void initWindow() { glfwInit(); @@ -114,6 +118,8 @@ class HelloTriangleApplication { createSwapChain(); createImageViews(); createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); } void mainLoop() { @@ -123,6 +129,8 @@ class HelloTriangleApplication { } void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } @@ -172,8 +180,9 @@ class HelloTriangleApplication { createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; @@ -238,7 +247,7 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { @@ -250,15 +259,19 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES }; - dynamicRenderingFeature.dynamicRendering = VK_TRUE; + VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeatures{}; + dynamicRenderingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + dynamicRenderingFeatures.pNext = nullptr; + dynamicRenderingFeatures.dynamicRendering = true; - VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeature { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT }; - shaderObjectFeature.pNext = &dynamicRenderingFeature; - shaderObjectFeature.shaderObject = VK_TRUE; + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = &dynamicRenderingFeatures; + shaderObjectFeatures.shaderObject = VK_TRUE; - VkPhysicalDeviceFeatures2 deviceFeatures2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; - deviceFeatures2.pNext = &shaderObjectFeature; + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -274,7 +287,8 @@ class HelloTriangleApplication { if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { + } + else { createInfo.enabledLayerCount = 0; } @@ -310,13 +324,14 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { + } + else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } @@ -371,11 +386,139 @@ class HelloTriangleApplication { vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + /* + // Provide information for dynamic rendering + VkPipelineRenderingCreateInfoKHR pipeline_create{ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; + pipeline_create.pNext = VK_NULL_HANDLE; + pipeline_create.colorAttachmentCount = 1; + pipeline_create.pColorAttachmentFormats = &color_rendering_format; + pipeline_create.depthAttachmentFormat = depth_format; + pipeline_create.stencilAttachmentFormat = depth_format; + + // Use the pNext to point to the rendering create struct + VkGraphicsPipelineCreateInfo graphics_create{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + graphics_create.pNext = &pipeline_create; // reference the new dynamic structure + graphics_create.renderPass = VK_NULL_HANDLE; // previously required non-null + */ + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffer() + { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } } + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + // --- bind shader objects (NO pipeline) --- + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; @@ -418,7 +561,8 @@ class HelloTriangleApplication { VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; - } else { + } + else { int width, height; glfwGetFramebufferSize(window, &width, &height); @@ -566,7 +710,7 @@ class HelloTriangleApplication { throw std::runtime_error("failed to open file!"); } - size_t fileSize = (size_t) file.tellg(); + size_t fileSize = (size_t)file.tellg(); std::vector buffer(fileSize); file.seekg(0); @@ -589,7 +733,8 @@ int main() { try { app.run(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } From 779db500e360313bfbef06bc17cd6d4732bbe01d Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 1 Feb 2026 22:26:52 +0100 Subject: [PATCH 09/47] Create 14_command_buffers.cpp --- code/14_command_buffers.cpp | 817 ++++++++++++++++++++++++++++++++++++ 1 file changed, 817 insertions(+) create mode 100644 code/14_command_buffers.cpp diff --git a/code/14_command_buffers.cpp b/code/14_command_buffers.cpp new file mode 100644 index 00000000..8332b5b1 --- /dev/null +++ b/code/14_command_buffers.cpp @@ -0,0 +1,817 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffer() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} From 77bdae88c281b9d16a9c41b2581d8005341c8713 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 22 Feb 2026 20:39:21 +0100 Subject: [PATCH 10/47] Hello Triangle Finished dynamic rendering and now have a hello triangle without pipelines :) --- code/09_command_buffers.cpp | 2 +- code/10_dynamic_rendering.cpp | 206 +++++++++++++++++++++++++++++----- 2 files changed, 180 insertions(+), 28 deletions(-) diff --git a/code/09_command_buffers.cpp b/code/09_command_buffers.cpp index e8c06a7c..4eb91fc3 100644 --- a/code/09_command_buffers.cpp +++ b/code/09_command_buffers.cpp @@ -24,7 +24,7 @@ const std::vector validationLayers = { const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, - VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, }; #ifdef NDEBUG diff --git a/code/10_dynamic_rendering.cpp b/code/10_dynamic_rendering.cpp index 72dd1004..1316560d 100644 --- a/code/10_dynamic_rendering.cpp +++ b/code/10_dynamic_rendering.cpp @@ -24,7 +24,8 @@ const std::vector validationLayers = { const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, - VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME }; #ifdef NDEBUG @@ -99,6 +100,11 @@ class HelloTriangleApplication { VkCommandPool commandPool; VkCommandBuffer commandBuffer; + + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkSemaphore timelineSemaphore; + uint64_t frameValue = 0; void initWindow() { glfwInit(); @@ -120,15 +126,20 @@ class HelloTriangleApplication { createGraphicsPipeline(); createCommandPool(); createCommandBuffer(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); + + drawFrame(); } } void cleanup() { + vkDestroySemaphore(device, timelineSemaphore, nullptr); + vkDestroyCommandPool(device, commandPool, nullptr); for (auto imageView : swapChainImageViews) { @@ -259,27 +270,33 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeatures{}; - dynamicRenderingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; - dynamicRenderingFeatures.pNext = nullptr; - dynamicRenderingFeatures.dynamicRendering = true; - VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; - shaderObjectFeatures.pNext = &dynamicRenderingFeatures; + shaderObjectFeatures.pNext = nullptr; shaderObjectFeatures.shaderObject = VK_TRUE; VkPhysicalDeviceFeatures2 deviceFeatures2{}; deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; deviceFeatures2.pNext = &shaderObjectFeatures; + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pNext = &deviceFeatures2; + createInfo.pNext = &vulkan13Features; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); @@ -385,21 +402,7 @@ class HelloTriangleApplication { vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); - - /* - // Provide information for dynamic rendering - VkPipelineRenderingCreateInfoKHR pipeline_create{ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; - pipeline_create.pNext = VK_NULL_HANDLE; - pipeline_create.colorAttachmentCount = 1; - pipeline_create.pColorAttachmentFormats = &color_rendering_format; - pipeline_create.depthAttachmentFormat = depth_format; - pipeline_create.stencilAttachmentFormat = depth_format; - - // Use the pNext to point to the rendering create struct - VkGraphicsPipelineCreateInfo graphics_create{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; - graphics_create.pNext = &pipeline_create; // reference the new dynamic structure - graphics_create.renderPass = VK_NULL_HANDLE; // previously required non-null - */ + return; } @@ -481,6 +484,27 @@ class HelloTriangleApplication { vkCmdBeginRendering(commandBuffer, &renderingInfo); { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr ); + // --- bind shader objects (NO pipeline) --- VkShaderStageFlagBits stages[] = { VK_SHADER_STAGE_VERTEX_BIT, @@ -501,23 +525,151 @@ class HelloTriangleApplication { viewport.height = (float)swapChainExtent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); VkRect2D scissor{}; scissor.offset = { 0, 0 }; scissor.extent = swapChainExtent; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); vkCmdDraw(commandBuffer, 3, 1, 0, 0); } vkCmdEndRendering(commandBuffer); + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } }; + void createSyncObjects() { + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = frameValue; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + frameValue++; + + vkResetCommandBuffer(commandBuffer, /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffer, imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphore; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphore; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = frameValue - 1;; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = frameValue; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffer; + commandBufferInfo.deviceMask = 0; + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphore; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + } + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; @@ -526,7 +678,7 @@ class HelloTriangleApplication { shaderCreateInfo.pCode = reinterpret_cast(code.data()); shaderCreateInfo.codeSize = code.size(); shaderCreateInfo.pName = "main"; - + VkShaderEXT shader; if (vkCreateShadersEXT(device, 1, &shaderCreateInfo, @@ -740,4 +892,4 @@ int main() { } return EXIT_SUCCESS; -} +} \ No newline at end of file From 633b59a495a4e6854b41af0153956c25a08146ef Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 28 Feb 2026 12:43:59 +0100 Subject: [PATCH 11/47] Update 10_dynamic_rendering.cpp --- code/10_dynamic_rendering.cpp | 50 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/code/10_dynamic_rendering.cpp b/code/10_dynamic_rendering.cpp index 1316560d..6a441eb6 100644 --- a/code/10_dynamic_rendering.cpp +++ b/code/10_dynamic_rendering.cpp @@ -434,6 +434,32 @@ class HelloTriangleApplication { } + + void setInitialRenderingState(VkCommandBuffer commandBuffer) + { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; @@ -484,28 +510,8 @@ class HelloTriangleApplication { vkCmdBeginRendering(commandBuffer, &renderingInfo); { - vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); - vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); - vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); - vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); - vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); - vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); - vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); - vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); - vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); - vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); - vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); - const VkSampleMask sample_mask = 0x1; - vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); - vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); - VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; - vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); - VkBool32 color_blend_enables[] = { VK_FALSE }; - vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); - vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr ); - - // --- bind shader objects (NO pipeline) --- + setInitialRenderingState(commandBuffer); + VkShaderStageFlagBits stages[] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT From be54603e74a1cd105f8d7a8806d4ca9e5455aa04 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 28 Feb 2026 12:45:33 +0100 Subject: [PATCH 12/47] Frames in flight --- code/11_frames_in_flight.cpp | 914 +++++++++++++++++++++++++++++++++++ 1 file changed, 914 insertions(+) create mode 100644 code/11_frames_in_flight.cpp diff --git a/code/11_frames_in_flight.cpp b/code/11_frames_in_flight.cpp new file mode 100644 index 00000000..cd55afaf --- /dev/null +++ b/code/11_frames_in_flight.cpp @@ -0,0 +1,914 @@ +#include "volk.h" +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t frameValue = 0; + uint32_t currentFrame = 0; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() + { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) + { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = frameValue; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + frameValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = frameValue - MAX_FRAMES_IN_FLIGHT + 1; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = frameValue; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From d0a9608dd390f1d6f224ca390de0100d7c5f70ba Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 28 Feb 2026 15:14:24 +0100 Subject: [PATCH 13/47] Swap chain recreations --- code/10_dynamic_rendering.cpp | 16 +- code/11_frames_in_flight.cpp | 54 +- code/12_swap_chain_recreation.cpp | 969 ++++++++++++++++++++++++++++++ 3 files changed, 1008 insertions(+), 31 deletions(-) create mode 100644 code/12_swap_chain_recreation.cpp diff --git a/code/10_dynamic_rendering.cpp b/code/10_dynamic_rendering.cpp index 6a441eb6..be25177c 100644 --- a/code/10_dynamic_rendering.cpp +++ b/code/10_dynamic_rendering.cpp @@ -104,7 +104,7 @@ class HelloTriangleApplication { VkSemaphore imageAvailableSemaphore; VkSemaphore renderFinishedSemaphore; VkSemaphore timelineSemaphore; - uint64_t frameValue = 0; + uint64_t timelineValue = 0; void initWindow() { glfwInit(); @@ -420,8 +420,7 @@ class HelloTriangleApplication { } - void createCommandBuffer() - { + void createCommandBuffer() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; @@ -435,8 +434,7 @@ class HelloTriangleApplication { - void setInitialRenderingState(VkCommandBuffer commandBuffer) - { + void setInitialRenderingState(VkCommandBuffer commandBuffer) { vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); @@ -602,14 +600,14 @@ class HelloTriangleApplication { waitInfo.semaphoreCount = 1; waitInfo.pSemaphores = &timelineSemaphore; - uint64_t waitValue = frameValue; + uint64_t waitValue = timelineValue; waitInfo.pValues = &waitValue; vkWaitSemaphores(device, &waitInfo, UINT64_MAX); uint32_t imageIndex; vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); - frameValue++; + timelineValue++; vkResetCommandBuffer(commandBuffer, /*VkCommandBufferResetFlagBits*/ 0); recordCommandBuffer(commandBuffer, imageIndex); @@ -629,14 +627,14 @@ class HelloTriangleApplication { waitSemaphoreInfo.semaphore = timelineSemaphore; waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; waitSemaphoreInfo.deviceIndex = 0; - waitSemaphoreInfo.value = frameValue - 1;; + waitSemaphoreInfo.value = timelineValue - 1;; VkSemaphoreSubmitInfo signalSemaphoreInfo{}; signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; signalSemaphoreInfo.semaphore = timelineSemaphore; signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; signalSemaphoreInfo.deviceIndex = 0; - signalSemaphoreInfo.value = frameValue; + signalSemaphoreInfo.value = timelineValue; VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; diff --git a/code/11_frames_in_flight.cpp b/code/11_frames_in_flight.cpp index cd55afaf..c41d3e1a 100644 --- a/code/11_frames_in_flight.cpp +++ b/code/11_frames_in_flight.cpp @@ -106,7 +106,7 @@ class HelloTriangleApplication { std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; VkSemaphore timelineSemaphore; - uint64_t frameValue = 0; + uint64_t timelineValue = 0; uint32_t currentFrame = 0; void initWindow() { @@ -429,8 +429,9 @@ class HelloTriangleApplication { } - void createCommandBuffers() - { + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; @@ -444,8 +445,7 @@ class HelloTriangleApplication { - void setInitialRenderingState(VkCommandBuffer commandBuffer) - { + void setInitialRenderingState(VkCommandBuffer commandBuffer) { vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); @@ -580,10 +580,13 @@ class HelloTriangleApplication { }; void createSyncObjects() { - + // Create semaphores VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) @@ -591,6 +594,7 @@ class HelloTriangleApplication { } + // Create timeline semaphore VkSemaphoreTypeCreateInfo typeInfo{}; typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; @@ -608,19 +612,22 @@ class HelloTriangleApplication { void drawFrame() { - VkSemaphoreWaitInfo waitInfo{}; - waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; - waitInfo.semaphoreCount = 1; - waitInfo.pSemaphores = &timelineSemaphore; + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; - uint64_t waitValue = frameValue; - waitInfo.pValues = &waitValue; + uint64_t waitValue = timelineValue; + waitInfo.pValues = &waitValue; - vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } uint32_t imageIndex; vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); - frameValue++; + timelineValue++; vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); recordCommandBuffer(commandBuffers[currentFrame], imageIndex); @@ -629,27 +636,28 @@ class HelloTriangleApplication { waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; - - VkSemaphoreSubmitInfo signalBinary{}; - signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; - signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; - signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; VkSemaphoreSubmitInfo waitSemaphoreInfo{}; waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; waitSemaphoreInfo.semaphore = timelineSemaphore; waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; waitSemaphoreInfo.deviceIndex = 0; - waitSemaphoreInfo.value = frameValue - MAX_FRAMES_IN_FLIGHT + 1; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; VkSemaphoreSubmitInfo signalSemaphoreInfo{}; signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; signalSemaphoreInfo.semaphore = timelineSemaphore; signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; signalSemaphoreInfo.deviceIndex = 0; - signalSemaphoreInfo.value = frameValue; + signalSemaphoreInfo.value = timelineValue; - VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; VkCommandBufferSubmitInfo commandBufferInfo{}; @@ -657,6 +665,7 @@ class HelloTriangleApplication { commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; commandBufferInfo.deviceMask = 0; + VkSubmitInfo2 submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; @@ -673,6 +682,7 @@ class HelloTriangleApplication { throw std::runtime_error("failed to submit draw command buffer!"); } + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; diff --git a/code/12_swap_chain_recreation.cpp b/code/12_swap_chain_recreation.cpp new file mode 100644 index 00000000..e5063812 --- /dev/null +++ b/code/12_swap_chain_recreation.cpp @@ -0,0 +1,969 @@ +#include "volk.h" +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From e6cb68de35a5e8b076e3e79c3c9147fef5de58e4 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 28 Feb 2026 16:45:20 +0100 Subject: [PATCH 14/47] Vertex input --- code/13_shader_vertexbuffer.frag | 9 + code/13_shader_vertexbuffer.vert | 11 + code/13_vertex_input.cpp | 1018 ++++++++++++++++++++++++++++++ code/CMakeLists.txt | 15 +- 4 files changed, 1045 insertions(+), 8 deletions(-) create mode 100644 code/13_shader_vertexbuffer.frag create mode 100644 code/13_shader_vertexbuffer.vert create mode 100644 code/13_vertex_input.cpp diff --git a/code/13_shader_vertexbuffer.frag b/code/13_shader_vertexbuffer.frag new file mode 100644 index 00000000..13009da8 --- /dev/null +++ b/code/13_shader_vertexbuffer.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/code/13_shader_vertexbuffer.vert b/code/13_shader_vertexbuffer.vert new file mode 100644 index 00000000..cb40e1e9 --- /dev/null +++ b/code/13_shader_vertexbuffer.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} \ No newline at end of file diff --git a/code/13_vertex_input.cpp b/code/13_vertex_input.cpp new file mode 100644 index 00000000..17135af4 --- /dev/null +++ b/code/13_vertex_input.cpp @@ -0,0 +1,1018 @@ +#include "volk.h" +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 27124b18..6d729ea0 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -39,7 +39,7 @@ function (add_shaders_target TARGET) add_custom_command ( OUTPUT ${SHADERS} COMMAND glslang::validator - ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet + ARGS --target-env vulkan1.0 ${SHADER_SOURCES} WORKING_DIRECTORY ${SHADERS_DIR} DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} COMMENT "Compiling Shaders" @@ -75,6 +75,8 @@ function (add_chapter CHAPTER_NAME) if (DEFINED CHAPTER_TEXTURES) file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) endif () + + endfunction () add_chapter (00_base_code) @@ -102,17 +104,14 @@ add_chapter (09_command_buffers add_chapter (10_dynamic_rendering SHADER 08_shader_base) -add_chapter (11_render_passes +add_chapter (11_frames_in_flight SHADER 08_shader_base) -add_chapter (12_graphics_pipeline_complete +add_chapter (12_swap_chain_recreation SHADER 08_shader_base) -add_chapter (13_framebuffers - SHADER 08_shader_base) - -add_chapter (14_command_buffers - SHADER 08_shader_base) +add_chapter (13_vertex_input + SHADER 13_shader_vertexbuffer) add_chapter (15_hello_triangle SHADER 08_shader_base) From 3b1a36112aebba68b0839fc25d2a08837f1239bf Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 21 Mar 2026 15:43:43 +0100 Subject: [PATCH 15/47] - Remove unnessesary stuff --- code/10_fixed_functions.cpp | 653 -------------------- code/11_render_passes.cpp | 688 --------------------- code/12_graphics_pipeline_complete.cpp | 710 --------------------- code/13_framebuffers.cpp | 739 ---------------------- code/14_command_buffers.cpp | 817 ------------------------- 5 files changed, 3607 deletions(-) delete mode 100644 code/10_fixed_functions.cpp delete mode 100644 code/11_render_passes.cpp delete mode 100644 code/12_graphics_pipeline_complete.cpp delete mode 100644 code/13_framebuffers.cpp delete mode 100644 code/14_command_buffers.cpp diff --git a/code/10_fixed_functions.cpp b/code/10_fixed_functions.cpp deleted file mode 100644 index 7abe5003..00000000 --- a/code/10_fixed_functions.cpp +++ /dev/null @@ -1,653 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window; - - VkInstance instance; - VkDebugUtilsMessengerEXT debugMessenger; - VkSurfaceKHR surface; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device; - - VkQueue graphicsQueue; - VkQueue presentQueue; - - VkSwapchainKHR swapChain; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; - std::vector swapChainImageViews; - - VkPipelineLayout pipelineLayout; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } - - void cleanup() { - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(device, imageView, nullptr); - } - - vkDestroySwapchainKHR(device, swapChain, nullptr); - vkDestroyDevice(device, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); - } - - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Hello Triangle"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } - } - - void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } - } - - void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - } - - void pickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - break; - } - } - - if (physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - createInfo.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.resize(swapChainImages.size()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } - - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; - } - - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; - } - - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); - } - - return details; - } - - bool isDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = findQueueFamilies(device); - - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; - } - - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); - } - - QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; - } - - static std::vector readFile(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) { - throw std::runtime_error("failed to open file!"); - } - - size_t fileSize = (size_t) file.tellg(); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), fileSize); - - file.close(); - - return buffer; - } - - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/code/11_render_passes.cpp b/code/11_render_passes.cpp deleted file mode 100644 index 3310eb00..00000000 --- a/code/11_render_passes.cpp +++ /dev/null @@ -1,688 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window; - - VkInstance instance; - VkDebugUtilsMessengerEXT debugMessenger; - VkSurfaceKHR surface; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device; - - VkQueue graphicsQueue; - VkQueue presentQueue; - - VkSwapchainKHR swapChain; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; - std::vector swapChainImageViews; - - VkRenderPass renderPass; - VkPipelineLayout pipelineLayout; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } - - void cleanup() { - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyRenderPass(device, renderPass, nullptr); - - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(device, imageView, nullptr); - } - - vkDestroySwapchainKHR(device, swapChain, nullptr); - vkDestroyDevice(device, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); - } - - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Hello Triangle"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } - } - - void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } - } - - void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - } - - void pickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - break; - } - } - - if (physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - createInfo.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.resize(swapChainImages.size()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } - } - - void createRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapChainImageFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } - - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; - } - - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; - } - - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); - } - - return details; - } - - bool isDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = findQueueFamilies(device); - - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; - } - - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); - } - - QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; - } - - static std::vector readFile(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) { - throw std::runtime_error("failed to open file!"); - } - - size_t fileSize = (size_t) file.tellg(); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), fileSize); - - file.close(); - - return buffer; - } - - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/code/12_graphics_pipeline_complete.cpp b/code/12_graphics_pipeline_complete.cpp deleted file mode 100644 index a30f38be..00000000 --- a/code/12_graphics_pipeline_complete.cpp +++ /dev/null @@ -1,710 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window; - - VkInstance instance; - VkDebugUtilsMessengerEXT debugMessenger; - VkSurfaceKHR surface; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device; - - VkQueue graphicsQueue; - VkQueue presentQueue; - - VkSwapchainKHR swapChain; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; - std::vector swapChainImageViews; - - VkRenderPass renderPass; - VkPipelineLayout pipelineLayout; - VkPipeline graphicsPipeline; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } - - void cleanup() { - vkDestroyPipeline(device, graphicsPipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyRenderPass(device, renderPass, nullptr); - - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(device, imageView, nullptr); - } - - vkDestroySwapchainKHR(device, swapChain, nullptr); - vkDestroyDevice(device, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); - } - - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Hello Triangle"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } - } - - void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } - } - - void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - } - - void pickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - break; - } - } - - if (physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - createInfo.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.resize(swapChainImages.size()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } - } - - void createRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapChainImageFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { - throw std::runtime_error("failed to create graphics pipeline!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } - - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; - } - - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; - } - - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); - } - - return details; - } - - bool isDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = findQueueFamilies(device); - - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; - } - - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); - } - - QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; - } - - static std::vector readFile(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) { - throw std::runtime_error("failed to open file!"); - } - - size_t fileSize = (size_t) file.tellg(); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), fileSize); - - file.close(); - - return buffer; - } - - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/code/13_framebuffers.cpp b/code/13_framebuffers.cpp deleted file mode 100644 index 95192d66..00000000 --- a/code/13_framebuffers.cpp +++ /dev/null @@ -1,739 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window; - - VkInstance instance; - VkDebugUtilsMessengerEXT debugMessenger; - VkSurfaceKHR surface; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device; - - VkQueue graphicsQueue; - VkQueue presentQueue; - - VkSwapchainKHR swapChain; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; - std::vector swapChainImageViews; - std::vector swapChainFramebuffers; - - VkRenderPass renderPass; - VkPipelineLayout pipelineLayout; - VkPipeline graphicsPipeline; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - createFramebuffers(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } - - void cleanup() { - for (auto framebuffer : swapChainFramebuffers) { - vkDestroyFramebuffer(device, framebuffer, nullptr); - } - - vkDestroyPipeline(device, graphicsPipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyRenderPass(device, renderPass, nullptr); - - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(device, imageView, nullptr); - } - - vkDestroySwapchainKHR(device, swapChain, nullptr); - vkDestroyDevice(device, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); - } - - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Hello Triangle"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } - } - - void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } - } - - void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - } - - void pickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - break; - } - } - - if (physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - createInfo.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.resize(swapChainImages.size()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } - } - - void createRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapChainImageFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { - throw std::runtime_error("failed to create graphics pipeline!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - VkImageView attachments[] = { - swapChainImageViews[i] - }; - - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = swapChainExtent.width; - framebufferInfo.height = swapChainExtent.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create framebuffer!"); - } - } - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } - - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; - } - - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; - } - - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); - } - - return details; - } - - bool isDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = findQueueFamilies(device); - - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; - } - - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); - } - - QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; - } - - static std::vector readFile(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) { - throw std::runtime_error("failed to open file!"); - } - - size_t fileSize = (size_t) file.tellg(); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), fileSize); - - file.close(); - - return buffer; - } - - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/code/14_command_buffers.cpp b/code/14_command_buffers.cpp deleted file mode 100644 index 8332b5b1..00000000 --- a/code/14_command_buffers.cpp +++ /dev/null @@ -1,817 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window; - - VkInstance instance; - VkDebugUtilsMessengerEXT debugMessenger; - VkSurfaceKHR surface; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device; - - VkQueue graphicsQueue; - VkQueue presentQueue; - - VkSwapchainKHR swapChain; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; - std::vector swapChainImageViews; - std::vector swapChainFramebuffers; - - VkRenderPass renderPass; - VkPipelineLayout pipelineLayout; - VkPipeline graphicsPipeline; - - VkCommandPool commandPool; - VkCommandBuffer commandBuffer; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - createFramebuffers(); - createCommandPool(); - createCommandBuffer(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } - - void cleanup() { - vkDestroyCommandPool(device, commandPool, nullptr); - - for (auto framebuffer : swapChainFramebuffers) { - vkDestroyFramebuffer(device, framebuffer, nullptr); - } - - vkDestroyPipeline(device, graphicsPipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyRenderPass(device, renderPass, nullptr); - - for (auto imageView : swapChainImageViews) { - vkDestroyImageView(device, imageView, nullptr); - } - - vkDestroySwapchainKHR(device, swapChain, nullptr); - vkDestroyDevice(device, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); - } - - vkDestroySurfaceKHR(instance, surface, nullptr); - vkDestroyInstance(instance, nullptr); - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Hello Triangle"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } - } - - void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } - } - - void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - } - - void pickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - break; - } - } - - if (physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - - createInfo.pEnabledFeatures = &deviceFeatures; - - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface; - - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - createInfo.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.resize(swapChainImages.size()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } - } - - void createRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapChainImageFormat; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); - VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertShaderStageInfo.module = vertShaderModule; - vertShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragShaderStageInfo.module = fragShaderModule; - fragShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputInfo.vertexBindingDescriptionCount = 0; - vertexInputInfo.vertexAttributeDescriptionCount = 0; - - VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - VkPipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = VK_POLYGON_MODE_FILL; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; - rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; - rasterizer.depthBiasEnable = VK_FALSE; - - VkPipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - colorBlendAttachment.blendEnable = VK_FALSE; - - VkPipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.logicOp = VK_LOGIC_OP_COPY; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; - colorBlending.blendConstants[0] = 0.0f; - colorBlending.blendConstants[1] = 0.0f; - colorBlending.blendConstants[2] = 0.0f; - colorBlending.blendConstants[3] = 0.0f; - - std::vector dynamicStates = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutInfo.setLayoutCount = 0; - pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { - throw std::runtime_error("failed to create pipeline layout!"); - } - - VkGraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { - throw std::runtime_error("failed to create graphics pipeline!"); - } - - vkDestroyShaderModule(device, fragShaderModule, nullptr); - vkDestroyShaderModule(device, vertShaderModule, nullptr); - } - - void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - VkImageView attachments[] = { - swapChainImageViews[i] - }; - - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = swapChainExtent.width; - framebufferInfo.height = swapChainExtent.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create framebuffer!"); - } - } - } - - void createCommandPool() { - QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - - VkCommandPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - - if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { - throw std::runtime_error("failed to create command pool!"); - } - } - - void createCommandBuffer() { - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; - - if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate command buffers!"); - } - } - - void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - - if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { - throw std::runtime_error("failed to begin recording command buffer!"); - } - - VkRenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = renderPass; - renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; - renderPassInfo.renderArea.offset = {0, 0}; - renderPassInfo.renderArea.extent = swapChainExtent; - - VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; - - vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = (float) swapChainExtent.width; - viewport.height = (float) swapChainExtent.height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.offset = {0, 0}; - scissor.extent = swapChainExtent; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - - vkCmdDraw(commandBuffer, 3, 1, 0, 0); - - vkCmdEndRenderPass(commandBuffer); - - if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } - } - - VkShaderModule createShaderModule(const std::vector& code) { - VkShaderModuleCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - createInfo.codeSize = code.size(); - createInfo.pCode = reinterpret_cast(code.data()); - - VkShaderModule shaderModule; - if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { - throw std::runtime_error("failed to create shader module!"); - } - - return shaderModule; - } - - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; - } - - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; - } - - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); - } - - return details; - } - - bool isDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = findQueueFamilies(device); - - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; - } - - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); - } - - QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; - } - - static std::vector readFile(const std::string& filename) { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - - if (!file.is_open()) { - throw std::runtime_error("failed to open file!"); - } - - size_t fileSize = (size_t) file.tellg(); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), fileSize); - - file.close(); - - return buffer; - } - - static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} From b97ead5a822d9bf7497372fdc736123e775258aa Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 21 Mar 2026 21:34:32 +0100 Subject: [PATCH 16/47] - Vertex Buffer --- code/14_vertex_buffer.cpp | 1080 +++++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 7 +- windows.sh | 2 +- 3 files changed, 1087 insertions(+), 2 deletions(-) create mode 100644 code/14_vertex_buffer.cpp diff --git a/code/14_vertex_buffer.cpp b/code/14_vertex_buffer.cpp new file mode 100644 index 00000000..b536790e --- /dev/null +++ b/code/14_vertex_buffer.cpp @@ -0,0 +1,1080 @@ +#include "volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + void createVertexBuffer() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &vertexBuffer, + &vertexAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, vertexAllocation, &data); + memcpy(data, vertices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, vertexAllocation); + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 6d729ea0..08f71803 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -7,6 +7,7 @@ find_package (glm REQUIRED) find_package (Vulkan REQUIRED) find_package (tinyobjloader REQUIRED) find_package (volk REQUIRED) +find_package(VulkanMemoryAllocator CONFIG REQUIRED) find_package (PkgConfig) pkg_get_variable (STB_INCLUDEDIR stb includedir) @@ -55,7 +56,8 @@ function (add_chapter CHAPTER_NAME) set_target_properties (${CHAPTER_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) - target_link_libraries (${CHAPTER_NAME} glfw volk::volk) + target_link_libraries (${CHAPTER_NAME} glfw volk::volk GPUOpen::VulkanMemoryAllocator) + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) set_target_properties(${CHAPTER_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME} ) @@ -113,6 +115,9 @@ add_chapter (12_swap_chain_recreation add_chapter (13_vertex_input SHADER 13_shader_vertexbuffer) +add_chapter (14_vertex_buffer + SHADER 13_shader_vertexbuffer) + add_chapter (15_hello_triangle SHADER 08_shader_base) diff --git a/windows.sh b/windows.sh index 8a786304..cc2b6348 100644 --- a/windows.sh +++ b/windows.sh @@ -35,7 +35,7 @@ fi # Install required packages echo "📥 Installing glfw3, glm, stb, volk, tinyobjloader..." -./vcpkg install glfw3 glm stb volk tinyobjloader --triplet x64-windows +./vcpkg install glfw3 glm stb volk tinyobjloader vulkan-memory-allocator --triplet x64-windows # === back to project and build === cd "$OLDPWD" From b8c2950d688b3f8f420a048bbdaeacda6683c71a Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 22 Mar 2026 10:40:58 +0100 Subject: [PATCH 17/47] - Added staging and vertex buffers --- code/14_vertex_buffer.cpp | 4 + code/15_staging_buffer.cpp | 1148 +++++++++++++++++++++++++++++++++ code/16_index_buffer.cpp | 1218 ++++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 7 +- 4 files changed, 2375 insertions(+), 2 deletions(-) create mode 100644 code/15_staging_buffer.cpp create mode 100644 code/16_index_buffer.cpp diff --git a/code/14_vertex_buffer.cpp b/code/14_vertex_buffer.cpp index b536790e..04d465ca 100644 --- a/code/14_vertex_buffer.cpp +++ b/code/14_vertex_buffer.cpp @@ -215,6 +215,9 @@ class HelloTriangleApplication { void cleanup() { cleanupSwapChain(); + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyAllocator(allocator); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); @@ -374,6 +377,7 @@ class HelloTriangleApplication { VkPhysicalDeviceVulkan12Features vulkan12Features{}; vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; vulkan12Features.pNext = &deviceFeatures2; VkPhysicalDeviceVulkan13Features vulkan13Features{}; diff --git a/code/15_staging_buffer.cpp b/code/15_staging_buffer.cpp new file mode 100644 index 00000000..2779f5c5 --- /dev/null +++ b/code/15_staging_buffer.cpp @@ -0,0 +1,1148 @@ +#include "volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &vertexBuffer, + &vertexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } + + copyBuffer(stagingBuffer, vertexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/16_index_buffer.cpp b/code/16_index_buffer.cpp new file mode 100644 index 00000000..967c27de --- /dev/null +++ b/code/16_index_buffer.cpp @@ -0,0 +1,1218 @@ +#include "volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &vertexBuffer, + &vertexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } + + copyBuffer(stagingBuffer, vertexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 08f71803..428bbf15 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -118,8 +118,11 @@ add_chapter (13_vertex_input add_chapter (14_vertex_buffer SHADER 13_shader_vertexbuffer) -add_chapter (15_hello_triangle - SHADER 08_shader_base) +add_chapter (15_staging_buffer + SHADER 13_shader_vertexbuffer) + +add_chapter (16_index_buffer + SHADER 13_shader_vertexbuffer) add_chapter (16_frames_in_flight SHADER 08_shader_base) From 2544a7ac04c67c55f5bb96e2e33cb98cbb70f438 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 3 Apr 2026 07:29:20 +0200 Subject: [PATCH 18/47] - Descriptor set stuff and cmake fix --- code/16_index_buffer.cpp | 4 +- code/17_descriptor_set_layout.cpp | 1278 +++++++++++++++++++++++++++++ code/17_shader_ubo.frag | 9 + code/17_shader_ubo.vert | 15 + code/CMakeLists.txt | 28 +- 5 files changed, 1329 insertions(+), 5 deletions(-) create mode 100644 code/17_descriptor_set_layout.cpp create mode 100644 code/17_shader_ubo.frag create mode 100644 code/17_shader_ubo.vert diff --git a/code/16_index_buffer.cpp b/code/16_index_buffer.cpp index 967c27de..50980802 100644 --- a/code/16_index_buffer.cpp +++ b/code/16_index_buffer.cpp @@ -1,7 +1,7 @@ -#include "volk.h" +#include "Volk/volk.h" #define VMA_IMPLEMENTATION #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 -#include "vk_mem_alloc.h" +#include "vma/vk_mem_alloc.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/17_descriptor_set_layout.cpp b/code/17_descriptor_set_layout.cpp new file mode 100644 index 00000000..40861ef6 --- /dev/null +++ b/code/17_descriptor_set_layout.cpp @@ -0,0 +1,1278 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + VkBuffer uniformBuffer; + VmaAllocation uniformAllocation; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT heapProperties; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createDescriptorHeap(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + vmaDestroyBuffer(allocator, uniformBuffer, uniformAllocation); + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &vulkan13Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void createDescriptorHeap() + { + + + + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + return; + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &vertexBuffer, + &vertexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } + + copyBuffer(stagingBuffer, vertexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createUniformBuffer() + { + VmaAllocator allocator; // assume created earlier + VkBuffer uniformBuffer; + VmaAllocation allocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + + vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, + &uniformBuffer, &allocation, nullptr); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + void* mapped; + vmaMapMemory(allocator, uniformAllocation, &mapped); + memcpy(mapped, &ubo, sizeof(ubo)); + vmaUnmapMemory(allocator, uniformAllocation); + }) + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/17_shader_ubo.frag b/code/17_shader_ubo.frag new file mode 100644 index 00000000..7c5b0e74 --- /dev/null +++ b/code/17_shader_ubo.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/17_shader_ubo.vert b/code/17_shader_ubo.vert new file mode 100644 index 00000000..924ac461 --- /dev/null +++ b/code/17_shader_ubo.vert @@ -0,0 +1,15 @@ +layout(push_constant) uniform PushData { + uint uboDescriptorOffset; +}; + +layout(buffer_reference, std430) readonly buffer UBORef { + mat4 model; + mat4 view; + mat4 proj; +}; + +void main() { + UBORef uboRef = UBORef(uboDescriptorOffset); + gl_Position = uboRef.proj * uboRef.view * uboRef.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 428bbf15..1fede111 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -6,8 +6,22 @@ find_package (glfw3 REQUIRED) find_package (glm REQUIRED) find_package (Vulkan REQUIRED) find_package (tinyobjloader REQUIRED) -find_package (volk REQUIRED) -find_package(VulkanMemoryAllocator CONFIG REQUIRED) + +# Volk +if(NOT DEFINED ENV{VULKAN_SDK}) + message(FATAL_ERROR "VULKAN_SDK environment variable is not set") +endif() + +set(VULKAN_SDK $ENV{VULKAN_SDK}) + +find_library(VOLK_LIB volk + PATHS ${VULKAN_SDK}/Lib ${VULKAN_SDK}/lib + NO_DEFAULT_PATH +) + +if(NOT VOLK_LIB) + message(FATAL_ERROR "volk library not found in ${VULKAN_SDK}/Lib or lib") +endif() find_package (PkgConfig) pkg_get_variable (STB_INCLUDEDIR stb includedir) @@ -23,6 +37,8 @@ add_executable (glslang::validator IMPORTED) find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED) set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") + + function (add_shaders_target TARGET) cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) @@ -56,8 +72,11 @@ function (add_chapter CHAPTER_NAME) set_target_properties (${CHAPTER_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) - target_link_libraries (${CHAPTER_NAME} glfw volk::volk GPUOpen::VulkanMemoryAllocator) + target_link_libraries (${CHAPTER_NAME} glfw Vulkan::Headers ${VOLK_LIB}) + #target_link_libraries (${CHAPTER_NAME} PRIVATE ) + + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) set_target_properties(${CHAPTER_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME} ) @@ -124,6 +143,9 @@ add_chapter (15_staging_buffer add_chapter (16_index_buffer SHADER 13_shader_vertexbuffer) +add_chapter (17_descriptor_set_layout + SHADER 17_shader_ubo) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From 7fe8900be8e8b7cd676a467e35f0382efb8b896a Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 3 Apr 2026 07:50:04 +0200 Subject: [PATCH 19/47] buildy --- windows.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/windows.sh b/windows.sh index cc2b6348..c4e680ae 100644 --- a/windows.sh +++ b/windows.sh @@ -16,39 +16,39 @@ trap exit_on_error ERR pushd . # === vcpkg setup === -VCPKG_DIR="$HOME/dev/vcpkg" # You can customize this +VCPKG_DIR="$HOME/dev/vcpkg" VCPKG_TOOLCHAIN_FILE="$VCPKG_DIR/scripts/buildsystems/vcpkg.cmake" -# Clone vcpkg if missing if [ ! -d "$VCPKG_DIR" ]; then echo "📦 Cloning vcpkg..." git clone https://github.com/microsoft/vcpkg.git "$VCPKG_DIR" fi -# Bootstrap vcpkg if needed cd "$VCPKG_DIR" -echo "$VCPKG_DIR" -if [ ! -f "./vcpkg.exe" ]; then - echo "🔧 Bootstrapping vcpkg..." + +echo "🔧 Bootstrapping vcpkg..." +if [ -f "./bootstrap-vcpkg.sh" ]; then + ./bootstrap-vcpkg.sh +elif [ -f "./bootstrap-vcpkg.bat" ]; then ./bootstrap-vcpkg.bat fi -# Install required packages -echo "📥 Installing glfw3, glm, stb, volk, tinyobjloader..." -./vcpkg install glfw3 glm stb volk tinyobjloader vulkan-memory-allocator --triplet x64-windows +echo "📥 Installing glfw3, glm, stb, tinyobjloader..." +./vcpkg install glfw3 stb tinyobjloader --triplet x64-windows # === back to project and build === -cd "$OLDPWD" +cd "$PROJECT_DIR" mkdir -p build/ cd build -# Configure with Visual Studio and vcpkg toolchain -echo "🛠️ Running CMake configuration with vcpkg toolchain..." +echo "🛠️ Running CMake configuration with vcpkg toolchain..." + cmake -G "Visual Studio 17 2022" -A "x64" ../code \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_TOOLCHAIN_FILE" "$@" popd -echo "✅ Build system is ready. You can now build the solution in Visual Studio or with cmake --build build" +echo "✅ Build system is ready." sleep 5 + From 9dbd454130effebec62920b9b56a9e0f23fbb244 Mon Sep 17 00:00:00 2001 From: Fietspompje <74879947+ECHekman@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:17:52 +0200 Subject: [PATCH 20/47] Fixes --- code/16_index_buffer.cpp | 4 ++++ code/CMakeLists.txt | 42 ++++++++++++++-------------------------- windows.sh | 3 +++ 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/code/16_index_buffer.cpp b/code/16_index_buffer.cpp index 50980802..e6d2c265 100644 --- a/code/16_index_buffer.cpp +++ b/code/16_index_buffer.cpp @@ -1002,6 +1002,10 @@ class HelloTriangleApplication { shaderCreateInfo.codeSize = code.size(); shaderCreateInfo.pName = "main"; + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkShaderEXT shader; if (vkCreateShadersEXT(device, 1, &shaderCreateInfo, diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 1fede111..cdfba07d 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required (VERSION 3.8) project (VulkanTutorial) find_package (glfw3 REQUIRED) -find_package (glm REQUIRED) find_package (Vulkan REQUIRED) find_package (tinyobjloader REQUIRED) @@ -56,7 +55,7 @@ function (add_shaders_target TARGET) add_custom_command ( OUTPUT ${SHADERS} COMMAND glslang::validator - ARGS --target-env vulkan1.0 ${SHADER_SOURCES} + ARGS --target-env vulkan1.3 ${SHADER_SOURCES} WORKING_DIRECTORY ${SHADERS_DIR} DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} COMMENT "Compiling Shaders" @@ -153,67 +152,56 @@ add_chapter (17_swap_chain_recreation SHADER 08_shader_base) add_chapter (18_vertex_input - SHADER 18_shader_vertexbuffer - LIBS glm::glm) + SHADER 18_shader_vertexbuffer) add_chapter (19_vertex_buffer - SHADER 18_shader_vertexbuffer - LIBS glm::glm) + SHADER 18_shader_vertexbuffer) add_chapter (20_staging_buffer - SHADER 18_shader_vertexbuffer - LIBS glm::glm) + SHADER 18_shader_vertexbuffer) add_chapter (21_index_buffer - SHADER 18_shader_vertexbuffer - LIBS glm::glm) + SHADER 18_shader_vertexbuffer) add_chapter (22_descriptor_set_layout - SHADER 22_shader_ubo - LIBS glm::glm) + SHADER 22_shader_ubo) add_chapter (23_descriptor_sets - SHADER 22_shader_ubo - LIBS glm::glm) + SHADER 22_shader_ubo) add_chapter (24_texture_image SHADER 22_shader_ubo - TEXTURES ../images/texture.jpg - LIBS glm::glm) + TEXTURES ../images/texture.jpg) add_chapter (25_sampler SHADER 22_shader_ubo - TEXTURES ../images/texture.jpg - LIBS glm::glm) + TEXTURES ../images/texture.jpg) add_chapter (26_texture_mapping SHADER 26_shader_textures - TEXTURES ../images/texture.jpg - LIBS glm::glm) + TEXTURES ../images/texture.jpg) add_chapter (27_depth_buffering SHADER 27_shader_depth - TEXTURES ../images/texture.jpg - LIBS glm::glm) + TEXTURES ../images/texture.jpg) add_chapter (28_model_loading SHADER 27_shader_depth MODELS ../resources/viking_room.obj TEXTURES ../resources/viking_room.png - LIBS glm::glm tinyobjloader::tinyobjloader) + LIBS tinyobjloader::tinyobjloader) add_chapter (29_mipmapping SHADER 27_shader_depth MODELS ../resources/viking_room.obj TEXTURES ../resources/viking_room.png - LIBS glm::glm tinyobjloader::tinyobjloader) + LIBS tinyobjloader::tinyobjloader) add_chapter (30_multisampling SHADER 27_shader_depth MODELS ../resources/viking_room.obj TEXTURES ../resources/viking_room.png - LIBS glm::glm tinyobjloader::tinyobjloader) + LIBS tinyobjloader::tinyobjloader) add_chapter (31_compute_shader - SHADER 31_shader_compute - LIBS glm::glm) + SHADER 31_shader_compute) diff --git a/windows.sh b/windows.sh index c4e680ae..b0b750dc 100644 --- a/windows.sh +++ b/windows.sh @@ -15,6 +15,9 @@ trap exit_on_error ERR pushd . +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +echo "Dir = $PROJECT_DIR" + # === vcpkg setup === VCPKG_DIR="$HOME/dev/vcpkg" VCPKG_TOOLCHAIN_FILE="$VCPKG_DIR/scripts/buildsystems/vcpkg.cmake" From 7e71f1b0d0d5be7b9f215821e82928379cb2dca6 Mon Sep 17 00:00:00 2001 From: Fietspompje <74879947+ECHekman@users.noreply.github.com> Date: Sun, 5 Apr 2026 01:58:33 +0200 Subject: [PATCH 21/47] Descriptor heaps --- code/16_index_buffer.cpp | 4 - code/17_descriptor_set_layout.cpp | 224 +++++++++++++++++++++++++++--- code/17_shader_ubo.frag | 5 + code/17_shader_ubo.vert | 17 ++- 4 files changed, 217 insertions(+), 33 deletions(-) diff --git a/code/16_index_buffer.cpp b/code/16_index_buffer.cpp index e6d2c265..50980802 100644 --- a/code/16_index_buffer.cpp +++ b/code/16_index_buffer.cpp @@ -1002,10 +1002,6 @@ class HelloTriangleApplication { shaderCreateInfo.codeSize = code.size(); shaderCreateInfo.pName = "main"; - if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) - shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; - - VkShaderEXT shader; if (vkCreateShadersEXT(device, 1, &shaderCreateInfo, diff --git a/code/17_descriptor_set_layout.cpp b/code/17_descriptor_set_layout.cpp index 40861ef6..dafc35d0 100644 --- a/code/17_descriptor_set_layout.cpp +++ b/code/17_descriptor_set_layout.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -34,7 +35,9 @@ const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, - VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, }; #ifdef NDEBUG @@ -127,6 +130,10 @@ const std::vector indices = { 0, 1, 2, 2, 3, 0 }; +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + class HelloTriangleApplication { public: void run() { @@ -148,6 +155,14 @@ class HelloTriangleApplication { VkDevice device; VmaAllocator allocator; + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + VkBuffer descriptorHeapResourcesBuffer; + VmaAllocation descriptorHeapResourcesAllocation; + VkDeviceSize bufferHeapOffset{ 0 }; + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize; + + VkQueue graphicsQueue; VkQueue presentQueue; @@ -168,10 +183,8 @@ class HelloTriangleApplication { VkBuffer indexBuffer; VmaAllocation indexAllocation; - VkBuffer uniformBuffer; - VmaAllocation uniformAllocation; - - VkPhysicalDeviceDescriptorHeapPropertiesEXT heapProperties; + std::vector uniformBuffers; + std::vector uniformAllocations; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; @@ -206,12 +219,12 @@ class HelloTriangleApplication { createVMA(); createSwapChain(); createImageViews(); - createDescriptorHeap(); createGraphicsPipeline(); createCommandPool(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); + prepareDescriptorHeap(); createCommandBuffers(); createSyncObjects(); } @@ -238,7 +251,10 @@ class HelloTriangleApplication { vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); - vmaDestroyBuffer(allocator, uniformBuffer, uniformAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + vmaDestroyAllocator(allocator); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { @@ -371,6 +387,18 @@ class HelloTriangleApplication { if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + } void createLogicalDevice() { @@ -410,13 +438,28 @@ class HelloTriangleApplication { vulkan13Features.dynamicRendering = VK_TRUE; vulkan13Features.pNext = &vulkan12Features; + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pNext = &vulkan13Features; + createInfo.pNext = &maintenance5Features; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); @@ -535,10 +578,72 @@ class HelloTriangleApplication { } } - void createDescriptorHeap() + void prepareDescriptorHeap() { - + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffer, + &descriptorHeapResourcesAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + auto resourceSize{ MAX_FRAMES_IN_FLIGHT }; + std::vector hostAddressRangesResources(resourceSize); + std::vector resourceDescriptorInfos(resourceSize); + + size_t heapResIndex{ 0 }; + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < uniformBuffers.size(); i++) { + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + resourceDescriptorInfos[heapResIndex] = {}; + resourceDescriptorInfos[heapResIndex].sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos[heapResIndex].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos[heapResIndex].data = {}; + resourceDescriptorInfos[heapResIndex].data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + + hostAddressRangesResources[heapResIndex] = {}; + hostAddressRangesResources[heapResIndex].address = static_cast(allocResult.pMappedData) + bufferDescriptorSize * i; + hostAddressRangesResources[heapResIndex].size = bufferDescriptorSize; + + heapResIndex++; + } + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } } @@ -548,6 +653,9 @@ class HelloTriangleApplication { vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + return; } @@ -670,12 +778,8 @@ class HelloTriangleApplication { } - void createUniformBuffer() + void createUniformBuffers() { - VmaAllocator allocator; // assume created earlier - VkBuffer uniformBuffer; - VmaAllocation allocation; - VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(UniformBufferObject); @@ -684,9 +788,23 @@ class HelloTriangleApplication { VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, - &uniformBuffer, &allocation, nullptr); + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + } } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { @@ -851,6 +969,29 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBufferDeviceAddressInfo addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo.buffer = descriptorHeapResourcesBuffer; + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; @@ -1042,26 +1183,63 @@ class HelloTriangleApplication { auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); + float negative = 1; + if (currentImage == 0) + negative = -1; + UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); ubo.proj[1][1] *= -1; // Vulkan clip correction void* mapped; - vmaMapMemory(allocator, uniformAllocation, &mapped); + vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); memcpy(mapped, &ubo, sizeof(ubo)); - vmaUnmapMemory(allocator, uniformAllocation); - }) + vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; shaderCreateInfo.pCode = reinterpret_cast(code.data()); shaderCreateInfo.codeSize = code.size(); shaderCreateInfo.pName = "main"; - + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + VkPushConstantRange pushConstantRange; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(uint32_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; + //shaderCreateInfo.pushConstantRangeCount = 1; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + VkShaderEXT shader; if (vkCreateShadersEXT(device, 1, &shaderCreateInfo, diff --git a/code/17_shader_ubo.frag b/code/17_shader_ubo.frag index 7c5b0e74..da3f5738 100644 --- a/code/17_shader_ubo.frag +++ b/code/17_shader_ubo.frag @@ -1,5 +1,10 @@ #version 450 +layout(push_constant) uniform PushData { + int offset; +} pushData; + + layout(location = 0) in vec3 fragColor; layout(location = 0) out vec4 outColor; diff --git a/code/17_shader_ubo.vert b/code/17_shader_ubo.vert index 924ac461..175c929a 100644 --- a/code/17_shader_ubo.vert +++ b/code/17_shader_ubo.vert @@ -1,15 +1,20 @@ +#version 450 + layout(push_constant) uniform PushData { - uint uboDescriptorOffset; -}; + int offset; +} pushData; -layout(buffer_reference, std430) readonly buffer UBORef { +layout(set = 0, binding = 0) uniform UBO { mat4 model; mat4 view; mat4 proj; -}; +} ubo[2]; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 0) out vec3 fragColor; void main() { - UBORef uboRef = UBORef(uboDescriptorOffset); - gl_Position = uboRef.proj * uboRef.view * uboRef.model * vec4(inPosition, 0.0, 1.0); + gl_Position = ubo[pushData.offset].proj * ubo[pushData.offset].view * ubo[pushData.offset].model * vec4(inPosition, 0.0, 1.0); fragColor = inColor; } \ No newline at end of file From 388fef1520f33a136d4933ba3932061c02919fa0 Mon Sep 17 00:00:00 2001 From: Fietspompje <74879947+ECHekman@users.noreply.github.com> Date: Sun, 5 Apr 2026 02:07:49 +0200 Subject: [PATCH 22/47] Good God --- code/17_descriptor_set_layout.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/17_descriptor_set_layout.cpp b/code/17_descriptor_set_layout.cpp index dafc35d0..c6a57ec1 100644 --- a/code/17_descriptor_set_layout.cpp +++ b/code/17_descriptor_set_layout.cpp @@ -1263,7 +1263,7 @@ class HelloTriangleApplication { VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { return availablePresentMode; } } From f4e142488b425a1e03b2625e5413cefefa9a0249 Mon Sep 17 00:00:00 2001 From: Fietspompje <74879947+ECHekman@users.noreply.github.com> Date: Sun, 5 Apr 2026 20:03:25 +0200 Subject: [PATCH 23/47] Descriptor Heap per frame --- code/17_descriptor_set_layout.cpp | 95 ++++++++++++++++--------------- code/17_shader_ubo.vert | 4 +- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/code/17_descriptor_set_layout.cpp b/code/17_descriptor_set_layout.cpp index c6a57ec1..e86c8192 100644 --- a/code/17_descriptor_set_layout.cpp +++ b/code/17_descriptor_set_layout.cpp @@ -156,8 +156,8 @@ class HelloTriangleApplication { VmaAllocator allocator; VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; - VkBuffer descriptorHeapResourcesBuffer; - VmaAllocation descriptorHeapResourcesAllocation; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; VkDeviceSize bufferHeapOffset{ 0 }; VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize heapbufferSize; @@ -581,40 +581,44 @@ class HelloTriangleApplication { void prepareDescriptorHeap() { heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); - VkBufferCreateInfo bufferInfo{}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = heapbufferSize; - bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; - - VmaAllocationCreateInfo allocInfo{}; - allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; - allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - - - VmaAllocationInfo allocResult{}; - if (vmaCreateBuffer( - allocator, - &bufferInfo, - &allocInfo, - &descriptorHeapResourcesBuffer, - &descriptorHeapResourcesAllocation, - &allocResult - ) != VK_SUCCESS) { - throw std::runtime_error("failed to create resource descriptor heap!"); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } } - auto resourceSize{ MAX_FRAMES_IN_FLIGHT }; - std::vector hostAddressRangesResources(resourceSize); - std::vector resourceDescriptorInfos(resourceSize); size_t heapResIndex{ 0 }; std::array addrInfo{}; std::array deviceAddressRangesUniformBuffer{}; - for (auto i = 0; i < uniformBuffers.size(); i++) { + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + VkHostAddressRangeEXT hostAddressRangesResources; + VkResourceDescriptorInfoEXT resourceDescriptorInfos; addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, addrInfo[i].buffer = uniformBuffers[i]; @@ -623,28 +627,27 @@ class HelloTriangleApplication { deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); - resourceDescriptorInfos[heapResIndex] = {}; - resourceDescriptorInfos[heapResIndex].sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; - resourceDescriptorInfos[heapResIndex].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - resourceDescriptorInfos[heapResIndex].data = {}; - resourceDescriptorInfos[heapResIndex].data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos = {}; + resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos.data = {}; + resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; - hostAddressRangesResources[heapResIndex] = {}; - hostAddressRangesResources[heapResIndex].address = static_cast(allocResult.pMappedData) + bufferDescriptorSize * i; - hostAddressRangesResources[heapResIndex].size = bufferDescriptorSize; + hostAddressRangesResources = {}; + hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResources.size = bufferDescriptorSize; heapResIndex++; - } - if (vkWriteResourceDescriptorsEXT( - device, - static_cast(resourceDescriptorInfos.size()), - resourceDescriptorInfos.data(), - hostAddressRangesResources.data() - ) != VK_SUCCESS) { - throw std::runtime_error("failed to write resource descriptors!"); + if (vkWriteResourceDescriptorsEXT( + device, + 1, + &resourceDescriptorInfos, + &hostAddressRangesResources + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } } - } void createGraphicsPipeline() { @@ -981,7 +984,7 @@ class HelloTriangleApplication { VkBufferDeviceAddressInfo addrInfo{}; addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - addrInfo.buffer = descriptorHeapResourcesBuffer; + addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; VkBindHeapInfoEXT bindHeapinfo{}; bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; diff --git a/code/17_shader_ubo.vert b/code/17_shader_ubo.vert index 175c929a..b69925a9 100644 --- a/code/17_shader_ubo.vert +++ b/code/17_shader_ubo.vert @@ -8,13 +8,13 @@ layout(set = 0, binding = 0) uniform UBO { mat4 model; mat4 view; mat4 proj; -} ubo[2]; +} ubo; layout(location = 0) in vec2 inPosition; layout(location = 1) in vec3 inColor; layout(location = 0) out vec3 fragColor; void main() { - gl_Position = ubo[pushData.offset].proj * ubo[pushData.offset].view * ubo[pushData.offset].model * vec4(inPosition, 0.0, 1.0); + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); fragColor = inColor; } \ No newline at end of file From 92e256098c53d087e3a8866f6556c6a0eb2868b9 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 18 Apr 2026 19:50:03 +0200 Subject: [PATCH 24/47] - Descriptor Heaps --- code/08_shader_objects.cpp | 2 +- code/09_command_buffers.cpp | 2 +- code/10_dynamic_rendering.cpp | 2 +- code/12_swap_chain_recreation.cpp | 2 +- code/13_vertex_input.cpp | 2 +- code/15_staging_buffer.cpp | 4 +- code/17_descriptor_heaps.cpp | 1460 +++++++++++++++++++++++++++++ code/17_descriptor_set_layout.cpp | 2 + 8 files changed, 1469 insertions(+), 7 deletions(-) create mode 100644 code/17_descriptor_heaps.cpp diff --git a/code/08_shader_objects.cpp b/code/08_shader_objects.cpp index 3707f82d..bd92e162 100644 --- a/code/08_shader_objects.cpp +++ b/code/08_shader_objects.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/09_command_buffers.cpp b/code/09_command_buffers.cpp index 4eb91fc3..cc1b7739 100644 --- a/code/09_command_buffers.cpp +++ b/code/09_command_buffers.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/10_dynamic_rendering.cpp b/code/10_dynamic_rendering.cpp index be25177c..1491f550 100644 --- a/code/10_dynamic_rendering.cpp +++ b/code/10_dynamic_rendering.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/12_swap_chain_recreation.cpp b/code/12_swap_chain_recreation.cpp index e5063812..6f93fb58 100644 --- a/code/12_swap_chain_recreation.cpp +++ b/code/12_swap_chain_recreation.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/13_vertex_input.cpp b/code/13_vertex_input.cpp index 17135af4..a6aa741f 100644 --- a/code/13_vertex_input.cpp +++ b/code/13_vertex_input.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/15_staging_buffer.cpp b/code/15_staging_buffer.cpp index 2779f5c5..fa96e514 100644 --- a/code/15_staging_buffer.cpp +++ b/code/15_staging_buffer.cpp @@ -1,7 +1,7 @@ -#include "volk.h" +#include "Volk/volk.h" #define VMA_IMPLEMENTATION #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 -#include "vk_mem_alloc.h" +#include "vma/vk_mem_alloc.h" #define GLFW_INCLUDE_VULKAN #include diff --git a/code/17_descriptor_heaps.cpp b/code/17_descriptor_heaps.cpp new file mode 100644 index 00000000..96aecdae --- /dev/null +++ b/code/17_descriptor_heaps.cpp @@ -0,0 +1,1460 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkDeviceSize bufferHeapOffset{ 0 }; + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + prepareDescriptorHeap(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + + size_t heapResIndex{ 0 }; + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + VkHostAddressRangeEXT hostAddressRangesResources; + VkResourceDescriptorInfoEXT resourceDescriptorInfos; + + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + resourceDescriptorInfos = {}; + resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos.data = {}; + resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + + hostAddressRangesResources = {}; + hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResources.size = bufferDescriptorSize; + + heapResIndex++; + + if (vkWriteResourceDescriptorsEXT( + device, + 1, + &resourceDescriptorInfos, + &hostAddressRangesResources + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + + return; + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(Vertex) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &vertexBuffer, + &vertexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create vertex buffer!"); + } + + copyBuffer(stagingBuffer, vertexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &stagingBuffer, + &stagingAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferInfo.size); + vmaUnmapMemory(allocator, stagingAllocation); + + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + } + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBufferDeviceAddressInfo addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + float negative = 1; + if (currentImage == 0) + negative = -1; + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + void* mapped; + vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); + memcpy(mapped, &ubo, sizeof(ubo)); + vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + VkPushConstantRange pushConstantRange; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(uint32_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; + //shaderCreateInfo.pushConstantRangeCount = 1; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/17_descriptor_set_layout.cpp b/code/17_descriptor_set_layout.cpp index e86c8192..39edf95d 100644 --- a/code/17_descriptor_set_layout.cpp +++ b/code/17_descriptor_set_layout.cpp @@ -21,6 +21,8 @@ #include #include #include +#include + const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; From 2ffa2bc33fbc52a93eb1248b517b16b4a0dfbcc4 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 18 Apr 2026 19:50:11 +0200 Subject: [PATCH 25/47] Update 11_frames_in_flight.cpp --- code/11_frames_in_flight.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/11_frames_in_flight.cpp b/code/11_frames_in_flight.cpp index c41d3e1a..cb78d7f9 100644 --- a/code/11_frames_in_flight.cpp +++ b/code/11_frames_in_flight.cpp @@ -1,4 +1,4 @@ -#include "volk.h" +#include "Volk/volk.h" #define GLFW_INCLUDE_VULKAN #include From e90425133599d183d18c95fefbd72b4c781773a0 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 18 Apr 2026 21:31:00 +0200 Subject: [PATCH 26/47] - Heap and texture image --- code/17_descriptor_heaps.cpp | 95 +- code/18_texture_image.cpp | 1572 ++++++++++++++++++++++++++++++++++ 2 files changed, 1629 insertions(+), 38 deletions(-) create mode 100644 code/18_texture_image.cpp diff --git a/code/17_descriptor_heaps.cpp b/code/17_descriptor_heaps.cpp index 96aecdae..03361c50 100644 --- a/code/17_descriptor_heaps.cpp +++ b/code/17_descriptor_heaps.cpp @@ -663,61 +663,80 @@ class HelloTriangleApplication { return; } - void createVertexBuffer() - { - VkBuffer stagingBuffer; - VmaAllocation stagingAllocation; + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = sizeof(Vertex) * vertices.size(); - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferInfo.size = size; + bufferInfo.usage = usage; VmaAllocationCreateInfo allocInfo{}; - allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; - allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; - VmaAllocationInfo allocResult{}; + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; if (vmaCreateBuffer( allocator, &bufferInfo, &allocInfo, - &stagingBuffer, - &stagingAllocation, - &allocResult + &buffer, + &bufferAllocation, + allocDst ) != VK_SUCCESS) { - throw std::runtime_error("failed to create staging buffer!"); - } + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + + VmaAllocationInfo stagingResult{}; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation, + &stagingResult + ); void* data = nullptr; vmaMapMemory(allocator, stagingAllocation, &data); - memcpy(data, vertices.data(), bufferInfo.size); + memcpy(data, vertices.data(), bufferSize); vmaUnmapMemory(allocator, stagingAllocation); - bufferInfo = {}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = sizeof(Vertex) * vertices.size(); - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - allocInfo = {}; - allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - - VmaAllocationInfo stagingAllocResult = {}; - if (vmaCreateBuffer( - allocator, - &bufferInfo, - &allocInfo, - &vertexBuffer, - &vertexAllocation, - &stagingAllocResult - ) != VK_SUCCESS) { - throw std::runtime_error("failed to create vertex buffer!"); - } + VmaAllocationInfo allocResult{}; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + vertexBuffer, + vertexAllocation, + &allocResult + ); - copyBuffer(stagingBuffer, vertexBuffer, allocResult.size); + copyBuffer(stagingBuffer, vertexBuffer, stagingResult.size); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } diff --git a/code/18_texture_image.cpp b/code/18_texture_image.cpp new file mode 100644 index 00000000..429b795a --- /dev/null +++ b/code/18_texture_image.cpp @@ -0,0 +1,1572 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkDeviceSize bufferHeapOffset{ 0 }; + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + + size_t heapResIndex{ 0 }; + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + VkHostAddressRangeEXT hostAddressRangesResources; + VkResourceDescriptorInfoEXT resourceDescriptorInfos; + + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + resourceDescriptorInfos = {}; + resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos.data = {}; + resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + + hostAddressRangesResources = {}; + hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResources.size = bufferDescriptorSize; + + heapResIndex++; + + if (vkWriteResourceDescriptorsEXT( + device, + 1, + &resourceDescriptorInfos, + &hostAddressRangesResources + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + + return; + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0])* indices.size(); + + VmaAllocationInfo allocResult{}; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation, + &allocResult + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + + VkBufferCreateInfo bufferInfo{}; + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + + + + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + } + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBufferDeviceAddressInfo addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + float negative = 1; + if (currentImage == 0) + negative = -1; + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + void* mapped; + vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); + memcpy(mapped, &ubo, sizeof(ubo)); + vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + VkPushConstantRange pushConstantRange; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(uint32_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; + //shaderCreateInfo.pushConstantRangeCount = 1; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From 3bb4f17ba85c270fd73306806ed9f3505631ba6d Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 18 Apr 2026 21:31:06 +0200 Subject: [PATCH 27/47] Update CMakeLists.txt --- code/CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index cdfba07d..7cd1fe92 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -142,9 +142,17 @@ add_chapter (15_staging_buffer add_chapter (16_index_buffer SHADER 13_shader_vertexbuffer) -add_chapter (17_descriptor_set_layout +add_chapter (17_descriptor_heaps SHADER 17_shader_ubo) +add_chapter (18_texture_image + SHADER 17_shader_ubo + TEXTURES ../images/texture.jpg) + +add_chapter (19_sampler + SHADER 17_shader_ubo + TEXTURES ../images/texture.jpg) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From a2b1cc7ac00825b4d67d3a3111bf526b9da76b30 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Mon, 20 Apr 2026 19:35:50 +0200 Subject: [PATCH 28/47] Update 18_texture_image.cpp --- code/18_texture_image.cpp | 123 +++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 7 deletions(-) diff --git a/code/18_texture_image.cpp b/code/18_texture_image.cpp index 429b795a..b3ae8d3d 100644 --- a/code/18_texture_image.cpp +++ b/code/18_texture_image.cpp @@ -256,12 +256,17 @@ class HelloTriangleApplication { void cleanup() { cleanupSwapChain(); + vmaDestroyImage(allocator, textureImage, textureImageAllocation); vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); } + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + vmaDestroyAllocator(allocator); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { @@ -888,9 +893,12 @@ class HelloTriangleApplication { textureImageAllocation ); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } @@ -923,7 +931,8 @@ class HelloTriangleApplication { } } - void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + + VkCommandBuffer beginSingleTimeCommands() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -939,10 +948,11 @@ class HelloTriangleApplication { vkBeginCommandBuffer(commandBuffer, &beginInfo); - VkBufferCopy copyRegion{}; - copyRegion.size = size; - vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo{}; @@ -950,13 +960,112 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; - vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + vkQueueWaitIdle(graphicsQueue); vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); } + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); From b31170dbe119e6b30a6f15ad8bc2834d06a17336 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 10 May 2026 19:14:18 +0200 Subject: [PATCH 29/47] Create 19_sampler.cpp --- code/19_sampler.cpp | 1681 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1681 insertions(+) create mode 100644 code/19_sampler.cpp diff --git a/code/19_sampler.cpp b/code/19_sampler.cpp new file mode 100644 index 00000000..b3ae8d3d --- /dev/null +++ b/code/19_sampler.cpp @@ -0,0 +1,1681 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkDeviceSize bufferHeapOffset{ 0 }; + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + + size_t heapResIndex{ 0 }; + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + VkHostAddressRangeEXT hostAddressRangesResources; + VkResourceDescriptorInfoEXT resourceDescriptorInfos; + + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + resourceDescriptorInfos = {}; + resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos.data = {}; + resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + + hostAddressRangesResources = {}; + hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResources.size = bufferDescriptorSize; + + heapResIndex++; + + if (vkWriteResourceDescriptorsEXT( + device, + 1, + &resourceDescriptorInfos, + &hostAddressRangesResources + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + + return; + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0])* indices.size(); + + VmaAllocationInfo allocResult{}; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation, + &allocResult + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + + VkBufferCreateInfo bufferInfo{}; + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBufferDeviceAddressInfo addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + float negative = 1; + if (currentImage == 0) + negative = -1; + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + void* mapped; + vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); + memcpy(mapped, &ubo, sizeof(ubo)); + vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + VkPushConstantRange pushConstantRange; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(uint32_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; + //shaderCreateInfo.pushConstantRangeCount = 1; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file From 03073753e5487fdaf8450c443da971b9969a144c Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 22 May 2026 09:44:23 +0200 Subject: [PATCH 30/47] Spacing --- code/18_texture_image.cpp | 34 +++++++++++++++++----------------- code/19_sampler.cpp | 34 +++++++++++++++++----------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/code/18_texture_image.cpp b/code/18_texture_image.cpp index b3ae8d3d..e8325878 100644 --- a/code/18_texture_image.cpp +++ b/code/18_texture_image.cpp @@ -166,7 +166,7 @@ class HelloTriangleApplication { VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize heapbufferSize; - + VkQueue graphicsQueue; VkQueue presentQueue; @@ -609,7 +609,7 @@ class HelloTriangleApplication { allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - + if (vmaCreateBuffer( allocator, &bufferInfo, @@ -633,7 +633,7 @@ class HelloTriangleApplication { VkResourceDescriptorInfoEXT resourceDescriptorInfos; addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, - addrInfo[i].buffer = uniformBuffers[i]; + addrInfo[i].buffer = uniformBuffers[i]; deviceAddressRangesUniformBuffer[i] = {}; deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); @@ -675,12 +675,12 @@ class HelloTriangleApplication { } void createBuffer( - VkDeviceSize size, - VkBufferUsageFlags usage, + VkDeviceSize size, + VkBufferUsageFlags usage, VmaMemoryUsage vmaUsage, VmaAllocationCreateFlags vmaFlags, VkMemoryPropertyFlags requiredFlags, - VkBuffer& buffer, + VkBuffer& buffer, VmaAllocation& bufferAllocation, VmaAllocationInfo* outAllocResult = 0 ) { @@ -752,7 +752,7 @@ class HelloTriangleApplication { VkBuffer stagingBuffer; VmaAllocation stagingAllocation; - VkDeviceSize bufferSize = sizeof(indices[0])* indices.size(); + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); VmaAllocationInfo allocResult{}; createBuffer( @@ -893,7 +893,7 @@ class HelloTriangleApplication { textureImageAllocation ); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -1151,7 +1151,7 @@ class HelloTriangleApplication { vkCmdPipelineBarrier2(commandBuffer, &dep); - + VkRenderingAttachmentInfo colorAttachment{}; colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; colorAttachment.imageView = swapChainImageViews[imageIndex]; @@ -1170,7 +1170,7 @@ class HelloTriangleApplication { vkCmdBeginRendering(commandBuffer, &renderingInfo); { setInitialRenderingState(commandBuffer); - + vkCmdSetVertexInputEXT(commandBuffer, 1, &Vertex::getBindingDescription(), Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() @@ -1203,7 +1203,7 @@ class HelloTriangleApplication { vkCmdPushDataEXT(commandBuffer, &pushDataInfo); - + VkBufferDeviceAddressInfo addrInfo{}; addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; @@ -1318,7 +1318,7 @@ class HelloTriangleApplication { else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to acquire swap chain image!"); } - + timelineValue++; updateUniformBuffer(currentFrame); @@ -1368,10 +1368,10 @@ class HelloTriangleApplication { submitInfo.commandBufferInfoCount = 1; submitInfo.pCommandBufferInfos = &commandBufferInfo; - + submitInfo.signalSemaphoreInfoCount = 2; submitInfo.pSignalSemaphoreInfos = signals; - + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1425,7 +1425,7 @@ class HelloTriangleApplication { } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { - + std::array setAndBindingMappings; // Buffer binding @@ -1440,9 +1440,9 @@ class HelloTriangleApplication { VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; - descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); - + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; diff --git a/code/19_sampler.cpp b/code/19_sampler.cpp index b3ae8d3d..e8325878 100644 --- a/code/19_sampler.cpp +++ b/code/19_sampler.cpp @@ -166,7 +166,7 @@ class HelloTriangleApplication { VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize heapbufferSize; - + VkQueue graphicsQueue; VkQueue presentQueue; @@ -609,7 +609,7 @@ class HelloTriangleApplication { allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - + if (vmaCreateBuffer( allocator, &bufferInfo, @@ -633,7 +633,7 @@ class HelloTriangleApplication { VkResourceDescriptorInfoEXT resourceDescriptorInfos; addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, - addrInfo[i].buffer = uniformBuffers[i]; + addrInfo[i].buffer = uniformBuffers[i]; deviceAddressRangesUniformBuffer[i] = {}; deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); @@ -675,12 +675,12 @@ class HelloTriangleApplication { } void createBuffer( - VkDeviceSize size, - VkBufferUsageFlags usage, + VkDeviceSize size, + VkBufferUsageFlags usage, VmaMemoryUsage vmaUsage, VmaAllocationCreateFlags vmaFlags, VkMemoryPropertyFlags requiredFlags, - VkBuffer& buffer, + VkBuffer& buffer, VmaAllocation& bufferAllocation, VmaAllocationInfo* outAllocResult = 0 ) { @@ -752,7 +752,7 @@ class HelloTriangleApplication { VkBuffer stagingBuffer; VmaAllocation stagingAllocation; - VkDeviceSize bufferSize = sizeof(indices[0])* indices.size(); + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); VmaAllocationInfo allocResult{}; createBuffer( @@ -893,7 +893,7 @@ class HelloTriangleApplication { textureImageAllocation ); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -1151,7 +1151,7 @@ class HelloTriangleApplication { vkCmdPipelineBarrier2(commandBuffer, &dep); - + VkRenderingAttachmentInfo colorAttachment{}; colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; colorAttachment.imageView = swapChainImageViews[imageIndex]; @@ -1170,7 +1170,7 @@ class HelloTriangleApplication { vkCmdBeginRendering(commandBuffer, &renderingInfo); { setInitialRenderingState(commandBuffer); - + vkCmdSetVertexInputEXT(commandBuffer, 1, &Vertex::getBindingDescription(), Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() @@ -1203,7 +1203,7 @@ class HelloTriangleApplication { vkCmdPushDataEXT(commandBuffer, &pushDataInfo); - + VkBufferDeviceAddressInfo addrInfo{}; addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; @@ -1318,7 +1318,7 @@ class HelloTriangleApplication { else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to acquire swap chain image!"); } - + timelineValue++; updateUniformBuffer(currentFrame); @@ -1368,10 +1368,10 @@ class HelloTriangleApplication { submitInfo.commandBufferInfoCount = 1; submitInfo.pCommandBufferInfos = &commandBufferInfo; - + submitInfo.signalSemaphoreInfoCount = 2; submitInfo.pSignalSemaphoreInfos = signals; - + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1425,7 +1425,7 @@ class HelloTriangleApplication { } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { - + std::array setAndBindingMappings; // Buffer binding @@ -1440,9 +1440,9 @@ class HelloTriangleApplication { VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; - descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); - + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; From 2e5dc642861854ba97d5a8112d2b52715f328be6 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 22 May 2026 11:22:04 +0200 Subject: [PATCH 31/47] Sampler --- code/19_sampler.cpp | 93 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/code/19_sampler.cpp b/code/19_sampler.cpp index e8325878..4ee28528 100644 --- a/code/19_sampler.cpp +++ b/code/19_sampler.cpp @@ -186,8 +186,11 @@ class HelloTriangleApplication { VmaAllocation vertexAllocation; VkBuffer indexBuffer; VmaAllocation indexAllocation; + VkImage textureImage; VmaAllocation textureImageAllocation; + VkImageView textureImageView; + VkSampler textureSampler; std::vector uniformBuffers; std::vector uniformAllocations; @@ -230,6 +233,8 @@ class HelloTriangleApplication { createVertexBuffer(); createIndexBuffer(); createTextureImage(); + createTextureImageView(); + createTextureSampler(); createUniformBuffers(); prepareDescriptorHeap(); createCommandBuffers(); @@ -254,9 +259,13 @@ class HelloTriangleApplication { } void cleanup() { + cleanupSwapChain(); + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); vmaDestroyImage(allocator, textureImage, textureImageAllocation); + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { @@ -436,6 +445,7 @@ class HelloTriangleApplication { VkPhysicalDeviceFeatures2 deviceFeatures2{}; deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; deviceFeatures2.pNext = &shaderObjectFeatures; VkPhysicalDeviceVulkan12Features vulkan12Features{}; @@ -565,28 +575,31 @@ class HelloTriangleApplication { swapChainExtent = extent; } + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); - for (size_t i = 0; i < swapChainImages.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapChainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapChainImageFormat; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } } @@ -902,6 +915,43 @@ class HelloTriangleApplication { } + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); + } + + + void createTextureSampler() { + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + void createUniformBuffers() { VkBufferCreateInfo bufferInfo{}; @@ -1545,13 +1595,16 @@ class HelloTriangleApplication { bool extensionsSupported = checkDeviceExtensionSupport(device); + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } - return indices.isComplete() && extensionsSupported && swapChainAdequate; + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { From 03083f7d9e91322a822a5f9e2a29733ab3f9018e Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Fri, 22 May 2026 11:22:17 +0200 Subject: [PATCH 32/47] Texture mapping --- code/20_shader_textures.frag | 12 + code/20_shader_textures.vert | 20 + code/20_texture_mapping.cpp | 1734 ++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 4 + 4 files changed, 1770 insertions(+) create mode 100644 code/20_shader_textures.frag create mode 100644 code/20_shader_textures.vert create mode 100644 code/20_texture_mapping.cpp diff --git a/code/20_shader_textures.frag b/code/20_shader_textures.frag new file mode 100644 index 00000000..873f5410 --- /dev/null +++ b/code/20_shader_textures.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(binding = 1) uniform sampler2D texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(texSampler, fragTexCoord); +} diff --git a/code/20_shader_textures.vert b/code/20_shader_textures.vert new file mode 100644 index 00000000..5510aa3f --- /dev/null +++ b/code/20_shader_textures.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp new file mode 100644 index 00000000..4ee28528 --- /dev/null +++ b/code/20_texture_mapping.cpp @@ -0,0 +1,1734 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkDeviceSize bufferHeapOffset{ 0 }; + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + VkImage textureImage; + VmaAllocation textureImageAllocation; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector uniformBuffers; + std::vector uniformAllocations; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createUniformBuffers(); + prepareDescriptorHeap(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + + size_t heapResIndex{ 0 }; + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + VkHostAddressRangeEXT hostAddressRangesResources; + VkResourceDescriptorInfoEXT resourceDescriptorInfos; + + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + resourceDescriptorInfos = {}; + resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos.data = {}; + resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + + hostAddressRangesResources = {}; + hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResources.size = bufferDescriptorSize; + + heapResIndex++; + + if (vkWriteResourceDescriptorsEXT( + device, + 1, + &resourceDescriptorInfos, + &hostAddressRangesResources + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + + + return; + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VmaAllocationInfo allocResult{}; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation, + &allocResult + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + + VkBufferCreateInfo bufferInfo{}; + bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(indices[0]) * indices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + VmaAllocationInfo stagingAllocResult = {}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &indexBuffer, + &indexAllocation, + &stagingAllocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create index buffer!"); + } + + copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); + } + + + void createTextureSampler() { + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_GREATER); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBufferDeviceAddressInfo addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + float negative = 1; + if (currentImage == 0) + negative = -1; + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + void* mapped; + vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); + memcpy(mapped, &ubo, sizeof(ubo)); + vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + VkPushConstantRange pushConstantRange; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(uint32_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; + //shaderCreateInfo.pushConstantRangeCount = 1; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 7cd1fe92..18a44cfd 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -153,6 +153,10 @@ add_chapter (19_sampler SHADER 17_shader_ubo TEXTURES ../images/texture.jpg) +add_chapter (20_texture_mapping + SHADER 26_shader_textures + TEXTURES ../images/texture.jpg) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From 338c84bb744b236c061596de88aa0f510ec90302 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 18:58:19 +0200 Subject: [PATCH 33/47] texture mapping --- code/18_texture_image.cpp | 4 +- code/20_shader_textures.frag | 10 ++- code/20_shader_textures.vert | 6 +- code/20_texture_mapping.cpp | 139 ++++++++++++++++++++++++++++++----- code/26_texture_mapping.cpp | 5 +- code/CMakeLists.txt | 2 +- 6 files changed, 141 insertions(+), 25 deletions(-) diff --git a/code/18_texture_image.cpp b/code/18_texture_image.cpp index e8325878..127c3ec3 100644 --- a/code/18_texture_image.cpp +++ b/code/18_texture_image.cpp @@ -632,8 +632,8 @@ class HelloTriangleApplication { VkHostAddressRangeEXT hostAddressRangesResources; VkResourceDescriptorInfoEXT resourceDescriptorInfos; - addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, - addrInfo[i].buffer = uniformBuffers[i]; + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; deviceAddressRangesUniformBuffer[i] = {}; deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); diff --git a/code/20_shader_textures.frag b/code/20_shader_textures.frag index 873f5410..3bb62619 100644 --- a/code/20_shader_textures.frag +++ b/code/20_shader_textures.frag @@ -1,6 +1,10 @@ #version 450 -layout(binding = 1) uniform sampler2D texSampler; +layout(push_constant) uniform PushData { + int offset; +} pushData; + +//layout(set = 0, binding = 1) uniform sampler2D texSampler; layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragTexCoord; @@ -8,5 +12,7 @@ layout(location = 1) in vec2 fragTexCoord; layout(location = 0) out vec4 outColor; void main() { - outColor = texture(texSampler, fragTexCoord); + //outColor = texture(texSampler, fragTexCoord); + outColor = vec4(fragTexCoord, 0.0, 1.0); + //outColor = vec4(fragColor, 1.0); } diff --git a/code/20_shader_textures.vert b/code/20_shader_textures.vert index 5510aa3f..c38a010c 100644 --- a/code/20_shader_textures.vert +++ b/code/20_shader_textures.vert @@ -1,6 +1,10 @@ #version 450 -layout(binding = 0) uniform UniformBufferObject { +layout(push_constant) uniform PushData { + int offset; +} pushData; + +layout(set = 0, binding = 0) uniform UBO { mat4 model; mat4 view; mat4 proj; diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index 4ee28528..c6da1f2a 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -86,6 +86,7 @@ struct SwapChainSupportDetails { struct Vertex { glm::vec2 pos; glm::vec3 color; + glm::vec2 texCoord; static VkVertexInputBindingDescription2EXT getBindingDescription() { VkVertexInputBindingDescription2EXT bindingDescription{}; @@ -98,8 +99,8 @@ struct Vertex { return bindingDescription; } - static std::array getAttributeDescriptions() { - std::array attributeDescriptions{}; + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; attributeDescriptions[0].binding = 0; @@ -113,6 +114,12 @@ struct Vertex { attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[1].offset = offsetof(Vertex, color); + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + return attributeDescriptions; } }; @@ -124,10 +131,10 @@ struct UniformBufferObject { }; const std::vector vertices = { - {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, - {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, - {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} }; const std::vector indices = { @@ -162,9 +169,16 @@ class HelloTriangleApplication { VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; std::vector descriptorHeapResourcesBuffers; std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + VkDeviceSize bufferHeapOffset{ 0 }; VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; VkDeviceSize heapbufferSize; + VkDeviceSize heapSamplerbufferSize; + VkQueue graphicsQueue; @@ -186,7 +200,7 @@ class HelloTriangleApplication { VmaAllocation vertexAllocation; VkBuffer indexBuffer; VmaAllocation indexAllocation; - + VkImage textureImage; VmaAllocation textureImageAllocation; VkImageView textureImageView; @@ -237,6 +251,7 @@ class HelloTriangleApplication { createTextureSampler(); createUniformBuffers(); prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); createCommandBuffers(); createSyncObjects(); } @@ -635,7 +650,7 @@ class HelloTriangleApplication { } } - + size_t heapResIndex{ 0 }; std::array addrInfo{}; @@ -643,20 +658,20 @@ class HelloTriangleApplication { for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VkHostAddressRangeEXT hostAddressRangesResources; - VkResourceDescriptorInfoEXT resourceDescriptorInfos; + VkResourceDescriptorInfoEXT resourceDescriptorInfos[2]; - addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, - addrInfo[i].buffer = uniformBuffers[i]; + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; deviceAddressRangesUniformBuffer[i] = {}; deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); - resourceDescriptorInfos = {}; - resourceDescriptorInfos.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; - resourceDescriptorInfos.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - resourceDescriptorInfos.data = {}; - resourceDescriptorInfos.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos[i] = {}; + resourceDescriptorInfos[i].sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfos[i].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfos[i].data = {}; + resourceDescriptorInfos[i].data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; hostAddressRangesResources = {}; hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); @@ -666,8 +681,8 @@ class HelloTriangleApplication { if (vkWriteResourceDescriptorsEXT( device, - 1, - &resourceDescriptorInfos, + 2, + resourceDescriptorInfos, &hostAddressRangesResources ) != VK_SUCCESS) { throw std::runtime_error("failed to write resource descriptors!"); @@ -675,6 +690,80 @@ class HelloTriangleApplication { } } + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + void createGraphicsPipeline() { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); @@ -949,6 +1038,8 @@ class HelloTriangleApplication { if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } + + } @@ -1267,6 +1358,18 @@ class HelloTriangleApplication { vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + VkBufferDeviceAddressInfo addrSamplerInfo{}; + addrSamplerInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrSamplerInfo.buffer = descriptorHeapSamplerBuffer; + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrSamplerInfo); + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; diff --git a/code/26_texture_mapping.cpp b/code/26_texture_mapping.cpp index 51168ec1..948d33ff 100644 --- a/code/26_texture_mapping.cpp +++ b/code/26_texture_mapping.cpp @@ -1,3 +1,5 @@ +#include "Volk/volk.h" + #define GLFW_INCLUDE_VULKAN #include @@ -128,6 +130,7 @@ const std::vector indices = { class HelloTriangleApplication { public: void run() { + volkInitialize(); initWindow(); initVulkan(); mainLoop(); @@ -326,7 +329,7 @@ class HelloTriangleApplication { appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 18a44cfd..239abbf2 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -154,7 +154,7 @@ add_chapter (19_sampler TEXTURES ../images/texture.jpg) add_chapter (20_texture_mapping - SHADER 26_shader_textures + SHADER 20_shader_textures TEXTURES ../images/texture.jpg) add_chapter (16_frames_in_flight From a093e8b30b736b5338feb50bde18a668fc8e9350 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 22:24:35 +0200 Subject: [PATCH 34/47] - Fixed texturemapping! --- code/20_shader_textures.frag | 10 ++-- code/20_texture_mapping.cpp | 106 +++++++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 26 deletions(-) diff --git a/code/20_shader_textures.frag b/code/20_shader_textures.frag index 3bb62619..f1af9769 100644 --- a/code/20_shader_textures.frag +++ b/code/20_shader_textures.frag @@ -1,10 +1,13 @@ + + #version 450 layout(push_constant) uniform PushData { int offset; } pushData; -//layout(set = 0, binding = 1) uniform sampler2D texSampler; +layout(set = 1, binding = 0) uniform texture2D texImage; +layout(set = 2, binding = 0) uniform sampler texSampler; layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragTexCoord; @@ -12,7 +15,6 @@ layout(location = 1) in vec2 fragTexCoord; layout(location = 0) out vec4 outColor; void main() { - //outColor = texture(texSampler, fragTexCoord); - outColor = vec4(fragTexCoord, 0.0, 1.0); - //outColor = vec4(fragColor, 1.0); + outColor = texture(sampler2D(texImage, texSampler), fragTexCoord); + //outColor = vec4(fragTexCoord, 0.0, 1.0); } diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index c6da1f2a..aea86cdb 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -176,9 +176,10 @@ class HelloTriangleApplication { VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize samplerHeapOffset{ 0 }; VkDeviceSize samplerDescriptorSize{ 0 }; - VkDeviceSize heapbufferSize; - VkDeviceSize heapSamplerbufferSize; - + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; VkQueue graphicsQueue; @@ -242,7 +243,6 @@ class HelloTriangleApplication { createVMA(); createSwapChain(); createImageViews(); - createGraphicsPipeline(); createCommandPool(); createVertexBuffer(); createIndexBuffer(); @@ -252,6 +252,7 @@ class HelloTriangleApplication { createUniformBuffers(); prepareDescriptorHeap(); prepareSamplerDescriptorHeap(); + createShaderObjects(); createCommandBuffers(); createSyncObjects(); } @@ -650,6 +651,9 @@ class HelloTriangleApplication { } } + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); size_t heapResIndex{ 0 }; @@ -657,9 +661,10 @@ class HelloTriangleApplication { std::array deviceAddressRangesUniformBuffer{}; for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - VkHostAddressRangeEXT hostAddressRangesResources; - VkResourceDescriptorInfoEXT resourceDescriptorInfos[2]; + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + // Uniform buffer addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addrInfo[i].buffer = uniformBuffers[i]; @@ -667,23 +672,56 @@ class HelloTriangleApplication { deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); - resourceDescriptorInfos[i] = {}; - resourceDescriptorInfos[i].sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; - resourceDescriptorInfos[i].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - resourceDescriptorInfos[i].data = {}; - resourceDescriptorInfos[i].data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); - hostAddressRangesResources = {}; - hostAddressRangesResources.address = static_cast(allocResult[i].pMappedData); - hostAddressRangesResources.size = bufferDescriptorSize; heapResIndex++; if (vkWriteResourceDescriptorsEXT( device, - 2, - resourceDescriptorInfos, - &hostAddressRangesResources + resourceDescriptorInfos.size(), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() ) != VK_SUCCESS) { throw std::runtime_error("failed to write resource descriptors!"); } @@ -699,7 +737,7 @@ class HelloTriangleApplication { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = heapbufferSize; + bufferInfo.size = heapSamplerbufferSize; bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VmaAllocationCreateInfo allocInfo{}; @@ -720,6 +758,7 @@ class HelloTriangleApplication { } + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; @@ -764,7 +803,7 @@ class HelloTriangleApplication { } } - void createGraphicsPipeline() { + void createShaderObjects() { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); @@ -1562,8 +1601,8 @@ class HelloTriangleApplication { float time = std::chrono::duration(currentTime - startTime).count(); float negative = 1; - if (currentImage == 0) - negative = -1; + //if (currentImage == 0) + // negative = -1; UniformBufferObject ubo{}; ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); @@ -1579,7 +1618,7 @@ class HelloTriangleApplication { VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { - std::array setAndBindingMappings; + std::array setAndBindingMappings; // Buffer binding setAndBindingMappings[0] = {}; @@ -1591,6 +1630,29 @@ class HelloTriangleApplication { setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); From 658b36afe3b03fe420b2f6e99319e65f7fda7a24 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 22:35:42 +0200 Subject: [PATCH 35/47] cleanup --- code/20_texture_mapping.cpp | 76 ++++++++++--------------------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index aea86cdb..64ac0c2d 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -204,8 +204,6 @@ class HelloTriangleApplication { VkImage textureImage; VmaAllocation textureImageAllocation; - VkImageView textureImageView; - VkSampler textureSampler; std::vector uniformBuffers; std::vector uniformAllocations; @@ -247,8 +245,6 @@ class HelloTriangleApplication { createVertexBuffer(); createIndexBuffer(); createTextureImage(); - createTextureImageView(); - createTextureSampler(); createUniformBuffers(); prepareDescriptorHeap(); prepareSamplerDescriptorHeap(); @@ -278,8 +274,6 @@ class HelloTriangleApplication { cleanupSwapChain(); - vkDestroySampler(device, textureSampler, nullptr); - vkDestroyImageView(device, textureImageView, nullptr); vmaDestroyImage(allocator, textureImage, textureImageAllocation); vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); @@ -292,12 +286,16 @@ class HelloTriangleApplication { vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); } + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + vmaDestroyAllocator(allocator); - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } vkDestroySemaphore(device, timelineSemaphore, nullptr); vkDestroyCommandPool(device, commandPool, nullptr); @@ -311,7 +309,6 @@ class HelloTriangleApplication { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } - vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); @@ -622,7 +619,7 @@ class HelloTriangleApplication { void prepareDescriptorHeap() { heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); - descriptorHeapResourcesAllocations.resize(2); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); std::vector allocResult{}; allocResult.resize(MAX_FRAMES_IN_FLIGHT); @@ -810,8 +807,6 @@ class HelloTriangleApplication { vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); - - return; } @@ -937,7 +932,7 @@ class HelloTriangleApplication { throw std::runtime_error("failed to create index buffer!"); } - copyBuffer(stagingBuffer, indexBuffer, allocResult.size); + copyBuffer(stagingBuffer, indexBuffer, bufferSize); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } @@ -1043,45 +1038,6 @@ class HelloTriangleApplication { } - void createTextureImageView() { - textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); - } - - - void createTextureSampler() { - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 1.0f; - - VkPhysicalDeviceProperties properties{}; - vkGetPhysicalDeviceProperties(physicalDevice, &properties); - - samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; - samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; - - if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { - throw std::runtime_error("failed to create texture sampler!"); - } - - - } - - void createUniformBuffers() { VkBufferCreateInfo bufferInfo{}; @@ -1460,14 +1416,20 @@ class HelloTriangleApplication { VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + // Acquire semaphores are per frame-in-flight; the render-finished semaphore is + // waited by present, which holds it until that swapchain image is re-acquired, + // so it must be per swapchain image and indexed by imageIndex. imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); } // Create timeline semaphore @@ -1534,7 +1496,7 @@ class HelloTriangleApplication { VkSemaphoreSubmitInfo signalBinary{}; signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; - signalBinary.semaphore = renderFinishedSemaphores[currentFrame]; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; VkSemaphoreSubmitInfo signalSemaphoreInfo{}; @@ -1573,7 +1535,7 @@ class HelloTriangleApplication { presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame]; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; VkSwapchainKHR swapChains[] = { swapChain }; presentInfo.swapchainCount = 1; From d3a64dbb66bf1b12609f8975f871d4287ea6a769 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 22:37:29 +0200 Subject: [PATCH 36/47] cleanup --- code/20_texture_mapping.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index 64ac0c2d..d375b606 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -788,7 +788,6 @@ class HelloTriangleApplication { // For multiple textures: // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i - if (vkWriteSamplerDescriptorsEXT( device, @@ -1416,9 +1415,6 @@ class HelloTriangleApplication { VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - // Acquire semaphores are per frame-in-flight; the render-finished semaphore is - // waited by present, which holds it until that swapchain image is re-acquired, - // so it must be per swapchain image and indexed by imageIndex. imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); renderFinishedSemaphores.resize(swapChainImages.size()); @@ -1562,12 +1558,8 @@ class HelloTriangleApplication { auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); - float negative = 1; - //if (currentImage == 0) - // negative = -1; - UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), negative * time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); ubo.proj[1][1] *= -1; // Vulkan clip correction From f18dec90b2217ad85e89c530e0a399a10a53e2de Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 22:43:19 +0200 Subject: [PATCH 37/47] -cleanup and improvements --- code/20_texture_mapping.cpp | 81 +++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index d375b606..a9bc4b6f 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -171,6 +171,8 @@ class HelloTriangleApplication { std::vector descriptorHeapResourcesAllocations; VkBuffer descriptorHeapSamplerBuffer; VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; VkDeviceSize bufferHeapOffset{ 0 }; VkDeviceSize bufferDescriptorSize{ 0 }; @@ -207,6 +209,7 @@ class HelloTriangleApplication { std::vector uniformBuffers; std::vector uniformAllocations; + std::vector uniformBuffersMapped; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; @@ -511,6 +514,9 @@ class HelloTriangleApplication { throw std::runtime_error("failed to create logical device!"); } + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } @@ -648,6 +654,15 @@ class HelloTriangleApplication { } } + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + // Image imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); @@ -754,6 +769,11 @@ class HelloTriangleApplication { throw std::runtime_error("failed to create resource descriptor heap!"); } + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); VkSamplerCreateInfo samplerInfo{}; @@ -888,17 +908,14 @@ class HelloTriangleApplication { VmaAllocation stagingAllocation; VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - VmaAllocationInfo allocResult{}; createBuffer( bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VMA_MEMORY_USAGE_AUTO, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, 0, stagingBuffer, - stagingAllocation, - &allocResult + stagingAllocation ); void* data = nullptr; @@ -906,30 +923,15 @@ class HelloTriangleApplication { memcpy(data, indices.data(), bufferSize); vmaUnmapMemory(allocator, stagingAllocation); - - VkBufferCreateInfo bufferInfo{}; - bufferInfo = {}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = sizeof(indices[0]) * indices.size(); - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - - VmaAllocationCreateInfo allocInfo{}; - allocInfo = {}; - allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; - allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; - allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; - - VmaAllocationInfo stagingAllocResult = {}; - if (vmaCreateBuffer( - allocator, - &bufferInfo, - &allocInfo, - &indexBuffer, - &indexAllocation, - &stagingAllocResult - ) != VK_SUCCESS) { - throw std::runtime_error("failed to create index buffer!"); - } + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); copyBuffer(stagingBuffer, indexBuffer, bufferSize); @@ -1047,10 +1049,12 @@ class HelloTriangleApplication { VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VmaAllocationInfo allocResult{}; if (vmaCreateBuffer( @@ -1063,6 +1067,8 @@ class HelloTriangleApplication { ) != VK_SUCCESS) { throw std::runtime_error("failed to create staging buffer!"); } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; } } @@ -1339,26 +1345,18 @@ class HelloTriangleApplication { vkCmdPushDataEXT(commandBuffer, &pushDataInfo); - VkBufferDeviceAddressInfo addrInfo{}; - addrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - addrInfo.buffer = descriptorHeapResourcesBuffers[currentFrame]; - VkBindHeapInfoEXT bindHeapinfo{}; bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; - bindHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrInfo); + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); - VkBufferDeviceAddressInfo addrSamplerInfo{}; - addrSamplerInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - addrSamplerInfo.buffer = descriptorHeapSamplerBuffer; - VkBindHeapInfoEXT bindSamplerHeapinfo{}; bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; - bindSamplerHeapinfo.heapRange.address = vkGetBufferDeviceAddress(device, &addrSamplerInfo); + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); @@ -1564,10 +1562,7 @@ class HelloTriangleApplication { ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); ubo.proj[1][1] *= -1; // Vulkan clip correction - void* mapped; - vmaMapMemory(allocator, uniformAllocations[currentImage], &mapped); - memcpy(mapped, &ubo, sizeof(ubo)); - vmaUnmapMemory(allocator, uniformAllocations[currentImage]); + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { From 1a61e441a8872579b5d33698dce4313d8a12fe69 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 20 Jun 2026 22:47:39 +0200 Subject: [PATCH 38/47] more cleanup --- code/20_texture_mapping.cpp | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index a9bc4b6f..5ad64678 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -174,7 +174,6 @@ class HelloTriangleApplication { std::vector descriptorHeapResourcesAddresses; VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; - VkDeviceSize bufferHeapOffset{ 0 }; VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize samplerHeapOffset{ 0 }; VkDeviceSize samplerDescriptorSize{ 0 }; @@ -666,8 +665,6 @@ class HelloTriangleApplication { // Image imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); - - size_t heapResIndex{ 0 }; std::array addrInfo{}; std::array deviceAddressRangesUniformBuffer{}; @@ -726,12 +723,9 @@ class HelloTriangleApplication { hostAddressRangesResourceImage.size = imageDescriptorSize; hostAddressRangesResources.push_back(hostAddressRangesResourceImage); - - heapResIndex++; - if (vkWriteResourceDescriptorsEXT( device, - resourceDescriptorInfos.size(), + static_cast(resourceDescriptorInfos.size()), resourceDescriptorInfos.data(), hostAddressRangesResources.data() ) != VK_SUCCESS) { @@ -939,20 +933,6 @@ class HelloTriangleApplication { } - uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createImage( uint32_t width, uint32_t height, @@ -1314,7 +1294,7 @@ class HelloTriangleApplication { vkCmdSetVertexInputEXT(commandBuffer, 1, &Vertex::getBindingDescription(), - Vertex::getAttributeDescriptions().size(), Vertex::getAttributeDescriptions().data() + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() ); VkShaderStageFlagBits stages[] = { @@ -1616,14 +1596,6 @@ class HelloTriangleApplication { shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; - VkPushConstantRange pushConstantRange; - pushConstantRange.offset = 0; - pushConstantRange.size = sizeof(uint32_t); - pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; - - //shaderCreateInfo.pPushConstantRanges = &pushConstantRange; - //shaderCreateInfo.pushConstantRangeCount = 1; - if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) { shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; From 88c521e46e1a26af42e44a397198bf128d8bde3c Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 28 Jun 2026 16:32:34 +0200 Subject: [PATCH 39/47] - depth buffering --- code/21_depth_buffering.cpp | 1923 ++++++++++++++++++++++++++++++++++ code/21_depth_buffering.frag | 20 + code/21_depth_buffering.vert | 24 + code/CMakeLists.txt | 4 + 4 files changed, 1971 insertions(+) create mode 100644 code/21_depth_buffering.cpp create mode 100644 code/21_depth_buffering.frag create mode 100644 code/21_depth_buffering.vert diff --git a/code/21_depth_buffering.cpp b/code/21_depth_buffering.cpp new file mode 100644 index 00000000..4862c9c2 --- /dev/null +++ b/code/21_depth_buffering.cpp @@ -0,0 +1,1923 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; + + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; + + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkImage depthImage; + VmaAllocation depthImageAllocation; + VkImageView depthImageView; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + std::vector uniformBuffersMapped; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createCommandPool(); + createDepthResources(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); + createShaderObjects(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + // Depth image is sized to the swapchain extent, so it lives with the swapchain. + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(allocator, depthImage, depthImageAllocation); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + + void createDepthResources() + { + VkFormat depthFormat = findDepthFormat(); + createImage( + swapChainExtent.width, + swapChainExtent.height, + depthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + depthImage, + depthImageAllocation + ); + + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Dynamic rendering does not auto-transition attachments. The depth image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_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; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + VkFormat findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + + // Uniform buffer + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapSamplerbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + + void createShaderObjects() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + return; + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/21_depth_buffering.frag b/code/21_depth_buffering.frag new file mode 100644 index 00000000..f1af9769 --- /dev/null +++ b/code/21_depth_buffering.frag @@ -0,0 +1,20 @@ + + +#version 450 + +layout(push_constant) uniform PushData { + int offset; +} pushData; + +layout(set = 1, binding = 0) uniform texture2D texImage; +layout(set = 2, binding = 0) uniform sampler texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(sampler2D(texImage, texSampler), fragTexCoord); + //outColor = vec4(fragTexCoord, 0.0, 1.0); +} diff --git a/code/21_depth_buffering.vert b/code/21_depth_buffering.vert new file mode 100644 index 00000000..39afba7c --- /dev/null +++ b/code/21_depth_buffering.vert @@ -0,0 +1,24 @@ +#version 450 + +layout(push_constant) uniform PushData { + int offset; +} pushData; + +layout(set = 0, binding = 0) uniform UBO { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 239abbf2..0002de7e 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -157,6 +157,10 @@ add_chapter (20_texture_mapping SHADER 20_shader_textures TEXTURES ../images/texture.jpg) +add_chapter (21_depth_buffering + SHADER 21_depth_buffering + TEXTURES ../images/texture.jpg) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From c208ee6709d65a5f4db3b76168a4d22c26f7dd77 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 28 Jun 2026 17:12:44 +0200 Subject: [PATCH 40/47] - model loading stub --- code/22_model_loading.cpp | 1923 +++++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 4 + 2 files changed, 1927 insertions(+) create mode 100644 code/22_model_loading.cpp diff --git a/code/22_model_loading.cpp b/code/22_model_loading.cpp new file mode 100644 index 00000000..4862c9c2 --- /dev/null +++ b/code/22_model_loading.cpp @@ -0,0 +1,1923 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; + + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; + + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkImage depthImage; + VmaAllocation depthImageAllocation; + VkImageView depthImageView; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + std::vector uniformBuffersMapped; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createCommandPool(); + createDepthResources(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); + createShaderObjects(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + // Depth image is sized to the swapchain extent, so it lives with the swapchain. + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(allocator, depthImage, depthImageAllocation); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + + void createDepthResources() + { + VkFormat depthFormat = findDepthFormat(); + createImage( + swapChainExtent.width, + swapChainExtent.height, + depthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + depthImage, + depthImageAllocation + ); + + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Dynamic rendering does not auto-transition attachments. The depth image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_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; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + VkFormat findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + + // Uniform buffer + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapSamplerbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + + void createShaderObjects() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + return; + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 0002de7e..129706d9 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -161,6 +161,10 @@ add_chapter (21_depth_buffering SHADER 21_depth_buffering TEXTURES ../images/texture.jpg) +add_chapter (22_depth_buffering + SHADER 21_depth_buffering + TEXTURES ../images/texture.jpg) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From eb7a10a8de5dd2260e1534b3fbd88ffafb27cb6f Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 5 Jul 2026 13:44:23 +0200 Subject: [PATCH 41/47] - model loading --- code/22_model_loading.cpp | 94 ++++++++++++++++++++++++++++++--------- code/CMakeLists.txt | 6 ++- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/code/22_model_loading.cpp b/code/22_model_loading.cpp index 4862c9c2..2ed9b5e8 100644 --- a/code/22_model_loading.cpp +++ b/code/22_model_loading.cpp @@ -6,12 +6,18 @@ #define STB_IMAGE_IMPLEMENTATION #include +#define TINYOBJLOADER_IMPLEMENTATION +#include + #define GLFW_INCLUDE_VULKAN #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL #include #include +#include +#include #include #include @@ -26,10 +32,15 @@ #include #include #include +#include + const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { @@ -84,6 +95,7 @@ struct SwapChainSupportDetails { }; + struct Vertex { glm::vec3 pos; glm::vec3 color; @@ -123,31 +135,26 @@ struct Vertex { return attributeDescriptions; } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + struct UniformBufferObject { glm::mat4 model; glm::mat4 view; glm::mat4 proj; }; -const std::vector vertices = { - {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - - {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} -}; - -const std::vector indices = { - 0, 1, 2, 2, 3, 0, - 4, 5, 6, 6, 7, 4 -}; - inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { return (size + alignment - 1) & ~(alignment - 1); @@ -210,6 +217,8 @@ class HelloTriangleApplication { VkCommandPool commandPool; std::vector commandBuffers; + std::vector vertices; + std::vector indices; VkBuffer vertexBuffer; VmaAllocation vertexAllocation; VkBuffer indexBuffer; @@ -257,6 +266,7 @@ class HelloTriangleApplication { createImageViews(); createCommandPool(); createDepthResources(); + loadModel(); createVertexBuffer(); createIndexBuffer(); createTextureImage(); @@ -917,6 +927,47 @@ class HelloTriangleApplication { return; } + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + std::string war; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &war, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = { 1.0f, 1.0f, 1.0f }; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + } + void createBuffer( VkDeviceSize size, VkBufferUsageFlags usage, @@ -1068,7 +1119,7 @@ class HelloTriangleApplication { void createTextureImage() { int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); VkDeviceSize imageSize = texWidth * texHeight * 4; if (!pixels) { @@ -1416,7 +1467,7 @@ class HelloTriangleApplication { VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); uint32_t pushconstants = currentFrame; @@ -1603,8 +1654,9 @@ class HelloTriangleApplication { submitInfo.signalSemaphoreInfoCount = 2; submitInfo.pSignalSemaphoreInfos = signals; - if (vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { - throw std::runtime_error("failed to submit draw command buffer!"); + VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + if (submitResult != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer! VkResult = " + std::to_string(submitResult) + " (frame " + std::to_string(timelineValue) + ")"); } diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 129706d9..7bffdedc 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -161,9 +161,11 @@ add_chapter (21_depth_buffering SHADER 21_depth_buffering TEXTURES ../images/texture.jpg) -add_chapter (22_depth_buffering +add_chapter (22_model_loading SHADER 21_depth_buffering - TEXTURES ../images/texture.jpg) + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS tinyobjloader::tinyobjloader) add_chapter (16_frames_in_flight SHADER 08_shader_base) From 638e49510c13ff8063eb7a09a78bd5d01b77bc4d Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 12 Jul 2026 18:52:23 +0200 Subject: [PATCH 42/47] Mipmapping --- code/23_mipmapping.cpp | 2102 ++++++++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 6 + 2 files changed, 2108 insertions(+) create mode 100644 code/23_mipmapping.cpp diff --git a/code/23_mipmapping.cpp b/code/23_mipmapping.cpp new file mode 100644 index 00000000..a63ea2c6 --- /dev/null +++ b/code/23_mipmapping.cpp @@ -0,0 +1,2102 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; + + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkImage depthImage; + VmaAllocation depthImageAllocation; + VkImageView depthImageView; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + uint32_t mipLevels; + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + std::vector uniformBuffersMapped; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createCommandPool(); + createDepthResources(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); + createShaderObjects(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + // Depth image is sized to the swapchain extent, so it lives with the swapchain. + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(allocator, depthImage, depthImageAllocation); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + + void createDepthResources() + { + VkFormat depthFormat = findDepthFormat(); + createImage( + swapChainExtent.width, + swapChainExtent.height, + depthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + depthImage, + depthImageAllocation + ); + + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Dynamic rendering does not auto-transition attachments. The depth image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_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; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + VkFormat findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + + // Uniform buffer + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapSamplerbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + + void createShaderObjects() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + return; + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + std::string war; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &war, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = { 1.0f, 1.0f, 1.0f }; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createImage( + uint32_t width, + uint32_t height, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + void generateMipmaps( + VkImage image, + VkFormat format, + uint32_t width, + uint32_t height, + uint32_t mipLevels) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + const auto features = props.optimalTilingFeatures; + + if (!(features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) || + !(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) || + !(features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) + { + throw std::runtime_error("Format does not support linear blitting."); + } + + VkCommandBuffer cmd = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + int32_t mipWidth = static_cast(width); + int32_t mipHeight = static_cast(height); + + for (uint32_t level = 1; level < mipLevels; ++level) + { + barrier.subresourceRange.baseMipLevel = level - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + + VkDependencyInfo dependency = {}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dependency); + + VkImageBlit blit{}; + + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = level - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; + + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = level; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { + std::max(1, mipWidth / 2), + std::max(1, mipHeight / 2), + 1 + }; + + vkCmdBlitImage( + cmd, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + vkCmdPipelineBarrier2(cmd, &dependency); + + mipWidth = std::max(1, mipWidth / 2); + mipHeight = std::max(1, mipHeight / 2); + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + VkDependencyInfo dependency; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + dependency.imageMemoryBarrierCount = 1, + dependency.pImageMemoryBarriers = &barrier, + + vkCmdPipelineBarrier2(cmd, &dependency); + + endSingleTimeCommands(cmd); + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, mipLevels); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels = 1) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + if (submitResult != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer! VkResult = " + std::to_string(submitResult) + " (frame " + std::to_string(timelineValue) + ")"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 7bffdedc..4b91fb2f 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -167,6 +167,12 @@ add_chapter (22_model_loading TEXTURES ../resources/viking_room.png LIBS tinyobjloader::tinyobjloader) +add_chapter (23_mipmapping + SHADER 21_depth_buffering + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS tinyobjloader::tinyobjloader) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From 994d2bea629ee1c273db20506d8704b45ff62b47 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Tue, 14 Jul 2026 09:46:29 +0200 Subject: [PATCH 43/47] Sampler heap fix --- code/20_texture_mapping.cpp | 2 ++ code/21_depth_buffering.cpp | 2 ++ code/22_model_loading.cpp | 2 ++ code/23_mipmapping.cpp | 57 +++++++++++++++++++------------------ 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/code/20_texture_mapping.cpp b/code/20_texture_mapping.cpp index 5ad64678..5407b9e8 100644 --- a/code/20_texture_mapping.cpp +++ b/code/20_texture_mapping.cpp @@ -1329,6 +1329,7 @@ class HelloTriangleApplication { bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); @@ -1338,6 +1339,7 @@ class HelloTriangleApplication { bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); diff --git a/code/21_depth_buffering.cpp b/code/21_depth_buffering.cpp index 4862c9c2..acbf25a3 100644 --- a/code/21_depth_buffering.cpp +++ b/code/21_depth_buffering.cpp @@ -1432,6 +1432,7 @@ class HelloTriangleApplication { bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); @@ -1441,6 +1442,7 @@ class HelloTriangleApplication { bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); diff --git a/code/22_model_loading.cpp b/code/22_model_loading.cpp index 2ed9b5e8..26347be2 100644 --- a/code/22_model_loading.cpp +++ b/code/22_model_loading.cpp @@ -1483,6 +1483,7 @@ class HelloTriangleApplication { bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); @@ -1492,6 +1493,7 @@ class HelloTriangleApplication { bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); diff --git a/code/23_mipmapping.cpp b/code/23_mipmapping.cpp index a63ea2c6..f9d2a5b1 100644 --- a/code/23_mipmapping.cpp +++ b/code/23_mipmapping.cpp @@ -622,7 +622,7 @@ class HelloTriangleApplication { swapChainExtent = extent; } - VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels = 1) { VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -633,7 +633,7 @@ class HelloTriangleApplication { viewInfo.subresourceRange.levelCount = mipLevels; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - + VkImageView imageView; if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create image view!"); @@ -657,6 +657,7 @@ class HelloTriangleApplication { createImage( swapChainExtent.width, swapChainExtent.height, + 1, depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depthImage, @@ -806,10 +807,10 @@ class HelloTriangleApplication { imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageViewCreateInfo.subresourceRange.baseMipLevel = 0; - imageViewCreateInfo.subresourceRange.levelCount = 1; + imageViewCreateInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; imageViewCreateInfo.subresourceRange.layerCount = 1; - + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; imageDescriptorInfo.pView = &imageViewCreateInfo; @@ -896,9 +897,9 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; + samplerInfo.mipLodBias = 1.0f; + samplerInfo.minLod = 5.0f; + samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; VkHostAddressRangeEXT hostAddressRangesSamplers = {}; @@ -1082,6 +1083,7 @@ class HelloTriangleApplication { void createImage( uint32_t width, uint32_t height, + uint32_t mipLevels, VkFormat format, VkImageUsageFlags usage, VkImage& image, @@ -1138,19 +1140,16 @@ class HelloTriangleApplication { VkCommandBuffer cmd = beginSingleTimeCommands(); - VkImageMemoryBarrier2 barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; int32_t mipWidth = static_cast(width); int32_t mipHeight = static_cast(height); @@ -1168,7 +1167,7 @@ class HelloTriangleApplication { barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; - VkDependencyInfo dependency = {}; + VkDependencyInfo dependency{}; dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependency.imageMemoryBarrierCount = 1; dependency.pImageMemoryBarriers = &barrier; @@ -1231,10 +1230,10 @@ class HelloTriangleApplication { barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; - VkDependencyInfo dependency; - dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - dependency.imageMemoryBarrierCount = 1, - dependency.pImageMemoryBarriers = &barrier, + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; vkCmdPipelineBarrier2(cmd, &dependency); @@ -1275,6 +1274,7 @@ class HelloTriangleApplication { createImage( texWidth, texHeight, + mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, textureImage, @@ -1606,12 +1606,14 @@ class HelloTriangleApplication { vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + // The reserved range is driver-internal and must not overlap app descriptors, + // which are written from offset 0 — so it goes at the tail of the heap. VkBindHeapInfoEXT bindHeapinfo{}; bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; - vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); @@ -1619,9 +1621,10 @@ class HelloTriangleApplication { bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); - + VkViewport viewport{}; viewport.x = 0.0f; From c0725be8ab72b7bb53eb92c14e8073efe70c1784 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Thu, 13 Aug 2026 09:26:22 +0200 Subject: [PATCH 44/47] - Added multisampling start --- code/23_mipmapping.cpp | 2 +- code/24_multisampling.cpp | 2105 +++++++++++++++++++++++++++++++++++++ code/CMakeLists.txt | 6 + 3 files changed, 2112 insertions(+), 1 deletion(-) create mode 100644 code/24_multisampling.cpp diff --git a/code/23_mipmapping.cpp b/code/23_mipmapping.cpp index f9d2a5b1..6c91ff33 100644 --- a/code/23_mipmapping.cpp +++ b/code/23_mipmapping.cpp @@ -898,7 +898,7 @@ class HelloTriangleApplication { samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 1.0f; - samplerInfo.minLod = 5.0f; + samplerInfo.minLod = 1.0f; samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; diff --git a/code/24_multisampling.cpp b/code/24_multisampling.cpp new file mode 100644 index 00000000..6c91ff33 --- /dev/null +++ b/code/24_multisampling.cpp @@ -0,0 +1,2105 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; + + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; + + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkImage depthImage; + VmaAllocation depthImageAllocation; + VkImageView depthImageView; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + uint32_t mipLevels; + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + std::vector uniformBuffersMapped; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createCommandPool(); + createDepthResources(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); + createShaderObjects(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + // Depth image is sized to the swapchain extent, so it lives with the swapchain. + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(allocator, depthImage, depthImageAllocation); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels = 1) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + + void createDepthResources() + { + VkFormat depthFormat = findDepthFormat(); + createImage( + swapChainExtent.width, + swapChainExtent.height, + 1, + depthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + depthImage, + depthImageAllocation + ); + + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Dynamic rendering does not auto-transition attachments. The depth image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_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; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + VkFormat findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + + // Uniform buffer + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapSamplerbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 1.0f; + samplerInfo.minLod = 1.0f; + samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + + void createShaderObjects() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + return; + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + std::string war; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &war, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = { 1.0f, 1.0f, 1.0f }; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createImage( + uint32_t width, + uint32_t height, + uint32_t mipLevels, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + void generateMipmaps( + VkImage image, + VkFormat format, + uint32_t width, + uint32_t height, + uint32_t mipLevels) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + const auto features = props.optimalTilingFeatures; + + if (!(features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) || + !(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) || + !(features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) + { + throw std::runtime_error("Format does not support linear blitting."); + } + + VkCommandBuffer cmd = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + int32_t mipWidth = static_cast(width); + int32_t mipHeight = static_cast(height); + + for (uint32_t level = 1; level < mipLevels; ++level) + { + barrier.subresourceRange.baseMipLevel = level - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dependency); + + VkImageBlit blit{}; + + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = level - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; + + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = level; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { + std::max(1, mipWidth / 2), + std::max(1, mipHeight / 2), + 1 + }; + + vkCmdBlitImage( + cmd, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + vkCmdPipelineBarrier2(cmd, &dependency); + + mipWidth = std::max(1, mipWidth / 2); + mipHeight = std::max(1, mipHeight / 2); + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dependency); + + endSingleTimeCommands(cmd); + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + mipLevels, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, mipLevels); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels = 1) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0x1; + vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = swapChainImageViews[imageIndex]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + // The reserved range is driver-internal and must not overlap app descriptors, + // which are written from offset 0 — so it goes at the tail of the heap. + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + if (submitResult != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer! VkResult = " + std::to_string(submitResult) + " (frame " + std::to_string(timelineValue) + ")"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4b91fb2f..1cf3ff77 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -173,6 +173,12 @@ add_chapter (23_mipmapping TEXTURES ../resources/viking_room.png LIBS tinyobjloader::tinyobjloader) +add_chapter (24_multisampling + SHADER 21_depth_buffering + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS tinyobjloader::tinyobjloader) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From 5bf30c4f325c6507fd15799bbe88a3b32131e8b4 Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sat, 15 Aug 2026 12:26:27 +0200 Subject: [PATCH 45/47] - Finished multisampling --- code/23_mipmapping.cpp | 4 +- code/24_multisampling.cpp | 98 +++++++++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/code/23_mipmapping.cpp b/code/23_mipmapping.cpp index 6c91ff33..0634ac3b 100644 --- a/code/23_mipmapping.cpp +++ b/code/23_mipmapping.cpp @@ -897,8 +897,8 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.mipLodBias = 1.0f; - samplerInfo.minLod = 1.0f; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; diff --git a/code/24_multisampling.cpp b/code/24_multisampling.cpp index 6c91ff33..198b0ec7 100644 --- a/code/24_multisampling.cpp +++ b/code/24_multisampling.cpp @@ -178,6 +178,7 @@ class HelloTriangleApplication { VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; VkDevice device; VmaAllocator allocator; @@ -207,6 +208,10 @@ class HelloTriangleApplication { VkExtent2D swapChainExtent; std::vector swapChainImageViews; + VkImage colorImage; + VmaAllocation colorImageAllocation; + VkImageView colorImageView; + VkImage depthImage; VmaAllocation depthImageAllocation; VkImageView depthImageView; @@ -266,6 +271,7 @@ class HelloTriangleApplication { createSwapChain(); createImageViews(); createCommandPool(); + createColorResources(); createDepthResources(); loadModel(); createVertexBuffer(); @@ -293,6 +299,9 @@ class HelloTriangleApplication { vkDestroyImageView(device, depthImageView, nullptr); vmaDestroyImage(allocator, depthImage, depthImageAllocation); + vkDestroyImageView(device, colorImageView, nullptr); + vmaDestroyImage(allocator, colorImage, colorImageAllocation); + for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } @@ -361,6 +370,7 @@ class HelloTriangleApplication { createSwapChain(); createImageViews(); + createColorResources(); createDepthResources(); } @@ -445,6 +455,7 @@ class HelloTriangleApplication { for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); break; } } @@ -651,6 +662,54 @@ class HelloTriangleApplication { } + void createColorResources() + { + VkFormat colorFormat = swapChainImageFormat; + createImage( + swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + colorFormat, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + colorImage, + colorImageAllocation + ); + + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT); + + // Dynamic rendering does not auto-transition attachments. The color image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = colorImage; + 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.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + void createDepthResources() { VkFormat depthFormat = findDepthFormat(); @@ -658,6 +717,7 @@ class HelloTriangleApplication { swapChainExtent.width, swapChainExtent.height, 1, + msaaSamples, depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depthImage, @@ -897,8 +957,8 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.mipLodBias = 1.0f; - samplerInfo.minLod = 1.0f; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; @@ -1084,6 +1144,7 @@ class HelloTriangleApplication { uint32_t width, uint32_t height, uint32_t mipLevels, + VkSampleCountFlagBits numSamples, VkFormat format, VkImageUsageFlags usage, VkImage& image, @@ -1101,7 +1162,7 @@ class HelloTriangleApplication { imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.samples = numSamples; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo allocInfo{}; @@ -1240,6 +1301,23 @@ class HelloTriangleApplication { endSingleTimeCommands(cmd); } + + VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; + } + + void createTextureImage() { int texWidth, texHeight, texChannels; stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); @@ -1275,6 +1353,7 @@ class HelloTriangleApplication { texWidth, texHeight, mipLevels, + VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, textureImage, @@ -1496,14 +1575,14 @@ class HelloTriangleApplication { vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); - vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); + vkCmdSetRasterizationSamplesEXT(commandBuffer, msaaSamples); vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); - const VkSampleMask sample_mask = 0x1; - vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); + const VkSampleMask sample_mask = 0xFFFFFFFF; + vkCmdSetSampleMaskEXT(commandBuffer, msaaSamples, &sample_mask); vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); @@ -1547,10 +1626,13 @@ class HelloTriangleApplication { VkRenderingAttachmentInfo colorAttachment{}; colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - colorAttachment.imageView = swapChainImageViews[imageIndex]; - colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + colorAttachment.imageView = colorImageView; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; + colorAttachment.resolveImageView = swapChainImageViews[imageIndex]; + colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; VkRenderingAttachmentInfo depthAttachment{}; From c39d532c446c506712380413f15ae1da9ca6c48c Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 16 Aug 2026 16:55:04 +0200 Subject: [PATCH 46/47] - Compute shader setup --- code/25_compute_shader.cpp | 2202 +++++++++++++++++++++++++++++++++++ code/25_shader_compute.comp | 40 + code/25_shader_compute.frag | 11 + code/25_shader_compute.vert | 13 + code/CMakeLists.txt | 58 +- 5 files changed, 2304 insertions(+), 20 deletions(-) create mode 100644 code/25_compute_shader.cpp create mode 100644 code/25_shader_compute.comp create mode 100644 code/25_shader_compute.frag create mode 100644 code/25_shader_compute.vert diff --git a/code/25_compute_shader.cpp b/code/25_compute_shader.cpp new file mode 100644 index 00000000..02797b18 --- /dev/null +++ b/code/25_compute_shader.cpp @@ -0,0 +1,2202 @@ +#include "Volk/volk.h" +#define VMA_IMPLEMENTATION +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vma/vk_mem_alloc.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME, +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + + + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + + +inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} + +class HelloTriangleApplication { +public: + void run() { + volkInitialize(); + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; + VkDevice device; + VmaAllocator allocator; + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; + std::vector descriptorHeapResourcesBuffers; + std::vector descriptorHeapResourcesAllocations; + VkBuffer descriptorHeapSamplerBuffer; + VmaAllocation descriptorHeapSamplerAllocation; + std::vector descriptorHeapResourcesAddresses; + VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; + + VkDeviceSize bufferDescriptorSize{ 0 }; + VkDeviceSize samplerHeapOffset{ 0 }; + VkDeviceSize samplerDescriptorSize{ 0 }; + VkDeviceSize heapbufferSize{ 0 }; + VkDeviceSize heapSamplerbufferSize{ 0 }; + VkDeviceSize imageHeapOffset{ 0 }; + VkDeviceSize imageDescriptorSize{ 0 }; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + + VkImage colorImage; + VmaAllocation colorImageAllocation; + VkImageView colorImageView; + + VkImage depthImage; + VmaAllocation depthImageAllocation; + VkImageView depthImageView; + + VkShaderEXT vertShader; + VkShaderEXT fragShader; + + VkShaderEXT vertShaderPart; + VkShaderEXT fragShaderPart; + VkShaderEXT compShaderPart; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VkBuffer indexBuffer; + VmaAllocation indexAllocation; + + uint32_t mipLevels; + VkImage textureImage; + VmaAllocation textureImageAllocation; + + std::vector uniformBuffers; + std::vector uniformAllocations; + std::vector uniformBuffersMapped; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + VkSemaphore timelineSemaphore; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createVMA(); + createSwapChain(); + createImageViews(); + createCommandPool(); + createColorResources(); + createDepthResources(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createTextureImage(); + createUniformBuffers(); + prepareDescriptorHeap(); + prepareSamplerDescriptorHeap(); + createShaderObjects(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + // Depth image is sized to the swapchain extent, so it lives with the swapchain. + vkDestroyImageView(device, depthImageView, nullptr); + vmaDestroyImage(allocator, depthImage, depthImageAllocation); + + vkDestroyImageView(device, colorImageView, nullptr); + vmaDestroyImage(allocator, colorImage, colorImageAllocation); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + + cleanupSwapChain(); + + vmaDestroyImage(allocator, textureImage, textureImageAllocation); + + vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); + vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + } + + for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { + vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); + } + + vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); + + vmaDestroyAllocator(allocator); + + for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + } + vkDestroySemaphore(device, timelineSemaphore, nullptr); + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyShaderEXT(device, fragShader, nullptr); + vkDestroyShaderEXT(device, vertShader, nullptr); + + vkDestroyShaderEXT(device, vertShaderPart, nullptr); + vkDestroyShaderEXT(device, fragShaderPart, nullptr); + vkDestroyShaderEXT(device, compShaderPart, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createColorResources(); + createDepthResources(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; + } + else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + + volkLoadInstance(instance); + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Get physical device properties + descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + + VkPhysicalDeviceProperties2 props{}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props.pNext = &descriptorHeapProperties; + + vkGetPhysicalDeviceProperties2(physicalDevice, &props); + + bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); + + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; + shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + shaderObjectFeatures.pNext = nullptr; + shaderObjectFeatures.shaderObject = VK_TRUE; + + VkPhysicalDeviceFeatures2 deviceFeatures2{}; + deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + deviceFeatures2.features.samplerAnisotropy = VK_TRUE; + deviceFeatures2.pNext = &shaderObjectFeatures; + + VkPhysicalDeviceVulkan12Features vulkan12Features{}; + vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = VK_TRUE; + vulkan12Features.pNext = &deviceFeatures2; + + VkPhysicalDeviceVulkan13Features vulkan13Features{}; + vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + vulkan13Features.synchronization2 = VK_TRUE; + vulkan13Features.dynamicRendering = VK_TRUE; + vulkan13Features.pNext = &vulkan12Features; + + VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; + descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + descriptorHeapFeatures.descriptorHeap = VK_TRUE; + descriptorHeapFeatures.pNext = &vulkan13Features; + + VkPhysicalDeviceMaintenance5Features maintenance5Features{}; + maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; + maintenance5Features.maintenance5 = VK_TRUE; + maintenance5Features.pNext = &descriptorHeapFeatures; + + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; + untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untypedPointersFeatures.pNext = &maintenance5Features; + untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pNext = &maintenance5Features; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + // Load device-level entry points directly (skips the instance dispatch hop). + volkLoadDevice(device); + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createVMA() + { + VmaVulkanFunctions funcs{}; + funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; + funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocatorInfo{}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + allocatorInfo.pVulkanFunctions = &funcs; + allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; + + if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { + throw std::runtime_error("failed to create vma allocator!"); + } + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels = 1) { + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + + void createColorResources() + { + VkFormat colorFormat = swapChainImageFormat; + createImage( + swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + colorFormat, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + colorImage, + colorImageAllocation + ); + + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT); + + // Dynamic rendering does not auto-transition attachments. The color image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = colorImage; + 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.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + void createDepthResources() + { + VkFormat depthFormat = findDepthFormat(); + createImage( + swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + depthFormat, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + depthImage, + depthImageAllocation + ); + + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); + + // Dynamic rendering does not auto-transition attachments. The depth image is never + // presented or sampled, so a single transition into the attachment layout suffices; + // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_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; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + VkFormat findDepthFormat() { + return findSupportedFormat( + { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + void prepareDescriptorHeap() + { + heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); + descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); + descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); + std::vector allocResult{}; + allocResult.resize(MAX_FRAMES_IN_FLIGHT); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapResourcesBuffers[i], + &descriptorHeapResourcesAllocations[i], + &allocResult[i] + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + } + + // Cache the per-frame heap device addresses (queried once, used every frame at bind time). + descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkBufferDeviceAddressInfo heapAddrInfo{}; + heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; + descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); + } + + // Image + imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + + std::array addrInfo{}; + std::array deviceAddressRangesUniformBuffer{}; + for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + + std::vector hostAddressRangesResources; + std::vector resourceDescriptorInfos; + + // Uniform buffer + addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addrInfo[i].buffer = uniformBuffers[i]; + + deviceAddressRangesUniformBuffer[i] = {}; + deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); + deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); + + VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; + resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + resourceDescriptorInfo.data = {}; + resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; + resourceDescriptorInfos.push_back(resourceDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResource = {}; + hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); + hostAddressRangesResource.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResource); + + // Image views + VkImageViewCreateInfo imageViewCreateInfo = {}; + imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + imageViewCreateInfo.image = textureImage; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.baseMipLevel = 0; + imageViewCreateInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; + imageViewCreateInfo.subresourceRange.layerCount = 1; + + VkImageDescriptorInfoEXT imageDescriptorInfo = {}; + imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + imageDescriptorInfo.pView = &imageViewCreateInfo; + imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; + resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resourceImageDescriptorInfo.data = {}; + resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; + resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); + + VkHostAddressRangeEXT hostAddressRangesResourceImage; + hostAddressRangesResourceImage = {}; + hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; + hostAddressRangesResourceImage.size = imageDescriptorSize; + hostAddressRangesResources.push_back(hostAddressRangesResourceImage); + + if (vkWriteResourceDescriptorsEXT( + device, + static_cast(resourceDescriptorInfos.size()), + resourceDescriptorInfos.data(), + hostAddressRangesResources.data() + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + } + + + + void prepareSamplerDescriptorHeap() + { + heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); + samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = heapSamplerbufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VmaAllocationInfo allocResult; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &descriptorHeapSamplerBuffer, + &descriptorHeapSamplerAllocation, + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create resource descriptor heap!"); + } + + // Cache the sampler heap device address (queried once, used every frame at bind time). + VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; + samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; + descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); + + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 1.0f; + + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; + + + VkHostAddressRangeEXT hostAddressRangesSamplers = {}; + hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); + hostAddressRangesSamplers.size = samplerDescriptorSize; + + // For multiple textures: + // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i + + if (vkWriteSamplerDescriptorsEXT( + device, + 1, + &samplerInfo, + &hostAddressRangesSamplers + ) != VK_SUCCESS) { + throw std::runtime_error("failed to write resource descriptors!"); + } + } + + void createShaderObjects() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); + + auto vertShaderPartCode = readFile("shaders/vert1.spv"); + auto fragShaderPartCode = readFile("shaders/frag1.spv"); + auto compShaderPartCode = readFile("shaders/frag1.spv"); + + vertShaderPart = createShaderObject(vertShaderPartCode, VK_SHADER_STAGE_VERTEX_BIT); + fragShaderPart = createShaderObject(fragShaderPartCode, VK_SHADER_STAGE_FRAGMENT_BIT); + compShaderPart = createShaderObject(compShaderPartCode, VK_SHADER_STAGE_COMPUTE_BIT); + + return; + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + std::string war; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &war, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = { 1.0f, 1.0f, 1.0f }; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + } + + void createBuffer( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaMemoryUsage vmaUsage, + VmaAllocationCreateFlags vmaFlags, + VkMemoryPropertyFlags requiredFlags, + VkBuffer& buffer, + VmaAllocation& bufferAllocation, + VmaAllocationInfo* outAllocResult = 0 + ) { + if (size == 0) { + throw std::runtime_error("Vertex buffer size is 0!"); + } + + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = vmaUsage; + allocInfo.flags = vmaFlags; + allocInfo.requiredFlags = requiredFlags; + + VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &buffer, + &bufferAllocation, + allocDst + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + } + + void createVertexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, vertices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + vertexBuffer, + vertexAllocation + ); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + void createIndexBuffer() + { + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, indices.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + indexBuffer, + indexAllocation + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createImage( + uint32_t width, + uint32_t height, + uint32_t mipLevels, + VkSampleCountFlagBits numSamples, + VkFormat format, + VkImageUsageFlags usage, + VkImage& image, + VmaAllocation& allocation + ) { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = numSamples; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + if (vmaCreateImage( + allocator, + &imageInfo, + &allocInfo, + &image, + &allocation, + nullptr + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + } + + void generateMipmaps( + VkImage image, + VkFormat format, + uint32_t width, + uint32_t height, + uint32_t mipLevels) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + const auto features = props.optimalTilingFeatures; + + if (!(features & VK_FORMAT_FEATURE_BLIT_SRC_BIT) || + !(features & VK_FORMAT_FEATURE_BLIT_DST_BIT) || + !(features & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) + { + throw std::runtime_error("Format does not support linear blitting."); + } + + VkCommandBuffer cmd = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + int32_t mipWidth = static_cast(width); + int32_t mipHeight = static_cast(height); + + for (uint32_t level = 1; level < mipLevels; ++level) + { + barrier.subresourceRange.baseMipLevel = level - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dependency); + + VkImageBlit blit{}; + + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = level - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; + + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = level; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { + std::max(1, mipWidth / 2), + std::max(1, mipHeight / 2), + 1 + }; + + vkCmdBlitImage( + cmd, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + vkCmdPipelineBarrier2(cmd, &dependency); + + mipWidth = std::max(1, mipWidth / 2); + mipHeight = std::max(1, mipHeight / 2); + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; + + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dependency); + + endSingleTimeCommands(cmd); + } + + + VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; + } + + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + + createBuffer( + imageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, pixels, imageSize); + vmaUnmapMemory(allocator, stagingAllocation); + + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + mipLevels, + VK_SAMPLE_COUNT_1_BIT, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + textureImage, + textureImageAllocation + ); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, mipLevels); + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); + } + + + void createUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(UniformBufferObject); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &uniformBuffers[i], + &uniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create staging buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + uniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("failed to end single time commands"); + } + + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels = 1) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier2 barrier{ }; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && + newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + barrier.srcAccessMask = 0; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + } + else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + VkDependencyInfo dependencyInfo{ }; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); + + endSingleTimeCommands(commandBuffer); + } + + + + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion + ); + + endSingleTimeCommands(commandBuffer); + } + + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + } + + + + void setInitialRenderingState(VkCommandBuffer commandBuffer) { + vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); + vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); + vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); + vkCmdSetRasterizationSamplesEXT(commandBuffer, msaaSamples); + vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); + vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); + vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); + vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); + const VkSampleMask sample_mask = 0xFFFFFFFF; + vkCmdSetSampleMaskEXT(commandBuffer, msaaSamples, &sample_mask); + vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); + VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; + vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); + VkBool32 color_blend_enables[] = { VK_FALSE }; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); + vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + // Transition swapchain image layout for optimal drawing + VkImageMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.srcAccessMask = 0; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR + barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrier.image = swapChainImages[imageIndex]; + barrier.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo dep{}; + dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dep); + + + VkRenderingAttachmentInfo colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachment.imageView = colorImageView; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; + colorAttachment.resolveImageView = swapChainImageViews[imageIndex]; + colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; + + VkRenderingAttachmentInfo depthAttachment{}; + depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass + depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; + + VkRenderingInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { {0, 0}, swapChainExtent }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + renderingInfo.pDepthAttachment = &depthAttachment; + + vkCmdBeginRendering(commandBuffer, &renderingInfo); + { + setInitialRenderingState(commandBuffer); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Vertex::getBindingDescription(), + static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() + ); + + VkShaderStageFlagBits stages[] = { + VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_STAGE_FRAGMENT_BIT + }; + + VkShaderEXT shaders[] = { + vertShader, + fragShader + }; + + vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); + + VkBuffer vertexBuffers[] = { vertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + uint32_t pushconstants = currentFrame; + + VkPushDataInfoEXT pushDataInfo{}; + pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + pushDataInfo.data.address = &pushconstants; + pushDataInfo.data.size = sizeof(uint32_t); + + vkCmdPushDataEXT(commandBuffer, &pushDataInfo); + + + // The reserved range is driver-internal and must not overlap app descriptors, + // which are written from offset 0 — so it goes at the tail of the heap. + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)swapChainExtent.width; + viewport.height = (float)swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = { 0, 0 }; + scissor.extent = swapChainExtent; + vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + } + vkCmdEndRendering(commandBuffer); + + VkImageMemoryBarrier2 barrierLayoutBack{}; + barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + barrierLayoutBack.dstAccessMask = 0; + barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; + barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrierLayoutBack.image = swapChainImages[imageIndex]; + barrierLayoutBack.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 + }; + + VkDependencyInfo depLayoutBack{}; + depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depLayoutBack.imageMemoryBarrierCount = 1; + depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; + + vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); + + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + }; + + void createSyncObjects() { + // Create semaphores + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(swapChainImages.size()); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + + // Create timeline semaphore + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = 0; + + VkSemaphoreCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + createInfo.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) + { + throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); + } + } + + void drawFrame() { + + if (timelineValue >= MAX_FRAMES_IN_FLIGHT) + { + VkSemaphoreWaitInfo waitInfo{}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &timelineSemaphore; + + uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; + waitInfo.pValues = &waitValue; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + timelineValue++; + + updateUniformBuffer(currentFrame); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + VkSemaphoreSubmitInfo waitAcquire{}; + waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; + waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + + VkSemaphoreSubmitInfo waitSemaphoreInfo{}; + waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + waitSemaphoreInfo.semaphore = timelineSemaphore; + waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + waitSemaphoreInfo.deviceIndex = 0; + waitSemaphoreInfo.value = timelineValue - 1; + + VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; + + VkSemaphoreSubmitInfo signalBinary{}; + signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; + signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + + VkSemaphoreSubmitInfo signalSemaphoreInfo{}; + signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; + signalSemaphoreInfo.semaphore = timelineSemaphore; + signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; + signalSemaphoreInfo.deviceIndex = 0; + signalSemaphoreInfo.value = timelineValue; + + VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; + + VkCommandBufferSubmitInfo commandBufferInfo{}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; + commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; + commandBufferInfo.deviceMask = 0; + + + VkSubmitInfo2 submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; + + submitInfo.waitSemaphoreInfoCount = 2; + submitInfo.pWaitSemaphoreInfos = waits; + + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &commandBufferInfo; + + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signals; + + VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + if (submitResult != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer! VkResult = " + std::to_string(submitResult) + " (frame " + std::to_string(timelineValue) + ")"); + } + + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; + + VkSwapchainKHR swapChains[] = { swapChain }; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); + ubo.proj[1][1] *= -1; // Vulkan clip correction + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { + + std::array setAndBindingMappings; + + // Buffer binding + setAndBindingMappings[0] = {}; + setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[0].descriptorSet = 0; + setAndBindingMappings[0].firstBinding = 0; + setAndBindingMappings[0].bindingCount = 1; + setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + + // Image binding + setAndBindingMappings[1] = {}; + setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[1].descriptorSet = 1; + setAndBindingMappings[1].firstBinding = 0; + setAndBindingMappings[1].bindingCount = 1; + setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; + setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); + setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); + + // Sampler binding + setAndBindingMappings[2] = {}; + setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + setAndBindingMappings[2].descriptorSet = 2; + setAndBindingMappings[2].firstBinding = 0; + setAndBindingMappings[2].bindingCount = 1; + setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; + setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); + setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; + descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + + VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; + shaderCreateInfo.stage = stageFlags; + shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfo.pCode = reinterpret_cast(code.data()); + shaderCreateInfo.codeSize = code.size(); + shaderCreateInfo.pName = "main"; + shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; + shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; + + if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) + { + shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + } + + VkShaderEXT shader; + if (vkCreateShadersEXT(device, 1, + &shaderCreateInfo, + nullptr, &shader) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader objects!"); + } + + return shader; + } + + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + static std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } + catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/code/25_shader_compute.comp b/code/25_shader_compute.comp new file mode 100644 index 00000000..5e188daa --- /dev/null +++ b/code/25_shader_compute.comp @@ -0,0 +1,40 @@ +#version 450 + +struct Particle { + vec2 position; + vec2 velocity; + vec4 color; +}; + +layout (binding = 0) uniform ParameterUBO { + float deltaTime; +} ubo; + +layout(std140, binding = 1) readonly buffer ParticleSSBOIn { + Particle particlesIn[ ]; +}; + +layout(std140, binding = 2) buffer ParticleSSBOOut { + Particle particlesOut[ ]; +}; + +layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; + +void main() +{ + uint index = gl_GlobalInvocationID.x; + + Particle particleIn = particlesIn[index]; + + particlesOut[index].position = particleIn.position + particleIn.velocity.xy * ubo.deltaTime; + particlesOut[index].velocity = particleIn.velocity; + + // Flip movement at window border + if ((particlesOut[index].position.x <= -1.0) || (particlesOut[index].position.x >= 1.0)) { + particlesOut[index].velocity.x = -particlesOut[index].velocity.x; + } + if ((particlesOut[index].position.y <= -1.0) || (particlesOut[index].position.y >= 1.0)) { + particlesOut[index].velocity.y = -particlesOut[index].velocity.y; + } + +} \ No newline at end of file diff --git a/code/25_shader_compute.frag b/code/25_shader_compute.frag new file mode 100644 index 00000000..94517ecd --- /dev/null +++ b/code/25_shader_compute.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + + vec2 coord = gl_PointCoord - vec2(0.5); + outColor = vec4(fragColor, 0.5 - length(coord)); +} diff --git a/code/25_shader_compute.vert b/code/25_shader_compute.vert new file mode 100644 index 00000000..9730d27d --- /dev/null +++ b/code/25_shader_compute.vert @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec4 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + + gl_PointSize = 14.0; + gl_Position = vec4(inPosition.xy, 1.0, 1.0); + fragColor = inColor.rgb; +} diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 1cf3ff77..56db792b 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -39,33 +39,45 @@ set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VA function (add_shaders_target TARGET) - cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "PREFIXES" ${ARGN}) set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) add_custom_command ( OUTPUT ${SHADERS_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} ) - set (SHADERS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) - # Some chapters may have compute shaders in addition to vertex and fragment shaders, - # so we conditionally check this and add them to the target - string(FIND "${SHADER_SOURCES}" "${CHAPTER_SHADER}.comp" COMPUTE_SHADER_INDEX) - if (${COMPUTE_SHADER_INDEX} GREATER -1) - set (SHADERS ${SHADERS} ${SHADERS_DIR}/comp.spv) - endif() - add_custom_command ( - OUTPUT ${SHADERS} - COMMAND glslang::validator - ARGS --target-env vulkan1.3 ${SHADER_SOURCES} - WORKING_DIRECTORY ${SHADERS_DIR} - DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} - COMMENT "Compiling Shaders" - VERBATIM - ) + # Each SHADER prefix is a shader set (vert/frag and optionally comp). The first + # set compiles to vert.spv/frag.spv/comp.spv; subsequent sets are numbered + # (vert1.spv, frag1.spv, comp1.spv, ...) so sets don't overwrite each other. + set (SHADERS "") + set (SET_INDEX 0) + foreach (SHADER_PREFIX ${SHADER_PREFIXES}) + if (SET_INDEX EQUAL 0) + set (SET_SUFFIX "") + else () + set (SET_SUFFIX ${SET_INDEX}) + endif () + foreach (STAGE vert frag comp) + set (SHADER_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_PREFIX}.${STAGE}) + if (EXISTS ${SHADER_SOURCE}) + set (SHADER_OUTPUT ${SHADERS_DIR}/${STAGE}${SET_SUFFIX}.spv) + add_custom_command ( + OUTPUT ${SHADER_OUTPUT} + COMMAND glslang::validator + ARGS --target-env vulkan1.3 -o ${SHADER_OUTPUT} ${SHADER_SOURCE} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCE} + COMMENT "Compiling ${SHADER_PREFIX}.${STAGE} -> ${STAGE}${SET_SUFFIX}.spv" + VERBATIM + ) + list (APPEND SHADERS ${SHADER_OUTPUT}) + endif () + endforeach () + math (EXPR SET_INDEX "${SET_INDEX} + 1") + endforeach () add_custom_target (${TARGET} DEPENDS ${SHADERS}) endfunction () function (add_chapter CHAPTER_NAME) - cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) + cmake_parse_arguments (CHAPTER "" "" "SHADER;LIBS;TEXTURES;MODELS" ${ARGN}) add_executable (${CHAPTER_NAME} ${CHAPTER_NAME}.cpp) set_target_properties (${CHAPTER_NAME} PROPERTIES @@ -82,8 +94,7 @@ function (add_chapter CHAPTER_NAME) if (DEFINED CHAPTER_SHADER) set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) - file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert ${CHAPTER_SHADER}.comp) - add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) + add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} PREFIXES ${CHAPTER_SHADER}) add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) endif () if (DEFINED CHAPTER_LIBS) @@ -179,6 +190,13 @@ add_chapter (24_multisampling TEXTURES ../resources/viking_room.png LIBS tinyobjloader::tinyobjloader) +add_chapter (25_compute_shader + SHADER 21_depth_buffering + SHADER 25_shader_compute + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS tinyobjloader::tinyobjloader) + add_chapter (16_frames_in_flight SHADER 08_shader_base) From cddee12adc597402b40f6507f541fb8d2904fe9a Mon Sep 17 00:00:00 2001 From: "chris.hekman" Date: Sun, 16 Aug 2026 17:59:44 +0200 Subject: [PATCH 47/47] Update 25_compute_shader.cpp --- code/25_compute_shader.cpp | 385 ++++++++++++++++++++++++++++++++++--- 1 file changed, 359 insertions(+), 26 deletions(-) diff --git a/code/25_compute_shader.cpp b/code/25_compute_shader.cpp index 02797b18..f3511220 100644 --- a/code/25_compute_shader.cpp +++ b/code/25_compute_shader.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include const uint32_t WIDTH = 800; @@ -43,6 +45,8 @@ const std::string TEXTURE_PATH = "textures/viking_room.png"; const int MAX_FRAMES_IN_FLIGHT = 2; +const uint32_t PARTICLE_COUNT = 8192; + const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; @@ -155,6 +159,46 @@ struct UniformBufferObject { glm::mat4 proj; }; +// Matches the std140 array stride in 25_shader_compute.comp (vec2, vec2, vec4 = 32 bytes). +struct Particle { + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static VkVertexInputBindingDescription2EXT getBindingDescription() { + VkVertexInputBindingDescription2EXT bindingDescription{}; + bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Particle); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindingDescription.divisor = 1; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Particle, position); + + attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Particle, color); + + return attributeDescriptions; + } +}; + +struct ComputeUBO { + float deltaTime; +}; + inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { return (size + alignment - 1) & ~(alignment - 1); @@ -240,6 +284,16 @@ class HelloTriangleApplication { std::vector uniformAllocations; std::vector uniformBuffersMapped; + std::vector particleBuffers; + std::vector particleAllocations; + + std::vector computeUniformBuffers; + std::vector computeUniformAllocations; + std::vector computeUniformBuffersMapped; + + float lastFrameTime = 0.0f; + double lastTime = 0.0; + std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; VkSemaphore timelineSemaphore; @@ -281,6 +335,8 @@ class HelloTriangleApplication { createIndexBuffer(); createTextureImage(); createUniformBuffers(); + createShaderStorageBuffers(); + createComputeUniformBuffers(); prepareDescriptorHeap(); prepareSamplerDescriptorHeap(); createShaderObjects(); @@ -289,9 +345,15 @@ class HelloTriangleApplication { } void mainLoop() { + lastTime = glfwGetTime(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); drawFrame(); + + // Frame delta drives the particle simulation (in milliseconds, like the tutorial). + double currentTime = glfwGetTime(); + lastFrameTime = static_cast((currentTime - lastTime) * 1000.0); + lastTime = currentTime; } vkDeviceWaitIdle(device); @@ -322,6 +384,8 @@ class HelloTriangleApplication { vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); + vmaDestroyBuffer(allocator, computeUniformBuffers[i], computeUniformAllocations[i]); + vmaDestroyBuffer(allocator, particleBuffers[i], particleAllocations[i]); } for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { @@ -835,8 +899,10 @@ class HelloTriangleApplication { descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); } - // Image - imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); + // Buffer descriptor slots: 0 = graphics UBO, 1 = compute UBO (deltaTime), + // 2 = particles in (last frame), 3 = particles out (this frame). Image after that. + const VkDeviceSize bufferDescriptorSlots = 4; + imageHeapOffset = alignUp(bufferDescriptorSlots * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); std::array addrInfo{}; @@ -866,6 +932,53 @@ class HelloTriangleApplication { hostAddressRangesResource.size = bufferDescriptorSize; hostAddressRangesResources.push_back(hostAddressRangesResource); + // Compute parameter UBO (deltaTime) — slot 1 + VkBufferDeviceAddressInfo computeUboAddrInfo{}; + computeUboAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + computeUboAddrInfo.buffer = computeUniformBuffers[i]; + + VkDeviceAddressRangeEXT computeUboRange{}; + computeUboRange.address = vkGetBufferDeviceAddress(device, &computeUboAddrInfo); + computeUboRange.size = sizeof(ComputeUBO); + + VkResourceDescriptorInfoEXT computeUboDescriptorInfo{}; + computeUboDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + computeUboDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + computeUboDescriptorInfo.data.pAddressRange = &computeUboRange; + resourceDescriptorInfos.push_back(computeUboDescriptorInfo); + + VkHostAddressRangeEXT computeUboHostRange{}; + computeUboHostRange.address = static_cast(allocResult[i].pMappedData) + 1 * bufferDescriptorSize; + computeUboHostRange.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(computeUboHostRange); + + // Particle SSBOs — slot 2 reads last frame's particles, slot 3 writes this frame's. + // The ping-pong is baked into each frame's heap since heaps are written once. + const int particleSlots[2] = { + (i + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT, // in + i // out + }; + std::array particleRanges{}; + for (int p = 0; p < 2; p++) { + VkBufferDeviceAddressInfo particleAddrInfo{}; + particleAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + particleAddrInfo.buffer = particleBuffers[particleSlots[p]]; + + particleRanges[p].address = vkGetBufferDeviceAddress(device, &particleAddrInfo); + particleRanges[p].size = sizeof(Particle) * PARTICLE_COUNT; + + VkResourceDescriptorInfoEXT particleDescriptorInfo{}; + particleDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + particleDescriptorInfo.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + particleDescriptorInfo.data.pAddressRange = &particleRanges[p]; + resourceDescriptorInfos.push_back(particleDescriptorInfo); + + VkHostAddressRangeEXT particleHostRange{}; + particleHostRange.address = static_cast(allocResult[i].pMappedData) + (2 + p) * bufferDescriptorSize; + particleHostRange.size = bufferDescriptorSize; + hostAddressRangesResources.push_back(particleHostRange); + } + // Image views VkImageViewCreateInfo imageViewCreateInfo = {}; imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; @@ -966,7 +1079,7 @@ class HelloTriangleApplication { samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 0.0f; samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = VK_REMAINING_MIP_LEVELS; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; VkHostAddressRangeEXT hostAddressRangesSamplers = {}; @@ -995,7 +1108,7 @@ class HelloTriangleApplication { auto vertShaderPartCode = readFile("shaders/vert1.spv"); auto fragShaderPartCode = readFile("shaders/frag1.spv"); - auto compShaderPartCode = readFile("shaders/frag1.spv"); + auto compShaderPartCode = readFile("shaders/comp1.spv"); vertShaderPart = createShaderObject(vertShaderPartCode, VK_SHADER_STAGE_VERTEX_BIT); fragShaderPart = createShaderObject(fragShaderPartCode, VK_SHADER_STAGE_FRAGMENT_BIT); @@ -1419,6 +1532,98 @@ class HelloTriangleApplication { } + void createShaderStorageBuffers() + { + std::default_random_engine rndEngine((unsigned)time(nullptr)); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + // Initial particle positions on a circle + std::vector particles(PARTICLE_COUNT); + for (auto& particle : particles) { + float r = 0.25f * sqrt(rndDist(rndEngine)); + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + float x = r * cos(theta) * HEIGHT / WIDTH; + float y = r * sin(theta); + particle.position = glm::vec2(x, y); + particle.velocity = glm::normalize(glm::vec2(x, y)) * 0.00025f; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + VkDeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + createBuffer( + bufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + 0, + stagingBuffer, + stagingAllocation + ); + + void* data = nullptr; + vmaMapMemory(allocator, stagingAllocation, &data); + memcpy(data, particles.data(), bufferSize); + vmaUnmapMemory(allocator, stagingAllocation); + + // One SSBO per frame in flight: the compute shader reads last frame's buffer and + // writes this frame's, and the particle draw consumes it as a vertex buffer. + // SHADER_DEVICE_ADDRESS is needed to write its heap descriptor by address. + particleBuffers.resize(MAX_FRAMES_IN_FLIGHT); + particleAllocations.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + createBuffer( + bufferSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + 0, + particleBuffers[i], + particleAllocations[i] + ); + copyBuffer(stagingBuffer, particleBuffers[i], bufferSize); + } + + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); + } + + + void createComputeUniformBuffers() + { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(ComputeUBO); + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + VmaAllocationCreateInfo allocInfo{}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write + allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + computeUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); + computeUniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); + computeUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VmaAllocationInfo allocResult{}; + if (vmaCreateBuffer( + allocator, + &bufferInfo, + &allocInfo, + &computeUniformBuffers[i], + &computeUniformAllocations[i], + &allocResult + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create compute uniform buffer!"); + } + // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. + computeUniformBuffersMapped[i] = allocResult.pMappedData; + } + } + + VkCommandBuffer beginSingleTimeCommands() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; @@ -1616,6 +1821,72 @@ class HelloTriangleApplication { throw std::runtime_error("failed to begin recording command buffer!"); } + // Heap binds apply command-buffer-wide (compute and graphics alike), so bind them + // up front where the particle dispatch can see them too. + // The reserved range is driver-internal and must not overlap app descriptors, + // which are written from offset 0 — so it goes at the tail of the heap. + VkBindHeapInfoEXT bindHeapinfo{}; + bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; + bindHeapinfo.heapRange.size = heapbufferSize; + bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; + bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; + vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); + + VkBindHeapInfoEXT bindSamplerHeapinfo{}; + bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; + bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; + bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; + bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; + vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); + + // --- Particle update (compute) --- + VkShaderStageFlagBits computeStage = VK_SHADER_STAGE_COMPUTE_BIT; + vkCmdBindShadersEXT(commandBuffer, 1, &computeStage, &compShaderPart); + + // The previous submission may still be reading these buffers as vertex input. + VkBufferMemoryBarrier2 particlePreBarriers[MAX_FRAMES_IN_FLIGHT]{}; + for (int b = 0; b < MAX_FRAMES_IN_FLIGHT; b++) { + particlePreBarriers[b].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + particlePreBarriers[b].srcStageMask = VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT; + particlePreBarriers[b].srcAccessMask = VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT; + particlePreBarriers[b].dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + particlePreBarriers[b].dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT; + particlePreBarriers[b].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + particlePreBarriers[b].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + particlePreBarriers[b].buffer = particleBuffers[b]; + particlePreBarriers[b].offset = 0; + particlePreBarriers[b].size = VK_WHOLE_SIZE; + } + + VkDependencyInfo preComputeDep{}; + preComputeDep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + preComputeDep.bufferMemoryBarrierCount = MAX_FRAMES_IN_FLIGHT; + preComputeDep.pBufferMemoryBarriers = particlePreBarriers; + vkCmdPipelineBarrier2(commandBuffer, &preComputeDep); + + vkCmdDispatch(commandBuffer, PARTICLE_COUNT / 256, 1, 1); + + // Make the compute results visible to the particle draw's vertex fetch. + VkBufferMemoryBarrier2 particlePostBarrier{}; + particlePostBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + particlePostBarrier.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + particlePostBarrier.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT; + particlePostBarrier.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT; + particlePostBarrier.dstAccessMask = VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT; + particlePostBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + particlePostBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + particlePostBarrier.buffer = particleBuffers[currentFrame]; + particlePostBarrier.offset = 0; + particlePostBarrier.size = VK_WHOLE_SIZE; + + VkDependencyInfo postComputeDep{}; + postComputeDep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + postComputeDep.bufferMemoryBarrierCount = 1; + postComputeDep.pBufferMemoryBarriers = &particlePostBarrier; + vkCmdPipelineBarrier2(commandBuffer, &postComputeDep); + // Transition swapchain image layout for optimal drawing VkImageMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; @@ -1703,26 +1974,6 @@ class HelloTriangleApplication { vkCmdPushDataEXT(commandBuffer, &pushDataInfo); - // The reserved range is driver-internal and must not overlap app descriptors, - // which are written from offset 0 — so it goes at the tail of the heap. - VkBindHeapInfoEXT bindHeapinfo{}; - bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; - bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; - bindHeapinfo.heapRange.size = heapbufferSize; - bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; - bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; - vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); - - - VkBindHeapInfoEXT bindSamplerHeapinfo{}; - bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; - bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; - bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; - bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; - bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; - vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); - - VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; @@ -1739,6 +1990,42 @@ class HelloTriangleApplication { vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + // --- Particle overlay --- + VkShaderEXT particleShaders[] = { + vertShaderPart, + fragShaderPart + }; + vkCmdBindShadersEXT(commandBuffer, 2, stages, particleShaders); + + vkCmdSetVertexInputEXT(commandBuffer, + 1, &Particle::getBindingDescription(), + static_cast(Particle::getAttributeDescriptions().size()), Particle::getAttributeDescriptions().data() + ); + + vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST); + + // The particle vert emits z = 1.0, which would fail the LESS test against the + // cleared depth of 1.0 — the overlay ignores depth entirely. + vkCmdSetDepthTestEnable(commandBuffer, VK_FALSE); + vkCmdSetDepthWriteEnable(commandBuffer, VK_FALSE); + + VkBool32 particleBlendEnable = VK_TRUE; + vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, &particleBlendEnable); + + VkColorBlendEquationEXT particleBlendEquation{}; + particleBlendEquation.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + particleBlendEquation.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + particleBlendEquation.colorBlendOp = VK_BLEND_OP_ADD; + particleBlendEquation.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + particleBlendEquation.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + particleBlendEquation.alphaBlendOp = VK_BLEND_OP_ADD; + vkCmdSetColorBlendEquationEXT(commandBuffer, 0, 1, &particleBlendEquation); + + VkDeviceSize particleOffsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &particleBuffers[currentFrame], particleOffsets); + + vkCmdDraw(commandBuffer, PARTICLE_COUNT, 1, 0, 0); + } vkCmdEndRendering(commandBuffer); @@ -1925,6 +2212,10 @@ class HelloTriangleApplication { ubo.proj[1][1] *= -1; // Vulkan clip correction memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + + ComputeUBO computeUbo{}; + computeUbo.deltaTime = lastFrameTime * 2.0f; + memcpy(computeUniformBuffersMapped[currentImage], &computeUbo, sizeof(computeUbo)); } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { @@ -1964,10 +2255,52 @@ class HelloTriangleApplication { setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); + // The particle compute shader (25_shader_compute.comp) has no set decorations, so + // everything lives in set 0: binding 0 = deltaTime UBO, 1 = particles in, 2 = particles + // out — mapped to buffer descriptor slots 1..3 of the per-frame resource heap. + std::array computeSetAndBindingMappings; + + computeSetAndBindingMappings[0] = {}; + computeSetAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + computeSetAndBindingMappings[0].descriptorSet = 0; + computeSetAndBindingMappings[0].firstBinding = 0; + computeSetAndBindingMappings[0].bindingCount = 1; + computeSetAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; + computeSetAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + computeSetAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + computeSetAndBindingMappings[0].sourceData.constantOffset.heapOffset = static_cast(1 * bufferDescriptorSize); + + computeSetAndBindingMappings[1] = {}; + computeSetAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + computeSetAndBindingMappings[1].descriptorSet = 0; + computeSetAndBindingMappings[1].firstBinding = 1; + computeSetAndBindingMappings[1].bindingCount = 1; + computeSetAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT; + computeSetAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + computeSetAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + computeSetAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(2 * bufferDescriptorSize); + + computeSetAndBindingMappings[2] = {}; + computeSetAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; + computeSetAndBindingMappings[2].descriptorSet = 0; + computeSetAndBindingMappings[2].firstBinding = 2; + computeSetAndBindingMappings[2].bindingCount = 1; + computeSetAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT; + computeSetAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; + computeSetAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); + computeSetAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(3 * bufferDescriptorSize); + + VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; - descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); - descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + if (stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) { + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(computeSetAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = computeSetAndBindingMappings.data(); + } + else { + descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); + descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); + } VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags;