Vulkan填坑學習Day12—渲染通道

Vulkan 渲染通道

Vulkan 渲染通道,在我們完成管線的創建工作之前,我們需要告訴Vulkan渲染時候使用的framebuffer幀緩衝區附件相關信息。我們需要指定多少個顏色和深度緩衝區將會被使用,指定多少個採樣器被用到及在整個渲染操作中相關的內容如何處理。所有的這些信息都被封裝在一個叫做 render pass 的對象中。

一、設置

我們新添加一個createRenderPass函數,在initVulkan函數中確保createGraphicsPipeline調用之前,調用它。

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

...

void createRenderPass() {

}

二、附件描述

在我們的例子中,我們將只有一個顏色緩衝區附件,它由交換鏈中的一個圖像所表示

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

format是顏色附件的格式,它應該與交換鏈中圖像的格式相匹配,同時我們不會做任何多重採樣的工作,所以採樣器設置爲1。

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

loadOp和storeOp決定了渲染前和渲染後數據在對應附件的操作行爲。對於 loadOp 我們有如下選項:

VK_ATTACHMENT_LOAD_OP_LOAD: 保存已經存在於當前附件的內容
VK_ATTACHMENT_LOAD_OP_CLEAR: 起始階段以一個常量清理附件內容

VK_ATTACHMENT_LOAD_OP_DONT_CARE: 存在的內容未定義,忽略它們

在繪製新的一幀內容之前,我們要做的是使用清理操作來清理幀緩衝區framebuffer爲黑色。同時對於 storeOp 僅有兩個選項:

VK_ATTACHMENT_STORE_OP_STORE: 渲染的內容會存儲在內存,並在之後進行讀取操作
VK_ATTACHMENT_STORE_OP_DONT_CARE: 幀緩衝區的內容在渲染操作完畢後設置爲undefined

我們要做的是渲染一個三角形在屏幕上,所以我們選擇存儲操作。

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

loadOp和storeOp應用在顏色和深度數據,同時stencilLoadOp / stencilStoreOp應用在模版數據。我們的應用程序不會做任何模版緩衝區的操作,所以它的loading和storing無關緊要。

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

紋理和幀緩衝區在Vulkan中通常用VkImage 對象配以某種像素格式來代表。但是像素在內存中的佈局可以基於預要對image圖像進行的操作發生內存佈局的變化。

一些常用的佈局:

VK_IMAGE_LAYOUT_COLOR_ATTACHMET_OPTIMAL: 圖像作爲顏色附件
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 圖像在交換鏈中被呈現
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 圖像作爲目標,用於內存COPY操作

我們會深入討論這些內容在紋理章節,現在最重要的是爲需要轉變的圖像指定合適的layout佈局進行操作。
在這裏插入圖片描述
initialLayout指定圖像在開始進入渲染通道render pass前將要使用的佈局結構。finalLayout指定當渲染通道結束自動變換時使用的佈局。使用VK_IMAGE_LAYOUT_UNDEFINED設置initialLayout,意爲不關心圖像之前的佈局。特殊值表明圖像的內容不確定會被保留,但是這並不總要,因爲無論如何我們都要清理它。我們希望圖像渲染完畢後使用交換鏈進行呈現,這就解釋了爲什麼finalLayout要設置爲VK_IMAGE_LAYOUT_PRESENT_SRC_KHR。

如果沒有搞清楚佈局存在的意義,進一步解釋layout請看如下圖示:
在這裏插入圖片描述
一般意義上,我們理解CPU進行內存中的數據讀寫往往都是線性排序的linear memory layout,可以看到AB與CD作爲來個連續的行來進行讀取。但是在很多時候對於像素紋理數據的操作是非線性連續的,這種情景更多發生在GPU操作中,所以GPU硬件更多的支持基於(Tiled)平鋪的或者成爲最佳的內存佈局結構,來提降低GPU處理數據的開銷。

所以從CPU linear layout 內存數據 到 GPU optimal layout 顯存數據的讀寫 往返之間存在數據存儲格式的優化轉變步驟。

三、子通道和附件引用

一個單獨的渲染通道可以由多個子通道組成。子通道是渲染操作的一個序列。子通道作用與後續的渲染操作,並依賴之前渲染通道輸出到幀緩衝區的內容。比如說後處理效果的序列通常每一步都依賴之前的操作。如果將這些渲染操作分組到一個渲染通道中,通過Vulkan將通道中的渲染操作進行重排序,可以節省內存從而獲得更好的性能。對於我們要繪製的三角形,我們只需要一個子通道。

每個子通道引用一個或者多個之前使用結構體描述的附件。這些引用本身就是VkAttachmentReference結構體:

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

attachment附件參數通過附件描述符集合中的索引來持有。我們的集合是由一個VkAttachmentDesription組成的,所以它的索引爲0。layout爲附件指定子通道在持有引用時候的layout。當子通道開始的時候Vulkan會自動轉變附件到這個layout。因爲我們期望附件起到顏色緩衝區的作用,layout設置爲VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL會給我們最好的性能。

子通道使用VkSubpassDescription結構體描述:

VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;

Vulkan在未來可能會支持關於compute subpasses的功能,所以在這裏我們明確指定graphics subpass圖形子通道。下一步爲它指定顏色附件的引用:

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

附件在數組中的索引直接從片段着色器引用,其layout(location = 0) out vec4 outColor 指令!

可以被子通道引用的附件類型如下:

pInputAttachments: 附件從着色器中讀取
pResolveAttachments: 附件用於顏色附件的多重採樣
pDepthStencilAttachment: 附件用於深度和模版數據
pPreserveAttachments: 附件不被子通道使用,但是數據被保存

四、渲染通道

現在附件和基本的子通道已經介紹過了,我們可以創建渲染通道了。首先新建一個類成員變量持有VkRenderPass對象,該變量在pipelineLayout上定義:

VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;

渲染通道對象創建通過填充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!");
}

就像pipeline layout一樣,渲染通道在整個程序生命週期內都被使用,所以需要在退出階段進行清理:

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

這看起來很多工作量,但是在下一章節我們會把所有的組件整合起來,創建最終的圖形管線對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章