Basic Draw Calls Misunderstanding

Hi, total noob here.
I’ve been following LunarG’s cube tutorial; towards the end when i tried to draw another cube beside the first one, i realised i’m still having a lot of misunderstandings about command submission orders and the rendering pipeline in general.

Here’s how i got one cube on the screen:

  • Start recording into the Command Buffer (vkBeginCommandBuffer)
    1- Begin the render pass
    2- Bind the pipeline
    3- Bind uniform descriptor sets
    4- Bind vertex buffer
    5- Set the dynamic viewport
    6- vkCmdDraw for 36 vertices (because no indices)
    7- End the render pass
  • Finished Recording CmdBuffer.

All works nice and well but then i don’t how to draw another one within the same Command Buffer.
I tried putting these between step 6 and 7 which didn’t work :

  • update uniform buffer’s model matrix using map/memcpy/unmap for a +2 translation in the X axis.
  • update the descriptor sets to inform the gpu. (VkUpdateDescriptorsSets)
  • vkCmdDraw.

It only updated the first cube’s position without drawing a new one. Should i submit the command buffer for the first cube, wait for completion fence and then flush-reuse it for every following draw call ? (finally present to the swap chain)
What are the many things that i’m getting wrong here?

Yea, that tutorial is missing any explanation of the synchronization system. Also it perhaps would be wiser to go for a triangle rendering for the first app (it does not need depth buffer, uniforms and descriptors).

You can keep it mapped if you plan to update it later. Then again it probably is in suboptimal memory type anyway so let’s not fret over details.

between step 6 and 7
The VkBuffer is global (not a part of the prebaked VkCmdBuffer), so if you change its data before the VkQueueSubmit, then only the changed data will be known to the submitted/executed command buffer.

vkCmdUpdateBuffer could be used to make small updates to a buffer inside a command buffer, but it can be used only outside a render pass instance.

update the descriptor sets to inform the gpu. (VkUpdateDescriptorsSets)

It also does not start with vkCmd*, which means it is also global (not part of the command buffer recording).

We should go over what command buffer is. Only vkCmd* are recorded inside the command buffer, but none of it is executed before vkQueueSubmit. On the other hand other commands do start executing immediately (such as vkUpdateDescriptorsSets, vkMapMemory and memcpy).

Also: the descriptors only contain the info about the bindings. Assuming you are using the same VkBuffer, it should not be necessary to call vkUpdateDescriptorsSets again.

It only updated the first cube’s position without drawing a new one. Should i submit the command buffer for the first cube, wait for completion fence and then flush-reuse it for every following draw call ?

That’s one (suboptimal) way to do it, and a good start. But you must also be careful not to clear your image in between.
0. clear framebuffer in some way

  1. submit the cube cmd buffer
  2. do vkQueueWaitIdle()
  3. update the uniform buffer
  4. submit the cube cmd buffer again
  5. present: two cubes!

Great help thanks! As you said, problem was the fact that vkMapMemory() is instant. So while it did draw two cubes, both were at the same location thus invisible.

What’s the better way to go? Now i can see two cubes using the first method i tried only with vkCmdUpdateBuffers(), but you mentioned reusing a command buffer is one sub optimal way. i don’t know if you meant it comparing to what i’ve done now but isn’t it better to have all draw calls within only one cmd buffer submission?

sub optimal way. I don’t know if you meant it comparing to

No, I meant my use of vkQueueWaitIdle\Fence in #2. It is highly inefficient, but useful (e.g. for beginner) to ensure the synchronization is right.

BTW vkCmdUpdateBuffers requires synchronization too (some Barriers before and after). Which requires some theory. It is tricky learning this from code, as wrongly synchronized code may in some instances just work right anyway, so you get no learning feedback…

What’s the better way to go?

It sounds like a typical use for instancing. E.g. one would have both the transf. matrices in the Buffer and would choose the appropriate one using gl_InstanceID. Which does not require to change the Buffer in middle of the drawing (and would probably even result in prettier code).

What about a more real scenario where there are multiples objects of different models being drawn,
Is it still reasonable to use only one command buffer for all drawings (Assuming they’re using the same shader stages?) and bind the different vertex buffers before drawing a new type of model?

Yea, if possible.

Let’s look at it this way:

[li]vkQueueSubmit is potentially expensive, so better have single call if possible[/li][li]Command buffers using semaphore in a separate batch may be more expensive than a single batch (VkSubmitInfo)[/li][li]Though I would assume command buffers in the same batch vs single command buffer to be similarly expensive. All it really does is concatenate the buffers into the queue. More command buffers can potentially be even better here, because they could be recorded in a multithreaded fashion (assuming you need to bake new command buffers in the middle of rendering).[/li][li]Lastly, I would expect single Render Pass instance to perform better than multiple.[/li][/ul]

Though this choice is usually out of your hands. If you have only static models, those could be dumped into single command buffer. More complex app may need to modularize its stuff somehow into multiple command buffers.

Thanks a lot for the effort. I found Sascha Willems’ vulkan examples on github, they could also be great for other beginners potentially reading this.