3
$\begingroup$

I am new to Vulkan. Compared to DX12, the resource binding procedure seems a little bit complicated. In terms of per draw call uniform buffers, I currently have two approaches in mind, and they both seem to work (haven't tested/benchmarked yet). Can someone shine some light on which is the standard way to do this, with pros and cons. You are welcome to provide other ways but I am not considering push constants because I want a universal approach that I can apply to other kinds of uniform buffers as well (i.e. per pass, per frame, per scene and etc.).

approach A) One descriptor set and multiple buffers (one buffer for each object). Bind the descriptor set to pipeline only once before rendering. Bind buffers to the only descriptor set between draw calls using vkCmdPushDescriptorSetKHR.

approach B) Multiple descriptor sets (one descriptor set for each object). (Multiple or single buffers, it doesn't really matter. If single buffer is used, I think it's called a dynamic uniform buffer and all that I need to do is to specify the offset when binding the descriptor set.) Bind buffer/buffers to descriptor sets only once before rendering. Bind the matching descriptor set to the pipeline between draw calls using vkCmdBindDescriptorSets.

I am used to DX12 so approach A seems more natural because in my understanding vkCmdPushDescriptorSetKHR in Vulkan is just like SetGraphicsRootConstantBufferView in DX12, because they both bind buffer to pipeline and this operation is buffered with other commands. But the Vulkan one do feels a little bit slower mainly because 1) it is not a root descriptor, 2) Vulkan does not use GPU address and also 3) a descriptor set write operation is needed.

$\endgroup$
  • $\begingroup$ "Compared to DX12, the resource binding procedure seems a little bit complicated." To be honest, D3D12's seems more complicated. Vulkan is just "descriptors" and "sets of descriptors"; it's all very simple and regular. I still haven't found out what a "root descriptor" is, or how that relates to non-"root" descriptors. $\endgroup$ – Nicol Bolas Jun 12 at 13:40
  • $\begingroup$ "I am not considering push constants because I want a universal approach that I can apply to other kinds of uniform buffers as well (i.e. per pass, per frame, per scene and etc.)." And why can't push constants be used for those too? Also, if efficiency matters so much, you should use the tool that gives you the best performance for the task at hand. Not the one that is the most "universal". $\endgroup$ – Nicol Bolas Jun 12 at 13:41
3
$\begingroup$

What you're ultimately trying to do is, between draw calls, change some state that allows one to select which set of read-only data to use in the rendering process. There are many techniques for this.

Push constants can be used to provide an index into an array of arbitrary size stored in an SSBO. Indeed, a single push constant can be used to provide multiple indices into multiple SSBOs, for different frequencies of state (per-frame vs. per-object). The upper 16-bits can be for one frequency, with the lower 16-bits for another.

That may sound slower than other methods, since you're accessing global data rather than an actual UBO. But on some hardware, it won't change anything. AMD hardware for example doesn't actually have any specialized hardware for UBOs, so they implement UBOs as just read-only SSBOs. Even on hardware with dedicated UBOs, the state data you're reading will likely be quickly cached, since all invocations will be reading the same values from the array. So unless your thrashing the SSBO caches, this shouldn't impact performance much.

Dynamic UBO descriptors also exist, given the VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC type. Basically, the idea is that, when you call vkCmdBindDescriptorSets, you provide a dynamic offset for any dynamic descriptors. The offset represents a byte offset from the buffer attached to that descriptor to where the UBO data you want to fetch lives. So you would have all of your data in a large buffer, and you would select which particular data you're using for a particular render call by invoking vkCmdBindDescriptorSets with a different dynamic offset.

Now vkCmdBindDescriptorSets may seem like a more heavyweight call than setting push constant. But the implementation has all the information needed to be able to see that you are using the same descriptor as you used in the previous call in that command buffer, and thus not do the actual heavy part of that operation, simply applying the dynamic offsets to any dynamic descriptors. So that part should not be a concern.

Push descriptors are mainly intended for texture/sampler/image bindings, which don't have a convenient mechanism like dynamic offsets that would allow you to quickly switch which image/sampler you're using. That being said, push descriptors are a useful concept even for UBO/SSBOs. Whether they are faster than dynamic descriptors is a matter of profiling.

The principle downside is the obvious: push descriptors is an extension. Dynamic UBO offsets and push constants are mandatory, required by every Vulkan 1.0 implementation. Push descriptors aren't even optional features of Vulkan. They're important enough to get a KHR extension, but they didn't go into Vulkan 1.1 as an optional feature. This is not a theoretical issue: NVIDIA and Intel seem to support it on desktop, but no official AMD drivers do.

So I would go with dynamic UBOs; this is exactly what they were made for, after all.

$\endgroup$
  • $\begingroup$ This is exactly what I need. Thanks so much! $\endgroup$ – ACskyline Jun 13 at 3:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.