Hi guys !
This article will deal with Mipmap’s generation.
What is a Mipmap ?
A mipmap is a kind of loop rescaling on a texture.
For example, take one 512×128 texture, you will divide the size by 4 at each level :
- level 0 : 512×128
- level 1: 256×64
- level 2: 128×32
- level 3: 64×16
- level 4: 32×8
- level 5: 16×4
- level 6: 8×2
- level 7: 4×1
- level 8: 2×1
- level 9: 1×1
Why do we need Mipmap ?
It can be seen as a Level of Detail (LoD). If the object is far from the camera, you do not need to use all details but only some. So, instead to send to the GPU all the texture, you can send a mipmap with a different level that is far away lighter than the original (level 0).
How to generate Mipmap in Vulkan ?
Contrary to OpenGL which provide glGenerateTextureMipmap function, Vulkan does not provide function to build mipmap by itself. You have to deal with it by yourself. There are two ways.
- Using the shaders and framebuffers. You use the shader to draw into the framebuffer which is half the size of the texture, and half of the half…
- Using the transfer queue and vkCmdBlitImage which blit one image into another.
We are going to see the second way.
To do it, we are going to use the Transferer class we saw prior.
First, the number of mipmaps level for one image is :
The idea of the algorithm to create the differents mipmap levels is easy.
- You initialize the level 0 (from a file for example) and put the layout to TRANSFER_SRC
- You set the level 1 to TRANSFER_DST
- You blit the level 0 to the level 1
- You set the level 1 to TRANSFER_SRC
- You reitere 2 3 4 for each level.
- You transition all levels to the layout you need.
So, here is our code :
Beginning with the CommandBuffer
void Transferer::buildMipMap(Image &src) { vk::CommandBuffer cmd = mCommandBufferSubmitter->createCommandBuffer(nullptr); vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); cmd.begin(beginInfo);
Prepare the blit !
for(uint32_t i = 1; i < src.getMipLevels(); ++i) { vk::ImageBlit blit; blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.srcSubresource.baseArrayLayer = 0; blit.srcSubresource.layerCount = 1; blit.srcSubresource.mipLevel = i - 1; blit.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.dstSubresource.baseArrayLayer = 0; blit.dstSubresource.layerCount = 1; blit.dstSubresource.mipLevel = i; // each mipmap is the size divided by two blit.srcOffsets[1] = vk::Offset3D(std::max(1u, src.getSize().width >> (i - 1)), std::max(1u, src.getSize().height >> (i - 1)), 1); blit.dstOffsets[1] = vk::Offset3D(std::max(1u, src.getSize().width >> i), std::max(1u, src.getSize().height >> i), 1);
The loop begins from 1 because the level 0 is already initialized.
After, you explain to Vulkan which level you will use as the source, and which one you will use as the destination.
Do not forget to use max when you compute the offset because if you do not use it, you will be unable to build mipmap for the last levels if your image is not with a 1:1 ratio.
Transition and Blit
After, you have to transition your mipmap level image layout you want to draw into to TRANSFER_DST
vk::ImageSubresourceRange range(vk::ImageAspectFlagBits::eColor, i, 1, 0, 1); // transferDst go to transferSrc because this mipmap will be the source for the next iteration (the next level) vk::ImageMemoryBarrier preBlit = transitionImage(src, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, range); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), nullptr, nullptr, preBlit); cmd.blitImage(src, vk::ImageLayout::eTransferSrcOptimal, src, vk::ImageLayout::eTransferDstOptimal, blit, vk::Filter::eLinear);
And you just use blitImage to blit it.
After, you have to transition the mipmap level image layout to TRANSFER_SRC
vk::ImageMemoryBarrier postBlit = transitionImage(src, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, range); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), nullptr, nullptr, postBlit);
Finish
You have to transition all mipmap levels to the layout you want to use
vk::ImageSubresourceRange range(vk::ImageAspectFlagBits::eColor, 0, VK_REMAINING_MIP_LEVELS, 0, 1); // transition all mipmap levels to shaderReadOnlyOptimal vk::ImageMemoryBarrier transition = transitionImage(src, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, range); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags(), nullptr, nullptr, transition);
Conclusion
This article was short, but mipmap are not that difficult to handle. Do you like this kind of short article?
Maybe the next article will be about descriptor set management.