Dynamic Uniform Buffer Offsets in Vulkan: Reduce Descriptor Set churn with Per‑Draw Offsets
Dynamic uniform buffer offsets let you bind one descriptor set and supply per‑draw offsets, cutting descriptor pool size and CPU binding overhead. Learn how to query alignment, set up the layout, and use the feature safely in Vulkan.
22 Jan 2026, 09:42 UTC

Why Descriptor Set Updates Are a Bottleneck
In Vulkan every draw call that uses a uniform buffer normally requires a descriptor set that points to that buffer. If you have dozens of objects each with its own constant data, you end up allocating a descriptor set per object. That inflates the descriptor pool, increases binding overhead, and can stall the driver when the GPU switches descriptor tables.
Dynamic Offsets: The One‑Set Solution
Vulkan’s VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC allows a single descriptor set to reference a large buffer that contains many constant blocks. At draw time you supply an offset that tells the GPU which part of the buffer to use. The descriptor set stays the same; only the offset changes.
Key requirements:
- Buffer must be created with
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT(or storage buffer equivalent). - Offset must be a multiple of
minUniformBufferOffsetAlignmentfromvkGetPhysicalDeviceProperties. - Offsets are passed via
vkCmdBindDescriptorSets’sdynamicOffsetsarray.
Worked Example – Per‑Object Constants in a Single Buffer
Assume we have 128 objects, each needs a 64‑byte uniform block. We allocate one 8 KiB buffer and bind it once:
# Create buffer
VkBufferCreateInfo bufInfo = {};
bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufInfo.size = 128 * 64; // 8 KiB
bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
// ... allocate & bind memory
# Descriptor set layout
VkDescriptorSetLayoutBinding ubBinding = {};
ubBinding.binding = 0;
ubBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
ubBinding.descriptorCount = 1;
ubBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
# Allocate one descriptor set
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = pool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &layout;
VkDescriptorSet set;
vkAllocateDescriptorSets(device, &allocInfo, &set);
# Write descriptor (no offset yet)
VkDescriptorBufferInfo bufInfo = {};
bufInfo.buffer = uniformBuffer;
bufInfo.offset = 0;
bufInfo.range = 64; // size of one block
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.dstSet = set;
write.dstBinding = 0;
write.dstArrayElement = 0;
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
write.descriptorCount = 1;
write.pBufferInfo = &bufInfo;
vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
# In the render loop
for (uint32_t i = 0; i < 128; ++i) {
uint32_t offset = i * 64; // must be aligned
uint32_t dynamicOffsets[] = { offset };
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &set, 1, dynamicOffsets);
vkCmdDraw(...);
}
Notice the descriptor set never changes; only the offset array changes per draw.
Trade‑offs and Caveats
- Alignment: If
offsetis not a multiple ofminUniformBufferOffsetAlignment, the command will fail validation and may crash the application. Always query this value for each device. - Descriptor Size: A descriptor that uses dynamic offsets counts as having a larger
descriptorCountwhen allocating pools, because each dynamic offset consumes a descriptor slot. On GPUs with tight descriptor cache budgets this can hurt performance. - Driver Support: While most modern GPUs honor dynamic offsets, very old hardware (e.g., early GTX 600/700 series) may ignore the offset or treat the buffer as a single block, leading to incorrect rendering.
- Profiling Needed: On multi‑GPU or heterogeneous target sets, benchmark both approaches (per‑set vs. dynamic offsets) to confirm that dynamic offsets actually reduce CPU overhead and do not introduce GPU stalls.
Actionable Takeaway
If you’re managing dozens of small uniform blocks and your target GPUs support it, replace per‑object descriptor sets with a single dynamic‑offset descriptor set. Follow these steps:
- Query
minUniformBufferOffsetAlignmentand design your buffer stride accordingly. - Create a descriptor set layout with
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC. - Allocate a single descriptor set and write the base buffer info.
- During rendering, compute the offset per object, pass it via
vkCmdBindDescriptorSets, and draw. - Run validation layers and performance counters to ensure no errors and that CPU overhead is reduced.
By reducing descriptor set churn, you free up descriptor pool space and lower CPU binding costs, leading to smoother frame rates in complex scenes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.