🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

How to manage vertex/index buffer content?

Started by
5 comments, last by Valakor 3 years, 10 months ago

For Vulkan is it still preferable to use a single/few large vertex buffer(s) then place multiple meshes within it, then offset, or use one buffer for each mesh? I'd expect the former, to support indirect drawing without rebinding.

But how to manage the content within the buffer if meshes can be loaded & unloaded dynamically? Vulkan support custom allocators, e.g. using VulkanMemoryAllocator, but that seems to be on “memory” level, not “buffer” level. My plan was to implement a custom (buddy) “allocator” to manage the content of the vertex & index buffers, is that how it's usually done?

My Google-fu maybe lacking but i can't find any discussion of this, rather fundamental, topic.

Advertisement

If you plan to retain your vertex buffer content (for example, it consists of meshes that you draw with transforms without altering vertex coordinates) you can maintain a free list of unused buffer portions and place newly loaded meshes in a sufficiently large gap or, if there is none, in a new buffer. Fragmentation can be measured exactly, taking action if necessary.

On the other hand whenever you need to update the portions of the buffer in actual use (often, but not necessarily, every frame) writing to a different place is free: you can compact your buffer to eliminate small gaps.

Omae Wa Mou Shindeiru

@LorenzoGatti So for static meshes, a simpler linear allocator, with intermittent compaction, is preferable? I guess internal fragmentation could be an issue.

A buddy allocator is a reasonable choice. I think that's what the popular Vulkan Memory Allocator (VMA) uses under the hood (i.e. allocate big 256MB chunks by default and sub-allocate/coalesce for new/free). In my homebrew renderer I started by just allocating buffers directly from VMA and letting it handle divvying up and coalescing memory, though of course that's not a super practical approach when talking about fragmentation. There is experimental support for defragmentation in the library that I haven't use though.

Of course, if you know your allocation characteristics ahead of time you can optimize specifically for those, e.g. I plan on having a model where I load “levels” in such a way that I know all the static memory characteristics up front and can allocate big linear regions during load, then free everything in one go when unloading.

@Valakor So you're using a single VkBuffer per mesh?

VMA seems to be very good, but as a noob i don't understand its value, unless you need to allocate more than 4096, or what the limit is, unique VkBuffer's. My plan only needs a one large vertex, and one index buffer, i.e. no need for VMA, maybe it becomes a necessity for VkImage's, when not using texture arrays?

Agreed, for a “level” based game/engine it's probably better to use a simple linear allocator, then just overwrite everything at the next level loading screen. If fragmentation becomes an issue, i may use different buffers with different "allocators" for persistent content, e.g. fonts, for long-lived content, e.g. "levels", and for short-lived transient stuff like effects.

@Valakor So you're using a single VkBuffer per mesh?

Roughly-ish. It depends on the vertex streams required by the mesh, but I try to compact vertex streams from separate sub-meshes with the same vertex layout to the same buffer.

VMA seems to be very good, but as a noob i don't understand its value, unless you need to allocate more than 4096, or what the limit is, unique VkBuffer's. My plan only needs a one large vertex, and one index buffer, i.e. no need for VMA, maybe it becomes a necessity for VkImage's, when not using texture arrays?

For clarification, the often-used 4096 number is referring to allocations (VkDeviceMemory), not buffers (VkBuffer or VkImage). You can (generally) have as many buffers/images as you want, but they must be backed by this more limited number of actual memory allocations. This is why you generally need some form of custom memory allocation scheme for sub-allocation.

VMA abstracts away some of the complexities of memory allocation. It does things like:

  • Remove the need to think about device memory and allocation limits (it handles allocating chunks of memory and sub-allocating for you, within the limits of your device)
  • Abstract away the ideas of memory heaps and memory types bits - you instead give it a simpler memory usage (e.g. CPU-TO-GPU or GPU-ONLY) and it figures out the correct type bits and heap to allocate from
  • Handle "dedicated" allocations (VK_KHR_dedicated_allocation)
  • Provide simpler map/unmap utilities
  • etc.

Of course you can use more advanced features as well - it supports custom memory pools, different allocation strategies, hinting or requiring specific type bits, etc. If nothing else the library documentation is worth a read to understand many of the concepts and problems related to memory allocation in Vulkan.

All that being said, your specific use case determines how you'll want to allocate. Using lots of different buffers instead of compacting into 1 may be simpler, but likely has some performance overheads when actually drawing if you have to change buffers more often.

Agreed, for a “level” based game/engine it's probably better to use a simple linear allocator, then just overwrite everything at the next level loading screen. If fragmentation becomes an issue, i may use different buffers with different "allocators" for persistent content, e.g. fonts, for long-lived content, e.g. "levels", and for short-lived transient stuff like effects.

Yes you'll likely end up with a toolbox of allocation strategies for different systems. You'll find that many classic CPU allocation strategies work great here as well: per-frame stack allocators for uniform buffers, buddy allocators and the like for transient allocations, one-off allocations for persistent content, stack sub-allocation for “levels” or “packs”, one-off pool or chunk allocators for specific systems like debug-draw vertices, etc. etc.

This topic is closed to new replies.

Advertisement