Creating a Render Pass#
The Renderpass object is the blueprint for Framebuffer objects.
Render passes are one of two ways of managing the resources used during rendering. These resources are known as attachments. A render pass contains information about the actual rendering process, such as the number and type of attachments, the rendering subpasses, and descriptions of dependencies between these subpasses.
Attachments such as colour, depth, and stencil buffers are what the application renders to. The render pass describes some of these through attachment descriptions, but it does not define the actual images that will be ultimately written to. This is handled by the framebuffers.
Render passes also contain a number of subpasses (always at least one), which are effectively batches of rendering. They represent a phase of rendering where a subset of the render passes’ attachments are read and written into. The rendering work is recorded into one or more of these subpasses. Multiple subpasses can be used to create multiple-stage rendering sequences where the data written into an attachment by one subpass can be read by the next subpass.
To create a render pass, the attributes of the required attachments and subpasses need to be described. This is done for attachments using the structs VkAttachmentReference and VkAttachmentDescription. The first struct describes the layout of the attachment and gives it an identifying index in the render pass. The second includes information about the attachment and how it is handled during rendering, such as image format, colour space, multi-sampling, and what to do with the contents of the attachment at the beginning or end of a subpass.
Note
The behaviour at the beginning of a render pass is determined by the loadOp
and behaviour at the end is determined by the storeOp
. These are critical for performance for PowerVR (and other tile-based architectures), as setting an operation to “don’t care” or “clear” allows PowerVR to skip writing the attachments to the main memory and only keep the results cached in the fast on-chip memory.
The descriptions of the required subpasses, created with a VkSubpassDescription struct, contain references to the required attachments and also the type of pipeline which will be performing the rendering operations.
To create the render pass object itself, a VkRenderPassCreateInfo struct is used. This references the information of the attachment and subpass descriptions that will be contained in the render pass. It also tracks any dependencies between the subpasses. Subpass dependencies describe the relationship between pairs of subpasses which operate on the same attachment. This ensures the subpasses read from and write to the attachment in the correct order. Explicitly defining these dependencies during render pass creation helps the application determine the ideal time to either flush, store, or clear memory.
The code example has a simple render pass – it only contains one colour attachment (the swapchain image) and one subpass. The contents of the colour attachment are cleared at the beginning of the subpass and stored so they can be displayed on the screen. Because there is only one subpass, no dependencies are needed. View in context.
void VulkanHelloAPI::initRenderPass()
{
// Create a description of the colour attachment that will be added to the render pass.
// This will tell the render pass what to do with the image (framebuffer) before, during, and after rendering.
// In this case the contents of the image will be cleared at the start of the subpass and stored at the
// end.
// Additionally, this description tells Vulkan that only one sample per pixel will be allowed for this image and the pixel layout will
// be transitioned to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR during the render pass. This layout is used
// when an image is going to be presented to a surface.
VkAttachmentDescription colorAttachmentDescription = {};
colorAttachmentDescription.format = appManager.surfaceFormat.format;
colorAttachmentDescription.flags = 0;
colorAttachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachmentDescription.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
colorAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
// Create a colour attachment reference.
// This tells the implementation that the first attachment at index 0 of this render pass will be a colour attachment.
VkAttachmentReference colorAttachmentReference = {};
colorAttachmentReference.attachment = 0;
colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// Declare and populate a struct which contains a description of the subpass.
// In this case the subpass only has a single colour attachment and will support a graphics pipeline.
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.flags = 0;
subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &colorAttachmentReference;
subpassDescription.pDepthStencilAttachment = nullptr;
subpassDescription.pInputAttachments = nullptr;
subpassDescription.inputAttachmentCount = 0;
subpassDescription.preserveAttachmentCount = 0;
subpassDescription.pPreserveAttachments = nullptr;
subpassDescription.pResolveAttachments = nullptr;
VkSubpassDependency subpassDependencies[2];
subpassDependencies[0] = {};
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
subpassDependencies[0].dstSubpass = 0;
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpassDependencies[0].srcAccessMask = 0;
subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
subpassDependencies[1] = {};
subpassDependencies[1].srcSubpass = 0;
subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
subpassDependencies[1].dstAccessMask = 0;
subpassDependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
// Populate a render pass creation info struct.
// Again, this simply references the single colour attachment and subpass.
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.attachmentCount = 1;
renderPassInfo.flags = 0;
renderPassInfo.pNext = nullptr;
renderPassInfo.subpassCount = 1;
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.pAttachments = &colorAttachmentDescription;
renderPassInfo.pSubpasses = &subpassDescription; // the subpass that was just created.
renderPassInfo.pDependencies = subpassDependencies;
renderPassInfo.dependencyCount = 2;
// Depth or stencil buffers are not needed since this application is simply rendering a
// triangle with no depth testing.
// Create the render pass object itself.
debugAssertFunctionResult(vk::CreateRenderPass(appManager.device, &renderPassInfo, nullptr, &appManager.renderPass), "Render pass Creation");
}