Vulkan教程(15): Graphics pipeline之Render passes(渲染通道)

29 篇文章 2 订阅
10 篇文章 3 订阅

Vulkan官方英文原文: https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes

对应的Vulkan技术规格说明书版本: Vulkan 1.3.2

Setup

设置

Before we can finish creating the pipeline, we need to tell Vulkan about the framebuffer attachments that will be used while rendering. We need to specify how many color and depth buffers there will be, how many samples to use for each of them and how their contents should be handled throughout the rendering operations. All of this information is wrapped in a render pass object, for which we'll create a new createRenderPass function. Call this function from initVulkan before createGraphicsPipeline.

在我们完成管线创建之前,我们需要告诉Vulkan在渲染过程中将被使用的帧缓冲区附件的相关信息。我们需要指定将有多少个颜色和深度缓冲区(被应用),有多少(多重采样的)采样数用到这些缓冲区上以及在整个渲染操作中相关的内容如何处理。所有的这些信息都被 render pass 对象封装在内,为此,我们将创建一个 createRenderPass 函数。此函数在 initVulkan 函数中的 createGraphicsPipeline 函数之前调用。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
}

...

void createRenderPass() {

}

Attachment description

附件描述

In our case we'll have just a single color buffer attachment represented by one of the images from the swap chain.

在我们目前的例子中,我们只需要单个颜色缓冲区附件,它由swap chain中的一个图像表示。

void createRenderPass() {
    VkAttachmentDescription colorAttachment{};
    colorAttachment.format = swapChainImageFormat;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
}

The format of the color attachment should match the format of the swap chain images, and we're not doing anything with multisampling yet, so we'll stick to 1 sample.

颜色缓冲区附件的格式应该和对应的 swap chain中的图像匹配,我们也不需要多重采样,因此我们将采样数设置为1。

colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;

The loadOp and storeOp determine what to do with the data in the attachment before rendering and after rendering. We have the following choices for loadOp:

  • VK_ATTACHMENT_LOAD_OP_LOAD: Preserve the existing contents of the attachment

  • VK_ATTACHMENT_LOAD_OP_CLEAR: Clear the values to a constant at the start

  • VK_ATTACHMENT_LOAD_OP_DONT_CARE: Existing contents are undefined; we don't care about them

In our case, we're going to use the clear operation to clear the framebuffer to black before drawing a new frame. There are only two possibilities for the storeOp:

  • VK_ATTACHMENT_STORE_OP_STORE: Rendered contents will be stored in memory and can be read later

  • VK_ATTACHMENT_STORE_OP_DONT_CARE: Contents of the framebuffer will be undefined after the rendering operation

We're interested in seeing the rendered triangle on the screen, so we're going with the store operation here.

loadOp 和storeOp 两个变量决定在渲染操作之前和之后,相关附件中的数据如何处理。这个loadOp 取值选项如下:

  • VK_ATTACHMENT_LOAD_OP_LOAD: 保存已经存在于当前附件中的内容,不会对它做任何其他操作

  • VK_ATTACHMENT_LOAD_OP_CLEAR: 起始阶段以一个常量值清理附件中内容(以常量值覆盖附件中的内容)

  • VK_ATTACHMENT_LOAD_OP_DONT_CARE: 存在的内容未定义,忽略它们

在我们目前的例子中,在绘制新的一帧画面之前,我们将用清除操作去清除帧缓冲区,使之呈现为黑色。对 storeOp变量来讲有两个可用的值:

  • VK_ATTACHMENT_STORE_OP_STORE: 渲染出来的内容将被存在内存中,用于后续的读取操作

  • VK_ATTACHMENT_STORE_OP_DONT_CARE: 在(已经渲染到目标的)渲染操作之后帧缓冲区中的内容被设定为未定义

我们想把三角形渲染到屏幕上,因此我们在这里选用存储操作。

colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

The loadOp and storeOp apply to color and depth data, and stencilLoadOp / stencilStoreOp apply to stencil data. Our application won't do anything with the stencil buffer, so the results of loading and storing are irrelevant.

loadOp 和storeOp 这两个字段应用于颜色和深度数据(的相关操作),而 stencilLoadOp / stencilStoreOp 这两个字段则应用于模板数据(的相关操作)。我们的应用程序不需要用到模板缓冲区,因此模板的载入和存储在这里没啥影响。

colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

Textures and framebuffers in Vulkan are represented by VkImage objects with a certain pixel format, however the layout of the pixels in memory can change based on what you're trying to do with an image.

Some of the most common layouts are:

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: Images used as color attachment

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: Images to be presented in the swap chain

  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: Images to be used as destination for a memory copy operation

We'll discuss this topic in more depth in the texturing chapter, but what's important to know right now is that images need to be transitioned to specific layouts that are suitable for the operation that they're going to be involved in next.

Vulkan中的纹理和帧缓冲区都用某种像素格式的 VkImage 对象表示,但是内存中的像素布局可以基于你想用图像做什么去改变。

常用的布局如下所示:

  • VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: 图像作为颜色附件

  • VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 图像在交换链中用于呈现

  • VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 图像用于内存拷贝的目标

我们会在纹理章节更深入的讨论这主题,但现在重要的是,要知道图像需要转换到适合后续相关操作的指定布局。

The initialLayout specifies which layout the image will have before the render pass begins. The finalLayout specifies the layout to automatically transition to when the render pass finishes. Using VK_IMAGE_LAYOUT_UNDEFINED for initialLayout means that we don't care what previous layout the image was in. The caveat of this special value is that the contents of the image are not guaranteed to be preserved, but that doesn't matter since we're going to clear it anyway. We want the image to be ready for presentation using the swap chain after rendering, which is why we use VK_IMAGE_LAYOUT_PRESENT_SRC_KHR as finalLayout.

这个initialLayout 字段指定图像在渲染通道相关操作之前用哪种布局。finalLayout 字段指定的图像布局是渲染通道操作结束后自动转换到的图像布局。initialLayout赋值为VK_IMAGE_LAYOUT_UNDEFINED 表示我们不关心图像之前的布局。此特殊值的附加说明是图像的内容不会保证被保留,不过也没关系,因为我们总会清除掉它。我们希望图像渲染完毕后使用 swap chain 来显示,这就是我们为什么用VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 给finalLayout赋值的原因。

一些图像布局的细节请见:Image Layouts

Subpasses and attachment references

子通道和附件引用

A single render pass can consist of multiple subpasses. Subpasses are subsequent rendering operations that depend on the contents of framebuffers in previous passes, for example a sequence of post-processing effects that are applied one after another. If you group these rendering operations into one render pass, then Vulkan is able to reorder the operations and conserve memory bandwidth for possibly better performance. For our very first triangle, however, we'll stick to a single subpass.

一个单独的渲染通道有多个子通道组成。子通道是后续的渲染操作,它基于之前在渲染通道中的帧缓冲区内容,例如,一个接一个应用的一系列后处理效果。假如你把这些渲染操作组成一个渲染通道,那么Vulkan能重新排序这些操作,并且节省内存带宽以便性能更好。然而,对于我们第一个三角形,我们只需单个子通道。

Every subpass references one or more of the attachments that we've described using the structure in the previous sections. These references are themselves VkAttachmentReference structs that look like this:

每一个子通道引用一个或多个附件,这些附件我们使用前些章节中讲的结构体信息来描述。这些引用本身就是 VkAttachmentReference 结构体,如下所示:

VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

The attachment parameter specifies which attachment to reference by its index in the attachment descriptions array. Our array consists of a single VkAttachmentDescription, so its index is 0. The layout specifies which layout we would like the attachment to have during a subpass that uses this reference. Vulkan will automatically transition the attachment to this layout when the subpass is started. We intend to use the attachment to function as a color buffer and the VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL layout will give us the best performance, as its name implies.

attachment 参数指定用附件描述数组中的序号来引用哪个附件。 我们的数组由单个VkAttachmentDescription结构体对象组成,因此序号为0。这个layout 字段指定我们希望的附件布局, 子通道中使用此附件的引用。当子通道开始之际,Vulkan自动转换附件到这种布局。我们打算用此附件实现颜色缓冲区的功能,并且VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 布局性能最佳,就如其名字的含义。

The subpass is described using a VkSubpassDescription structure:

子通道用一个VkSubpassDescription 结构体描述:

VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;

Vulkan may also support compute subpasses in the future, so we have to be explicit about this being a graphics subpass. Next, we specify the reference to the color attachment:

Vulkan未来可能也会支持 compute subpasses 的功能,我们必须明确这是一个图形管线的子通道。接下来,我们指定颜色附件的引用:

subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;

The index of the attachment in this array is directly referenced from the fragment shader with the layout(location = 0) out vec4 outColor directive!

The following other types of attachments can be referenced by a subpass:

  • pInputAttachments: Attachments that are read from a shader

  • pResolveAttachments: Attachments used for multisampling color attachments

  • pDepthStencilAttachment: Attachment for depth and stencil data

  • pPreserveAttachments: Attachments that are not used by this subpass, but for which the data must be preserved

这个数组中附件的索引是被片段着色器直接用的,用 layout(location = 0) out vec4 outColor 的指令实现。

下面是能被子通道引用的其他附件类型:

  • pInputAttachments: 从shader中读取数据的附件

  • pResolveAttachments: 这类附件用于多重采用颜色附件

  • pDepthStencilAttachment: 深度和缓冲区数据附件

  • pPreserveAttachments: 这类附件不能被用于图形管线子通道,但是数据必须被保留

Render pass

渲染通道

Now that the attachment and a basic subpass referencing it have been described, we can create the render pass itself. Create a new class member variable to hold the VkRenderPass object right above the pipelineLayout variable:

现在已经介绍了附件和基本的子通道,我们能创建渲染通道本身了。在pipelineLayout 变量定义处之上创建一个新的类成员变量来持有 VkRenderPass 对象的引用:

VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;

The render pass object can then be created by filling in the VkRenderPassCreateInfo structure with an array of attachments and subpasses. The VkAttachmentReference objects reference attachments using the indices of this array.

然后通过用若干附件和子通道填充 VkRenderPassCreateInfo 结构体来创建此渲染通道对象。VkAttachmentReference 对象用数组索引来引用这些附件。

VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;

if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
    throw std::runtime_error("failed to create render pass!");
}

Just like the pipeline layout, the render pass will be referenced throughout the program, so it should only be cleaned up at the end:

就如图形管线布局一样,渲染通道对象也被程序全程引用,因此最后记得销毁:

void cleanup() {
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
    vkDestroyRenderPass(device, renderPass, nullptr);
    ...
}

That was a lot of work, but in the next chapter it all comes together to finally create the graphics pipeline object!

真是不少工作,不过要在下一个章所有的相关对象最终一起创建处图形管线对象!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值