When to Use Vulkan Push Constants vs Uniform Buffer Objects for Per‑Draw Data
A decision guide that compares push constants and UBOs, outlines constraints, shows a sample implementation, and explains how to validate the choice on your target hardware.
05 Dec 2025, 21:01 UTC

Decision and Constraints
Choose between Vulkan push constants and uniform buffer objects (UBOs) when you need to supply per‑draw data that changes frequently (e.g., model matrices, material parameters). The decision hinges on three constraints:
- Data size per draw – push constants are limited by
VkPhysicalDeviceLimits::maxPushConstantsSize(minimum 128 bytes, often more). - Update frequency – push constants can be changed mid‑command buffer without extra descriptor set binds or memory barriers.
- Shader layout compatibility – the push constant range declared in the pipeline layout must exactly match the
layout(push_constant)block in the shader.
If your data fits within the push constant budget and you update it every draw (or every few draws), push constants usually give lower CPU overhead. For larger blocks or data that is reused across many draws, UBOs remain the safer choice.
Comparison of Options
| Aspect | Push Constants | Uniform Buffer Objects |
|---|---|---|
| Maximum size per stage | maxPushConstantsSize (≥128 B, vendor‑specific) | Limited only by device memory and maxUniformBufferRange |
| Update cost | CPU‑side vkCmdPushConstants – no descriptor set bind, no memory barrier | Requires updating buffer (map/unmap or vkCmdUpdateBuffer) and a descriptor set bind |
| Access latency | Stored in fast registers or cache; lower latency than global memory | Fetched from device memory via descriptor; higher latency |
| Descriptor set pressure | None – data lives in command buffer | Consumes a descriptor set binding per pipeline layout |
| Typical use case | Per‑object transforms, small material tweaks, push‑constant‑only shaders | Large arrays, lighting buffers, shared material data, storage buffers |
Trade‑offs
- Performance: Push constants eliminate the descriptor set bind and associated CPU work, which can be significant when recording thousands of draw calls per frame. However, if the push constant size approaches the hardware limit, the compiler may spill to memory, eroding the advantage.
- Flexibility: UBOs support arbitrary sized arrays and can be shared across pipeline stages without redeclaring ranges. Push constants require a fixed layout known at pipeline creation.
- Validation safety: Exceeding
maxPushConstantsSizetriggers validation layer errors or driver crashes. UBOs are limited only by available memory, making them less prone to hard limits. - Power: On some mobile GPUs, push constants consume valuable register file space; excessive use can increase register pressure and reduce occupancy.
Concrete Implementation Example
The following C++ snippet shows how to record a draw call using push constants for a per‑object model matrix. Assume VkPipelineLayout pipelineLayout was created with a push constant range of sizeof(glm::mat4) bytes, stage flag VK_SHADER_STAGE_VERTEX_BIT, and offset 0.
// per‑frame uniform data (could be computed on CPU)
glm::mat4 modelMatrix = computeModelMatrix(objectId);
// Record command buffer
vkCmdBeginCommandBuffer(cmdBuf, &beginInfo);
vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
// Push the model matrix – no descriptor set bind needed
vkCmdPushConstants(cmdBuf,
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT,
0,
sizeof(modelMatrix),
&modelMatrix);
vkCmdDraw(cmdBuf, vertexCount, 1, 0, 0);
vkCmdEndCommandBuffer(cmdBuf);
In the vertex shader, declare the matching block:
layout(push_constant) uniform PushConstants {
mat4 model;
} pc;
void main() {
gl_Position = projection * view * pc.model * vec4(inPosition, 1.0);
}
Validation and Verification Steps
- Query limits – before creating the pipeline layout, call
vkGetPhysicalDevicePropertiesand readlimits.maxPushConstantsSize. Ensuresizeof(PushConstants)≤ this value. - Enable validation layers – run with
VK_LAYER_KHRONOS_validation. Any mismatch between the pipeline layout push constant range and the shader layout will be reported asVUID-vkCmdPushConstants-pushConstants-00355or similar. - Measure impact – record two command buffers: one using push constants as above, another using a UBO bound per draw. Use a CPU timer (e.g.,
std::chrono) or GPU timestamp queries to compare frame time over a representative workload (e.g., 2000 draws). Look for reduced CPU time in the push constant version. - Check for spilling – on desktop GPUs you can inspect shader assembly via tools like
RenderDocorNsight Graphicsto see if the push constant data is stored in registers or spilled to memory. If spilling occurs, consider reducing the push constant size or moving data to a UBO.
Limitations and Practical Checks
- Size limit – If your per‑draw data exceeds
maxPushConstantsSize, you must fall back to UBOs or split data across multiple push constant ranges (which increases bind cost). - Layout strictness – Any change to the shader’s push constant block requires recreating the pipeline layout; otherwise validation will fail.
- Register pressure – On tile‑based or mobile GPUs, large push constant usage can increase register usage and lower occupancy. Monitor GPU utilization counters if available.
- Practical verification – After integrating push constants, run the application with validation layers enabled and verify that no
VUID-vkCmdPushConstants-*warnings appear. Additionally, capture a frame with a GPU profiler and confirm that the push constant bytes appear in the “push constant” section of the draw call metadata.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.