From ddb6f976460ecf5ce88dc7259f81e568989df78c Mon Sep 17 00:00:00 2001 From: Alexandre Date: Tue, 14 Apr 2026 01:15:37 +0200 Subject: [PATCH] Added LoDs --- src/main.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 190 insertions(+), 21 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f8a3a19..19ea37e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include #include // std::numeric_limits #include // to load shaders +#include #include #include #include @@ -183,6 +184,8 @@ class HelloTriangleApplication { vk::raii::Image depthImage = nullptr; vk::raii::DeviceMemory depthImageMemory = nullptr; vk::raii::ImageView depthImageView = nullptr; + // Minmaps + uint32_t mipLevels; // Indexes uint32_t queueIndex = ~0; uint32_t frameIndex = 0; @@ -490,7 +493,8 @@ class HelloTriangleApplication { swapChainImageViews.emplace_back(createImageView( swapChainImages[i], swapChainImageFormat, - vk::ImageAspectFlagBits::eColor + vk::ImageAspectFlagBits::eColor, + 1 )); } #ifndef NDEBUG @@ -684,6 +688,7 @@ class HelloTriangleApplication { createImage( swapChainExtent.width, swapChainExtent.height, + 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, @@ -694,7 +699,8 @@ class HelloTriangleApplication { depthImageView = createImageView( depthImage, depthFormat, - vk::ImageAspectFlagBits::eDepth + vk::ImageAspectFlagBits::eDepth, + 1 ); #ifndef NDEBUG std::cout << "Depth ressouce created !\n"; @@ -709,6 +715,8 @@ class HelloTriangleApplication { stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast (std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + if (!pixels) { throw std::runtime_error("Failed to load texture image!\nAborting..."); } @@ -731,17 +739,41 @@ class HelloTriangleApplication { createImage( texWidth, texHeight, + mipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory ); - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + transitionImageLayout( + textureImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, + mipLevels + ); + copyBufferToImage( + stagingBuffer, + textureImage, + static_cast(texWidth), + static_cast(texHeight) + ); + transitionImageLayout( + textureImage, + vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, + mipLevels + ); + + generateMipmaps( + textureImage, + vk::Format::eR8G8B8A8Srgb, + texWidth, + texHeight, + mipLevels + ); #ifndef NDEBUG std::cout << "Texture image created !\n"; @@ -755,7 +787,8 @@ class HelloTriangleApplication { textureImageView = createImageView( textureImage, vk::Format::eR8G8B8A8Srgb, - vk::ImageAspectFlagBits::eColor + vk::ImageAspectFlagBits::eColor, + mipLevels ); #ifndef NDEBUG std::cout << "Texture Image created !\n"; @@ -765,6 +798,7 @@ class HelloTriangleApplication { void createImage( uint32_t width, uint32_t height, + uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, @@ -779,7 +813,7 @@ class HelloTriangleApplication { .imageType = vk::ImageType::e2D, .format = format, .extent = { width, height, 1 }, - .mipLevels = 1, + .mipLevels = mipLevels, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, @@ -820,7 +854,7 @@ class HelloTriangleApplication { .compareEnable = vk::True, .compareOp = vk::CompareOp::eAlways, .minLod = 0.0f, - .maxLod = 0.0f, + .maxLod = vk::LodClampNone, .borderColor = vk::BorderColor::eIntOpaqueBlack, .unnormalizedCoordinates = vk::False }; @@ -1222,7 +1256,8 @@ class HelloTriangleApplication { void transitionImageLayout( const vk::raii::Image& image, vk::ImageLayout oldLayout, - vk::ImageLayout newLayout + vk::ImageLayout newLayout, + uint32_t mipLevels ) { auto commandBuffer = beginSingleTimeCommands(); @@ -1236,7 +1271,7 @@ class HelloTriangleApplication { .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, - 1, + mipLevels, 0, 1 } @@ -1444,21 +1479,28 @@ class HelloTriangleApplication { const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void * ){ - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning){ #ifndef NDEBUG std::cerr << "Validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; #endif } return vk::False; - } + }; - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + static void framebufferResizeCallback( + GLFWwindow* window, + int width, + int height + ){ auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; - } + }; - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + uint32_t findMemoryType( + uint32_t typeFilter, + vk::MemoryPropertyFlags properties + ){ vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { @@ -1593,11 +1635,12 @@ class HelloTriangleApplication { queue.waitIdle(); } - vk::raii::ImageView createImageView( - vk::Image image, + [[nodiscard]] vk::raii::ImageView createImageView( + const vk::Image& image, vk::Format format, - vk::ImageAspectFlags aspectFlags - ) { + vk::ImageAspectFlags aspectFlags, + uint32_t mipLevels + ) const { vk::ImageViewCreateInfo viewInfo{ .image = image, .viewType = vk::ImageViewType::e2D, @@ -1605,7 +1648,7 @@ class HelloTriangleApplication { .subresourceRange = { aspectFlags, 0, - 1, + mipLevels, 0, 1 } @@ -1649,6 +1692,132 @@ class HelloTriangleApplication { return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; } + void generateMipmaps( + vk::raii::Image& image, + vk::Format imageFormat, + int32_t texWidth, + int32_t texHeight, + uint32_t mipLevels + ){ + #ifndef NDEBUG + std::cout << "Generating mipmaps...\n"; + #endif + vk::FormatProperties formatProperties = physicalDevice . getFormatProperties(imageFormat); + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { + throw std::runtime_error("Texture image format does not support linear blitting !\nAborting..."); + } + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{}; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcQueueFamilyIndex = vk::QueueFamilyIgnored; + barrier.dstQueueFamilyIndex = vk::QueueFamilyIgnored; + barrier.image = image; + + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t 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, + {}, + {}, + {}, + barrier + ); + + std::array srcOffsets = { + vk::Offset3D{0, 0, 0}, + vk::Offset3D{mipWidth, mipHeight, 1} + }; + std::array dstOffsets = { + vk::Offset3D{0, 0, 0}, + vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1} + }; + + vk::ImageBlit blit = { + .srcSubresource = {}, + .srcOffsets = srcOffsets, + .dstSubresource = {}, + .dstOffsets = dstOffsets, + }; + + blit.srcSubresource = vk::ImageSubresourceLayers( + vk::ImageAspectFlagBits::eColor, + i - 1, + 0, + 1 + ); + blit.dstSubresource = vk::ImageSubresourceLayers( + vk::ImageAspectFlagBits::eColor, + i, + 0, + 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, + {}, + {}, + {}, + barrier + ); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; + } + + 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 + ); + + endSingleTimeCommands(commandBuffer); + #ifndef NDEBUG + std::cout << "Mipmaps generated !\n"; + #endif + } + }; int main() {