设置
在我们创建管线前,我们需要告诉Vulkan我们在渲染时需要得帧缓冲附件。我们需要指定有多少颜色和深度缓冲区,每个缓冲区使用多少个采样,它们的内容在渲染时该被如何处理。这些内容被封装到一个渲染通道对象。我们创建一个函数createRenderPass
来创建它。然后在createGraphicsPipeline
后面调用:
void initVulkan() {
createInstance();
setupDebugMessenger();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
}
...
void createRenderPass() {
}
附件说明(Attachment description)
我们这里只使用一个颜色缓冲区附件,它用交换链中的一个图像表示。
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
:内容未定义,我们不关系它们
我们这里选择在渲染一帧前清除帧缓冲。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;
loadOp
和storeOp
应用于颜色和深度数据,而stencilLoadOp
和stencilStoreOp
应用于模板数据。我们这边不会用到模板,所以加载和存储的结果无关紧要。
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
纹理和帧缓冲在Vulkan中由VkImage
表示。图像对象需要一个确定的像素格式,但像素的布局取决于你怎么用。
常见的布局有:
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
:作为颜色缓冲的图像VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
:将在交换链中呈现的图像VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
:将作为内存复制操作的目标图像
之后在纹理章节,我们还会更深入的讨论这个内容,我们这里要知道图像不同的操作需要不同的布局。
initialLayout
指定渲染开始时图像的布局,finalLayout
指定渲染完成时自动变换到的图像布局。将initialLayout
设置为VK_IMAGE_LAYOUT_UNDEFINED
意思是我们不关心以前的布局。使用它的图像的内容不能保证被保留,但这不重要,我们总归要在开始绘制时清理图像。我们希望图像在渲染后使用交换链进行展示,这就是我们将finalLayout
设为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
的原因。
子通道和附件引用(Subpasses and attachment references)
一个渲染通道可以包含多个子通道。子通道是渲染操作的后续渲染操作,依赖前一个通道中帧缓冲区的内容,例如一个接一个的后处理效果序列。如果我将这些渲染操作合并到一个渲染通道中,Vulkan可以重排这些操作节省内存带宽并获得更好的性能。对于我们第一个三角形,我们使用一个子通道就行了。
每个子通道都引用了一个或多个附件。VkAttachmentReference
结构体:
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment
参数通过附件描述数组的下标指定要引用的附件。我们的数组只会包含一个VkAtachmentDescription
,所以这里下标用0
。layout
指定了我们希望附件在子通道中的布局。子通道启用时,Vulkan会自动将附件转换到这个布局。我们会将附件用作颜色缓冲区,VK_IMAGE_LAYOUT_COLOR_ATACHMENT_OPTIMAL
布局会提供最佳性能。
子通道通过VkSubpassDescription
描述:
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
Vulkan将来可能支持计算子通道,所以我们需要明确说明这是一个图形子通道,然后,设置对颜色附件的引用:
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
附件的序号被片元着色器通过layout(location=0)out vec4 outColor
直接引用。
其他类型的附件同样可以被子通道引用:
pInputAttachments
:被着色器读的附件pResolveAtachments
:用于多重采样颜色附件的附件pDepthStencilAtachment
:用于深度和模板数据的附件pPreserveAtachments
:不被附件使用但需要被保留的数据
渲染通道
现在,一个基本的子通道和其附件已经声明好了,我们可以创建渲染通道了。在pipelineLayout
上面创建一个新的类成员来保存VkRenderPass
对象:
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!");
}
同管线布局,渲染管线会在程序生命周期内使用,所以在最后销毁它:
void cleanup() {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr);
...
}
我们已经做了很多工作了,在下一章中,我们就将它们都汇集到一起,最终创建图形管线对象。
目录
上一节 绘制一个三角形/图形管线基础/固定功能(Fixed functions)
下一节 绘制一个三角形/图形管线基础/汇总(Conclusion)