From 4cb6e319bc9c156bbd25f2f3bea9992827988393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20S=C3=BC=C3=9Fenbach?= Date: Thu, 25 Jun 2026 18:09:29 +0200 Subject: [PATCH] Align docu in 09_Generating_Mipmaps.adoc with source in 29_mipmapping.cpp --- attachments/29_mipmapping.cpp | 495 ++++++++++++++++------------------ en/09_Generating_Mipmaps.adoc | 328 ++++++++++++---------- 2 files changed, 427 insertions(+), 396 deletions(-) diff --git a/attachments/29_mipmapping.cpp b/attachments/29_mipmapping.cpp index 8cb72419a..d80a73cb1 100644 --- a/attachments/29_mipmapping.cpp +++ b/attachments/29_mipmapping.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 @@ -171,7 +170,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; } @@ -218,7 +217,7 @@ class HelloTriangleApplication swapChain = nullptr; } - void cleanup() const + void cleanup() { glfwDestroyWindow(window); @@ -228,7 +227,6 @@ class HelloTriangleApplication void recreateSwapChain() { int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); while (width == 0 || height == 0) { glfwGetFramebufferSize(window, &width, &height); @@ -250,6 +248,7 @@ class HelloTriangleApplication .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = vk::ApiVersion14}; + // Get the required layers std::vector requiredLayers; if (enableValidationLayers) @@ -301,7 +300,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}; @@ -338,7 +337,6 @@ class HelloTriangleApplication // Check if the physicalDevice supports the required features auto features = physicalDevice.template getFeatures2(); bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && @@ -433,21 +431,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); @@ -461,30 +456,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 = vk::SampleCountFlagBits::e1, - .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 = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ .depthTestEnable = vk::True, .depthWriteEnable = vk::True, @@ -494,18 +484,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(); @@ -530,9 +516,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); } @@ -540,8 +525,14 @@ class HelloTriangleApplication { vk::Format depthFormat = findDepthFormat(); - createImage(swapChainExtent.width, swapChainExtent.height, 1, 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, + 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 @@ -549,12 +540,8 @@ class HelloTriangleApplication for (const auto format : candidates) { vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) - { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + if (((tiling == vk::ImageTiling::eLinear) && ((props.linearTilingFeatures & features) == features)) || + ((tiling == vk::ImageTiling::eOptimal) && ((props.optimalTilingFeatures & features) == features))) { return format; } @@ -565,15 +552,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() @@ -588,9 +569,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); @@ -598,31 +578,44 @@ class HelloTriangleApplication stbi_image_free(pixels); - createImage(texWidth, texHeight, mipLevels, 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); + std::tie(textureImage, textureImageMemory) = + createImage(texWidth, + texHeight, + mipLevels, + 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::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + 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 blit-ing + // 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; @@ -635,30 +628,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; @@ -667,78 +660,84 @@ 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); } 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, - .minLod = 0.0f, - .maxLod = vk::LodClampNone}; + .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, + .minLod = 0.0f, + .maxLod = vk::LodClampNone}; 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::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + std::pair + createImage(uint32_t width, + uint32_t height, + uint32_t mipLevels, + 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 = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined}; - image = vk::raii::Image(device, imageInfo); + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .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; @@ -763,22 +762,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() @@ -788,7 +783,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); } @@ -812,78 +807,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); } @@ -891,73 +891,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(); @@ -968,13 +941,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) @@ -1003,7 +972,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, @@ -1013,6 +983,7 @@ class HelloTriangleApplication vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout transition_image_layout( *depthImage, @@ -1047,16 +1018,18 @@ class HelloTriangleApplication .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentInfo, .pDepthAttachment = &depthAttachmentInfo}; + commandBuffer.beginRendering(renderingInfo); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.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, @@ -1090,11 +1063,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, @@ -1128,7 +1101,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)); @@ -1263,7 +1237,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/09_Generating_Mipmaps.adoc b/en/09_Generating_Mipmaps.adoc index 46f6f74a3..5cd57ae03 100644 --- a/en/09_Generating_Mipmaps.adoc +++ b/en/09_Generating_Mipmaps.adoc @@ -10,7 +10,7 @@ Mipmaps are widely used in games and rendering software, and Vulkan gives us com Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one. -Mipmaps are used as a form of _Level of Detail_ or _LOD._ Objects that are far away from the camera will sample their textures from the smaller mip images. +Mipmaps are used as a form of _Level of Detail_ or _LOD_. Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as https://en.wikipedia.org/wiki/Moir%C3%A9_pattern[Moiré patterns]. An example of what mipmaps look like: @@ -18,10 +18,10 @@ image::/images/mipmaps_example.jpg[] == Image creation -In Vulkan, each of the mip images is stored in different _mip levels_ of a `VkImage`. +In Vulkan, each of the mip images is stored in different _mip levels_ of a `vk::Image`. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the _mip chain._ -The number of mip levels is specified when the `VkImage` is created. +The number of mip levels is specified when the `vk::Image` is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number: @@ -29,8 +29,8 @@ First, add a class member to store this number: [,c++] ---- ... -uint32_t mipLevels; -std::unique_ptr textureImage; +uint32_t mipLevels = 0; +vk::raii::Image textureImage = nullptr; ... ---- @@ -38,8 +38,8 @@ The value for `mipLevels` can be found once we've loaded the texture in `createT [,c++] ---- -int texWidth, texHeight, texChannels; -stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +int texWidth, texHeight, texChannels; +stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); ... mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; ---- @@ -55,26 +55,47 @@ Add a `mipLevels` parameter to the functions: [,c++] ---- -void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) const { - ... - imageInfo.mipLevels = mipLevels; +std::pair + createImage(uint32_t width, + uint32_t height, + uint32_t mipLevels, + 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, ... } ---- [,c++] ---- -[[nodiscard]] std::unique_ptr createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - ... - viewInfo.subresourceRange.levelCount = mipLevels; +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 = {.aspectMask = aspectFlags, .levelCount = mipLevels, .layerCount = 1}}; ... +} ---- [,c++] ---- -void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) const { - ... - barrier.subresourceRange.levelCount = mipLevels; +void transitionImageLayout( + vk::raii::CommandBuffer &commandBuffer, vk::raii::Image const &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) +{ + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = mipLevels, .layerCount = 1}}; ... ---- @@ -82,25 +103,36 @@ Update all calls to these functions to use the right values: [,c++] ---- -createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); +std::tie(depthImage, depthImageMemory) = createImage(swapChainExtent.width, + swapChainExtent.height, + 1, + depthFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal); ... -createImage(texWidth, texHeight, mipLevels, 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::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); ---- [,c++] ---- -swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, vk::ImageAspectFlagBits::eColor, 1); +swapChainImageViews.emplace_back(createImageView(image, swapChainSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1)); ... -depthImageView = createImageView(depthImage, depthFormat,vk::ImageAspectFlagBits::eDepth, 1); +depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); ... -textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); +textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); ---- [,c++] ---- -transitionImageLayout(depthImage, depthFormat, vk::ImageLayout::eUndefined,vk::ImageLayout::eDepthStencilAttachmentOptimal, 1); -... -transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); +transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); ---- == Generating Mipmaps @@ -108,102 +140,106 @@ transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels, we need to generate the data from the single level that we have. -We will use the `vkCmdBlitImage` command. +We will use the `vk::raii::CommandBuffer::blitImage` command. This command performs copying, scaling, and filtering operations. We will call this multiple times to _blit_ data to each level of our texture image. -`vkCmdBlitImage` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. -Add `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` to the texture image's usage flags in `createTextureImage`: +`vk::raii::CommandBuffer::blitImage` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. +Add `vk::ImageUsageFlagBits::eTransferSrc` to the texture image's usage flags in `createTextureImage`: [,c++] ---- ... -createImage(texWidth, texHeight, mipLevels, 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::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); ... ---- -Like other image operations, `vkCmdBlitImage` depends on the layout of the image it operates on. -We could transition the entire image to `VK_IMAGE_LAYOUT_GENERAL`, but this will most likely be slow. -For optimal performance, the source image should be in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination image should be in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. +Like other image operations, `vk::raii::CommandBuffer::blitImage` depends on the layout of the image it operates on. +We could transition the entire image to `vk::ImageLayout::eGeneral`, but this will most likely be slow. +For optimal performance, the source image should be in `vk::ImageLayout::eTransferSrcOptimal` and the destination image should be in `vk::ImageLayout::eTransferDstOptimal`. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands. `transitionImageLayout` only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. -Remove the existing transition to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` in `createTextureImage`: +Remove the existing transition to `vk::ImageLayout::eShaderReadOnlyOptimal` in `createTextureImage`: [,c++] ---- ... -transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); -//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps +transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); +copyBufferToImage(commandBuffer, stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitioned to vk::ImageLayout::eShaderReadOnlyOptimal while generating mipmaps ... ---- -This will leave each level of the texture image in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. -Each level will be transitioned to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` after the blit command reading from it is finished. +This will leave each level of the texture image in `vk::ImageLayout::eTransferDstOptimal`. +Each level will be transitioned to `vk::ImageLayout::eShaderReadOnlyOptimal` after the blit command reading from it is finished. We're now going to write the function that generates the mipmaps: [,c++] ---- -void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier (vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eTransferRead - , vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal - , vk::QueueFamilyIgnored, vk::QueueFamilyIgnored, image); - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - endSingleTimeCommands(commandBuffer); +void generateMipmaps(vk::raii::CommandBuffer &commandBuffer, + vk::raii::Image &image, + int32_t texWidth, + int32_t texHeight, + uint32_t mipLevels) +{ + 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, } ---- -We're going to make several transitions, so we'll reuse this `VkImageMemoryBarrier`. +We're going to make several transitions, so we'll reuse this `vk::ImageMemoryBarrier`. The fields set above will remain the same for all barriers. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, and `dstAccessMask` will be changed for each transition. [,c++] ---- -int32_t mipWidth = texWidth; +int32_t mipWidth = texWidth; int32_t mipHeight = texHeight; -for (uint32_t i = 1; i < mipLevels; i++) { - +for (uint32_t i = 1; i < mipLevels; i++) +{ } ---- -This loop will record each of the `VkCmdBlitImage` commands. +This loop will record each of the `vk::raii::CommandBuffer::blitImage` commands. Note that the loop variable starts at 1, not 0. [,c++] ---- barrier.subresourceRange.baseMipLevel = i - 1; -barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; -barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; -barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; -barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; +barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; +barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; +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); ---- -First, we transition level `i - 1` to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. -This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vkCmdCopyBufferToImage`. +First, we transition level `i - 1` to `vk::ImageLayout::eTransferSrcOptimal`. +This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vk::raii::CommandBuffer::copyBufferToImage`. The current blit command will wait on this transition. [,c++] ---- -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}})}; ---- Next, we specify the regions that will be used in the blit operation. @@ -215,39 +251,45 @@ The Z dimension of `srcOffsets[1]` and `dstOffsets[1]` must be 1, since a 2D ima [,c++] ---- -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); ---- Now, we record the blit command. -Note that `textureImage` is used for both the `srcImage` and `dstImage` parameter. +Note that `image` is used for both the `srcImage` and `dstImage` parameter. This is because we're blitting between different levels of the same image. -The source mip level was just transitioned to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination level is still in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` from `createTextureImage`. +The source mip level was just transitioned to `vk::ImageLayout::eTransferSrcOptimal` and the destination level is still in `vk::ImageLayout::eTransferDstOptimal` from `createTextureImage`. -Beware if you are using a dedicated transfer queue (as suggested in xref:04_Vertex_buffers/02_Staging_buffer.adoc[Vertex buffers]): `vkCmdBlitImage` must be submitted to a queue with graphics capability. +Beware if you are using a dedicated transfer queue (as suggested in xref:04_Vertex_buffers/02_Staging_buffer.adoc[Vertex buffers]): `vk::raii::CommandBuffer::blitImage` must be submitted to a queue with graphics capability. -The last parameter allows us to specify a `VkFilter` to use in the blit. -We have the same filtering options here that we had when making the `VkSampler`. -We use the `VK_FILTER_LINEAR` to enable interpolation. +The last parameter allows us to specify a `vk::Filter` to use in the blit. +We have the same filtering options here that we had when making the `vk::raii::Sampler`. +We use the `vk::Filter::eLinear` to enable interpolation. [,c++] ---- -barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; -barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; +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); ---- -This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. +This barrier transitions mip level `i - 1` to `vk::ImageLayout::eShaderReadOnlyOptimal`. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish. [,c++] ---- ... - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; + if (1 < mipWidth) + { + mipWidth /= 2; + } + if (1 < mipHeight) + { + mipHeight /= 2; + } } ---- @@ -258,20 +300,18 @@ When this happens, that dimension should remain 1 for all remaining levels. [,c++] ---- - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - endSingleTimeCommands(*commandBuffer); + commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); } ---- -Before we end the command buffer, we insert one more pipeline barrier. -This barrier transitions the last mip level from `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. +Finally, we insert one more pipeline barrier. +This barrier transitions the last mip level from `vk::ImageLayout::eTransferDstOptimal` to `vk::ImageLayout::eShaderReadOnlyOptimal`. The loop didn't handle this, since the last mip level is never blitted from. Finally, add the call to `generateMipmaps` in `createTextureImage`: @@ -280,55 +320,64 @@ Finally, add the call to `generateMipmaps` in `createTextureImage`: ---- transitionImageLayout(*textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); copyBufferToImage(stagingBuffer, *textureImage, static_cast(texWidth), static_cast(texHeight)); -//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps -... -generateMipmaps(textureImage, texWidth, texHeight, mipLevels); +//transitioned to vk::ImageLayout::eShaderReadOnlyOptimal while generating mipmaps +generateMipmaps(commandBuffer, textureImage, texWidth, texHeight, mipLevels); ---- Our texture image's mipmaps are now filled. == Linear filtering support -It is very convenient to use a built-in function like `vkCmdBlitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. -It requires the texture image format we use to support linear filtering, which can be checked with the `vkGetPhysicalDeviceFormatProperties` function. +It is very convenient to use a built-in function like `vk::raii::CommandBuffer::blitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. +It requires the texture image format we use to support linear filtering, which can be checked with the `vk::raii::PhysicalDevice::getFormatProperties` function. We will add a check to the `generateMipmaps` function for this. First, add a parameter that specifies the image format: [,c++] ---- -void createTextureImage() { +void createTextureImage() +{ ... - - generateMipmaps(*textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + generateMipmaps(commandBuffer, 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) { - +void generateMipmaps(vk::raii::CommandBuffer &commandBuffer, + vk::raii::Image &image, + vk::Format imageFormat, + int32_t texWidth, + int32_t texHeight, + uint32_t mipLevels) +{ ... } ---- -In the `generateMipmaps` function, use `vkGetPhysicalDeviceFormatProperties` to request the properties of the texture image format: +In the `generateMipmaps` function, use `vk::raii::PhysicalDevice::getFormatProperties` to request the properties of the texture image format: [,c++] ---- -void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - +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 blit-ing vk::FormatProperties formatProperties = physicalDevice->getFormatProperties(imageFormat); - ... ---- -The `VkFormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. +The `vk::FormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check `optimalTilingFeatures`. -Support for the linear filtering feature can be checked with the `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`: +Support for the linear filtering feature can be checked with the `vk::FormatFeatureFlagBits::eSampledImageFilterLinear`: [,c++] ---- -if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); +if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) +{ + throw std::runtime_error("texture image format does not support linear blitting!"); } ---- @@ -337,13 +386,12 @@ You could implement a function that searches common texture image formats for on Each mip level can then be loaded into the image in the same way that you loaded the original image. It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. -Usually they are pre-generated and stored in the texture file alongside the -base level to improve loading speed. +Usually they are pre-generated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader. == Sampler -While the `VkImage` holds the mipmap data, `VkSampler` controls how that data is read while rendering. +While the `vk::raii::Image` holds the mipmap data, `vk::raii::Sampler` controls how that data is read while rendering. Vulkan allows us to specify `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode` ("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode: @@ -354,24 +402,30 @@ lod = clamp(lod + mipLodBias, minLod, maxLod); level = clamp(floor(lod), 0, texture.mipLevels - 1); //clamped to the number of mip levels in the texture -if (mipmapMode == vk::SamplerMipmapMode::eNearest) { +if (mipmapMode == vk::SamplerMipmapMode::eNearest) +{ color = sample(level); -} else { +} +else +{ color = blend(sample(level), sample(level + 1)); } ---- -If `samplerInfo.mipmapMode` is `VK_SAMPLER_MIPMAP_MODE_NEAREST`, `lod` selects the mip level to sample from. -If the mipmap mode is `VK_SAMPLER_MIPMAP_MODE_LINEAR`, `lod` is used to select two mip levels to be sampled. +If `samplerInfo.mipmapMode` is `vk::SamplerMipmapMode::eNearest`, `lod` selects the mip level to sample from. +If the mipmap mode is `vk::SamplerMipmapMode::eLinear`, `lod` is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended. The sample operation is also affected by `lod`: [,c++] ---- -if (lod <= 0) { +if (lod <= 0) +{ color = readTexture(uv, magFilter); -} else { +} +else +{ color = readTexture(uv, minFilter); } ---- @@ -382,35 +436,35 @@ Normally, `lod` is non-negative, and is only 0 when close the camera. `mipLodBias` lets us force Vulkan to use lower `lod` and `level` than it would normally use. To see the results of this chapter, we need to choose values for our `textureSampler`. -We've already set the `minFilter` and `magFilter` to use `VK_FILTER_LINEAR`. +We've already set the `minFilter` and `magFilter` to use `vk::Filter::eLinear`. We just need to choose values for `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode`. [,c++] ---- -void createTextureSampler() { +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, - .minLod = 0.0f, - .maxLod = vk::LodClampNone - }; + 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, + .minLod = 0.0f, + .maxLod = vk::LodClampNone}; ... } ---- In the code above, we've set up the sampler with linear filtering for both minification and magnification, and linear interpolation between mip levels. We've also set the mip level bias to 0.0f. -The `minLod` and `maxLod` are used to effectively set the range of mip levels to be used by clamping the minimum and maximum LOD values. By setting `minLod` to `0.0f` and `maxLod` to `VK_LOD_CLAMP_NONE` we ensure the full range of mip levels will be used. +The `minLod` and `maxLod` are used to effectively set the range of mip levels to be used by clamping the minimum and maximum LOD values. By setting `minLod` to `0.0f` and `maxLod` to `vk::LodClampNone` we ensure the full range of mip levels will be used. Now run your program, and you should see the following: