From 14aebeca0426caf9ad0375581af0b4f55d1a8fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20S=C3=BC=C3=9Fenbach?= Date: Thu, 9 Jul 2026 09:51:06 +0200 Subject: [PATCH] Align docu in 10_Multisampling.adoc and 30_multisampling.cpp --- attachments/30_multisampling.cpp | 510 +++++++++++++++---------------- en/10_Multisampling.adoc | 304 +++++++++--------- 2 files changed, 412 insertions(+), 402 deletions(-) diff --git a/attachments/30_multisampling.cpp b/attachments/30_multisampling.cpp index 862a96f0f..9a312fa2b 100644 --- a/attachments/30_multisampling.cpp +++ b/attachments/30_multisampling.cpp @@ -56,15 +56,14 @@ struct Vertex static vk::VertexInputBindingDescription getBindingDescription() { - return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + return {.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; } static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), - vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), - vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + return {{{.location = 0, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, pos)}, + {.location = 1, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, color)}, + {.location = 2, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, texCoord)}}}; } bool operator==(const Vertex &other) const @@ -176,7 +175,7 @@ class HelloTriangleApplication static void framebufferResizeCallback(GLFWwindow *window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } @@ -186,7 +185,6 @@ class HelloTriangleApplication setupDebugMessenger(); createSurface(); pickPhysicalDevice(); - msaaSamples = getMaxUsableSampleCount(); createLogicalDevice(); createSwapChain(); createImageViews(); @@ -225,7 +223,7 @@ class HelloTriangleApplication swapChain = nullptr; } - void cleanup() const + void cleanup() { glfwDestroyWindow(window); @@ -235,7 +233,6 @@ class HelloTriangleApplication void recreateSwapChain() { int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); while (width == 0 || height == 0) { glfwGetFramebufferSize(window, &width, &height); @@ -310,7 +307,7 @@ class HelloTriangleApplication vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{.messageSeverity = severityFlags, .messageType = messageTypeFlags, .pfnUserCallback = &debugCallback}; @@ -347,7 +344,6 @@ class HelloTriangleApplication // Check if the physicalDevice supports the required features auto features = physicalDevice.template getFeatures2(); bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && @@ -367,6 +363,7 @@ class HelloTriangleApplication throw std::runtime_error("failed to find a suitable GPU!"); } physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); } void createLogicalDevice() @@ -442,21 +439,18 @@ class HelloTriangleApplication { assert(swapChainImageViews.empty()); - vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + swapChainImageViews.reserve(swapChainImages.size()); for (auto &image : swapChainImages) { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back(device, imageViewCreateInfo); + swapChainImageViews.emplace_back(createImageView(image, swapChainSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1)); } } void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + std::array bindings{ + {{.binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex}, + {.binding = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment}}}; vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); @@ -470,30 +464,25 @@ class HelloTriangleApplication vk::PipelineShaderStageCreateInfo fragShaderStageInfo{.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"}; vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data()}; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False}; - 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::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = vk::False}; + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + 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::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = msaaSamples, .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ .depthTestEnable = vk::True, .depthWriteEnable = vk::True, @@ -503,18 +492,14 @@ class HelloTriangleApplication 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}; + .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 = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); vk::Format depthFormat = findDepthFormat(); @@ -539,9 +524,8 @@ class HelloTriangleApplication void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex}; + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; commandPool = vk::raii::CommandPool(device, poolInfo); } @@ -549,16 +533,30 @@ class HelloTriangleApplication { vk::Format colorFormat = swapChainSurfaceFormat.format; - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + std::tie(colorImage, colorImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + colorFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); } void createDepthResources() { vk::Format depthFormat = findDepthFormat(); - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + std::tie(depthImage, depthImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + depthFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); } vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const @@ -582,15 +580,9 @@ class HelloTriangleApplication [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment); - } - - static bool hasStencilComponent(vk::Format format) - { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + return findSupportedFormat({vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); } void createTextureImage() @@ -605,9 +597,8 @@ class HelloTriangleApplication throw std::runtime_error("failed to load texture image!"); } - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, imageSize); memcpy(data, pixels, imageSize); @@ -615,31 +606,45 @@ class HelloTriangleApplication stbi_image_free(pixels); - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) - { - // Check if image format supports linear blit-ing + std::tie(textureImage, textureImageMemory) = + createImage(texWidth, + texHeight, + mipLevels, + vk::SampleCountFlagBits::e1, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); + + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(commandBuffer, stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + generateMipmaps(commandBuffer, textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + endSingleTimeCommands(std::move(commandBuffer)); + } + + void generateMipmaps(vk::raii::CommandBuffer &commandBuffer, + vk::raii::Image &image, + vk::Format imageFormat, + int32_t texWidth, + int32_t texHeight, + uint32_t mipLevels) + { + // Check if image format supports linear blitting vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { throw std::runtime_error("texture image format does not support linear blitting!"); } - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eTransferSrcOptimal, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = 1, .layerCount = 1}}; int32_t mipWidth = texWidth; int32_t mipHeight = texHeight; @@ -652,30 +657,30 @@ class HelloTriangleApplication barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; - blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + vk::ImageBlit blit = {.srcSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = i - 1, .layerCount = 1}, + .srcOffsets = std::array({{}, {mipWidth, mipHeight, 1}}), + .dstSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = i, .layerCount = 1}, + .dstOffsets = std::array({{}, {1 < mipWidth ? mipWidth / 2 : 1, 1 < mipHeight ? mipHeight / 2 : 1, 1}})}; - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + commandBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, blit, vk::Filter::eLinear); barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - if (mipWidth > 1) + if (1 < mipWidth) + { mipWidth /= 2; - if (mipHeight > 1) + } + if (1 < mipHeight) + { mipHeight /= 2; + } } barrier.subresourceRange.baseMipLevel = mipLevels - 1; @@ -684,9 +689,7 @@ class HelloTriangleApplication barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); } vk::SampleCountFlagBits getMaxUsableSampleCount() @@ -724,69 +727,78 @@ class HelloTriangleApplication void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); } void createTextureSampler() { vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways}; + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; textureSampler = vk::raii::Sampler(device, samplerInfo); } - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + vk::raii::ImageView createImageView(vk::Image const &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { vk::ImageViewCreateInfo viewInfo{ .image = image, .viewType = vk::ImageViewType::e2D, .format = format, - .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + .subresourceRange = {.aspectMask = aspectFlags, .baseMipLevel = 0, .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = 1}}; return vk::raii::ImageView(device, viewInfo); } - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) - { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined}; - image = vk::raii::Image(device, imageInfo); + std::pair + createImage(uint32_t width, + uint32_t height, + uint32_t mipLevels, + vk::SampleCountFlagBits numSamples, + vk::Format format, + vk::ImageTiling tiling, + vk::ImageUsageFlags usage, + vk::MemoryPropertyFlags properties) + { + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + + vk::raii::Image image = vk::raii::Image(device, imageInfo); vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory imageMemory = vk::raii::DeviceMemory(device, allocInfo); image.bindMemory(imageMemory, 0); + + return {std::move(image), std::move(imageMemory)}; } - void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + void transitionImageLayout( + vk::raii::CommandBuffer &commandBuffer, vk::raii::Image const &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = mipLevels, .layerCount = 1}}; vk::PipelineStageFlags sourceStage; vk::PipelineStageFlags destinationStage; @@ -811,22 +823,18 @@ class HelloTriangleApplication { throw std::invalid_argument("unsupported layout transition!"); } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); } - void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + void copyBufferToImage(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1}}; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); + vk::BufferImageCopy region{.bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); } void loadModel() @@ -836,7 +844,7 @@ class HelloTriangleApplication std::vector materials; std::string warn, err; - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { throw std::runtime_error(warn + err); } @@ -860,78 +868,83 @@ class HelloTriangleApplication vertex.color = {1.0f, 1.0f, 1.0f}; - if (!uniqueVertices.contains(vertex)) + auto [it, inserted] = uniqueVertices.insert({vertex, static_cast(vertices.size())}); + if (inserted) { - uniqueVertices[vertex] = static_cast(vertices.size()); vertices.push_back(vertex); } - indices.push_back(uniqueVertices[vertex]); + indices.push_back(it->second); } } } void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); memcpy(dataStaging, vertices.data(), bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + std::tie(vertexBuffer, vertexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); } + std::pair createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + vk::raii::Buffer buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + return {std::move(buffer), std::move(bufferMemory)}; + } + void createIndexBuffer() { vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + std::tie(indexBuffer, indexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, indexBuffer, bufferSize); } void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + auto [buffer, bufferMem] = createBuffer( + bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); uniformBuffers.emplace_back(std::move(buffer)); uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory.back().mapMemory(0, bufferSize)); } } void createDescriptorPool() { - std::array poolSize{ - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data()}; + std::array poolSize{{{.type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT}, + {.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_FRAMES_IN_FLIGHT}}}; + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; descriptorPool = vk::raii::DescriptorPool(device, poolInfo); } @@ -939,73 +952,46 @@ class HelloTriangleApplication { std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data()}; + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; descriptorSets.clear(); descriptorSets = device.allocateDescriptorSets(allocInfo); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject)}; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo}, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo}}; + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{.sampler = textureSampler, .imageView = textureImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites{{{.dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + {.dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}}; device.updateDescriptorSets(descriptorWrites, {}); } } - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive}; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() - { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1}; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; - commandBuffer->begin(beginInfo); + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); - return commandBuffer; + return std::move(commandBuffer); } - void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + void endSingleTimeCommands(vk::raii::CommandBuffer &&commandBuffer) { commandBuffer.end(); @@ -1016,13 +1002,9 @@ class HelloTriangleApplication void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); - queue.waitIdle(); + endSingleTimeCommands(std::move(commandCopyBuffer)); } uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) @@ -1051,7 +1033,8 @@ class HelloTriangleApplication { auto &commandBuffer = commandBuffers[frameIndex]; commandBuffer.begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + + // Before starting rendering, transition the swapchain image to vk::ImageLayout::eColorAttachmentOptimal transition_image_layout( swapChainImages[imageIndex], vk::ImageLayout::eUndefined, @@ -1061,7 +1044,8 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage vk::ImageAspectFlagBits::eColor); - // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL + + // Transition the multisampled color image to vk::ImageLayout::eColorAttachmentOptimal transition_image_layout( *colorImage, vk::ImageLayout::eUndefined, @@ -1071,7 +1055,8 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::ImageAspectFlagBits::eColor); - // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL + + // Transition the depth image to vk::ImageLayout::eDepthAttachmentOptimal transition_image_layout( *depthImage, vk::ImageLayout::eUndefined, @@ -1115,11 +1100,12 @@ class HelloTriangleApplication 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.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); - commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC + + // After rendering, transition the swapchain image to vk::ImageLayout::ePresentSrcKHR transition_image_layout( swapChainImages[imageIndex], vk::ImageLayout::eColorAttachmentOptimal, @@ -1153,11 +1139,11 @@ class HelloTriangleApplication .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange = { - .aspectMask = image_aspect_flags, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; + .aspectMask = image_aspect_flags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; vk::DependencyInfo dependency_info = { .dependencyFlags = {}, .imageMemoryBarrierCount = 1, @@ -1191,7 +1177,8 @@ class HelloTriangleApplication UniformBufferObject ubo{}; ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj = + glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); ubo.proj[1][1] *= -1; memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); @@ -1326,7 +1313,10 @@ class HelloTriangleApplication return extensions; } - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + 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) { diff --git a/en/10_Multisampling.adoc b/en/10_Multisampling.adoc index e33c2af85..8e08c99d2 100644 --- a/en/10_Multisampling.adoc +++ b/en/10_Multisampling.adoc @@ -15,7 +15,7 @@ Since there are no displays out there with unlimited resolution, it will always There are a number of ways to fix this, and in this chapter we'll focus on one of the more popular ones: https://en.wikipedia.org/wiki/Multisample_anti-aliasing[Multisample antialiasing] (MSAA). In ordinary rendering, the pixel color is determined based on a single sample point which in most cases is the center of the target pixel on screen. -If part of the drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect. +If part of a drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect. image::/images/aliasing.png[] @@ -41,43 +41,40 @@ vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; ---- By default, we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. -The exact maximum number of samples can be extracted from `VkPhysicalDeviceProperties` associated with our selected physical device. -We're using a depth buffer, so we have to take into account the sample count for both color and depth. -The highest sample count that both support (and) will be the maximum we can support. +The exact maximum number of samples can be extracted from `vk::PhysicalDeviceProperties` associated with our selected physical device. +We're using a depth buffer as well, so we have to take into account the sample count for both color and depth. +The highest sample count that both support will be the maximum we can support. Add a function that will fetch this information for us: [,c++] ---- -vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice->getProperties(); - - 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; } - - return vk::SampleCountFlagBits::e1; +vk::SampleCountFlagBits getMaxUsableSampleCount() +{ + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice->getProperties(); + + 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; } + + return vk::SampleCountFlagBits::e1; } ---- We will now use this function to set the `msaaSamples` variable during the physical device selection process. -For this, we have to slightly modify the `pickPhysicalDevice` function: +For this, we slightly extend the `pickPhysicalDevice` function: [,c++] ---- -void pickPhysicalDevice() { - ... - for (const auto& device : devices) { - if (isDeviceSuitable(device)) { - physicalDevice = device; - msaaSamples = getMaxUsableSampleCount(); - break; - } - } - ... +void pickPhysicalDevice() +{ + ... + physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); + ... } ---- @@ -93,30 +90,62 @@ Add the following class members: [,c++] ---- ... -vk::raii::Image colorImage = nullptr; +vk::raii::Image colorImage = nullptr; vk::raii::DeviceMemory colorImageMemory = nullptr; -vk::raii::ImageView colorImageView = nullptr; +vk::raii::ImageView colorImageView = nullptr; ... ---- -This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the image creation process. +This new image will have to store the desired number of samples per pixel, so we need to pass this number to `vk::ImageCreateInfo` during the image creation process. Modify the `createImage` function by adding a `numSamples` parameter: [,c++] ---- -void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) const { - ... - imageInfo.samples = numSamples; +std::pair + createImage(uint32_t width, + uint32_t height, + uint32_t mipLevels, + vk::SampleCountFlagBits numSamples, + vk::Format format, + vk::ImageTiling tiling, + vk::ImageUsageFlags usage, + vk::MemoryPropertyFlags properties) +{ + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; ... ---- -For now, update all calls to this function using `VK_SAMPLE_COUNT_1_BIT` - we will be replacing this with proper values as we progress with implementation: +For now, update all calls to this function using `vk::SampleCountFlagBits::e1` - we will be replacing this with proper values as we progress with implementation: [,c++] ---- -createImage(swapChainExtent.width, swapChainExtent.height, 1, vk::SampleCountFlagBits::e1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); +std::tie(depthImage, depthImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + vk::SampleCountFlagBits::e1, + depthFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); ... -createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); +std::tie(textureImage, textureImageMemory) = + createImage(texWidth, + texHeight, + mipLevels, + vk::SampleCountFlagBits::e1, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); ---- We will now create a multi-sampled color buffer. @@ -126,11 +155,19 @@ Also, this color buffer doesn't need mipmaps since it's not going to be used as [,c++] ---- -void createColorResources() { - vk::Format colorFormat = swapChainImageFormat; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); +void createColorResources() +{ + vk::Format colorFormat = swapChainSurfaceFormat.format; + + std::tie(colorImage, colorImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + colorFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); } ---- @@ -138,11 +175,12 @@ For consistency, call the function right before `createDepthResources`: [,c++] ---- -void initVulkan() { - ... - createColorResources(); - createDepthResources(); - ... +void initVulkan() +{ + ... + createColorResources(); + createDepthResources(); + ... } ---- @@ -151,10 +189,18 @@ Modify `createDepthResources` and update the number of samples used by the depth [,c++] ---- -void createDepthResources() { - ... - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage_, depthImageMemory_); - ... +void createDepthResources() +{ + ... + std::tie(depthImage, depthImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + msaaSamples, + depthFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); + ... } ---- @@ -162,106 +208,76 @@ And update the `recreateSwapChain` so that the new color image can be recreated [,c++] ---- -void recreateSwapChain() { - ... - createImageViews(); - createColorResources(); - createDepthResources(); - ... +void recreateSwapChain() +{ + ... + createImageViews(); + createColorResources(); + createDepthResources(); + ... } ---- -We made it past the initial MSAA setup, now we need to start using this new resource in our graphics pipeline, framebuffer, render pass and see the results! +We made it past the initial MSAA setup, now we need to start using this new resource in our graphics pipeline and see the results! -== Adding new attachments +== Using new attachments -Let's take care of the render pass first. -Modify `createRenderPass` and update color and depth attachment creation info structs: +First, tell the graphics pipeline to use more than one sample by modifying `createGraphicsPipeline`: [,c++] ---- -void createRenderPass() { - ... - colorAttachment.samples = msaaSamples; - colorAttachment.finalLayout = vk::ImageLayout::eColorAttachmentOptimal; - ... - depthAttachment.samples = msaaSamples; - ... ----- - -You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. -That's because multi-sampled images cannot be presented directly. -We first need to resolve them to a regular image. -This requirement does not apply to the depth buffer, since it won't be presented at any point. -Therefore, we will have to add only one new attachment for color, which is a so-called resolve attachment: - -[,c++] ----- - ... - vk::AttachmentDescription colorAttachmentResolve({}, swapChainImageFormat, vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eDontCare, - vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, - vk::ImageLayout::ePresentSrcKHR); - ... ----- - -The render pass now has to be instructed to resolve multi-sampled color image into regular attachment. -Create a new attachment reference that will point to the color buffer which will serve as the resolve target: - -[,c++] ----- - ... - vk::AttachmentReference colorAttachmentResolveRef(2, vk::ImageLayout::eColorAttachmentOptimal); - ... ----- - -Set the `pResolveAttachments` subpass struct member to point to the newly created attachment reference. -This is enough to let the render pass define a multisample resolve operation which will let us render the image to screen: - ----- - ... - subpass.pResolveAttachments = &colorAttachmentResolveRef; - ... ----- - -Since we're reusing the multi-sampled color image, it's necessary to update -the `srcAccessMask` of the `VkSubpassDependency`. -This update ensures that any write operations to the color attachment are completed before later ones begin, thus preventing write-after-write hazards that can lead to unstable rendering results: - -[,c++] ----- - ... - dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - ... ----- - -Now update render pass info struct with the new color attachment: - -[,c++] ----- - ... - std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; - ... +void createGraphicsPipeline() +{ + ... + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = msaaSamples}; + ... +} ---- -With the render pass in place, modify `createFramebuffers` and add the new image view to the list: +Then we need to extend `recordCommandBuffer`. +In addition to transitioning the `swapChainImages[imageIndex]` and the `depthImage` from `vk::ImageLayout::eUndefined` to +`vk::ImageLayout::eColorAttachmentOptimal` and `vk::ImageLayout::eDepthAttachmentOptimal`, respectively, we need to transition the +new multisampled `colorImage` from `vk::ImageLayout::eUndefined` to `vk::ImageLayout::eColorAttachmentOptimal`: [,c++] ---- -void createFramebuffers() { - ... - vk::ImageView attachments[] = { *colorImageView, *depthImageView, view }; - ... +void recordCommandBuffer(uint32_t imageIndex) +{ + ... + // Transition the multisampled color image to vk::ImageLayout::eColorAttachmentOptimal + transition_image_layout( + *colorImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + ... } ---- -Finally, tell the newly created pipeline to use more than one sample by modifying `createGraphicsPipeline`: +Next, instead of using the `swapChainImageViews[imageIndex]` as the color attachment in the `vk::RenderingInfo` with +`vk::CommandBuffer::beginRendering`, we're using the `colorImageView` with the `swapChainImageViews[imageIndex]` as the resolve +image view, using `vk::ResolveModeFlagBits::eAverage` as the resolve mode: [,c++] ---- -void createGraphicsPipeline() { - ... - multisampling.rasterizationSamples = msaaSamples; - ... +void recordCommandBuffer(uint32_t imageIndex) +{ + ... + // Color attachment (multisampled) with resolve attachment + vk::RenderingAttachmentInfo colorAttachment = { + .imageView = colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + ... } ---- @@ -288,19 +304,23 @@ One way to approach this problem is to enable https://docs.vulkan.org/spec/lates [,c++] ---- - -void createLogicalDevice() { - ... - deviceFeatures.sampleRateShading = vk::True; // enable sample shading - feature for the device - ... +void createLogicalDevice() +{ + ... + vk::StructureChain featureChain = { + {.features = {.sampleRateShading = vk::True, .samplerAnisotropy = vk::True}}, // vk::PhysicalDeviceFeatures2 + ... } -void createGraphicsPipeline() { - ... - multisampling.sampleShadingEnable = vk::True; // enable sample shading in the pipeline - multisampling.minSampleShading = .2f; // min fraction for sample shading; closer to one is smoother - ... +void createGraphicsPipeline() +{ + ... + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = vk::True, // enable sample shading in the pipeline + .minSampleShading = 0.2f // min fraction for sample shading; closer to one is smoother + }; + ... } ----