Category Archives: Vulkan

Mipmap generation : Transfers, transition layout

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 :

  1. level 0 : 512×128
  2. level 1: 256×64
  3. level 2: 128×32
  4. level 3: 64×16
  5. level 4: 32×8
  6. level 5: 16×4
  7. level 6: 8×2
  8. level 7: 4×1
  9. level 8: 2×1
  10. level 9: 1×1

Mipmap

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.

  1. 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…
  2. 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 :
levels=floor(log_2(max(width, height))) + 1

The idea of the algorithm to create the differents mipmap levels is easy.

  1. You initialize the level 0 (from a file for example) and put the layout to TRANSFER_SRC
  2. You set the level 1 to TRANSFER_DST
  3. You blit the level 0 to the level 1
  4. You set the level 1 to TRANSFER_SRC
  5. You reitere 2 3 4 for each level.
  6. You transition all levels to the layout you need.

So, here is our code :

Beginning with the CommandBuffer

Prepare the blit !

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

And you just use blitImage to blit it.

After, you have to transition the mipmap level image layout to TRANSFER_SRC

Finish

You have to transition all mipmap levels to the layout you want to use

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.

Reference

Sascha Willems Mipmap

Buffer management with Vulkan : transfer, Staging buffer

Hi guys !
I keep my promise and I am coming with explanations and implementation on how to use and manage one (or several) buffer in Vulkan application.

How I manage my resources ?

shared_ptr?

Firstly, I have a way to manage Vulkan resource that is a bit weird. The idea is to “emulate” the behaviour of shared_ptr and enable copy / move.
So, if you do that :

b1 and b2 are exactly the same Vulkan’s Image.

A counter?

To emulate the behaviour of a shared_ptr, I created one class that is simply a Counter.

A Vulkan Resource

A Vulkan resource lives through a device. So I wrote this little class that represents a Vulkan Resource :

Buffer in Vulkan

Unlike OpenGL, buffers in Vulkan are separated from memory. You must bind the memory to them. Since you can choose if you want the memory on the device_local heap or in the host_visible, you can chose which heap your buffer will use.

So what are buffer made with ?

Buffers are made with a size, a usage (vertex? uniform ?), one block of memory, one ptr if the buffer is HOST_VISIBLE etc.
My buffer class is :

It may be is a bit complicate, but it is not really that difficult. A buffer will be created with an usage, one size and one boolean to put or not this buffer in device_local memory.
The creation of the buffer is quite simple. You just have to give the size and the usage :

The last line is to get the memory requirements. It will give you the real size you need (padding or other things) and list of memory types that can be used with the buffer.
To get the memory type index, I developed this function which cares about device local memory or host visible memory :

This code was made with the specifications themselves.
Now we should allocate memory for our buffers :

As you can see, you allocate the memory, and you bind the memory. If the memory is host visible, you can map it.

Now we have a class to manage our buffers. But it is not finished at all !

Staging resources

We cannot write directly to the device_local memory. We must use something that we call a staging resource. Staging resources can be buffers or images. The idea is to bind a host visible memory to a staging resource, and transfer the memory through the staging resource to a resource with memory that resides in device_local memory.

staging buffer

Command Buffers submitting

Before to transfer anything, I wanted to have a class that manages the submitting of command buffers. When the work is done, the command submitter should notify transferer object that use it. I used an observer pattern :

The code is not difficult, it allocates if needed one command buffer and return it and use fencing to know if works are completed.

Buffer transferer

You guessed that our Buffer transferer must implement the abstract class :

The idea is to have several buffers ready to transfer data. Why this idea? Because users may don’t care about the CPU buffer and only want a GPU Buffer ! Thanks to that, if he wants to transfer data like glBufferSubData, he actually can !

The code to transfer a buffer is not complicated at all. However, you just have to be careful about the memory barrier. Personally, I use one from Transfer to ALL_Commands in this case.

I do not manage the reallocation if the dst buffer is too small, or loop / recursion the transfer when our staging buffer is too small, but with our architecture, It would not be difficult to manage these cases !

How to use it??

Simply like that !

I saw that a high number of my visits comes from twitter. If you want to follow me: it is here.

Kisses and see you soon to see how to load / manage images !