diff --git a/src/main.cpp b/src/main.cpp index 2badc34..0f0abce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -124,6 +124,7 @@ class VulkanApp { vk::UniqueDeviceMemory mDepthImageMemory; vk::UniqueImageView mDepthImageView; + u32 mMipLevels; vk::UniqueImage mTextureImage; vk::UniqueDeviceMemory mTextureImageMemory; vk::UniqueImageView mTextureImageView; @@ -439,7 +440,7 @@ class VulkanApp { for (u32 i = 0; i < mSwapChainImages.size(); i++) mSwapChainImageViews[i] = - createImageView(mSwapChainImages[i], mSwapChainImageFormat, vk::ImageAspectFlagBits::eColor); + createImageView(mSwapChainImages[i], mSwapChainImageFormat, vk::ImageAspectFlagBits::eColor, 1); } fn createRenderPass() -> void { @@ -663,6 +664,7 @@ class VulkanApp { createImage( mSwapChainExtent.width, mSwapChainExtent.height, + 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, @@ -671,7 +673,7 @@ class VulkanApp { mDepthImageMemory ); - mDepthImageView = createImageView(mDepthImage.get(), depthFormat, vk::ImageAspectFlagBits::eDepth); + mDepthImageView = createImageView(mDepthImage.get(), depthFormat, vk::ImageAspectFlagBits::eDepth, 1); } fn findSupportedFormat( @@ -704,11 +706,14 @@ class VulkanApp { fn createTextureImage() -> void { i32 texWidth = 0, texHeight = 0, texChannels = 0; + u8* pixels = stbi_load(TEXTURE_PATH, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); vk::DeviceSize imageSize = static_cast(texWidth) * static_cast(texHeight) * 4; + mMipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + if (!pixels) throw std::runtime_error("Failed to load texture image!"); @@ -730,36 +735,131 @@ class VulkanApp { createImage( static_cast(texWidth), static_cast(texHeight), + mMipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, mTextureImage, mTextureImageMemory ); transitionImageLayout( - mTextureImage.get(), - vk::Format::eR8G8B8A8Srgb, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eTransferDstOptimal + mTextureImage.get(), vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mMipLevels ); copyBufferToImage( stagingBuffer.get(), mTextureImage.get(), static_cast(texWidth), static_cast(texHeight) ); - transitionImageLayout( - mTextureImage.get(), - vk::Format::eR8G8B8A8Srgb, - vk::ImageLayout::eTransferDstOptimal, - vk::ImageLayout::eShaderReadOnlyOptimal + generateMipmaps(mTextureImage.get(), vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mMipLevels); + } + + fn generateMipmaps(vk::Image image, vk::Format imageFormat, i32 texWidth, i32 texHeight, u32 mipLevels) + -> void { + vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + throw std::runtime_error("Texture image format does not support linear blitting!"); + + vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier { + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 } + }; + + i32 mipWidth = texWidth; + i32 mipHeight = texHeight; + + for (u32 i = 1; i < mipLevels; i++) { + 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; + + commandBuffer->pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, + {}, + nullptr, + nullptr, + barrier + ); + + vk::ImageBlit blit { + .srcSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = i - 1, + .baseArrayLayer = 0, + .layerCount = 1 }, + .srcOffsets = std::array { { { 0, 0, 0 }, { mipWidth, mipHeight, 1 } } }, + .dstSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = i, + .baseArrayLayer = 0, + .layerCount = 1 }, + .dstOffsets = + std::array { + { { 0, 0, 0 }, { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 } } } + }; + + 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, + {}, + nullptr, + nullptr, + barrier + ); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mMipLevels - 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, + {}, + nullptr, + nullptr, + barrier ); + + endSingleTimeCommands(std::move(commandBuffer)); } fn createTextureImageView() -> void { - mTextureImageView = - createImageView(mTextureImage.get(), vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + mTextureImageView = createImageView( + mTextureImage.get(), vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mMipLevels + ); } fn createTextureSampler() -> void { @@ -778,7 +878,7 @@ class VulkanApp { .compareEnable = vk::False, .compareOp = vk::CompareOp::eAlways, .minLod = 0.0F, - .maxLod = 0.0F, + .maxLod = static_cast(mMipLevels), .borderColor = vk::BorderColor::eIntOpaqueBlack, .unnormalizedCoordinates = vk::False, }; @@ -786,13 +886,13 @@ class VulkanApp { mTextureSampler = mDevice->createSamplerUnique(samplerInfo); } - fn createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags) + fn createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, u32 mipLevels) -> vk::UniqueImageView { vk::ImageViewCreateInfo viewInfo { .image = image, .viewType = vk::ImageViewType::e2D, .format = format, .subresourceRange = { .aspectMask = aspectFlags, .baseMipLevel = 0, - .levelCount = 1, + .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = 1, } @@ -804,6 +904,7 @@ class VulkanApp { fn createImage( u32 width, u32 height, + u32 mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, @@ -815,7 +916,7 @@ class VulkanApp { .imageType = vk::ImageType::e2D, .format = format, .extent = { .width = width, .height = height, .depth = 1 }, - .mipLevels = 1, + .mipLevels = mipLevels, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, @@ -840,9 +941,9 @@ class VulkanApp { fn transitionImageLayout( vk::Image image, - vk::Format format, vk::ImageLayout oldLayout, - vk::ImageLayout newLayout + vk::ImageLayout newLayout, + u32 mipLevels ) -> void { vk::UniqueCommandBuffer commandBuffer = beginSingleTimeCommands(); @@ -855,7 +956,7 @@ class VulkanApp { // clang-format off .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, - .levelCount = 1, + .levelCount = mipLevels, .baseArrayLayer = 0, .layerCount = 1 } // clang-format on