Fuck RenderPass and fuck Frambuffer, meet the new kid in town, dynamic rendering :).
WHY?
The old RenderPass and Framebuffer combination were painful. Only the usage of subpasses made it relevant but most engines that I came across had ditched subpasses for the ease of code maintainability at the cost of slight performance. When using dynamic rendering (let’s call it dynamo), we specify the attachment details without creating a truck load of struct objects. The other advantage is a uniform image layout transitioning and sync mechanism, irrespective of their usage as attachment or shader input, an image barrier will do it for both.
HOW?
Let’s get into the usage. The setup phase includes adding the required extensions.
The required vulkan instance extension: VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME. The api version of 1.3 is required to enable this feature or else some additional supporting extensions become mandatory.
std::vector<const char*> instanceExtensions{ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME};
vk::ApplicationInfo appInfo{};
appInfo.setApiVersion(VK_API_VERSION_1_3);
The corresponding device extension: VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME
std::vector<const char*> EnabledVkDeviceExtensions = {
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME
};
The extension pNext chain will allow us to add the dynamo feature.
VkPhysicalDeviceDynamicRenderingFeatures dynamic_rendering_feature{};
dynamic_rendering_feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
dynamic_rendering_feature.dynamicRendering = VK_TRUE;
auto deviceCreateInfo = vk::DeviceCreateInfo{}
.setQueueCreateInfoCount(1u)
.setPQueueCreateInfos(&queueCreateInfo)
.setEnabledExtensionCount(static_cast<uint32_t>(EnabledVkDeviceExtensions.size()))
.setPNext(&dynamic_rendering_feature)
.setPpEnabledExtensionNames(EnabledVkDeviceExtensions.data());
auto device = physicalDevice.createDevice(deviceCreateInfo);
The usage details:
The vk::RenderingAttachmentInfo struct allows you to describe the color and depth attachments required in rendering.
std::array<vk::ClearValue, 2> clearValues{
vk::ClearValue{ vk::ClearColorValue{ std::array<float, 4>{0.7f, 0.2f, 0.5f, 1.0f} } },
vk::ClearValue{ vk::ClearDepthStencilValue{ 1.0f, 0u }}
};
vk::RenderingAttachmentInfo depthInfo{};
depthInfo.setClearValue(clearValues[1]);
depthInfo.setImageLayout( vk::ImageLayout::eDepthAttachmentOptimal);
depthInfo.setImageView(depthImageView);
depthInfo.setLoadOp(vk::AttachmentLoadOp::eClear);
depthInfo.setStoreOp(vk::AttachmentStoreOp::eDontCare);
std::array<vk::RenderingAttachmentInfo, CONCURRENT_FRAMES> colorInfoList;
for (uint32_t i = 0; i < CONCURRENT_FRAMES; i++)
{
colorInfoList[i].setClearValue(clearValues[0]);
colorInfoList[i].setImageLayout(vk::ImageLayout::eColorAttachmentOptimal);
colorInfoList[i].setImageView(colorImageViews[i]);
colorInfoList[i].setLoadOp(vk::AttachmentLoadOp::eLoad);
colorInfoList[i].setStoreOp(vk::AttachmentStoreOp::eStore);
}
The vk::RenderingInfo struct is where you accumulate the above attachment details.
std::vector<vk::RenderingInfo> renderInfoList;
for (uint32_t i = 0; i < CONCURRENT_FRAMES; i++)
{
vk::RenderingInfo info{};
info.setColorAttachmentCount(1);
info.setLayerCount(1);
info.setPColorAttachments(&colorInfoList[i]);
info.setPDepthAttachment(&depthInfo);
info.setRenderArea(VkRect2D{ {0, 0}, {(uint32_t)WIDTH, (uint32_t)HEIGHT} });
renderInfoList.push_back(std::move(info));
}
The vk::GraphicsPipelineCreateInfo needs some adjustment, as it used to depend on the old vk::RenderPass struct. The new vk::PipelineRenderingCreateInfo object, which mentions the image formats that might be used while rendering, needs to be added to the pNext.
vk::PipelineRenderingCreateInfo pipeline_rendering_create_info{};
pipeline_rendering_create_info.setColorAttachmentCount(1);
pipeline_rendering_create_info.setDepthAttachmentFormat( vk::Format::eD32Sfloat);
pipeline_rendering_create_info.setPColorAttachmentFormats(&selectedFormatAndColorSpace.format);
auto pipelineCreateInfo = vk::GraphicsPipelineCreateInfo{}
.setStageCount(2u).setPStages(shaderStages.data())
.setPVertexInputState(&vertexInputState)
.setPInputAssemblyState(&inputAssemblyState)
.setPViewportState(&viewportState)
.setPRasterizationState(&rasterizerState)
.setPMultisampleState(&multisampleState)
.setPDepthStencilState(&depthStencilState)
.setPColorBlendState(&colorBlendState)
.setLayout(pipelineLayout)
.setPNext(&pipeline_rendering_create_info);
Now make sure the images are transitioned into correct layout using barriers before starting with actual rendering. The rendering is initiated using vk::CommandBuffer::beginRendering instead of vk::CommandBuffe::beginRenderPass and the ending also follows a similar suit.
helpers::establish_pipeline_barrier_with_image_layout_transition(commandBuffer,
vk::PipelineStageFlagBits::eTransfer, /* src -> dst */ vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eTransferWrite, /* src -> dst */ vk::AccessFlagBits::eColorAttachmentRead,
swapchainImages[i], vk::ImageLayout::eTransferDstOptimal, /* old -> new */ vk::ImageLayout::eColorAttachmentOptimal);
commandBuffer.beginRendering(renderInfoList[i]);
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline.value);
commandBuffer.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics, pipelineLayout,
0u, 1u, &descriptorSets[i],
0u, nullptr
);
commandBuffer.bindVertexBuffers(0u, 2u, vertexBuffers.data(), vertexBufferOffsets.data());
commandBuffer.draw(numberOfVertices, 1u, 0u, 0u);
commandBuffer.endRendering();
helpers::establish_pipeline_barrier_with_image_layout_transition(commandBuffer,
vk::PipelineStageFlagBits::eColorAttachmentOutput, /* src -> dst */ vk::PipelineStageFlagBits::eBottomOfPipe,
vk::AccessFlagBits::eColorAttachmentWrite, /* src -> dst */ vk::AccessFlagBits{},
swapchainImages[i], vk::ImageLayout::eColorAttachmentOptimal, /* old -> new */ vk::ImageLayout::ePresentSrcKHR);
and Ta da :)