Category Archives: C++

Vulkan Pipelines, Barrier, memory management

Hi!
Once again, I am going to present you some vulkan features, like pipelines, barriers, memory management, and all things useful for prior ones. This article will be long, but it will be separating into several chapters.

Memory Management

In Vulkan application, it is up to the developer to manage himself the memory. The number of allocations is limited. Make one allocation for one buffer, or one image is really a bad design in Vulkan. One good design is to make a big allocation (let’s call that a chunk), and manage it yourself, and allocate buffer or image within the chunk.

A Chunk Allocator

We need a simple object which has responsibility for allocations of chunks. It just has to select the good heap and call allocate and free from Vulkan API.

This piece of code is quite simple and easy to read.

Memory Pool

Memory pools are structures used to optimize dynamic allocation performances. In video games, it is not an option to use a memory pool. Ideas are the same I told in the first part. Allocate a chunk, and sub allocate yourself within the chunk. I made a simple generic memory pool.
There is a little scheme which explains what I wanted to do.

Memory Pool
Memory Pool

As you can see, video memory is separated into several parts (4 here) and each “Block” in the linked list describes one sub-allocation.
One block is described by :

  1. Size of the block
  2. Offset of the block relatively with the DeviceMemory
  3. A pointer to set data from the host (map)
  4. Boolean to know about the freeness of the block

A sub-allocation within a chunk is performed as follows :

  1. Traverse the linked list until we find a well-sized free block
  2. Modify the size and set the boolean to false
  3. Create a new block, set size, offset and put boolean to true and insert it after the current one.

A free is quite simple, you just have to put the boolean to true.
A good other method could be a “shrink to fit”. If there are some following others with the boolean set to true, we merge all blocks into one.

Buffers

Buffers are a well-known part in OpenGL. In Vulkan, it is approximately the same, but you have to manage yourself the memory through one memory pool.

When you create one buffer, you have to give him a size, an usage (uniform buffer, index buffer, vertex buffer, …). You also could ask for a sparse buffer (Sparse resources will be a subject of an article one day ^_^). You also could tell him to be in a mode concurrent. Thanks to that, you could access the same buffer through two different queues.

I chose to have a host visible and host coherent memory. But it is not especially useful. Indeed, to achieve a better performance, you could want to use a non coherent memory (but you will have to flush/invalidate your memory!!).
For the host visible memory, it is not especially useful as well, indeed, for indirect rendering, it could be smart to perform culling with the GPU to fill all structures!

Shaders

Shaders are Different parts of your pipelines. It is an approximation obviously. But, for each part (vertex processing, geometry processing, fragment processing…), shader associated is invoked. In Vulkan, shaders are wrote with SPIR-V.
SPIR-V is “.class” are for Java. You may compile your GLSL sources to SPIR-V using glslangvalidator.

Why is SPIR-V so powerful ?

SPIR-V allows developers to provide their application without the shader’s source.
SPIR-V is an intermediate representation. Thanks to that, vendor implementation does not have to write a specific language compiler. It results in a lower complexity for the driver and it could more optimize, and compile it faster.

Shaders in Vulkan

Contrary to OpenGL’s shader, it is really easy to compile in Vulkan.
My implementation keeps in memory all shaders into a hashtable. It lets to prevent any shader’s recompilation.

Pipelines

Pipelines are objects used for dispatch (compute pipelines) or render something (graphic pipelines).

The beginning of this part is going to be a summarize of the Vulkan’s specs.

Descriptors

Shaders access buffer and image resources through special variables. These variables are organized into a set of bindings. One set is described by one descriptor.

Descriptor Set Layout

They describe one set. One set is compound with an array of bindings. Each bindings are described by :

  1. A binding number
  2. One type : Image, uniform buffer, SSBO, …
  3. The number of values (Could be an array of textures)
  4. Stage where shader could access the binding.

Allocation of Descriptor Sets

They are allocated from descriptor pool objects.
One descriptor pool object is described by a number of set allocation possible, and an array of descriptor type / count it can allocate.

Once you have the descriptor pool, you could allocate from it sets (using both descriptor pool and descriptor set layout).
When you destroy the pool, sets also are destroyed.

Give buffer / image to sets

Now, we have descriptors, but we have to tell Vulkan where shaders can get data from.

Pipeline Layouts

Pipeline layouts are a kind of bridge between the pipeline and descriptor sets. They let you manage push constant as well (we’ll see them in a future article).

Implementation

Since descriptor sets are not coupled with pipelines layout. We could separate pipeline layout and descriptor pool / sets, but currently, I prefer to keep them coupled. It is a choice, and it will maybe change in the future.

The idea is quite easy. You create all your descriptor set layouts, then you allocate them through a pool.

Graphics Pipelines in a nutshell

Graphics Pipelines describe exactly what will happened on the rendering part.
They describe

  1. Shader stages
  2. Which kind of data you want to deal with (Position, normal,…)
  3. Which kind of primitive you want to draw (triangle, lines, points)
  4. Which operator you want to use for Stencil and Depth
  5. Multi sampling, color blending,…

The creation of a Graphic Pipeline is really easy, the main difficulty is the configuration.

I used a kind of builder design pattern to configure pipelines.

For the example, I configure my pipeline as follows :

  1. 2 stages : vertex shader and fragment shader
  2. Position 4D (x, y, z, w)
  3. No depth / stencil test
  4. An uniform buffer for one color

This code is a bit long, but it gives all the steps you have to follow to create simple pipelines.

Pipelines and descriptor sets give you an unmatched flexibility.

The main.cpp is this one

And now, we have our perfect triangle !!!!

Triangle using pipelines, shaders
Triangle using pipelines

Barrier and explanations for the main

I am going to explain quickly what memory barriers are.
The idea behind the memory barrier is ensured writes are performed.
When you performed one compute or one render, it is your duty to ensure that data will be visible when you want to re-use them.

In our main.cpp example, I draw a triangle into a frame buffer and present it.

The first barrier is :

Image barriers are compound with access, layout, and pipeline barrier with stage.
Since the presentation is a read of a framebuffer, srcAccessMask is VK_ACCESS_MEMORY_READ_BIT.
Now, we want to render inside this image via a framebuffer, so dstAccessMask is VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT.

We were presented the image, and now we want to render inside it, so, layouts are obvious.
When we submit image memory barrier to the command buffer, we have to tell it which stages are affected. Here, we wait for all commands and we begin for the first stage of the pipeline.

The second image memory barrier is

The only difference is the order and stageMasks. Here we wait for the color attachement (and not the Fragment one !!!!) and we begin with the end of the stages (It is not really easy to explain… but it does not sound not logic).

Steps to render something using pipelines are:

  1. Create pipelines
  2. Create command pools, command buffer and begin them
  3. Create vertex / index buffers
  4. Bind pipelines to their subpass, bind buffers and descriptor sets
  5. VkCmdDraw

References

Specification

It was a long article, I hope it was not unclear and that I didn’t do to much mistakes ^^.

Kiss !!!!

Flux: Qt Quick with unidirectional data flow

Hello !

Today is an important day, is the day of the first article of Qt I wrote.

Have you ever heard anything about Model View Controller or Model View Delegate???? Yes obviously, but right now, we’re going to talk about another thing (yes I am funny I know) . We are going to talk about Facebook, I mean a pattern which comes from Facebook.

What are we going to talk about??

We are going to talk about the Flux pattern, this pattern says data flow should be unidirectional as opposed to the Model View Delegate pattern.

modelview-overviewModel View Delegate multi directional data flow : Credit Qt

Flux pattern representation

Flux unidirectional data flow

What is the advantages to use Flux pattern?

  1. Signals propagation is easy and do not require any copy and paste.
  2. The code is easy to read.
  3. There is low coupling

What is the Action Creator?

When a user wants to interact with the application, he want to do an “Action“. For example, he could wants to add things to a todo list, so he could launch an Action(“Things to do”);
The Action Creator is here to give Action to our dispatcher.

What is the Dispatcher?

A dispatcher takes an action and its arguments and dispatchs it through all stores.

What is the Store?

A store is like a collection of datas but it also has logic buried inside it.

What is the View?

It shows all data.

What are we going to see?

We are going to see how to write a little application using Flux.
Our application could seem to that :
Screenshot_2016-02-19-20-22-20

Let’s code !

First, we need to know what our application has to do.
It must have possibility to add a counter, increment and decrement them. We exactly have 3 actions. It is that simple !

Now, we are going to see what is the AppDispatcher.

A dispatcher should take as argument the action type and a message (id for example). This dispatcher should dispatch this action as well.

You easily could improve the dispatch behaviour. Indeed, it could be more safe to use a queue… But in this example, I just show the mechanism and try to don’t overcomplicate the app.

I remind dispatcher dispatchs through the stores.
A store is a singleton which manages all objects of the same type. Our store manages all counters in the app.

A counter is an object with an id and a value, knowing that, we easily get :

On the onDispatched check if it is the good action, and if it is, do the required work.

Now we just need a view, as you see before, we use a “model” to store  all data, it will be the same for the view / delegate, but even if we use model view delegate, we will keep a unidirectional data flow.

The delegate will explains how an item should be rendered.
It is componed by 2 buttons (+ and -) and a value :

Yeah I know, there is some duplication of code, it is not good…
Now, we have the possibility to render items, we should print many of them.
Flux tells us datas are coming from Store, so let’s implement what Flux says!

It is the end, if you have any questions, please, let me know !
Hope you enjoyed it and learned somethings !

References

Flux by Facebook
Quick Flux : Problems about MVC and introduction

Thanks !