让我们从添加我们需要添加到类中的新成员开始。
vk_engine.h
class VulkanEngine {
public:
//--- other code ---
VkRenderPass _renderPass;
std::vector<VkFramebuffer> _framebuffers;
private:
//--- other code ---
void init_default_renderpass();
void init_framebuffers();
}
我们添加一个到我们将要创建的 RenderPass 的句柄,以及一个帧缓冲区数组。这个数组将等同于 _swapchainImages 和 _swapchainImageViews。
在 cpp 方面,我们将其添加到 init 函数中,在其他函数之后。 vk_engine.cpp
void VulkanEngine::init()
{
// SDL STUFF ----
init_vulkan();
init_swapchain();
init_commands();
init_default_renderpass();
init_framebuffers();
}
渲染通道
我们需要在帧缓冲区之前创建渲染通道,因为帧缓冲区是为特定的渲染通道创建的。
让我们开始填充 init_default_renderpass()
函数
void VulkanEngine::init_default_renderpass()
{
// the renderpass will use this color attachment.
VkAttachmentDescription color_attachment = {};
//the attachment will have the format needed by the swapchain
color_attachment.format = _swapchainImageFormat;
//1 sample, we won't be doing MSAA
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
// we Clear when this attachment is loaded
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
// we keep the attachment stored when the renderpass ends
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
//we don't care about stencil
color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
//we don't know or care about the starting layout of the attachment
color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
//after the renderpass ends, the image has to be on a layout ready for display
color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
}
我们开始编写的第一件事是颜色附件。这是我们将使用渲染命令写入的图像的描述。
图像将使用我们从交换链获得的格式(因此它是兼容的),我们将在渲染通道开始时清除它。当渲染通道结束时,我们将存储图像以便稍后可以读取它。
在渲染通道之前,图像布局将是未定义的,这意味着“我们不在乎”。在渲染通道结束后,布局将准备好通过交换链显示。
现在我们的主要图像目标已定义,我们需要添加一个子通道,它将渲染到其中。这在定义附件后立即进行
VkAttachmentReference color_attachment_ref = {};
//attachment number will index into the pAttachments array in the parent renderpass itself
color_attachment_ref.attachment = 0;
color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
//we are going to create 1 subpass, which is the minimum you can do
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_attachment_ref;
我们将描述 1 个子通道,它将使用上面的颜色附件。上面的颜色附件也将在布局 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
中在此子通道期间使用。子通道 vkAttachmentReference
将在创建渲染通道时指向附件数组。
图像生命周期将如下所示
未定义 -> 渲染通道开始 -> 子通道 0 开始(转换为附件最佳) -> 子通道 0 渲染 -> 子通道 0 结束 -> 渲染通道结束(转换为呈现源)
当使用渲染通道时,Vulkan 驱动程序将为我们执行布局转换。如果我们不使用渲染通道(从计算着色器绘制),我们将需要显式地执行相同的转换。
现在主要附件和子通道已完成,我们可以创建渲染通道
VkRenderPassCreateInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
//connect the color attachment to the info
render_pass_info.attachmentCount = 1;
render_pass_info.pAttachments = &color_attachment;
//connect the subpass to the info
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
VK_CHECK(vkCreateRenderPass(_device, &render_pass_info, nullptr, &_renderPass));
我们将只有一个附件,这将是上面定义的 color_attachment,用于我们的主要颜色目标。然后我们还将子通道连接到它。
就这样,我们现在创建了非常基本的渲染通道。当我们添加需要定义更多附件的深度测试时,我们将稍后回到这段代码。
帧缓冲区
一旦渲染通道被创建,我们就可以使用它来创建帧缓冲区。帧缓冲区是从给定的渲染通道创建的,它们充当渲染通道的附件和它们应该渲染到的真实图像之间的链接。
void VulkanEngine::init_framebuffers()
{
//create the framebuffers for the swapchain images. This will connect the render-pass to the images for rendering
VkFramebufferCreateInfo fb_info = {};
fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fb_info.pNext = nullptr;
fb_info.renderPass = _renderPass;
fb_info.attachmentCount = 1;
fb_info.width = _windowExtent.width;
fb_info.height = _windowExtent.height;
fb_info.layers = 1;
//grab how many images we have in the swapchain
const uint32_t swapchain_imagecount = _swapchainImages.size();
_framebuffers = std::vector<VkFramebuffer>(swapchain_imagecount);
//create framebuffers for each of the swapchain image views
for (int i = 0; i < swapchain_imagecount; i++) {
fb_info.pAttachments = &_swapchainImageViews[i];
VK_CHECK(vkCreateFramebuffer(_device, &fb_info, nullptr, &_framebuffers[i]));
}
}
我们用通用参数填充 VkFrameBufferCreateInfo,然后我们为交换链的每个图像创建一个帧缓冲区。
渲染时,交换链将为我们提供要渲染到的图像的索引,因此我们将使用相同索引的帧缓冲区。
完成渲染通道设置后,我们可以继续进行渲染循环本身。
清理
像往常一样,我们将新创建的对象添加到 cleanup()
函数中。由于帧缓冲区是从图像创建的,因此它们需要与图像一起删除。我们还需要删除渲染通道。
在 VulkanEngine::Cleanup()
中
vkDestroySwapchainKHR(_device, _swapchain, nullptr);
//destroy the main renderpass
vkDestroyRenderPass(_device, _renderPass, nullptr);
//destroy swapchain resources
for (int i = 0; i < _framebuffers.size(); i++) {
vkDestroyFramebuffer(_device, _framebuffers[i], nullptr);
vkDestroyImageView(_device, _swapchainImageViews[i], nullptr);
}
下一步: 主循环