diff --git a/src/main.cpp b/src/main.cpp index 27b72dd..53ba0a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,8 @@ #include // String formatting #include +// Unordered sets +#include // Make depth go from 0 to 1 instead of -1 to 1 #define GLM_FORCE_DEPTH_ZERO_TO_ONE @@ -53,6 +55,11 @@ constexpr const char* VERTEX_SHADER_PATH = "shaders/vert.spv"; // Maximum number of frames to be worked on at the same time constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; +// Enable validation layers only in debug mode +#ifndef NDEBUG +constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; +#endif + // macOS requires the portability extension to be enabled #ifdef __APPLE__ constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName, @@ -61,14 +68,6 @@ constexpr std::array deviceExtensions = { vk::KHRSwapchainExtens constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName }; #endif -// Enable validation layers only in debug mode -#ifdef NDEBUG -constexpr bool enableValidationLayers = false; -#else -constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; -constexpr bool enableValidationLayers = true; -#endif - class VulkanApp { public: fn run() -> void { @@ -174,36 +173,46 @@ class VulkanApp { alignas(16) glm::mat4 proj; }; + // Read a file into a vector of chars static fn readFile(const std::string& filename) -> std::vector { - std::ifstream file(filename, std::ios::ate | std::ios::binary); + // Open the file in binary mode + std::ifstream file(filename, std::ios::binary); - if (!file.is_open()) - throw std::runtime_error("Failed to open file! " + filename); + // Make sure the file was opened + if (!file) + throw std::runtime_error("Failed to open file: " + filename); - usize fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); - - file.seekg(0); - file.read(buffer.data(), static_cast(fileSize)); - - file.close(); + // Read the contents of the file into a vector + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return buffer; } + // Create a GLFW window fn initWindow() -> void { + // Initialize GLFW mVKFWInstance = vkfw::initUnique(); - vkfw::WindowHints hints { .clientAPI = vkfw::ClientAPI::eNone }; + // Set window hints + vkfw::WindowHints hints { + .clientAPI = vkfw::ClientAPI::eNone, // No client API (Vulkan) + }; + // Create the window mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints); + + // Set the window user pointer to this class mWindow->setUserPointer(this); + + // Set the framebuffer resize callback mWindow->callbacks()->on_window_resize = - [](const vkfw::Window& window, u32 /*width*/, u32 /*height*/) -> void { + [](const vkfw::Window& window, usize /*width*/, usize /*height*/) -> void { + // Set the framebuffer resized flag std::bit_cast(window.getUserPointer())->mFramebufferResized = true; }; } + // Initialize Vulkan fn initVulkan() -> void { createInstance(); setupDebugMessenger(); @@ -232,15 +241,22 @@ class VulkanApp { createSyncObjects(); } + // Main loop fn mainLoop() -> void { + // While the window is open, while (!mWindow->shouldClose()) { + // Poll for events vkfw::pollEvents(); + + // Draw a frame drawFrame(); } + // Wait for the device to finish mDevice->waitIdle(); } + // Clean up the swap chain fn cleanupSwapChain() -> void { for (vk::UniqueFramebuffer& mSwapChainFramebuffer : mSwapChainFramebuffers) mSwapChainFramebuffer.reset(); for (vk::UniqueImageView& mSwapChainImageView : mSwapChainImageViews) mSwapChainImageView.reset(); @@ -248,19 +264,24 @@ class VulkanApp { mSwapChain.reset(); } + // Recreate the swap chain fn recreateSwapChain() -> void { - u32 width = 0, height = 0; - std::tie(width, height) = mWindow->getFramebufferSize(); + // Get the new width and height + auto [width, height] = mWindow->getFramebufferSize(); + // If the width or height are 0, wait for events while (width == 0 || height == 0) { std::tie(width, height) = mWindow->getFramebufferSize(); vkfw::waitEvents(); } + // Wait for the device to finish mDevice->waitIdle(); + // Clean up the swap chain cleanupSwapChain(); + // Create a new swap chain createSwapChain(); createImageViews(); createColorResources(); @@ -268,53 +289,78 @@ class VulkanApp { createFramebuffers(); } + // Create a Vulkan instance fn createInstance() -> void { - if (enableValidationLayers && !checkValidationLayerSupport()) +#ifndef NDEBUG + // Make sure validation layers are supported + if (!checkValidationLayerSupport()) throw std::runtime_error("Validation layers requested, but not available!"); +#endif - vk::ApplicationInfo appInfo { .pApplicationName = "Hello Triangle", - .applicationVersion = 1, - .pEngineName = "No Engine", - .engineVersion = 1, - .apiVersion = vk::ApiVersion12 }; + // Application metadata + vk::ApplicationInfo appInfo { + .pApplicationName = "Hello Triangle", + .applicationVersion = 1, + .pEngineName = "No Engine", + .engineVersion = 1, + .apiVersion = vk::ApiVersion12, + }; - // Retrieve extensions using custom function - std::vector extensions = getRequiredExtensions(); + // Get the required extensions + std::span extensionsSpan = vkfw::getRequiredInstanceExtensions(); + + // Convert the span to a vector + std::vector extensions(extensionsSpan.begin(), extensionsSpan.end()); + +#ifndef NDEBUG + // Add the debug utils extension + extensions.emplace_back(vk::EXTDebugUtilsExtensionName); +#endif #ifdef __APPLE__ - // Enable the portability extension and set flags + // Add the portability extension extensions.emplace_back(vk::KHRPortabilityEnumerationExtensionName); - // Technically deprecated but vulkan complains if I don't include it for macOS - // So instead of using the vk::KHRPortabilitySubsetExtensionName, I just use + // Technically deprecated but Vulkan complains if I don't include it for macOS, + // so instead of using the vk::KHRPortabilitySubsetExtensionName, I just use // the direct string. extensions.emplace_back("VK_KHR_get_physical_device_properties2"); #endif + // Create the instance vk::InstanceCreateInfo createInfo { #ifdef __APPLE__ + // Enable the portability extension .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, #endif - .pApplicationInfo = &appInfo, - .enabledLayerCount = enableValidationLayers ? static_cast(validationLayers.size()) : 0, - .ppEnabledLayerNames = enableValidationLayers ? validationLayers.data() : nullptr, + .pApplicationInfo = &appInfo, +#ifdef NDEBUG + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, +#else + .enabledLayerCount = static_cast(validationLayers.size()), + .ppEnabledLayerNames = validationLayers.data(), +#endif .enabledExtensionCount = static_cast(extensions.size()), .ppEnabledExtensionNames = extensions.data() }; #ifndef NDEBUG fmt::println("Available extensions:"); - for (const char* extension : extensions) fmt::println("\t{}", extension); #endif + // Create the instance mInstance = vk::createInstanceUnique(createInfo); + // Load the instance functions VULKAN_HPP_DEFAULT_DISPATCHER.init(mInstance.get()); } + // Create the debug messenger fn setupDebugMessenger() -> void { - if (!enableValidationLayers) - return; +#ifdef NDEBUG + return; +#endif vk::DebugUtilsMessengerCreateInfoEXT messengerCreateInfo { .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | @@ -329,11 +375,15 @@ class VulkanApp { mDebugMessenger = mInstance->createDebugUtilsMessengerEXTUnique(messengerCreateInfo, nullptr); } + // Create the GLFW window surface fn createSurface() -> void { mSurface = vkfw::createWindowSurfaceUnique(mInstance.get(), mWindow.get()); } + // Choose the best-suited physical device fn pickPhysicalDevice() -> void { + // Get all physical devices std::vector devices = mInstance->enumeratePhysicalDevices(); + // Make sure there are supported devices if (devices.empty()) throw std::runtime_error("Failed to find GPUs with Vulkan support!"); @@ -341,12 +391,14 @@ class VulkanApp { fmt::println("Available devices:"); #endif + // For each device, for (const vk::PhysicalDevice& device : devices) { #ifndef NDEBUG vk::PhysicalDeviceProperties properties = device.getProperties(); fmt::println("\t{}", properties.deviceName.data()); #endif + // Set the first suitable device as the physical device if (isDeviceSuitable(device)) { mPhysicalDevice = device; mMsaaSamples = getMaxUsableSampleCount(); @@ -354,37 +406,51 @@ class VulkanApp { } } + // If no suitable device was found, throw an error if (!mPhysicalDevice) throw std::runtime_error("Failed to find a suitable GPU!"); } + // Create the logical device fn createLogicalDevice() -> void { + // Get the queue families QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = { qfIndices.graphics_family.value(), - qfIndices.present_family.value() }; + std::set uniqueQueueFamilies = { + qfIndices.graphics_family.value(), + qfIndices.present_family.value(), + }; + + // Set the queue priority f32 queuePriority = 1.0F; + // For each unique queue family, create a new queue for (u32 queueFamily : uniqueQueueFamilies) { - vk::DeviceQueueCreateInfo queueCreateInfo { .queueFamilyIndex = queueFamily, - .queueCount = 1, - .pQueuePriorities = &queuePriority }; + vk::DeviceQueueCreateInfo queueCreateInfo { + .queueFamilyIndex = queueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; queueCreateInfos.emplace_back(queueCreateInfo); } + // Enable anisotropic filtering vk::PhysicalDeviceFeatures deviceFeatures { .samplerAnisotropy = vk::True, }; - vk::DeviceCreateInfo createInfo { .queueCreateInfoCount = static_cast(queueCreateInfos.size()), - .pQueueCreateInfos = queueCreateInfos.data(), - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data(), - .pEnabledFeatures = &deviceFeatures }; + vk::DeviceCreateInfo createInfo { + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data(), + .pEnabledFeatures = &deviceFeatures, + }; + // Create the logical device and set the graphics and present queues mDevice = mPhysicalDevice.createDeviceUnique(createInfo); mGraphicsQueue = mDevice->getQueue(qfIndices.graphics_family.value(), 0); mPresentQueue = mDevice->getQueue(qfIndices.present_family.value(), 0); @@ -404,8 +470,10 @@ class VulkanApp { imageCount = swapChainSupport.capabilities.maxImageCount; QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); - std::array queueFamilyIndices = { qfIndices.graphics_family.value(), - qfIndices.present_family.value() }; + std::array queueFamilyIndices = { + qfIndices.graphics_family.value(), + qfIndices.present_family.value(), + }; vk::SwapchainCreateInfoKHR createInfo { .surface = mSurface.get(), @@ -444,69 +512,88 @@ class VulkanApp { } fn createRenderPass() -> void { - vk::AttachmentDescription colorAttachment { .format = mSwapChainImageFormat, - .samples = mMsaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal }; + vk::AttachmentDescription colorAttachment { + .format = mSwapChainImageFormat, + .samples = mMsaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eColorAttachmentOptimal, + }; - vk::AttachmentDescription depthAttachment { .format = findDepthFormat(), - .samples = mMsaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = - vk::ImageLayout::eDepthStencilAttachmentOptimal }; + vk::AttachmentDescription depthAttachment { + .format = findDepthFormat(), + .samples = mMsaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + }; - vk::AttachmentDescription colorAttachmentResolve { .format = mSwapChainImageFormat, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eDontCare, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR }; + vk::AttachmentDescription colorAttachmentResolve { + .format = mSwapChainImageFormat, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR, + }; - vk::AttachmentReference colorAttachmentRef { .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal }; + vk::AttachmentReference colorAttachmentRef { + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal, + }; - vk::AttachmentReference depthAttachmentRef { .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal }; + vk::AttachmentReference depthAttachmentRef { + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + }; - vk::AttachmentReference colorAttachmentResolveRef { .attachment = 2, - .layout = vk::ImageLayout::eColorAttachmentOptimal }; + vk::AttachmentReference colorAttachmentResolveRef { + .attachment = 2, + .layout = vk::ImageLayout::eColorAttachmentOptimal, + }; - vk::SubpassDescription subpass { .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef, - .pResolveAttachments = &colorAttachmentResolveRef, - .pDepthStencilAttachment = &depthAttachmentRef }; + vk::SubpassDescription subpass { + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = &colorAttachmentResolveRef, + .pDepthStencilAttachment = &depthAttachmentRef, + }; - vk::SubpassDependency dependency { .srcSubpass = vk::SubpassExternal, - .dstSubpass = {}, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | - vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | - vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = {}, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | - vk::AccessFlagBits::eDepthStencilAttachmentWrite }; + vk::SubpassDependency dependency { + .srcSubpass = vk::SubpassExternal, + .dstSubpass = {}, + .srcStageMask = + vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = + vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = {}, + .dstAccessMask = + vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite, + }; - std::array attachments = { colorAttachment, - depthAttachment, - colorAttachmentResolve }; + std::array attachments = { + colorAttachment, + depthAttachment, + colorAttachmentResolve, + }; - vk::RenderPassCreateInfo renderPassInfo { .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency }; + vk::RenderPassCreateInfo renderPassInfo { + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency, + }; mRenderPass = mDevice->createRenderPassUnique(renderPassInfo); } @@ -545,16 +632,22 @@ class VulkanApp { vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode); vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode); - vk::PipelineShaderStageCreateInfo vertShaderStageInfo { .stage = vk::ShaderStageFlagBits::eVertex, - .module = vertShaderModule.get(), - .pName = "main" }; + vk::PipelineShaderStageCreateInfo vertShaderStageInfo { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = vertShaderModule.get(), + .pName = "main", + }; - vk::PipelineShaderStageCreateInfo fragShaderStageInfo { .stage = vk::ShaderStageFlagBits::eFragment, - .module = fragShaderModule.get(), - .pName = "main" }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = fragShaderModule.get(), + .pName = "main", + }; - std::array shaderStages = { vertShaderStageInfo, - fragShaderStageInfo }; + std::array shaderStages = { + vertShaderStageInfo, + fragShaderStageInfo, + }; vk::VertexInputBindingDescription bindingDescription = Vertex::getBindingDescription(); std::array attributeDescriptions = @@ -564,35 +657,46 @@ class VulkanApp { .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() + .pVertexAttributeDescriptions = attributeDescriptions.data(), }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly { .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False }; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly { + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False, + }; - vk::PipelineViewportStateCreateInfo viewportState { .viewportCount = 1, .scissorCount = 1 }; + 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::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0F }; + vk::PipelineRasterizationStateCreateInfo rasterizer { + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0F, + }; - vk::PipelineMultisampleStateCreateInfo multisampling { .rasterizationSamples = mMsaaSamples, - .sampleShadingEnable = vk::False }; + vk::PipelineMultisampleStateCreateInfo multisampling { + .rasterizationSamples = mMsaaSamples, + .sampleShadingEnable = vk::False, + }; - vk::PipelineDepthStencilStateCreateInfo depthStencil { .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False }; + vk::PipelineDepthStencilStateCreateInfo depthStencil { + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False, + }; vk::PipelineColorBlendAttachmentState colorBlendAttachment { .blendEnable = vk::False, .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, }; vk::PipelineColorBlendStateCreateInfo colorBlending { @@ -600,33 +704,38 @@ class VulkanApp { .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment, - .blendConstants = std::array { 0.0F, 0.0F, 0.0F, 0.0F } + .blendConstants = std::array { 0.0F, 0.0F, 0.0F, 0.0F }, }; std::vector dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor }; - vk::PipelineDynamicStateCreateInfo dynamicState { .dynamicStateCount = - static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data() }; + vk::PipelineDynamicStateCreateInfo dynamicState { + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data(), + }; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo { .setLayoutCount = 1, - .pSetLayouts = &mDescriptorSetLayout.get() }; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo { + .setLayoutCount = 1, + .pSetLayouts = &mDescriptorSetLayout.get(), + }; mPipelineLayout = mDevice->createPipelineLayoutUnique(pipelineLayoutInfo); - vk::GraphicsPipelineCreateInfo pipelineInfo { .stageCount = static_cast(shaderStages.size()), - .pStages = shaderStages.data(), - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = mPipelineLayout.get(), - .renderPass = mRenderPass.get(), - .subpass = 0 }; + vk::GraphicsPipelineCreateInfo pipelineInfo { + .stageCount = static_cast(shaderStages.size()), + .pStages = shaderStages.data(), + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = mPipelineLayout.get(), + .renderPass = mRenderPass.get(), + .subpass = 0, + }; vk::Result graphicsPipelineResult = vk::Result::eSuccess; vk::UniquePipeline graphicsPipelineValue; @@ -671,7 +780,7 @@ class VulkanApp { fn createColorResources() -> void { vk::Format colorFormat = mSwapChainImageFormat; - createImage( + std::tie(mColorImage, mColorImageMemory) = createImage( mSwapChainExtent.width, mSwapChainExtent.height, 1, @@ -679,9 +788,7 @@ class VulkanApp { colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, - vk::MemoryPropertyFlagBits::eDeviceLocal, - mColorImage, - mColorImageMemory + vk::MemoryPropertyFlagBits::eDeviceLocal ); mColorImageView = createImageView(mColorImage.get(), colorFormat, vk::ImageAspectFlagBits::eColor, 1); @@ -690,7 +797,7 @@ class VulkanApp { fn createDepthResources() -> void { vk::Format depthFormat = findDepthFormat(); - createImage( + std::tie(mDepthImage, mDepthImageMemory) = createImage( mSwapChainExtent.width, mSwapChainExtent.height, 1, @@ -698,9 +805,7 @@ class VulkanApp { depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, - vk::MemoryPropertyFlagBits::eDeviceLocal, - mDepthImage, - mDepthImageMemory + vk::MemoryPropertyFlagBits::eDeviceLocal ); mDepthImageView = createImageView(mDepthImage.get(), depthFormat, vk::ImageAspectFlagBits::eDepth, 1); @@ -708,13 +813,15 @@ class VulkanApp { fn findSupportedFormat( const std::vector& candidates, - vk::ImageTiling tiling, - vk::FormatFeatureFlags features + const vk::ImageTiling& tiling, + const vk::FormatFeatureFlags& features ) -> vk::Format { for (vk::Format format : candidates) { vk::FormatProperties props = mPhysicalDevice.getFormatProperties(format); + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) return format; + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) return format; } @@ -751,17 +858,15 @@ class VulkanApp { vk::UniqueBuffer stagingBuffer; vk::UniqueDeviceMemory stagingBufferMemory; - createBuffer( + std::tie(stagingBuffer, stagingBufferMemory) = createBuffer( imageSize, vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, - stagingBufferMemory + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent ); copyData(stagingBufferMemory.get(), imageSize, pixels); - createImage( + std::tie(mTextureImage, mTextureImageMemory) = createImage( static_cast(texWidth), static_cast(texHeight), mMipLevels, @@ -770,9 +875,7 @@ class VulkanApp { vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, - mTextureImage, - mTextureImageMemory + vk::MemoryPropertyFlagBits::eDeviceLocal ); transitionImageLayout( @@ -793,7 +896,7 @@ class VulkanApp { if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) throw std::runtime_error("Texture image format does not support linear blitting!"); - vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); + vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); vk::ImageMemoryBarrier barrier { .srcQueueFamilyIndex = vk::QueueFamilyIgnored, @@ -815,7 +918,7 @@ class VulkanApp { barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - commandBuffer->pipelineBarrier( + commandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, @@ -846,7 +949,7 @@ class VulkanApp { } } }; - commandBuffer->blitImage( + commandBuffer.blitImage( image, vk::ImageLayout::eTransferSrcOptimal, image, @@ -860,7 +963,7 @@ class VulkanApp { barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - commandBuffer->pipelineBarrier( + commandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, @@ -881,7 +984,7 @@ class VulkanApp { barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - commandBuffer->pipelineBarrier( + commandBuffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, @@ -890,7 +993,7 @@ class VulkanApp { barrier ); - endSingleTimeCommands(std::move(commandBuffer)); + endSingleTimeCommands(commandBuffer); } fn getMaxUsableSampleCount() -> vk::SampleCountFlagBits { @@ -899,19 +1002,19 @@ class VulkanApp { vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) - return vk::SampleCountFlagBits::e64; - if (counts & vk::SampleCountFlagBits::e32) - return vk::SampleCountFlagBits::e32; - if (counts & vk::SampleCountFlagBits::e16) - return vk::SampleCountFlagBits::e16; - if (counts & vk::SampleCountFlagBits::e8) - return vk::SampleCountFlagBits::e8; - if (counts & vk::SampleCountFlagBits::e4) - return vk::SampleCountFlagBits::e4; - if (counts & vk::SampleCountFlagBits::e2) - return vk::SampleCountFlagBits::e2; + // Define an array of sample counts in descending order + const std::array sampleCounts = { + vk::SampleCountFlagBits::e64, vk::SampleCountFlagBits::e32, vk::SampleCountFlagBits::e16, + vk::SampleCountFlagBits::e8, vk::SampleCountFlagBits::e4, vk::SampleCountFlagBits::e2, + vk::SampleCountFlagBits::e1, + }; + // Loop through the array and return the first supported sample count + for (const vk::SampleCountFlagBits& count : sampleCounts) + if (counts & count) + return count; + + // Return e1 if no other sample count is supported return vk::SampleCountFlagBits::e1; } @@ -945,15 +1048,19 @@ class VulkanApp { mTextureSampler = mDevice->createSamplerUnique(samplerInfo); } - fn createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, u32 mipLevels) - -> vk::UniqueImageView { + fn createImageView( + const vk::Image& image, + const vk::Format& format, + const vk::ImageAspectFlags& aspectFlags, + const u32& mipLevels + ) -> vk::UniqueImageView { vk::ImageViewCreateInfo viewInfo { .image = image, .viewType = vk::ImageViewType::e2D, .format = format, .subresourceRange = { - .aspectMask = aspectFlags, - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1, + .aspectMask = aspectFlags, + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, } }; @@ -961,17 +1068,16 @@ class VulkanApp { } fn createImage( - u32 width, - u32 height, - u32 mipLevels, - vk::SampleCountFlagBits numSamples, - vk::Format format, - vk::ImageTiling tiling, - vk::ImageUsageFlags usage, - vk::MemoryPropertyFlags properties, - vk::UniqueImage& image, - vk::UniqueDeviceMemory& imageMemory - ) -> void { + const u32& width, + const u32& height, + const u32& mipLevels, + const vk::SampleCountFlagBits& numSamples, + const vk::Format& format, + const vk::ImageTiling& tiling, + const vk::ImageUsageFlags& usage, + const vk::MemoryPropertyFlags& properties + ) -> std::pair { + // Define the image creation info vk::ImageCreateInfo imageInfo { .imageType = vk::ImageType::e2D, .format = format, @@ -985,27 +1091,35 @@ class VulkanApp { .initialLayout = vk::ImageLayout::eUndefined, }; - image = mDevice->createImageUnique(imageInfo); + // Create the image + vk::UniqueImage image = mDevice->createImageUnique(imageInfo); + // Get the memory requirements for the image vk::MemoryRequirements memRequirements = mDevice->getImageMemoryRequirements(image.get()); + // Memory allocation info vk::MemoryAllocateInfo allocInfo { .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties), }; - imageMemory = mDevice->allocateMemoryUnique(allocInfo); + // Allocate memory + vk::UniqueDeviceMemory imageMemory = mDevice->allocateMemoryUnique(allocInfo); + // Bind the allocated memory to the image mDevice->bindImageMemory(image.get(), imageMemory.get(), 0); + + // Return the unique image + return { std::move(image), std::move(imageMemory) }; } fn transitionImageLayout( - vk::Image image, - vk::ImageLayout oldLayout, - vk::ImageLayout newLayout, - u32 mipLevels + const vk::Image& image, + const vk::ImageLayout& oldLayout, + const vk::ImageLayout& newLayout, + const u32& mipLevels ) -> void { - vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); + vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); vk::ImageMemoryBarrier barrier { .oldLayout = oldLayout, @@ -1013,12 +1127,13 @@ class VulkanApp { .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image, - // clang-format off - .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1 } // clang-format on + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + }, }; vk::PipelineStageFlags sourceStage; @@ -1041,13 +1156,14 @@ class VulkanApp { throw std::invalid_argument("Unsupported layout transition!"); } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); - endSingleTimeCommands(std::move(commandBuffer)); + endSingleTimeCommands(commandBuffer); } - fn copyBufferToImage(vk::Buffer buffer, vk::Image image, u32 width, u32 height) -> void { - vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); + fn copyBufferToImage(const vk::Buffer& buffer, const vk::Image& image, const u32& width, const u32& height) + -> void { + vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); vk::BufferImageCopy region { .bufferOffset = 0, @@ -1061,9 +1177,9 @@ class VulkanApp { .imageExtent = { .width = width, .height = height, .depth = 1 }, }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, ®ion); + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, ®ion); - endSingleTimeCommands(std::move(commandBuffer)); + endSingleTimeCommands(commandBuffer); } fn loadModel() -> void { @@ -1089,7 +1205,7 @@ class VulkanApp { .tex_coord = { attrib.texcoords[static_cast((2 * index.texcoord_index) + 0)], 1.0F - attrib.texcoords[static_cast((2 * index.texcoord_index) + 1)], - } + }, }; if (!uniqueVertices.contains(vertex)) { @@ -1108,22 +1224,18 @@ class VulkanApp { vk::UniqueBuffer stagingBuffer; vk::UniqueDeviceMemory stagingBufferMemory; - createBuffer( + std::tie(stagingBuffer, stagingBufferMemory) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, - stagingBufferMemory + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent ); copyData(stagingBufferMemory.get(), bufferSize, mVertices.data()); - createBuffer( + std::tie(mVertexBuffer, mVertexBufferMemory) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - mVertexBuffer, - mVertexBufferMemory + vk::MemoryPropertyFlagBits::eDeviceLocal ); copyBuffer(stagingBuffer.get(), mVertexBuffer.get(), bufferSize); @@ -1138,22 +1250,18 @@ class VulkanApp { vk::UniqueBuffer stagingBuffer; vk::UniqueDeviceMemory stagingBufferMemory; - createBuffer( + std::tie(stagingBuffer, stagingBufferMemory) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, - stagingBufferMemory + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent ); copyData(stagingBufferMemory.get(), bufferSize, mIndices.data()); - createBuffer( + std::tie(mIndexBuffer, mIndexBufferMemory) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, - vk::MemoryPropertyFlagBits::eDeviceLocal, - mIndexBuffer, - mIndexBufferMemory + vk::MemoryPropertyFlagBits::eDeviceLocal ); copyBuffer(stagingBuffer.get(), mIndexBuffer.get(), bufferSize); @@ -1170,12 +1278,10 @@ class VulkanApp { mUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); for (usize i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - createBuffer( + std::tie(mUniformBuffers[i], mUniformBuffersMemory[i]) = createBuffer( bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - mUniformBuffers[i], - mUniformBuffersMemory[i] + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent ); mUniformBuffersMapped[i] = mDevice->mapMemory(mUniformBuffersMemory[i].get(), 0, bufferSize); @@ -1184,8 +1290,14 @@ class VulkanApp { fn createDescriptorPool() -> void { std::array poolSizes = { - { { .type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT }, - { .type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_FRAMES_IN_FLIGHT } }, + vk::DescriptorPoolSize { + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = MAX_FRAMES_IN_FLIGHT, + }, + vk::DescriptorPoolSize { + .type = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = MAX_FRAMES_IN_FLIGHT, + }, }; vk::DescriptorPoolCreateInfo poolInfo { @@ -1222,21 +1334,22 @@ class VulkanApp { }; std::array descriptorWrites = { - { { - .dstSet = mDescriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo, - }, { - .dstSet = mDescriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo, - } } + vk::WriteDescriptorSet { + .dstSet = mDescriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo, + }, + vk::WriteDescriptorSet { + .dstSet = mDescriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo, + }, }; mDevice->updateDescriptorSets(descriptorWrites, {}); @@ -1244,116 +1357,175 @@ class VulkanApp { } fn createBuffer( - vk::DeviceSize deviceSize, - vk::BufferUsageFlags bufferUsageFlags, - vk::MemoryPropertyFlags memoryPropertyFlags, - vk::UniqueBuffer& buffer, - vk::UniqueDeviceMemory& bufferMemory - ) -> void { + const vk::DeviceSize& deviceSize, + const vk::BufferUsageFlags& bufferUsageFlags, + const vk::MemoryPropertyFlags& memoryPropertyFlags + ) -> std::pair { vk::BufferCreateInfo bufferInfo { .size = deviceSize, .usage = bufferUsageFlags, .sharingMode = vk::SharingMode::eExclusive, }; - buffer = mDevice->createBufferUnique(bufferInfo); + // Create the buffer + vk::UniqueBuffer buffer = mDevice->createBufferUnique(bufferInfo); + // Get the memory requirements for the buffer vk::MemoryRequirements memRequirements = mDevice->getBufferMemoryRequirements(buffer.get()); + // Memory allocation info vk::MemoryAllocateInfo allocInfo { .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, memoryPropertyFlags), }; - bufferMemory = mDevice->allocateMemoryUnique(allocInfo); + // Allocate memory + vk::UniqueDeviceMemory bufferMemory = mDevice->allocateMemoryUnique(allocInfo); + // Bind the allocated memory to the buffer mDevice->bindBufferMemory(buffer.get(), bufferMemory.get(), 0); + + // Return both the unique buffer and its associated memory + return { std::move(buffer), std::move(bufferMemory) }; } - fn beginSingleTimeCommands() -> vk::UniqueCommandBuffer { + // Create the command buffers + fn beginSingleTimeCommands() -> vk::CommandBuffer { + // Define the command buffer allocation info vk::CommandBufferAllocateInfo allocInfo { .commandPool = mCommandPool.get(), .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1, }; - vk::UniqueCommandBuffer commandBuffer = std::move(mDevice->allocateCommandBuffersUnique(allocInfo)[0]); + // Allocate the command buffer + vk::CommandBuffer commandBuffer = mDevice->allocateCommandBuffers(allocInfo)[0]; + // Define the command buffer begin info vk::CommandBufferBeginInfo beginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer->begin(beginInfo); + // Begin the command buffer + commandBuffer.begin(beginInfo); return commandBuffer; } - fn endSingleTimeCommands(vk::UniqueCommandBuffer commandBuffer) -> void { - commandBuffer->end(); + // End the single time command buffer + fn endSingleTimeCommands(const vk::CommandBuffer& commandBuffer) -> void { + // End the command buffer + commandBuffer.end(); - vk::SubmitInfo submitInfo { .commandBufferCount = 1, .pCommandBuffers = &commandBuffer.get() }; + // Define the submit info + vk::SubmitInfo submitInfo { .commandBufferCount = 1, .pCommandBuffers = &commandBuffer }; + // Submit the command buffer mGraphicsQueue.submit(submitInfo, nullptr); + + // Wait for the queue to finish mGraphicsQueue.waitIdle(); + + // Free the command buffer + mDevice->freeCommandBuffers(mCommandPool.get(), commandBuffer); } - fn copyData(vk::DeviceMemory stagingBufferMemory, vk::DeviceSize bufferSize, const void* src) -> void { + // Copy data from src to dst + fn copyData(const vk::DeviceMemory& stagingBufferMemory, const vk::DeviceSize& bufferSize, const void* src) + -> void { + // Map the memory void* data = mDevice->mapMemory(stagingBufferMemory, 0, bufferSize); + + // Copy the data with memcpy - memcpy(dst, src, size) memcpy(data, src, static_cast(bufferSize)); + + // Unmap the memory mDevice->unmapMemory(stagingBufferMemory); } - fn copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize deviceSize) -> void { - vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); + // Copy buffer from src to dst + fn copyBuffer(const vk::Buffer& srcBuffer, const vk::Buffer& dstBuffer, const vk::DeviceSize& deviceSize) + -> void { + // Begin a single time command buffer + vk::CommandBuffer commandBuffer = beginSingleTimeCommands(); + // Define the copy region vk::BufferCopy copyRegion { .size = deviceSize }; - commandBuffer->copyBuffer(srcBuffer, dstBuffer, 1, ©Region); + // Copy the buffer + commandBuffer.copyBuffer(srcBuffer, dstBuffer, 1, ©Region); - endSingleTimeCommands(std::move(commandBuffer)); + // End the single time command buffer + endSingleTimeCommands(commandBuffer); } - fn findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) -> u32 { + // Find the memory type of the physical device + fn findMemoryType(const u32& typeFilter, const vk::MemoryPropertyFlags& properties) -> u32 { + // Get the memory properties of the physical device vk::PhysicalDeviceMemoryProperties memProperties = mPhysicalDevice.getMemoryProperties(); - for (u32 i = 0; i < memProperties.memoryTypeCount; i++) - if ((typeFilter & (1 << i)) && - (memProperties.memoryTypes.at(i).propertyFlags & properties) == properties) - return i; + // Loop through the memory types and find the one that matches the filter + for (u32 idx = 0; idx < memProperties.memoryTypeCount; idx++) + if ((typeFilter & (1 << idx)) && + (memProperties.memoryTypes.at(idx).propertyFlags & properties) == properties) + return idx; + // Throw an error if no suitable memory type is found throw std::runtime_error("Failed to find a suitable memory type!"); } + // Create the command buffers fn createCommandBuffers() -> void { + // Resize the command buffers to hold the maximum number of frames in flight mCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - vk::CommandBufferAllocateInfo allocInfo { .commandPool = mCommandPool.get(), - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = - static_cast(mCommandBuffers.size()) }; + // Define the command buffer allocation info + vk::CommandBufferAllocateInfo allocInfo { + .commandPool = mCommandPool.get(), + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = static_cast(mCommandBuffers.size()), + }; + // Allocate the command buffers mCommandBuffers = mDevice->allocateCommandBuffersUnique(allocInfo); } - fn recordCommandBuffer(vk::CommandBuffer commandBuffer, u32 imageIndex) -> void { + // Record the command buffer + fn recordCommandBuffer(const vk::CommandBuffer& commandBuffer, const u32& imageIndex) -> void { + // Define the command buffer begin info vk::CommandBufferBeginInfo beginInfo {}; + // Begin the command buffer commandBuffer.begin(beginInfo); + // Define the render pass begin info std::array clearValues { - { { .color = { .float32 = std::array { 0.0F, 0.0F, 0.0F, 1.0F } } }, - { .depthStencil = { .depth = 1.0F, .stencil = 0 } } } + // Set the color buffer to black + vk::ClearValue { .color = { .uint32 = std::array { 0, 0, 0, 255 } } }, + // Set the depth buffer to 1.0F + vk::ClearValue { .depthStencil = { .depth = 1.0F, .stencil = 0 } }, }; + // Define the render pass info vk::RenderPassBeginInfo renderPassInfo { - .renderPass = mRenderPass.get(), - .framebuffer = mSwapChainFramebuffers[imageIndex].get(), - .renderArea = { .offset = { .x = 0, .y = 0 }, .extent = mSwapChainExtent }, + // Render pass itself + .renderPass = mRenderPass.get(), + // Current framebuffer + .framebuffer = mSwapChainFramebuffers[imageIndex].get(), + // Render area (entire framebuffer) + .renderArea = { .offset = { .x = 0, .y = 0 }, .extent = mSwapChainExtent }, + // Clear values for the attachments .clearValueCount = static_cast(clearValues.size()), - .pClearValues = clearValues.data() + .pClearValues = clearValues.data(), }; + // Begin the render pass commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + + // Bind the graphics pipeline to the command buffer commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mGraphicsPipeline.get()); + // Create the viewport and scissor. + // If the scissor is smaller than the + // viewport, then it will be clipped. vk::Viewport viewport { .x = 0.0F, .y = 0.0F, @@ -1363,18 +1535,23 @@ class VulkanApp { .maxDepth = 1.0F, }; + // Create the scissor vk::Rect2D scissor { .offset = { .x = 0, .y = 0 }, .extent = mSwapChainExtent, }; + // Set the viewport and scissor commandBuffer.setViewport(0, viewport); commandBuffer.setScissor(0, scissor); + // Bind the vertex buffer commandBuffer.bindVertexBuffers(0, mVertexBuffer.get(), { 0 }); + // Bind the index buffer commandBuffer.bindIndexBuffer(mIndexBuffer.get(), 0, vk::IndexType::eUint32); + // Bind the descriptor sets commandBuffer.bindDescriptorSets( vk::PipelineBindPoint::eGraphics, mPipelineLayout.get(), @@ -1385,21 +1562,28 @@ class VulkanApp { nullptr ); + // Draw the indexed vertices commandBuffer.drawIndexed(static_cast(mIndices.size()), 1, 0, 0, 0); + // End the render pass commandBuffer.endRenderPass(); + // End the command buffer commandBuffer.end(); } + // Create the semaphores and fences fn createSyncObjects() -> void { + // Resize the vectors to hold the maximum number of frames in flight mImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); mRenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); mInFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + // Create the semaphore and fence info vk::SemaphoreCreateInfo semaphoreInfo {}; vk::FenceCreateInfo fenceInfo { .flags = vk::FenceCreateFlagBits::eSignaled }; + // Loop through the maximum number of frames in flight and create the semaphores and fences for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) { mImageAvailableSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo); mRenderFinishedSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo); @@ -1407,11 +1591,13 @@ class VulkanApp { } } - fn updateUniformBuffer(u32 currentImage) -> void { - static auto StartTime = std::chrono::high_resolution_clock::now(); + fn updateUniformBuffer(const u32& currentImage) -> void { + using namespace std::chrono; + using time_point = high_resolution_clock::time_point; + static time_point StartTime = high_resolution_clock::now(); - auto currentTime = std::chrono::high_resolution_clock::now(); - f32 time = std::chrono::duration(currentTime - StartTime).count(); + time_point currentTime = high_resolution_clock::now(); + f32 time = duration(currentTime - StartTime).count(); UniformBufferObject ubo { .model = glm::rotate(glm::mat4(1.0F), time * glm::radians(90.0F), glm::vec3(0.0F, 0.0F, 1.0F)), @@ -1503,101 +1689,129 @@ class VulkanApp { } fn createShaderModule(const std::vector& code) -> vk::UniqueShaderModule { - vk::ShaderModuleCreateInfo createInfo { .codeSize = code.size(), - .pCode = std::bit_cast(code.data()) }; + vk::ShaderModuleCreateInfo createInfo { + .codeSize = code.size(), + .pCode = std::bit_cast(code.data()), + }; return mDevice->createShaderModuleUnique(createInfo); } + // Choose the color format for the swap chain static fn chooseSwapSurfaceFormat(const std::vector& availableFormats ) -> vk::SurfaceFormatKHR { - for (const auto& availableFormat : availableFormats) + // If SRGB is available, use it + for (const vk::SurfaceFormatKHR& availableFormat : availableFormats) if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) return availableFormat; + // Otherwise, use the first available format return availableFormats[0]; } + // Choose the swap chain presentation mode static fn chooseSwapPresentMode(const std::vector& availablePresentModes ) -> vk::PresentModeKHR { - for (const auto& availablePresentMode : availablePresentModes) + // Check if mailbox mode is available (adaptive sync) + for (const vk::PresentModeKHR& availablePresentMode : availablePresentModes) if (availablePresentMode == vk::PresentModeKHR::eMailbox) return availablePresentMode; + // If mailbox mode is not available, use FIFO mode (vsync) return vk::PresentModeKHR::eFifo; } - fn chooseSwapExtent(const vk::SurfaceCapabilitiesKHR capabilities) -> vk::Extent2D { + // Choose the swap chain extent (resolution) + fn chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) -> vk::Extent2D { + // If the resolution is not UINT32_MAX, return it + // Otherwise, we need to set the resolution manually if (capabilities.currentExtent.width != UINT32_MAX) return capabilities.currentExtent; + // Get the window's resolution u32 width = 0, height = 0; std::tie(width, height) = mWindow->getFramebufferSize(); - vk::Extent2D actualExtent = { .width = width, .height = height }; - - actualExtent.width = - std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = - std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; + // Return the resolution clamped to the supported range + return { + .width = std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + .height = std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; } - fn querySwapChainSupport(vk::PhysicalDevice device) -> SwapChainSupportDetails { - SwapChainSupportDetails details; - - details.capabilities = device.getSurfaceCapabilitiesKHR(mSurface.get()); - details.formats = device.getSurfaceFormatsKHR(mSurface.get()); - details.present_modes = device.getSurfacePresentModesKHR(mSurface.get()); - - return details; + // Check if the swap chain is adequate + fn querySwapChainSupport(const vk::PhysicalDevice& device) -> SwapChainSupportDetails { + return { + .capabilities = device.getSurfaceCapabilitiesKHR(mSurface.get()), + .formats = device.getSurfaceFormatsKHR(mSurface.get()), + .present_modes = device.getSurfacePresentModesKHR(mSurface.get()), + }; } - fn isDeviceSuitable(vk::PhysicalDevice device) -> bool { + // Check if the device is suitable for the application + fn isDeviceSuitable(const vk::PhysicalDevice& device) -> bool { + // Get the queue families that support the required operations QueueFamilyIndices qfIndices = findQueueFamilies(device); + // Check if the device supports the required extensions bool extensionsSupported = checkDeviceExtensionSupport(device); bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + // Check if the swap chain is adequate (make sure it has + // at least one supported format and presentation mode) swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty(); } + // Check if the device supports the required features vk::PhysicalDeviceFeatures supportedFeatures = device.getFeatures(); + // If the device supports everything required, return true return qfIndices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } - static fn checkDeviceExtensionSupport(vk::PhysicalDevice device) -> bool { + // Check if the device supports the required extensions + static fn checkDeviceExtensionSupport(const vk::PhysicalDevice& device) -> bool { + // Get the available extensions std::vector availableExtensions = device.enumerateDeviceExtensionProperties(); + // Create a set of required extension names std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + // Remove each required extension from the set of available extensions for (const vk::ExtensionProperties& extension : availableExtensions) requiredExtensions.erase(extension.extensionName); + // If the set is empty, all required extensions are supported return requiredExtensions.empty(); } - fn findQueueFamilies(vk::PhysicalDevice device) -> QueueFamilyIndices { + // Find the queue families that support the required operations + fn findQueueFamilies(const vk::PhysicalDevice& device) -> QueueFamilyIndices { + // Create a struct to store the queue family indices QueueFamilyIndices qfIndices; + // Get the queue family properties std::vector queueFamilies = device.getQueueFamilyProperties(); + // For every queue family, for (u32 i = 0; i < queueFamilies.size(); i++) { + // Check if the queue family supports the required operations if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics) qfIndices.graphics_family = i; + // Check if the queue family supports presentation vk::Bool32 queuePresentSupport = device.getSurfaceSupportKHR(i, mSurface.get()); + // If the queue family supports presentation, set the present family index if (queuePresentSupport) qfIndices.present_family = i; + // If the queue family supports both operations, we're done if (qfIndices.isComplete()) break; } @@ -1605,42 +1819,33 @@ class VulkanApp { return qfIndices; } - static fn getRequiredExtensions() -> std::vector { - std::span extensionsSpan = vkfw::getRequiredInstanceExtensions(); - - std::vector extensions(extensionsSpan.begin(), extensionsSpan.end()); - - if (enableValidationLayers) - extensions.emplace_back(vk::EXTDebugUtilsExtensionName); - - return extensions; - } - + // Check if all requested layers are available static fn checkValidationLayerSupport() -> bool { std::vector availableLayers = vk::enumerateInstanceLayerProperties(); - for (const char* layerName : validationLayers) { - bool layerFound = false; + // Create a set of available layer names + std::unordered_set availableLayerNames; + for (const vk::LayerProperties& layerProperties : availableLayers) + availableLayerNames.emplace(layerProperties.layerName); - for (const vk::LayerProperties& layerProperties : availableLayers) - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } + // Check if each validation layer is available + for (const std::string_view layerName : validationLayers) + if (availableLayerNames.find(layerName) == availableLayerNames.end()) + return false; // Layer not found - if (!layerFound) - return false; - } - - return true; + return true; // All validation layers are available } + // Callback function used to report validation layer messages static VKAPI_ATTR fn VKAPI_CALL debugCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/, - VkDebugUtilsMessageTypeFlagsEXT /*messageType*/, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* /*pUserData*/ + VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/, // Severity: verbose, info, warning, error + VkDebugUtilsMessageTypeFlagsEXT /*messageType*/, // Type: general, validation, performance + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, // Message details + void* /*pUserData*/ // Optional user data ) -> vk::Bool32 { + // Print the message to the console + // Because pCallbackData already gives the message severity + // and type, we don't need to print them, so they're unused. fmt::println("Validation layer: {}", pCallbackData->pMessage); return vk::False; @@ -1648,8 +1853,10 @@ class VulkanApp { }; fn main() -> i32 { + // Initialize dynamic function dispatcher VULKAN_HPP_DEFAULT_DISPATCHER.init(); + // Create an app instance VulkanApp app; try {