buffervulkanmemory-reallocation

Vulkan - 1 uniform buffer, N meshes - dynamic VkDeviceMemory


Suppose I'm rendering simple cubes at random positions.

Having 3 of them as the starting number of cubes, the application acquires a VkBuffer handle and binds it to a VkDeviceMemory in order to store the model matrices of all cubes consecutively in it, and which is later on accessed by the shader via the descriptor set. The VkDeviceMemory has just enough memory for those 3 cubes.

What I want to do is, every time the user presses a key, a new cube should pop up somewhere. My question is, how should I go about resizing that memory? Could you provide an overview of the steps I should go through?

I realize I could use separate VkBuffer/VkDeviceMemory for each cube but I do not want to do that. Everywhere I read it is stated that's sort of an anti-pattern.

Should I just discard the VkDeviceMemory, allocate a new one with the right size, and call it a day? What about descriptor sets, do they need any special handling?

In some places I have read you could allocate a very big chunk of data, so you are on the safe side while dealing with more and more cubes up to a point in which, I suppose, you would stop permitting more of them to pop up because a limit has been reached. Is there a way around this self-imposed limit?

EDIT: I also realize allocating one small chunk at a time is a bad idea. What I'm interested in is the reallocation itself, and what it entails.


Solution

  • To answer the question "how do I reallocate and start using the new memory", ignoring questions about allocation strategy: reallocation is no different than allocating a new thing, populating it with the data you want, and then starting to use it. So you need essentially all of the same steps as for your initial allocation.

    The thing to be aware of is that most objects that get referenced in a command buffer can't safely be modified until that command buffer finishes executing. Typically, you'll be recording commands for frame N+1 while the commands for frame N are still executing. So you want to avoid updating mutable objects (like descriptor sets) to start using the new allocation; instead, you want a new descriptor set.

    So here's the list of things you need:

    1. The buffer itself: a VkBuffer and a VkDeviceMemory. If you allocated extra space in your current VkDeviceMemory so it's big enough for both the old and new VkBuffers, then you don't need a new VkDeviceMemory object. Either way, create a new VkBuffer of the desired size, and bind it to an unused portion of a VkDeviceMemory object.

    2. A way to bind the buffer to the pipeline: a VkDescriptorSet. You'll use the same descriptor set layout as before, that doesn't change. So allocate a new descriptor set from your descriptor pool, and use vkUpdateDescriptorSet to set the buffer descriptor to point to your new buffer (you can also copy other descriptors from your previous descriptor set if they don't need to change).

    3. Finally, when building the command buffer for the frame where you want to use the new buffer, pass the new descriptor set to vkCmdBindDescriptorSets instead of the old one.

    4. Eventually, after all the command buffers that used the old buffer and descriptor set have finished, you can free the buffer and descriptor set. For the descriptor set, you might just return it to the pool, or keep it around and reuse it the next time you need to reallocate the buffer. The device memory used by the old buffer can then be deallocated, or you can keep it around for reuse later.