Using Vulkan Dynamic Rendering Without VkRenderPass
Vulkan dynamic rendering lets you record draw commands without VkRenderPass or framebuffers, using VkRenderingInfo instead.
17 Oct 2025, 16:48 UTC

Start rendering without creating a render pass
Traditional Vulkan rendering requires a VkRenderPass and at least one VkFramebuffer before you can record draw commands. That approach is explicit, but it forces you to declare attachment layouts, load and store operations, and subpass dependencies up front, even when your render graph changes every frame. A render pass describes the structure of a rendering operation; a framebuffer binds the actual images to that structure.
Dynamic rendering, core in Vulkan 1.3 and available through the VK_KHR_dynamic_rendering extension on Vulkan 1.1 and 1.2, lets you begin and end rendering directly in a command buffer using VkRenderingInfo. You still describe attachments and formats, but you do not create a render pass object or a matching framebuffer. This is useful when pass structure is not known ahead of time or when you want fewer pipeline variants tied to render pass compatibility.
Check device support before recording commands
Confirm support in your application before using dynamic rendering APIs. On Vulkan 1.3, dynamic rendering is core. On Vulkan 1.1 or 1.2, the device must advertise VK_KHR_dynamic_rendering in its extension properties, and you must enable the extension when creating the logical device.
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(physicalDevice, &props);
VkBool32 dynamicRenderingCore = props.apiVersion >= VK_API_VERSION_1_3;
If dynamicRenderingCore is false, enumerate device extensions and check for VK_KHR_dynamic_rendering. If neither is available, fall back to a static render pass.
Declare attachment formats at pipeline creation
Because there is no render pass to define attachment formats, the graphics pipeline must declare them itself. Add VkPipelineRenderingCreateInfo to the pNext chain of VkGraphicsPipelineCreateInfo. This structure lists the color, depth, and stencil formats the pipeline expects.
VkPipelineRenderingCreateInfo renderingInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &colorFormat,
.depthAttachmentFormat = depthFormat,
.stencilAttachmentFormat = depthFormat,
};
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &renderingInfo,
.renderPass = VK_NULL_HANDLE,
};
Set renderPass to VK_NULL_HANDLE when using dynamic rendering. The formats in VkPipelineRenderingCreateInfo must match the formats you later pass to VkRenderingInfo; a mismatch causes validation errors or pipeline creation failure.
Record begin and end rendering commands
Run these calls on the host while recording a command buffer allocated from a queue family that supports graphics operations. The logical device must have been created with dynamic rendering enabled. Placeholders such as device, physicalDevice, commandBuffer, colorImageView, width, and height refer to objects created earlier in your application.
Inside a command buffer, call vkCmdBeginRendering with a VkRenderingInfo structure before any draw commands, and vkCmdEndRendering after the last draw. Each attachment is described by VkRenderingAttachmentInfo, which identifies the image view, its current layout, and the load and store operations.
VkRenderingAttachmentInfo colorAttachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = colorImageView,
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue.clearColor = { { 0.0f, 0.0f, 0.0f, 1.0f } },
};
VkRenderingInfo renderingInfo = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = { { 0, 0 }, { width, height } },
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachment,
.pDepthAttachment = &depthAttachment,
.pStencilAttachment = NULL,
};
vkCmdBeginRendering(commandBuffer, &renderingInfo);
/* bind pipeline, descriptor sets, vertex buffers, draw */
vkCmdEndRendering(commandBuffer);
A risk is that calling begin with an attachment in the wrong layout causes undefined behavior, not just a validation error. loadOp controls whether attachment contents are cleared or loaded at the start; storeOp controls whether they are written to memory at the end. These settings replace the equivalent render pass subpass descriptions.
Transition attachment layouts before you begin
Dynamic rendering does not remove image layout transitions. Attachments must already be in a valid layout when vkCmdBeginRendering executes. For color attachments, that is usually VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; for depth-stencil, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. Use an image memory barrier before recording the begin command if the image was previously used for sampling, transfer, or presentation.
Limits compared to static render passes
Dynamic rendering removes render pass objects, but it does not replicate every render pass capability. Static render passes can declare subpass dependencies that let the GPU reorder work and optimize tile-based memory. Dynamic rendering has no subpass concept, so those dependencies and some tile-based optimizations are unavailable. Multisampled color attachments and resolve attachments require explicit VkRenderingAttachmentInfo entries with matching sample counts. You also cannot mix dynamic rendering and a static render pass inside the same render region; finish one and transition layouts before starting the other.
Common mistakes and how to check
- Omitting
VkPipelineRenderingCreateInfo. The pipeline has no attachment format information, so creation fails or validation reports a missing format. - Format mismatch. The format in
VkPipelineRenderingCreateInfomust equal the format in theVkRenderingAttachmentInfoyou pass tovkCmdBeginRendering. - Wrong layout. Calling begin while an attachment is in
VK_IMAGE_LAYOUT_UNDEFINEDor a shader-read layout produces undefined behavior or validation errors. - Wrong load or store operations. Forgetting to set
loadOpto clear can leave garbage in the attachment; forgettingstoreOpcan discard results.
Run with Vulkan validation layers enabled. If there are no VUID errors related to dynamic rendering, pipeline creation, or attachment layouts, the configuration is consistent. After submission, verify the framebuffer contents or depth output matches expectations; validation layers catch API misuse, but they cannot confirm that your clear values or draw calls produced the intended pixels.
Practical verification checklist
- Query
VkPhysicalDeviceProperties.apiVersionor device extension properties forVK_KHR_dynamic_rendering. - Create the graphics pipeline with
renderPass = VK_NULL_HANDLEandVkPipelineRenderingCreateInfoinpNext. - Transition all attachments to attachment-optimal layouts before recording
vkCmdBeginRendering. - Match attachment formats and sample counts between pipeline creation and
VkRenderingInfo. - Record
vkCmdBeginRendering, draw commands, thenvkCmdEndRendering. - Run with validation layers and confirm no dynamic-rendering VUID errors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.