From a0dd0f23df90805ff6b40f32d1bc7d5b07700f05 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Wed, 5 Nov 2025 17:49:54 +0100 Subject: [PATCH 1/4] Fix vector sizes for presentCompleteSemaphores in chapter 16. --- attachments/15_hello_triangle.cpp | 1113 +++++++++++++------------- attachments/16_frames_in_flight.cpp | 1151 +++++++++++++-------------- 2 files changed, 1114 insertions(+), 1150 deletions(-) diff --git a/attachments/15_hello_triangle.cpp b/attachments/15_hello_triangle.cpp index 1848011e..6342bbc2 100644 --- a/attachments/15_hello_triangle.cpp +++ b/attachments/15_hello_triangle.cpp @@ -1,28 +1,29 @@ -#include -#include -#include -#include -#include #include -#include -#include +#include #include #include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -# include +#include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation"}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,561 +31,539 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication -{ - public: - void run() - { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - - private: - GLFWwindow *window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - vk::raii::Semaphore presentCompleteSemaphore = nullptr; - vk::raii::Semaphore renderFinishedSemaphore = nullptr; - vk::raii::Fence drawFence = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName}; - - 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(); - createCommandBuffer(); - createSyncObjects(); - } - - void mainLoop() - { - while (!glfwWindowShouldClose(window)) - { - glfwPollEvents(); - drawFrame(); - } - device.waitIdle(); // wait for device to finish operations before destroying resources - } - - void cleanup() - { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() - { - constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Hello Triangle", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = vk::ApiVersion14}; - - // Get the required layers - std::vector requiredLayers; - if (enableValidationLayers) - { - requiredLayers.assign(validationLayers.begin(), validationLayers.end()); - } - - // Check if the required layers are supported by the Vulkan implementation. - auto layerProperties = context.enumerateInstanceLayerProperties(); - for (auto const &requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const &requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data()}; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() - { - if (!enableValidationLayers) - return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback}; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void createSurface() - { - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) - { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); - } - - void pickPhysicalDevice() - { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const &device) { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const &requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().shaderDrawParameters && - features.template get().synchronization2 && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if (devIter != devices.end()) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() - { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; - vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data()}; - - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() - { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); - swapChainExtent = chooseSwapExtent(surfaceCapabilities); - swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, - .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = surfaceCapabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(*surface)), - .clipped = true}; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() - { - assert(swapChainImageViews.empty()); - - vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; - for (auto &image : swapChainImages) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back(device, imageViewCreateInfo); - } - } - - void createGraphicsPipeline() - { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{.stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain"}; - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"}; - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; - vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; - - vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor}; - vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr}, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() - { - vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex}; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() - { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; - commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) - { - commandBuffer.begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor}; - vk::RenderingInfo renderingInfo = { - .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo}; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t currentFrame, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask) - { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[currentFrame], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier}; - commandBuffer.pipelineBarrier2(dependency_info); - } - - void createSyncObjects() - { - presentCompleteSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); - } - - void drawFrame() - { - queue.waitIdle(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame - // In the next chapter you see how to use multiple frames in flight and fences to sync - - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore, nullptr); - recordCommandBuffer(imageIndex); - - device.resetFences(*drawFence); - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore}; - queue.submit(submitInfo, *drawFence); - while (vk::Result::eTimeout == device.waitForFences(*drawFence, vk::True, UINT64_MAX)) - ; - - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; - result = queue.presentKHR(presentInfoKHR); - switch (result) - { - case vk::Result::eSuccess: - break; - case vk::Result::eSuboptimalKHR: - std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; - break; - default: - break; // an unexpected result is returned! - } - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const - { - vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; - vk::raii::ShaderModule shaderModule{device, createInfo}; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) - { - auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) - { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) - { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) - { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? - vk::PresentModeKHR::eMailbox : - vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) - { - if (capabilities.currentExtent.width != 0xFFFFFFFF) - { - return capabilities.currentExtent; - } - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; - } - - std::vector getRequiredExtensions() - { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) - { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) - { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) - { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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!"); - } - std::vector buffer(file.tellg()); - file.seekg(0, std::ios::beg); - file.read(buffer.data(), static_cast(buffer.size())); - file.close(); - return buffer; - } +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow * window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + vk::raii::Semaphore presentCompleteSemaphore = nullptr; + vk::raii::Semaphore renderFinishedSemaphore = nullptr; + vk::raii::Fence drawFence = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName + }; + + 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(); + createCommandBuffer(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + device.waitIdle(); // wait for device to finish operations before destroying resources + } + + void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .apiVersion = vk::ApiVersion14 }; + + // Get the required layers + std::vector requiredLayers; + if (enableValidationLayers) { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation. + auto layerProperties = context.enumerateInstanceLayerProperties(); + for (auto const& requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const& layerProperty) + { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const& requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const& extensionProperty) + { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() }; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback + }; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void createSurface() { + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); + } + + void pickPhysicalDevice() { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&]( auto const & device ) + { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of( requiredDeviceExtension, + [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) + { + return std::ranges::any_of( availableDeviceExtensions, + [requiredDeviceExtension]( auto const & availableDeviceExtension ) + { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); + } ); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().shaderDrawParameters && + features.template get().synchronization2 && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + } ); + if ( devIter != devices.end() ) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error( "failed to find a suitable GPU!" ); + } + } + + void createLogicalDevice() { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features + {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; + vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data() }; + + device = vk::raii::Device( physicalDevice, deviceCreateInfo ); + queue = vk::raii::Queue( device, queueIndex, 0 ); + } + + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); + swapChainExtent = chooseSwapExtent( surfaceCapabilities ); + swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, + .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode( physicalDevice.getSurfacePresentModesKHR( *surface ) ), + .clipped = true }; + + swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() { + assert(swapChainImageViews.empty()); + + vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; + for (auto& image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back( device, imageViewCreateInfo ); + } + } + + void createGraphicsPipeline() { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain" }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain" }; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; + vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; + + vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, + .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor + }; + vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; + + pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); + + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr }, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } + }; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + void createCommandPool() { + vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex }; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() { + vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1 }; + commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) { + commandBuffer.begin( {} ); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor + }; + vk::RenderingInfo renderingInfo = { + .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo + }; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask + ) { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + } + }; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier + }; + commandBuffer.pipelineBarrier2(dependency_info); + } + + void createSyncObjects() { + presentCompleteSemaphore =vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); + } + + void drawFrame() { + queue.waitIdle(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame + // In the next chapter you see how to use multiple frames in flight and fences to sync + + auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore, nullptr ); + recordCommandBuffer(imageIndex); + + device.resetFences( *drawFence ); + vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); + const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, + .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, + .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore }; + queue.submit(submitInfo, *drawFence); + while ( vk::Result::eTimeout == device.waitForFences( *drawFence, vk::True, UINT64_MAX ) ) + ; + + const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, + .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; + result = queue.presentKHR( presentInfoKHR ); + switch ( result ) + { + case vk::Result::eSuccess: break; + case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; + default: break; // an unexpected result is returned! + } + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { + vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; + vk::raii::ShaderModule shaderModule{ device, createInfo }; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { + auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != 0xFFFFFFFF) { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) + }; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) { + extensions.push_back(vk::EXTDebugUtilsExtensionName ); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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!"); + } + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + return buffer; + } }; -int main() -{ - try - { - HelloTriangleApplication app; - app.run(); - } - catch (const std::exception &e) - { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() { + try { + HelloTriangleApplication app; + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/16_frames_in_flight.cpp b/attachments/16_frames_in_flight.cpp index 5e6725c2..7a6be986 100644 --- a/attachments/16_frames_in_flight.cpp +++ b/attachments/16_frames_in_flight.cpp @@ -1,29 +1,30 @@ -#include -#include -#include -#include -#include #include -#include -#include +#include #include #include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -# include +#include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation"}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,575 +32,559 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication -{ - public: - void run() - { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - - private: - GLFWwindow *window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName}; - - 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(); - } - - device.waitIdle(); - } - - void cleanup() - { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() - { - constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Hello Triangle", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = vk::ApiVersion14}; - - // Get the required layers - std::vector requiredLayers; - if (enableValidationLayers) - { - requiredLayers.assign(validationLayers.begin(), validationLayers.end()); - } - - // Check if the required layers are supported by the Vulkan implementation. - auto layerProperties = context.enumerateInstanceLayerProperties(); - for (auto const &requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const &requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data()}; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() - { - if (!enableValidationLayers) - return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback}; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void createSurface() - { - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) - { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); - } - - void pickPhysicalDevice() - { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const &device) { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const &requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().shaderDrawParameters && - features.template get().synchronization2 && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if (devIter != devices.end()) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() - { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; - vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data()}; - - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() - { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); - swapChainExtent = chooseSwapExtent(surfaceCapabilities); - swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, - .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = surfaceCapabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(*surface)), - .clipped = true}; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() - { - assert(swapChainImageViews.empty()); - - vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; - for (auto image : swapChainImages) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back(device, imageViewCreateInfo); - } - } - - void createGraphicsPipeline() - { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{.stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain"}; - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"}; - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; - vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; - - vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor}; - vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr}, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() - { - vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex}; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() - { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) - { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor}; - vk::RenderingInfo renderingInfo = { - .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask) - { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() - { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) - { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) - { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); - } - } - - void drawFrame() - { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) - ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); - - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; - result = queue.presentKHR(presentInfoKHR); - switch (result) - { - case vk::Result::eSuccess: - break; - case vk::Result::eSuboptimalKHR: - std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; - break; - default: - break; // an unexpected result is returned! - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const - { - vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; - vk::raii::ShaderModule shaderModule{device, createInfo}; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) - { - auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) - { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) - { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) - { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? - vk::PresentModeKHR::eMailbox : - vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) - { - if (capabilities.currentExtent.width != 0xFFFFFFFF) - { - return capabilities.currentExtent; - } - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; - } - - std::vector getRequiredExtensions() - { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) - { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) - { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) - { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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!"); - } - std::vector buffer(file.tellg()); - file.seekg(0, std::ios::beg); - file.read(buffer.data(), static_cast(buffer.size())); - file.close(); - return buffer; - } +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow * window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t frameIndex = 0; + + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName + }; + + 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(); + } + + device.waitIdle(); + } + + void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() { + constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), + .apiVersion = vk::ApiVersion14 }; + + // Get the required layers + std::vector requiredLayers; + if (enableValidationLayers) { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation. + auto layerProperties = context.enumerateInstanceLayerProperties(); + for (auto const& requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const& layerProperty) + { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const& requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const& extensionProperty) + { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() }; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback + }; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void createSurface() { + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); + } + + void pickPhysicalDevice() { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&]( auto const & device ) + { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of( requiredDeviceExtension, + [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) + { + return std::ranges::any_of( availableDeviceExtensions, + [requiredDeviceExtension]( auto const & availableDeviceExtension ) + { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); + } ); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().shaderDrawParameters && + features.template get().synchronization2 && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + } ); + if ( devIter != devices.end() ) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error( "failed to find a suitable GPU!" ); + } + } + + void createLogicalDevice() { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features + {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; + vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data() }; + + device = vk::raii::Device( physicalDevice, deviceCreateInfo ); + queue = vk::raii::Queue( device, queueIndex, 0 ); + } + + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); + swapChainExtent = chooseSwapExtent( surfaceCapabilities ); + swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, + .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode( physicalDevice.getSurfacePresentModesKHR( *surface ) ), + .clipped = true }; + + swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() { + assert(swapChainImageViews.empty()); + + vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; + for ( auto image : swapChainImages ) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back( device, imageViewCreateInfo ); + } + } + + void createGraphicsPipeline() { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain" }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain" }; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; + vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; + + vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, + .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor + }; + vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; + + pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); + + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr }, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } + }; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + void createCommandPool() { + vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex }; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() { + vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; + commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); + } + + void recordCommandBuffer(uint32_t imageIndex) { + auto& commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin( {} ); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor + }; + vk::RenderingInfo renderingInfo = { + .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo + }; + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask + ) { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + } + }; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier + }; + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() { + assert( presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); + } + } + + void drawFrame() { + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[frameIndex], vk::True, UINT64_MAX ) ) + ; + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr ); + + commandBuffers[frameIndex].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); + const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex] }; + queue.submit(submitInfo, *inFlightFences[frameIndex]); + + const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex }; + result = queue.presentKHR( presentInfoKHR ); + switch ( result ) + { + case vk::Result::eSuccess: break; + case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; + default: break; // an unexpected result is returned! + } + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { + vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; + vk::raii::ShaderModule shaderModule{ device, createInfo }; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { + auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != 0xFFFFFFFF) { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) + }; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) { + extensions.push_back(vk::EXTDebugUtilsExtensionName ); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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!"); + } + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + return buffer; + } }; -int main() -{ - try - { - HelloTriangleApplication app; - app.run(); - } - catch (const std::exception &e) - { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() { + try { + HelloTriangleApplication app; + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } From dd21bdce10560db26fb2fa1db2ec54867f2f58e6 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Mon, 10 Nov 2025 13:03:08 +0100 Subject: [PATCH 2/4] Resolve conflicts --- attachments/15_hello_triangle.cpp | 1113 ++++++++++++------------- attachments/16_frames_in_flight.cpp | 1161 ++++++++++++++------------- 2 files changed, 1160 insertions(+), 1114 deletions(-) diff --git a/attachments/15_hello_triangle.cpp b/attachments/15_hello_triangle.cpp index 6342bbc2..b6036460 100644 --- a/attachments/15_hello_triangle.cpp +++ b/attachments/15_hello_triangle.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,539 +30,561 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - vk::raii::Semaphore presentCompleteSemaphore = nullptr; - vk::raii::Semaphore renderFinishedSemaphore = nullptr; - vk::raii::Fence drawFence = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - 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(); - createCommandBuffer(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - device.waitIdle(); // wait for device to finish operations before destroying resources - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", - .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .apiVersion = vk::ApiVersion14 }; - - // Get the required layers - std::vector requiredLayers; - if (enableValidationLayers) { - requiredLayers.assign(validationLayers.begin(), validationLayers.end()); - } - - // Check if the required layers are supported by the Vulkan implementation. - auto layerProperties = context.enumerateInstanceLayerProperties(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void createSurface() { - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); - } - - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().shaderDrawParameters && - features.template get().synchronization2 && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = surfaceCapabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode( physicalDevice.getSurfacePresentModesKHR( *surface ) ), - .clipped = true }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - assert(swapChainImageViews.empty()); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for (auto& image : swapChainImages) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain" }; - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain" }; - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 }; - commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffer.pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore =vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); - } - - void drawFrame() { - queue.waitIdle(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame - // In the next chapter you see how to use multiple frames in flight and fences to sync - - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore, nullptr ); - recordCommandBuffer(imageIndex); - - device.resetFences( *drawFence ); - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore }; - queue.submit(submitInfo, *drawFence); - while ( vk::Result::eTimeout == device.waitForFences( *drawFence, vk::True, UINT64_MAX ) ) - ; - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - return capabilities.currentExtent; - } - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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!"); - } - std::vector buffer(file.tellg()); - file.seekg(0, std::ios::beg); - file.read(buffer.data(), static_cast(buffer.size())); - file.close(); - return buffer; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + vk::raii::Semaphore presentCompleteSemaphore = nullptr; + vk::raii::Semaphore renderFinishedSemaphore = nullptr; + vk::raii::Fence drawFence = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + 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(); + createCommandBuffer(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + device.waitIdle(); // wait for device to finish operations before destroying resources + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + + // Get the required layers + std::vector requiredLayers; + if (enableValidationLayers) + { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation. + auto layerProperties = context.enumerateInstanceLayerProperties(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void createSurface() + { + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); + } + + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().shaderDrawParameters && + features.template get().synchronization2 && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(*surface)), + .clipped = true}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + assert(swapChainImageViews.empty()); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto &image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{.stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain"}; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"}; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffer.pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); + } + + void drawFrame() + { + queue.waitIdle(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame + // In the next chapter you see how to use multiple frames in flight and fences to sync + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore, nullptr); + recordCommandBuffer(imageIndex); + + device.resetFences(*drawFence); + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore}; + queue.submit(submitInfo, *drawFence); + while (vk::Result::eTimeout == device.waitForFences(*drawFence, vk::True, UINT64_MAX)) + ; + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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!"); + } + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + return buffer; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/16_frames_in_flight.cpp b/attachments/16_frames_in_flight.cpp index 7a6be986..8c65b06b 100644 --- a/attachments/16_frames_in_flight.cpp +++ b/attachments/16_frames_in_flight.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,559 +31,585 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - uint32_t frameIndex = 0; - - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - 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(); - } - - device.waitIdle(); - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void createInstance() { - constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", - .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .apiVersion = vk::ApiVersion14 }; - - // Get the required layers - std::vector requiredLayers; - if (enableValidationLayers) { - requiredLayers.assign(validationLayers.begin(), validationLayers.end()); - } - - // Check if the required layers are supported by the Vulkan implementation. - auto layerProperties = context.enumerateInstanceLayerProperties(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void createSurface() { - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); - } - - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().shaderDrawParameters && - features.template get().synchronization2 && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = surfaceCapabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode( physicalDevice.getSurfacePresentModesKHR( *surface ) ), - .clipped = true }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - assert(swapChainImageViews.empty()); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain" }; - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain" }; - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - auto& commandBuffer = commandBuffers[frameIndex]; - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[frameIndex].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - assert( presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, - // while renderFinishedSemaphores is indexed by imageIndex - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[frameIndex], vk::True, UINT64_MAX ) ) - ; - device.resetFences(*inFlightFences[frameIndex]); - - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr ); - - commandBuffers[frameIndex].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[frameIndex], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[frameIndex]); - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - return capabilities.currentExtent; - } - int width, height; - glfwGetFramebufferSize(window, &width, &height); - - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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!"); - } - std::vector buffer(file.tellg()); - file.seekg(0, std::ios::beg); - file.read(buffer.data(), static_cast(buffer.size())); - file.close(); - return buffer; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t frameIndex = 0; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + 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(); + } + + device.waitIdle(); + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + + // Get the required layers + std::vector requiredLayers; + if (enableValidationLayers) + { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation. + auto layerProperties = context.enumerateInstanceLayerProperties(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void createSurface() + { + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); + } + + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().shaderDrawParameters && + features.template get().synchronization2 && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(*surface)), + .clipped = true}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + assert(swapChainImageViews.empty()); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{.stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain"}; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"}; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + + commandBuffers[frameIndex].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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!"); + } + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + return buffer; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } From d436b8d23de8d365841806f7828e7af7d45fe883 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Mon, 10 Nov 2025 14:21:54 +0100 Subject: [PATCH 3/4] Carry changes to all other chapters --- attachments/17_swap_chain_recreation.cpp | 67 +++++++------ attachments/18_vertex_input.cpp | 64 +++++++------ attachments/19_vertex_buffer.cpp | 66 +++++++------ attachments/20_staging_buffer.cpp | 66 +++++++------ attachments/21_index_buffer.cpp | 68 ++++++++------ attachments/22_descriptor_layout.cpp | 70 ++++++++------ attachments/23_descriptor_sets.cpp | 72 ++++++++------ attachments/24_texture_image.cpp | 72 ++++++++------ attachments/25_sampler.cpp | 72 ++++++++------ attachments/26_texture_mapping.cpp | 72 ++++++++------ attachments/27_depth_buffering.cpp | 72 ++++++++------ attachments/28_model_loading.cpp | 72 ++++++++------ attachments/29_mipmapping.cpp | 72 ++++++++------ attachments/30_multisampling.cpp | 72 ++++++++------ attachments/31_compute_shader.cpp | 52 +++++----- attachments/32_ecosystem_utilities.cpp | 115 ++++++++++------------- attachments/33_vulkan_profiles.cpp | 49 +++++----- attachments/34_android.cpp | 24 ++--- attachments/35_gltf_ktx.cpp | 70 ++++++++------ attachments/36_multiple_objects.cpp | 84 ++++++++--------- attachments/37_multithreading.cpp | 42 ++++----- attachments/38_ray_tracing.cpp | 78 ++++++++------- 22 files changed, 823 insertions(+), 668 deletions(-) diff --git a/attachments/17_swap_chain_recreation.cpp b/attachments/17_swap_chain_recreation.cpp index 6f7f5b1b..0079b781 100644 --- a/attachments/17_swap_chain_recreation.cpp +++ b/attachments/17_swap_chain_recreation.cpp @@ -64,11 +64,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -411,7 +410,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -434,12 +434,12 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -450,7 +450,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -482,32 +482,34 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -519,19 +521,29 @@ class HelloTriangleApplication throw std::runtime_error("failed to acquire swap chain image!"); } - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + device.resetFences(*inFlightFences[frameIndex]); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + if (result == vk::Result::eSuboptimalKHR || framebufferResized) { framebufferResized = false; recreateSwapChain(); @@ -553,8 +565,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/18_vertex_input.cpp b/attachments/18_vertex_input.cpp index 2aaa6406..2ae5adff 100644 --- a/attachments/18_vertex_input.cpp +++ b/attachments/18_vertex_input.cpp @@ -89,11 +89,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -430,7 +429,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -453,12 +453,12 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -469,7 +469,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -501,32 +501,34 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -538,17 +540,26 @@ class HelloTriangleApplication throw std::runtime_error("failed to acquire swap chain image!"); } - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -572,8 +583,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/19_vertex_buffer.cpp b/attachments/19_vertex_buffer.cpp index 169a5332..17a1310e 100644 --- a/attachments/19_vertex_buffer.cpp +++ b/attachments/19_vertex_buffer.cpp @@ -92,11 +92,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -465,7 +464,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -488,13 +488,13 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -505,7 +505,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -537,32 +537,34 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -574,17 +576,26 @@ class HelloTriangleApplication throw std::runtime_error("failed to acquire swap chain image!"); } - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -608,8 +619,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/20_staging_buffer.cpp b/attachments/20_staging_buffer.cpp index 0f8bd493..d5c1be50 100644 --- a/attachments/20_staging_buffer.cpp +++ b/attachments/20_staging_buffer.cpp @@ -92,11 +92,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -485,7 +484,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -508,13 +508,13 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -525,7 +525,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -557,32 +557,34 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -594,17 +596,26 @@ class HelloTriangleApplication throw std::runtime_error("failed to acquire swap chain image!"); } - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -628,8 +639,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/21_index_buffer.cpp b/attachments/21_index_buffer.cpp index 1b589051..9866806a 100644 --- a/attachments/21_index_buffer.cpp +++ b/attachments/21_index_buffer.cpp @@ -98,11 +98,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -510,7 +509,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -533,14 +533,14 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -551,7 +551,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -583,32 +583,34 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -620,17 +622,26 @@ class HelloTriangleApplication throw std::runtime_error("failed to acquire swap chain image!"); } - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -654,8 +665,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/22_descriptor_layout.cpp b/attachments/22_descriptor_layout.cpp index 9e93618d..fd807e59 100644 --- a/attachments/22_descriptor_layout.cpp +++ b/attachments/22_descriptor_layout.cpp @@ -114,11 +114,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -553,7 +552,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -576,14 +576,14 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -594,7 +594,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -626,23 +626,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -665,9 +663,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -678,19 +680,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -714,8 +725,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/23_descriptor_sets.cpp b/attachments/23_descriptor_sets.cpp index 0bb63cd0..096e655d 100644 --- a/attachments/23_descriptor_sets.cpp +++ b/attachments/23_descriptor_sets.cpp @@ -117,11 +117,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -580,7 +579,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -603,15 +603,15 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -622,7 +622,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -654,23 +654,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -693,9 +691,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -706,19 +708,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -742,8 +753,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/24_texture_image.cpp b/attachments/24_texture_image.cpp index 066e9e07..7b523383 100644 --- a/attachments/24_texture_image.cpp +++ b/attachments/24_texture_image.cpp @@ -123,11 +123,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -689,7 +688,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -712,15 +712,15 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -731,7 +731,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -763,23 +763,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -802,9 +800,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -815,19 +817,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -851,8 +862,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/25_sampler.cpp b/attachments/25_sampler.cpp index 7e644e2d..7f422b95 100644 --- a/attachments/25_sampler.cpp +++ b/attachments/25_sampler.cpp @@ -125,11 +125,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -725,7 +724,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -748,15 +748,15 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -767,7 +767,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -799,23 +799,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -838,9 +836,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -851,19 +853,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -887,8 +898,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/26_texture_mapping.cpp b/attachments/26_texture_mapping.cpp index ea516cd1..80f862c7 100644 --- a/attachments/26_texture_mapping.cpp +++ b/attachments/26_texture_mapping.cpp @@ -127,11 +127,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -790,7 +789,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -813,15 +813,15 @@ class HelloTriangleApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -832,7 +832,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -864,23 +864,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -903,9 +901,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -916,19 +918,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -952,8 +963,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/27_depth_buffering.cpp b/attachments/27_depth_buffering.cpp index 98a46715..ada64ce0 100644 --- a/attachments/27_depth_buffering.cpp +++ b/attachments/27_depth_buffering.cpp @@ -138,11 +138,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -870,7 +869,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -915,15 +915,15 @@ class HelloTriangleApplication .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -934,7 +934,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -967,23 +967,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1006,9 +1004,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1019,19 +1021,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1055,8 +1066,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/28_model_loading.cpp b/attachments/28_model_loading.cpp index 6a9f0ed1..200deb09 100644 --- a/attachments/28_model_loading.cpp +++ b/attachments/28_model_loading.cpp @@ -146,11 +146,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -926,7 +925,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -971,15 +971,15 @@ class HelloTriangleApplication .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -990,7 +990,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1023,23 +1023,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1062,9 +1060,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1075,19 +1077,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1111,8 +1122,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/29_mipmapping.cpp b/attachments/29_mipmapping.cpp index 798ac1ba..f9c78533 100644 --- a/attachments/29_mipmapping.cpp +++ b/attachments/29_mipmapping.cpp @@ -147,11 +147,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -994,7 +993,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -1039,15 +1039,15 @@ class HelloTriangleApplication .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -1058,7 +1058,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1091,23 +1091,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1130,9 +1128,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1143,19 +1145,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1179,8 +1190,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/30_multisampling.cpp b/attachments/30_multisampling.cpp index 6f5262c5..6a71e98a 100644 --- a/attachments/30_multisampling.cpp +++ b/attachments/30_multisampling.cpp @@ -152,11 +152,10 @@ class HelloTriangleApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -1044,7 +1043,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -1104,15 +1104,15 @@ class HelloTriangleApplication .colorAttachmentCount = 1, .pColorAttachments = &colorAttachment, .pDepthAttachment = &depthAttachment}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -1123,7 +1123,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1156,23 +1156,21 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1195,9 +1193,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1208,19 +1210,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1244,8 +1255,7 @@ class HelloTriangleApplication throw; } } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/31_compute_shader.cpp b/attachments/31_compute_shader.cpp index 9afe1fa3..84fc1149 100644 --- a/attachments/31_compute_shader.cpp +++ b/attachments/31_compute_shader.cpp @@ -118,7 +118,7 @@ class ComputeShaderApplication vk::raii::Semaphore semaphore = nullptr; uint64_t timelineValue = 0; std::vector inFlightFences; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; double lastFrameTime = 0.0; @@ -697,8 +697,9 @@ class ComputeShaderApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].reset(); - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.reset(); + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( imageIndex, @@ -721,13 +722,13 @@ class ComputeShaderApplication .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); - commandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, {shaderStorageBuffers[frameIndex]}, {0}); + commandBuffer.draw(PARTICLE_COUNT, 1, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( imageIndex, @@ -738,7 +739,7 @@ class ComputeShaderApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage ); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -770,17 +771,18 @@ class ComputeShaderApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void recordComputeCommandBuffer() { - computeCommandBuffers[currentFrame].reset(); - computeCommandBuffers[currentFrame].begin({}); - computeCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); - computeCommandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); - computeCommandBuffers[currentFrame].dispatch(PARTICLE_COUNT / 256, 1, 1); - computeCommandBuffers[currentFrame].end(); + auto &commandBuffer = computeCommandBuffers[frameIndex]; + commandBuffer.reset(); + commandBuffer.begin({}); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[frameIndex]}, {}); + commandBuffer.dispatch(PARTICLE_COUNT / 256, 1, 1); + commandBuffer.end(); } void createSyncObjects() @@ -808,10 +810,10 @@ class ComputeShaderApplication void drawFrame() { - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[currentFrame]); - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[frameIndex]); + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - device.resetFences(*inFlightFences[currentFrame]); + device.resetFences(*inFlightFences[frameIndex]); // Update timeline value for this frame uint64_t computeWaitValue = timelineValue; @@ -819,7 +821,7 @@ class ComputeShaderApplication uint64_t graphicsWaitValue = computeSignalValue; uint64_t graphicsSignalValue = ++timelineValue; - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); { recordComputeCommandBuffer(); @@ -838,7 +840,7 @@ class ComputeShaderApplication .pWaitSemaphores = &*semaphore, .pWaitDstStageMask = waitStages, .commandBufferCount = 1, - .pCommandBuffers = &*computeCommandBuffers[currentFrame], + .pCommandBuffers = &*computeCommandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*semaphore}; @@ -862,7 +864,7 @@ class ComputeShaderApplication .pWaitSemaphores = &*semaphore, .pWaitDstStageMask = &waitStage, .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], + .pCommandBuffers = &*commandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*semaphore}; @@ -911,7 +913,7 @@ class ComputeShaderApplication } } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/32_ecosystem_utilities.cpp b/attachments/32_ecosystem_utilities.cpp index a57dfd55..0ddcfc2a 100644 --- a/attachments/32_ecosystem_utilities.cpp +++ b/attachments/32_ecosystem_utilities.cpp @@ -162,12 +162,12 @@ class HelloTriangleApplication std::vector commandBuffers; // Synchronization objects - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; + uint32_t frameIndex = 0; vk::raii::Semaphore timelineSemaphore = nullptr; uint64_t timelineValue = 0; - uint32_t currentFrame = 0; bool framebufferResized = false; @@ -1341,7 +1341,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); @@ -1388,7 +1389,7 @@ class HelloTriangleApplication .imageMemoryBarrierCount = static_cast(barriers.size()), .pImageMemoryBarriers = barriers.data()}; - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + commandBuffer.pipelineBarrier2(dependencyInfo); } else { @@ -1424,7 +1425,7 @@ class HelloTriangleApplication .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; std::array barriers = {colorBarrier, depthBarrier, swapchainBarrier}; - commandBuffers[currentFrame].pipelineBarrier( + commandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, vk::DependencyFlagBits::eByRegion, @@ -1458,7 +1459,7 @@ class HelloTriangleApplication .pColorAttachments = &colorAttachment, .pDepthAttachment = &depthAttachment}; - commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffer.beginRendering(renderingInfo); } else { @@ -1472,21 +1473,21 @@ class HelloTriangleApplication .clearValueCount = static_cast(clearValues.size()), .pClearValues = clearValues.data()}; - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); } // Common rendering commands - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); if (appInfo.dynamicRenderingSupported) { - commandBuffers[currentFrame].endRendering(); + commandBuffer.endRendering(); // Transition swapchain image to present layout if (appInfo.synchronization2Supported) @@ -1505,7 +1506,7 @@ class HelloTriangleApplication .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + commandBuffer.pipelineBarrier2(dependencyInfo); } else { @@ -1519,7 +1520,7 @@ class HelloTriangleApplication .image = swapChainImages[imageIndex], .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; - commandBuffers[currentFrame].pipelineBarrier( + commandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eBottomOfPipe, vk::DependencyFlagBits::eByRegion, @@ -1530,17 +1531,15 @@ class HelloTriangleApplication } else { - commandBuffers[currentFrame].endRenderPass(); + commandBuffer.endRenderPass(); } - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); if (appInfo.timelineSemaphoresSupported) { @@ -1554,27 +1553,16 @@ class HelloTriangleApplication .pNext = &timelineCreateInfo}; timelineSemaphore = vk::raii::Semaphore(device, semaphoreInfo); - - // Still need binary semaphores for swapchain operations - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) - { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } } - else + + for (size_t i = 0; i < swapChainImages.size(); i++) { - // Create binary semaphores and fences - std::cout << "Creating binary semaphores and fences\n"; - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) - { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1597,9 +1585,13 @@ class HelloTriangleApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1610,10 +1602,9 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); if (appInfo.timelineSemaphoresSupported) @@ -1627,11 +1618,11 @@ class HelloTriangleApplication .signalSemaphoreValueCount = 1, .pSignalSemaphoreValues = &signalValue}; - std::array waitSemaphores = {*presentCompleteSemaphore[currentFrame], *timelineSemaphore}; + std::array waitSemaphores = {*presentCompleteSemaphores[frameIndex], *timelineSemaphore}; std::array waitStages = {vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eVertexInput}; std::array waitValues = {0, waitValue}; // Binary semaphore value is ignored - std::array signalSemaphores = {*renderFinishedSemaphore[currentFrame], *timelineSemaphore}; + std::array signalSemaphores = {*renderFinishedSemaphores[imageIndex], *timelineSemaphore}; std::array signalValues = {0, signalValue}; // Binary semaphore value is ignored timelineInfo.waitSemaphoreValueCount = 1; // Only for the timeline semaphore @@ -1645,35 +1636,33 @@ class HelloTriangleApplication .pWaitSemaphores = &waitSemaphores[0], .pWaitDstStageMask = &waitStages[0], .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], + .pCommandBuffers = &*commandBuffers[frameIndex], .signalSemaphoreCount = 2, // Signal both semaphores .pSignalSemaphores = signalSemaphores.data()}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + queue.submit(submitInfo, *inFlightFences[frameIndex]); } else { // Use traditional binary semaphores vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); } try { - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[currentFrame], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1697,7 +1686,7 @@ class HelloTriangleApplication throw; } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/33_vulkan_profiles.cpp b/attachments/33_vulkan_profiles.cpp index c2d3d60c..eccfcdcb 100644 --- a/attachments/33_vulkan_profiles.cpp +++ b/attachments/33_vulkan_profiles.cpp @@ -129,7 +129,7 @@ class HelloTriangleApplication std::vector renderFinishedSemaphores; std::vector inFlightFences; std::vector presentCompleteSemaphore; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; vk::raii::Buffer vertexBuffer = nullptr; vk::raii::DeviceMemory vertexBufferMemory = nullptr; @@ -1345,7 +1345,8 @@ class HelloTriangleApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Transition the attachments to the correct layouts for dynamic rendering @@ -1418,7 +1419,7 @@ class HelloTriangleApplication .pColorAttachments = &colorAttachment, .pDepthAttachment = &depthAttachment}; - commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffer.beginRendering(renderingInfo); } else { @@ -1430,10 +1431,10 @@ class HelloTriangleApplication .clearValueCount = static_cast(clearValues.size()), .pClearValues = clearValues.data()}; - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); } - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); vk::Viewport viewport{ .x = 0.0f, @@ -1442,21 +1443,21 @@ class HelloTriangleApplication .height = static_cast(swapChainExtent.height), .minDepth = 0.0f, .maxDepth = 1.0f}; - commandBuffers[currentFrame].setViewport(0, viewport); + commandBuffer.setViewport(0, viewport); vk::Rect2D scissor{ .offset = {0, 0}, .extent = swapChainExtent}; - commandBuffers[currentFrame].setScissor(0, scissor); + commandBuffer.setScissor(0, scissor); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); if (appInfo.profileSupported) { - commandBuffers[currentFrame].endRendering(); + commandBuffer.endRendering(); // Transition the swapchain image to the correct layout for presentation transition_image_layout( @@ -1471,11 +1472,11 @@ class HelloTriangleApplication } else { - commandBuffers[currentFrame].endRenderPass(); + commandBuffer.endRenderPass(); // Traditional render pass already transitions the image to the correct layout } - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1508,7 +1509,7 @@ class HelloTriangleApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() @@ -1553,12 +1554,12 @@ class HelloTriangleApplication void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, UINT64_MAX)); + static_cast(device.waitForFences({*inFlightFences[frameIndex]}, VK_TRUE, UINT64_MAX)); uint32_t imageIndex; try { - auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame]); + auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[frameIndex]); imageIndex = idx; } catch (vk::OutOfDateKHRError &) @@ -1567,23 +1568,23 @@ class HelloTriangleApplication return; } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences({*inFlightFences[currentFrame]}); + device.resetFences({*inFlightFences[frameIndex]}); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitSemaphores = &*imageAvailableSemaphores[frameIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], + .pCommandBuffers = &*commandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*presentCompleteSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { @@ -1616,7 +1617,7 @@ class HelloTriangleApplication throw; } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } vk::SampleCountFlagBits getMaxUsableSampleCount() diff --git a/attachments/34_android.cpp b/attachments/34_android.cpp index 76e67937..8752ea1d 100644 --- a/attachments/34_android.cpp +++ b/attachments/34_android.cpp @@ -365,7 +365,7 @@ class HelloTriangleApplication std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; // Application info AppInfo appInfo; @@ -1269,7 +1269,7 @@ class HelloTriangleApplication commandBuffer.bindVertexBuffers(0, {*vertexBuffer}, {0}); commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[currentFrame]}, nullptr); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[frameIndex]}, nullptr); commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); commandBuffer.endRenderPass(); @@ -1279,12 +1279,12 @@ class HelloTriangleApplication // Draw frame void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, UINT64_MAX)); + static_cast(device.waitForFences({*inFlightFences[frameIndex]}, VK_TRUE, UINT64_MAX)); uint32_t imageIndex; try { - auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame]); + auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[frameIndex]); imageIndex = idx; } catch (vk::OutOfDateKHRError &) @@ -1294,23 +1294,23 @@ class HelloTriangleApplication } // Update uniform buffer with current transformation - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences({*inFlightFences[currentFrame]}); + device.resetFences({*inFlightFences[frameIndex]}); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + commandBuffers[frameIndex].reset(); + recordCommandBuffer(commandBuffers[frameIndex], imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitSemaphores = &*imageAvailableSemaphores[frameIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], + .pCommandBuffers = &*commandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { @@ -1346,7 +1346,7 @@ class HelloTriangleApplication } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } // Recreate swap chain diff --git a/attachments/35_gltf_ktx.cpp b/attachments/35_gltf_ktx.cpp index 318d78e9..767ef3dd 100644 --- a/attachments/35_gltf_ktx.cpp +++ b/attachments/35_gltf_ktx.cpp @@ -313,10 +313,10 @@ class VulkanApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -1260,7 +1260,8 @@ class VulkanApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); transition_image_layout( swapChainImages[imageIndex], vk::ImageLayout::eUndefined, @@ -1301,15 +1302,15 @@ class VulkanApplication .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -1320,7 +1321,7 @@ class VulkanApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1353,23 +1354,21 @@ class VulkanApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1394,9 +1393,13 @@ class VulkanApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1407,19 +1410,28 @@ class VulkanApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1443,7 +1455,7 @@ class VulkanApplication throw; } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/36_multiple_objects.cpp b/attachments/36_multiple_objects.cpp index 2f248a0a..4b9ea030 100644 --- a/attachments/36_multiple_objects.cpp +++ b/attachments/36_multiple_objects.cpp @@ -365,10 +365,10 @@ class VulkanApplication vk::raii::CommandPool commandPool = nullptr; std::vector commandBuffers; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -1359,7 +1359,8 @@ class VulkanApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -1401,31 +1402,31 @@ class VulkanApplication .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); // Bind vertex and index buffers (shared by all objects) - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); // Draw each object with its own descriptor set for (const auto &gameObject : gameObjects) { // Bind the descriptor set for this object - commandBuffers[currentFrame].bindDescriptorSets( + commandBuffer.bindDescriptorSets( vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, - *gameObject.descriptorSets[currentFrame], + *gameObject.descriptorSets[frameIndex], nullptr); // Draw the object - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); } - commandBuffers[currentFrame].endRendering(); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( swapChainImages[imageIndex], @@ -1436,7 +1437,7 @@ class VulkanApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1469,23 +1470,21 @@ class VulkanApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1521,15 +1520,19 @@ class VulkanApplication .proj = proj}; // Copy the UBO data to the mapped memory - memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); + memcpy(gameObject.uniformBuffersMapped[frameIndex], &ubo, sizeof(ubo)); } } void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1544,29 +1547,26 @@ class VulkanApplication // Update uniform buffers for all objects updateUniformBuffers(); - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); try { - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex}; + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = queue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1590,7 +1590,7 @@ class VulkanApplication throw; } } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const diff --git a/attachments/37_multithreading.cpp b/attachments/37_multithreading.cpp index fc254156..6c98bd79 100644 --- a/attachments/37_multithreading.cpp +++ b/attachments/37_multithreading.cpp @@ -202,7 +202,7 @@ class MultithreadedApplication uint64_t timelineValue = 0; std::vector imageAvailableSemaphores; std::vector inFlightFences; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; double lastFrameTime = 0.0; @@ -1002,7 +1002,7 @@ class MultithreadedApplication cmdBuffer.begin(beginInfo); cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); - cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); + cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[frameIndex]}, {}); struct PushConstants { @@ -1020,11 +1020,11 @@ class MultithreadedApplication void recordGraphicsCommandBuffer(uint32_t imageIndex) { - graphicsCommandBuffers[currentFrame].reset(); + graphicsCommandBuffers[frameIndex].reset(); vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; - graphicsCommandBuffers[currentFrame].begin(beginInfo); + graphicsCommandBuffers[frameIndex].begin(beginInfo); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( @@ -1050,14 +1050,14 @@ class MultithreadedApplication .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo}; - graphicsCommandBuffers[currentFrame].beginRendering(renderingInfo); + graphicsCommandBuffers[frameIndex].beginRendering(renderingInfo); - graphicsCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - graphicsCommandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - graphicsCommandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - graphicsCommandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); - graphicsCommandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); - graphicsCommandBuffers[currentFrame].endRendering(); + graphicsCommandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + graphicsCommandBuffers[frameIndex].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + graphicsCommandBuffers[frameIndex].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + graphicsCommandBuffers[frameIndex].bindVertexBuffers(0, {shaderStorageBuffers[frameIndex]}, {0}); + graphicsCommandBuffers[frameIndex].draw(PARTICLE_COUNT, 1, 0, 0); + graphicsCommandBuffers[frameIndex].endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( @@ -1070,7 +1070,7 @@ class MultithreadedApplication vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - graphicsCommandBuffers[currentFrame].end(); + graphicsCommandBuffers[frameIndex].end(); } void transition_image_layout( @@ -1103,7 +1103,7 @@ class MultithreadedApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - graphicsCommandBuffers[currentFrame].pipelineBarrier2(dependency_info); + graphicsCommandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void signalThreadsToWork() @@ -1185,7 +1185,7 @@ class MultithreadedApplication void drawFrame() { // Wait for the previous frame to finish - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; // If the framebuffer was resized, rebuild the swap chain before acquiring a new image @@ -1197,7 +1197,7 @@ class MultithreadedApplication } // Acquire the next image - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { recreateSwapChain(); @@ -1215,7 +1215,7 @@ class MultithreadedApplication uint64_t graphicsSignalValue = ++timelineValue; // Update uniform buffer with the latest delta time - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); // Signal worker threads to start processing particles signalThreadsToWork(); @@ -1275,7 +1275,7 @@ class MultithreadedApplication // Set up graphics submission vk::PipelineStageFlags graphicsWaitStages[] = {vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput}; - std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[currentFrame]}; + std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[frameIndex]}; std::array waitSemaphoreValues = {graphicsWaitValue, 0}; vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ @@ -1290,15 +1290,15 @@ class MultithreadedApplication .pWaitSemaphores = waitSemaphores.data(), .pWaitDstStageMask = graphicsWaitStages, .commandBufferCount = 1, - .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], + .pCommandBuffers = &*graphicsCommandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*timelineSemaphore}; // Submit graphics work { std::lock_guard lock(queueSubmitMutex); - device.resetFences(*inFlightFences[currentFrame]); - queue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); + device.resetFences(*inFlightFences[frameIndex]); + queue.submit(graphicsSubmitInfo, *inFlightFences[frameIndex]); } // Wait for graphics to complete before presenting @@ -1335,7 +1335,7 @@ class MultithreadedApplication } // Move to the next frame - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } }; diff --git a/attachments/38_ray_tracing.cpp b/attachments/38_ray_tracing.cpp index 4ba405c3..029ddc3b 100644 --- a/attachments/38_ray_tracing.cpp +++ b/attachments/38_ray_tracing.cpp @@ -216,11 +216,10 @@ class VulkanRaytracingApplication std::vector commandBuffers; uint32_t graphicsIndex = 0; - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; + uint32_t frameIndex = 0; bool framebufferResized = false; @@ -1558,7 +1557,8 @@ class VulkanRaytracingApplication void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin({}); // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL transition_image_layout( swapChainImages[imageIndex], @@ -1614,15 +1614,15 @@ class VulkanRaytracingApplication .pDepthAttachment = &depthAttachmentInfo}; // Note: .beginRendering replaces the previous .beginRenderPass call. - commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffer.beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *globalDescriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 1, *materialDescriptorSets[0], nullptr); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *globalDescriptorSets[frameIndex], nullptr); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 1, *materialDescriptorSets[0], nullptr); for (auto &sub : submeshes) { @@ -1633,12 +1633,12 @@ class VulkanRaytracingApplication .reflective = sub.reflective #endif // LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS }; - commandBuffers[currentFrame].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); + commandBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); - commandBuffers[currentFrame].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); + commandBuffer.drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); } - commandBuffers[currentFrame].endRendering(); + commandBuffer.endRendering(); // After rendering, transition the swapchain image to PRESENT_SRC transition_image_layout( @@ -1651,7 +1651,7 @@ class VulkanRaytracingApplication vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage vk::ImageAspectFlagBits::eColor); - commandBuffers[currentFrame].end(); + commandBuffer.end(); } void transition_image_layout( @@ -1684,23 +1684,21 @@ class VulkanRaytracingApplication .dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier}; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + commandBuffers[frameIndex].pipelineBarrier2(dependency_info); } void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); } } @@ -1825,9 +1823,13 @@ class VulkanRaytracingApplication void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, + // while renderFinishedSemaphores is indexed by imageIndex + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR) { @@ -1838,21 +1840,30 @@ class VulkanRaytracingApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); #if LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION // TASK06: Update the TLAS with the current model matrix updateTopLevelAS(ubo.model); #endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); + commandBuffers[frameIndex].reset(); recordCommandBuffer(imageIndex); vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; - graphicsQueue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + graphicsQueue.submit(submitInfo, *inFlightFences[frameIndex]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; result = presentQueue.presentKHR(presentInfoKHR); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { @@ -1863,8 +1874,7 @@ class VulkanRaytracingApplication { throw std::runtime_error("failed to present swap chain image!"); } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const From 94768d73fc7679012ada1978df7820755b739007 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Thu, 27 Nov 2025 18:29:54 +0100 Subject: [PATCH 4/4] Adjust documentation to source changes --- .../00_Setup/01_Instance.adoc | 19 +++--- .../03_Drawing/03_Frames_in_flight.adoc | 64 +++++++++---------- .../04_Swap_chain_recreation.adoc | 10 +-- .../01_Vertex_buffer_creation.adoc | 6 +- en/04_Vertex_buffers/03_Index_buffer.adoc | 6 +- .../00_Descriptor_set_layout_and_buffer.adoc | 12 ++-- .../01_Descriptor_pool_and_sets.adoc | 4 +- en/08_Loading_models.adoc | 2 +- en/11_Compute_Shader.adoc | 54 ++++++++-------- ...Ecosystem_Utilities_and_Compatibility.adoc | 11 +++- en/16_Multiple_Objects.adoc | 12 ++-- en/17_Multithreading.adoc | 32 +++++----- .../18_Ray_tracing/01_Dynamic_rendering.adoc | 2 +- .../18_Ray_tracing/04_TLAS_animation.adoc | 2 +- .../05_Shadow_transparency.adoc | 4 +- en/courses/18_Ray_tracing/06_Reflections.adoc | 4 +- 16 files changed, 127 insertions(+), 117 deletions(-) diff --git a/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc b/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc index fad586ad..e750f205 100644 --- a/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc +++ b/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc @@ -150,14 +150,17 @@ Or use the tuple: [,c++] ---- - auto [result, imageIndex] = swapChain->acquireNextImage( UINT64_MAX, **presentCompleteSemaphore[currentFrame], nullptr ); - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } ---- Those examples are from later parts of our tutorial, this is just an example diff --git a/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.adoc b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.adoc index 36a7d27f..45cbec26 100644 --- a/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.adoc +++ b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.adoc @@ -60,15 +60,18 @@ The `createSyncObjects` function should be changed to create all the objects: [,c++] ---- void createSyncObjects() { - presentCompleteSemaphores.clear(); - renderFinishedSemaphores.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - inFlightFences.emplace_back(device, vk::FenceCreateInfo(vk::FenceCreateFlagBits::eSignaled)); - } + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } } ---- @@ -77,7 +80,7 @@ We will use a frame index for that purpose: [,c++] ---- -uint32_t currentFrame = 0; +uint32_t frameIndex 0; ---- The `drawFrame` function can now be modified to use the right objects: @@ -85,27 +88,24 @@ The `drawFrame` function can now be modified to use the right objects: [,c++] ---- void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, presentCompleteSemaphores[currentFrame], nullptr ); - - device.resetFences( inFlightFences[currentFrame] ); - - ... - - commandBuffers[currentFrame].reset(); - recordCommandBuffer(commandBuffers[currentFrame], imageIndex); - - ... - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphores[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame] }; - - ... - - graphicsQueue.submit(submitInfo, inFlightFences[currentFrame]); + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + + commandBuffers[frameIndex].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[frameIndex]); } ---- @@ -116,7 +116,7 @@ Of course, we shouldn't forget to advance to the next frame every time: void drawFrame() { ... - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } ---- diff --git a/en/03_Drawing_a_triangle/04_Swap_chain_recreation.adoc b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.adoc index 9904c6b0..705057d1 100644 --- a/en/03_Drawing_a_triangle/04_Swap_chain_recreation.adoc +++ b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.adoc @@ -87,7 +87,7 @@ Usually happens after a window resize. [,c++] ---- -auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphores[currentFrame], nullptr ); +auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr ); if (result == vk::Result::eErrorOutOfDateKHR) { recreateSwapChain(); @@ -113,7 +113,7 @@ if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptima throw std::runtime_error("failed to present swap chain image!"); } -currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; ---- The `vkQueuePresentKHR` function returns the same values with the same meaning. @@ -135,10 +135,10 @@ The beginning of `drawFrame` should now look like this: [,c++] ---- -vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); +vkWaitForFences(device, 1, &inFlightFences[frameIndex], VK_TRUE, UINT64_MAX); uint32_t imageIndex; -VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[frameIndex], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -148,7 +148,7 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR) { } // Only reset the fence if we are submitting work -vkResetFences(device, 1, &inFlightFences[currentFrame]); +vkResetFences(device, 1, &inFlightFences[frameIndex]); ---- == Handling resizes explicitly diff --git a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc index e3eba4c9..92b04ef5 100644 --- a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc @@ -230,11 +230,11 @@ We're going to extend the `recordCommandBuffer` function to do that. [,c++] ---- -commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); +commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); -commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); +commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); -commandBuffers[currentFrame].draw(3, 1, 0, 0); +commandBuffers[frameIndex].draw(3, 1, 0, 0); ---- The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to bindings, like the one we set up in the previous chapter. diff --git a/en/04_Vertex_buffers/03_Index_buffer.adoc b/en/04_Vertex_buffers/03_Index_buffer.adoc index bd99c3d3..ff8c3eaf 100644 --- a/en/04_Vertex_buffers/03_Index_buffer.adoc +++ b/en/04_Vertex_buffers/03_Index_buffer.adoc @@ -102,8 +102,8 @@ It's unfortunately not possible to use different indices for each vertex attribu [,c++] ---- -commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); -commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); +commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); +commandBuffers[frameIndex].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); ---- An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer, a byte offset into it, and the type of index data as parameters. @@ -114,7 +114,7 @@ Remove the `vkCmdDraw` line and replace it with `vkCmdDrawIndexed`: [,c++] ---- -commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); +commandBuffers[frameIndex].drawIndexed(indices.size(), 1, 0, 0, 0); ---- A call to this function is very similar to `vkCmdDraw`. diff --git a/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc b/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc index bc123670..980dd8e2 100644 --- a/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc +++ b/en/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc @@ -267,13 +267,17 @@ Create a new function `updateUniformBuffer` and add a call to it from the `drawF void drawFrame() { ... - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); ... - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame] }; + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; ... } diff --git a/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.adoc b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.adoc index 0e67ed74..24c7bcd1 100644 --- a/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.adoc +++ b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.adoc @@ -165,8 +165,8 @@ This needs to be done before the `vkCmdDrawIndexed` call: [,c++] ---- -commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); -commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); +commandBuffers[frameIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); +commandBuffers[frameIndex].drawIndexed(indices.size(), 1, 0, 0, 0); ---- Unlike vertex and index buffers, descriptor sets are not unique to graphics pipelines. diff --git a/en/08_Loading_models.adoc b/en/08_Loading_models.adoc index 21ba7bbe..a83a2d81 100644 --- a/en/08_Loading_models.adoc +++ b/en/08_Loading_models.adoc @@ -71,7 +71,7 @@ Remember to also change the `vkCmdBindIndexBuffer` parameter: [,c++] ---- -commandBuffers[currentFrame]->bindIndexBuffer( **indexBuffer, 0, vk::IndexType::eUint32 ); +commandBuffers[frameIndex]->bindIndexBuffer( **indexBuffer, 0, vk::IndexType::eUint32 ); ---- The tinyobjloader library is included in the same way as STB libraries. diff --git a/en/11_Compute_Shader.adoc b/en/11_Compute_Shader.adoc index 55105b70..e7ce6cee 100644 --- a/en/11_Compute_Shader.adoc +++ b/en/11_Compute_Shader.adoc @@ -443,7 +443,7 @@ This image shows the relation between these two in three dimensions: image::/images/compute_space.svg[] -The number of dimensions for work groups (defined by `computeCommandBuffers[currentFrame]->dispatch`) and invocations depends (defined by the local sizes in the compute shader) on how input data is structured. +The number of dimensions for work groups (defined by `computeCommandBuffers[frameIndex]->dispatch`) and invocations depends (defined by the local sizes in the compute shader) on how input data is structured. If you e.g., work on a one-dimensional array, like we do in this chapter, you only have to specify the x dimension for both. @@ -534,27 +534,27 @@ We use this to index into our particle array. === Dispatch Now it's time to actually tell the GPU to do some compute. -This is done by calling `computeCommandBuffers[currentFrame]->dispatch` inside a command buffer. -While not perfectly true, a dispatch is for compute as a draw call like `commandBuffers[currentFrame]->draw` is for graphics. +This is done by calling `computeCommandBuffers[frameIndex]->dispatch` inside a command buffer. +While not perfectly true, a dispatch is for compute as a draw call like `commandBuffers[frameIndex]->draw` is for graphics. This dispatches a given number of compute work items in at max. three dimensions. [,c++] ---- -computeCommandBuffers[currentFrame]->begin({}); +computeCommandBuffers[frameIndex]->begin({}); ... -computeCommandBuffers[currentFrame]->bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); -computeCommandBuffers[currentFrame]->bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); +computeCommandBuffers[frameIndex]->bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); +computeCommandBuffers[frameIndex]->bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {computeDescriptorSets[frameIndex]}, {}); -computeCommandBuffers[currentFrame]->dispatch( PARTICLE_COUNT / 256, 1, 1 ); +computeCommandBuffers[frameIndex]->dispatch( PARTICLE_COUNT / 256, 1, 1 ); ... -computeCommandBuffers[currentFrame]->end(); +computeCommandBuffers[frameIndex]->end(); ---- -The `computeCommandBuffers[currentFrame]->dispatch` will dispatch `PARTICLE_COUNT / 256` local work groups in the x dimension. +The `computeCommandBuffers[frameIndex]->dispatch` will dispatch `PARTICLE_COUNT / 256` local work groups in the x dimension. As our particle array is linear, we leave the other two dimensions at one, resulting in a one-dimensional dispatch. But why do we divide the number of particles (in our array) by 256? That's because in the previous paragraph, we defined that every compute shader in a work group will do 256 invocations. @@ -574,9 +574,9 @@ As our sample does both compute and graphics operations, we'll be doing two subm [,c++] ---- ... -computeQueue->submit(submitInfo, **computeInFlightFences[currentFrame]); +computeQueue->submit(submitInfo, **computeInFlightFences[frameIndex]); ... -graphicsQueue->submit(submitInfo, **inFlightFences[currentFrame]); +graphicsQueue->submit(submitInfo, **inFlightFences[frameIndex]); ---- The first submit to the compute queue updates the particle positions using the compute shader, and the second submit will then use that updated data to draw the particle system. @@ -623,30 +623,30 @@ We then use these to synchronize the compute buffer submission with the graphics ---- { // Compute submission - while ( vk::Result::eTimeout == device->waitForFences(**computeInFlightFences[currentFrame], vk::True, UINT64_MAX) ) + while ( vk::Result::eTimeout == device->waitForFences(**computeInFlightFences[frameIndex], vk::True, UINT64_MAX) ) ; - updateUniformBuffer(currentFrame); - device->resetFences( **computeInFlightFences[currentFrame] ); - computeCommandBuffers[currentFrame]->reset(); + updateUniformBuffer(frameIndex); + device->resetFences( **computeInFlightFences[frameIndex] ); + computeCommandBuffers[frameIndex]->reset(); recordComputeCommandBuffer(); - const vk::SubmitInfo submitInfo({}, {}, {**computeCommandBuffers[currentFrame]}, { **computeFinishedSemaphores[currentFrame]}); - computeQueue->submit(submitInfo, **computeInFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo({}, {}, {**computeCommandBuffers[frameIndex]}, { **computeFinishedSemaphores[frameIndex]}); + computeQueue->submit(submitInfo, **computeInFlightFences[frameIndex]); } { // Graphics submission - while ( vk::Result::eTimeout == device->waitForFences(**inFlightFences[currentFrame], vk::True, UINT64_MAX)) + while ( vk::Result::eTimeout == device->waitForFences(**inFlightFences[frameIndex], vk::True, UINT64_MAX)) ... - device->resetFences( **inFlightFences[currentFrame] ); - commandBuffers[currentFrame]->reset(); + device->resetFences( **inFlightFences[frameIndex] ); + commandBuffers[frameIndex]->reset(); recordCommandBuffer(imageIndex); - vk::Semaphore waitSemaphores[] = {**presentCompleteSemaphore[currentFrame], **computeFinishedSemaphores[currentFrame]}; + vk::Semaphore waitSemaphores[] = {**presentCompleteSemaphore[frameIndex], **computeFinishedSemaphores[frameIndex]}; vk::PipelineStageFlags waitDestinationStageMask[] = { vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput }; - const vk::SubmitInfo submitInfo( waitSemaphores, waitDestinationStageMask, {**commandBuffers[currentFrame]}, {**renderFinishedSemaphore[currentFrame]} ); - graphicsQueue->submit(submitInfo, **inFlightFences[currentFrame]); + const vk::SubmitInfo submitInfo( waitSemaphores, waitDestinationStageMask, {**commandBuffers[frameIndex]}, {**renderFinishedSemaphore[frameIndex]} ); + graphicsQueue->submit(submitInfo, **inFlightFences[frameIndex]); ---- Similar to the sample in the @@ -724,7 +724,7 @@ vk::SubmitInfo computeSubmitInfo{ .pWaitSemaphores = &*semaphore, .pWaitDstStageMask = waitStages, .commandBufferCount = 1, - .pCommandBuffers = &*computeCommandBuffers[currentFrame], + .pCommandBuffers = &*computeCommandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*semaphore }; @@ -750,7 +750,7 @@ vk::SubmitInfo graphicsSubmitInfo{ .pWaitSemaphores = &*semaphore, .pWaitDstStageMask = &waitStage, .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], + .pCommandBuffers = &*commandBuffers[frameIndex], .signalSemaphoreCount = 1, .pSignalSemaphores = &*semaphore }; @@ -817,9 +817,9 @@ We then bind and draw it like we would with any vertex buffer: [,c++] ---- -commandBuffers[currentFrame]->bindVertexBuffers(0, { *shaderStorageBuffers[currentFrame] }, {0}); +commandBuffers[frameIndex]->bindVertexBuffers(0, { *shaderStorageBuffers[frameIndex] }, {0}); -commandBuffers[currentFrame]->draw( PARTICLE_COUNT, 1, 0, 0 ); +commandBuffers[frameIndex]->draw( PARTICLE_COUNT, 1, 0, 0 ); ---- == Conclusion diff --git a/en/12_Ecosystem_Utilities_and_Compatibility.adoc b/en/12_Ecosystem_Utilities_and_Compatibility.adoc index 87f090af..3ee7c1a4 100644 --- a/en/12_Ecosystem_Utilities_and_Compatibility.adoc +++ b/en/12_Ecosystem_Utilities_and_Compatibility.adoc @@ -449,11 +449,16 @@ if (timelineSemaphoresSupported) { vk::SemaphoreCreateInfo semaphoreInfo{}; vk::FenceCreateInfo fenceInfo{.flags = vk::FenceCreateFlagBits::eSignaled}; - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores[i] = device.createSemaphore(semaphoreInfo); + for (size_t i = 0; i < swapChainImages.size(); i++) + { renderFinishedSemaphores[i] = device.createSemaphore(semaphoreInfo); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores[i] = device.createSemaphore(semaphoreInfo); inFlightFences[i] = device.createFence(fenceInfo); - } + } } ---- diff --git a/en/16_Multiple_Objects.adoc b/en/16_Multiple_Objects.adoc index e9f84c99..c84b017e 100644 --- a/en/16_Multiple_Objects.adoc +++ b/en/16_Multiple_Objects.adoc @@ -257,7 +257,7 @@ void updateUniformBuffers() { }; // Copy the UBO data to the mapped memory - memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); + memcpy(gameObject.uniformBuffersMapped[frameIndex], &ubo, sizeof(ubo)); } } ---- @@ -274,22 +274,22 @@ void recordCommandBuffer(uint32_t imageIndex) { // ... (beginning of the method remains the same) // Bind vertex and index buffers (shared by all objects) - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[frameIndex].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); // Draw each object with its own descriptor set for (const auto& gameObject : gameObjects) { // Bind the descriptor set for this object - commandBuffers[currentFrame].bindDescriptorSets( + commandBuffers[frameIndex].bindDescriptorSets( vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, - *gameObject.descriptorSets[currentFrame], + *gameObject.descriptorSets[frameIndex], nullptr ); // Draw the object - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[frameIndex].drawIndexed(indices.size(), 1, 0, 0, 0); } // ... (end of the method remains the same) diff --git a/en/17_Multithreading.adoc b/en/17_Multithreading.adoc index 45a5b957..40373ece 100644 --- a/en/17_Multithreading.adoc +++ b/en/17_Multithreading.adoc @@ -187,7 +187,7 @@ public: // Bind compute pipeline and descriptor sets cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); - cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); + cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[frameIndex]}, {}); // Add a push constant to specify the particle range for this thread struct PushConstants { @@ -297,11 +297,11 @@ Finally, we'll update our main loop to coordinate the worker threads: ---- void drawFrame() { // Wait for the previous frame to finish - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)); - device.resetFences(*inFlightFences[currentFrame]); + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX)); + device.resetFences(*inFlightFences[frameIndex]); // Acquire the next image - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[frameIndex], nullptr); if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { framebufferResized = false; @@ -310,7 +310,7 @@ void drawFrame() { } // Update uniform buffers - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); // Signal worker threads to start recording compute command buffers signalThreadsToWork(); @@ -342,25 +342,23 @@ void drawFrame() { vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eVertexInput}; // Submit graphics work - vk::SubmitInfo graphicsSubmitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], - .pWaitDstStageMask = waitStages, - .commandBufferCount = 1, - .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame] - }; + vk::SubmitInfo graphisSubmitInfo{.waitSemaphoreCount = 1, + .pWaitSemaphores = &*imageAvailableSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*graphicsCommandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; { std::lock_guard lock(queueSubmitMutex); - graphicsQueue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); + graphicsQueue.submit(graphicsSubmitInfo, *inFlightFences[frameIndex]); } // Present the image vk::PresentInfoKHR presentInfo{ .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[currentFrame], + .pWaitSemaphores = &*renderFinishedSemaphores[frameIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex @@ -375,7 +373,7 @@ void drawFrame() { throw std::runtime_error("failed to present swap chain image!"); } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } ---- diff --git a/en/courses/18_Ray_tracing/01_Dynamic_rendering.adoc b/en/courses/18_Ray_tracing/01_Dynamic_rendering.adoc index e5e27606..c17ba79a 100644 --- a/en/courses/18_Ray_tracing/01_Dynamic_rendering.adoc +++ b/en/courses/18_Ray_tracing/01_Dynamic_rendering.adoc @@ -78,7 +78,7 @@ vk::RenderingInfo renderingInfo = { }; // Note: .beginRendering replaces the previous .beginRenderPass call. -commandBuffers[currentFrame].beginRendering(renderingInfo); +commandBuffers[frameIndex].beginRendering(renderingInfo); ---- For more context, refer to the previous tutorial link:../../03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.adoc[chapter]. diff --git a/en/courses/18_Ray_tracing/04_TLAS_animation.adoc b/en/courses/18_Ray_tracing/04_TLAS_animation.adoc index 5693190d..de134e9d 100644 --- a/en/courses/18_Ray_tracing/04_TLAS_animation.adoc +++ b/en/courses/18_Ray_tracing/04_TLAS_animation.adoc @@ -138,7 +138,7 @@ Verify that the function is called in `drawFrame()` after the model matrix is up [,c{pp}] ---- - updateUniformBuffer(currentFrame); + updateUniformBuffer(frameIndex); // TASK06: Update the TLAS with the current model matrix updateTopLevelAS(ubo.model); ---- diff --git a/en/courses/18_Ray_tracing/05_Shadow_transparency.adoc b/en/courses/18_Ray_tracing/05_Shadow_transparency.adoc index a31f87d1..6f41f125 100644 --- a/en/courses/18_Ray_tracing/05_Shadow_transparency.adoc +++ b/en/courses/18_Ray_tracing/05_Shadow_transparency.adoc @@ -94,9 +94,9 @@ Note how the renderer does not bind separate material textures for each submesh. PushConstant pushConstant = { .materialIndex = sub.materialID < 0 ? 0u : static_cast(sub.materialID), }; - commandBuffers[currentFrame].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); + commandBuffers[frameIndex].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); - commandBuffers[currentFrame].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); + commandBuffers[frameIndex].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); } ---- diff --git a/en/courses/18_Ray_tracing/06_Reflections.adoc b/en/courses/18_Ray_tracing/06_Reflections.adoc index 99d907d5..45eaa893 100644 --- a/en/courses/18_Ray_tracing/06_Reflections.adoc +++ b/en/courses/18_Ray_tracing/06_Reflections.adoc @@ -40,9 +40,9 @@ PushConstant pushConstant = { .materialIndex = sub.materialID < 0 ? 0u : static_cast(sub.materialID), .reflective = sub.reflective }; -commandBuffers[currentFrame].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); +commandBuffers[frameIndex].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); -commandBuffers[currentFrame].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); +commandBuffers[frameIndex].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); ---- We will then retrieve this in the fragment shader, before we apply the shadow effect, to call a helper function that will modify the fragment color in-place, based on the reflection ray query: