Vulkan 学习(13)---- Vulkan Framebuffercommand buffer
目录
- Vulkan Framebuffer
- 创建 VkFramebuffer
- VkFrameBuffer 创建示例
- Vulkan command buffer
- command buffer pool
- 分配指令缓存池
- 释放指令缓存池
- 录制 command buffer
- 提交 command buffer
Vulkan Framebuffer
Vulkan
帧缓冲区(FrameBuffer
) 是一个容器对象(资源管理类的对象),包含了一组图像视图(Image View),用于在渲染通道中作为附件进行渲染
每个帧缓冲区的附件和渲染通道的附件描述相对应,它们共同定义了渲染的目标,
帧缓存(VkFramebuffer
)在 Vulkan
中的概念类似于 OpenGL
中的帧缓存对象 FBO
每个帧缓存区包含一组图像视图,这些图像视图表示实际的图像资源,可以是颜色附件,深度附件或者模板附件。帧缓冲区用于将渲染输出写入到这些图像中(就是用于存储渲染通道(renderpass
输出的图像))
在 Vulkan
中,每个帧缓冲区至少需要一个颜色附件(color attachement
),这个颜色附件可以是和 Swapchain
关联的 ImageView
创建 VkFramebuffer
帧缓冲区的创建过程通常涉及下面的步骤:
-
确定帧缓冲区需要兼容的
Renderpass
,因为RenderPass
定义了渲染操作的结构和执行顺序,以及需要的附件的数量、类型和格式 -
为每个
SwapChain
图像创建对应的ImageView
,因为渲染的图像最终需要显示在屏幕上,Swapchain
的ImageView
就是用来呈现到屏幕上的 -
使用
VkFrameBufferCreateInfo
结构体创建Framebuffer
,指定渲染通道,附件个数附件信息、宽度、高度和图层信息
用于创建帧缓冲区的结构体 VkFramebufferCreateInfo
定义如下:
typedef struct VkFramebufferCreateInfo {
// 必须是 VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
VkStructureType sType;
const void* pNext;
VkFramebufferCreateFlags flags;// 帧缓冲区创建标志,目前保留,必须为0
VkRenderPass renderPass;// 和帧缓冲区相关联的渲染通道
uint32_t attachmentCount;// 帧缓冲区的附件数量
const VkImageView* pAttachments;// 指向 VkImageView 的指针数组,每个指针指向一个附件
uint32_t width;// 帧缓冲区宽度
uint32_t height;// 帧缓冲区高度
uint32_t layers;// 帧缓冲区的层数(通常为 1)
} VkFramebufferCreateInfo;
VkFrameBuffer 创建示例
// 创建swapChainImageViews 作为颜色附件
swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = swapChainImageFormat;
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create image views!");
}
}
swapChainFramebuffers.resize(swapChainImageViews.size());
// 创建帧缓冲区
for (size_t i = 0; i < swapChainImageViews.size(); i++) {
VkImageView attachments[] = {
swapChainImageViews[i]
};
VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
// 前面创建好的渲染通道
framebufferInfo.renderPass = renderPass;
// 附件数量
framebufferInfo.attachmentCount = 1;
// 附件内容
framebufferInfo.pAttachments = attachments;
// 缓冲区宽度
framebufferInfo.width = swapChainExtent.width;
// 缓冲区高度
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;
if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to create framebuffer!");
}
}
Vulkan command buffer
Vulkan
中,指令缓存(command buffer
)是用于记录和存储一系列绘制和计算指令的对象
这些指令将在 GPU
上执行,可以执行不同类型的工作,包括绑定顶点缓存、绑定流水线、录制渲染通道指令、设置视口和裁切矩形,设置绘制指令,执行图像和缓存内容的复制操作等
指令缓存提交到硬件队列的过程和它们被执行的过程是异步进行的
指令缓存的类型主要有两种:主指令缓存和次指令缓存
主指令缓存(primary command buffer
):包括次指令缓存,负责执行它们,可以提交给队列执行
次指令缓存(secondary command buffer
):不能直接提交给队列,必须嵌套在主指令缓存中执行,可以用于记录可重用的指令或者多线程录制指令
command buffer pool
一个应用程序的指令缓存数量是非常多的,Vulkan API
设计师为了最大的提高性能,指令缓存的分配通过 VkCommandPool
完成,从而降低多个指令缓存之间的资源创建带来的性能消耗
指令缓存可以是一直存在的,只需创建一个,就可以一直反复使用
分配指令缓存池
首先创建一个command buffer pool
,用于分配指令缓存
VkCommandPool
的创建:
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
// 也可以指定为 0
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
// 指定 queueFamilyIndex Graphic using graphic Compute using compute
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}
- 分配指令缓存
使用VkCommandBufferAllocateInfo
用于分配指令缓存
typedef struct VkCommandBufferAllocateInfo {
// 结构体类型,必须为 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
VkStructureType sType;
// 可选的扩展指针,通常为 nullptr
const void* pNext;
// 用于分配命令的 command pool
VkCommandPool commandPool;
// 命令缓冲区的级别,主级别或者次级别
VkCommandBufferLevel level;
// 要分配的命令缓冲区的数量,一个或者多个
uint32_t commandBufferCount;
} VkCommandBufferAllocateInfo;
其中 level 属性可以选择的下面的值:
VK_COMMAND_BUFFER_LEVEL_PRIMARY
: 主命令缓冲区,可以直接提交到对垒并执行VK_COMMAND_BUFFER_LEVEL_SECONDARY
:次级命令缓存区,不能直接提交,需要在主命令缓存区内调用
分配 command buffer
的参考代码:
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
释放指令缓存池
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
vkDestroyCommandPool(device, commandPool, nullptr);
录制 command buffer
指令缓存是用来记录指令的,通过函数 vkBeginCommandBuffer()
和 vkEndCommandBuffer()
完成,这两个函数定义个范围,这个范围内的所有 vulkan 指令都会被记录下来
// 定义缓冲区的开始信息结构体
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = 0;
beginInfo.pInheritanceInfo = nullptr;
VkResult result = vkBeginCommandBuffer(commandBuffer, &beginInfo);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to begin recording command buffer!");
}
// 录制vulkan 指令
// 1. 设置视口(可选)
VkViewport viewport = {};
viewport.x = 0.0f; // viewport 左上角 x 坐标
viewport.y = 0.0f; // viewport 左上角 y 坐标
viewport.width = (float)swapChainExtent.width; // 视口宽度,通常是交换链的宽度
viewport.height = (float)swapChainExtent.height; //视口高度,通常是交换链的高度
viewport.minDepth = 0.0f; // 最小深度值
viewport.maxDepth = 1.0f; // 最大深度值
vkCmdSetViewport(commandBuffer, 0, 1, &viewport); // 录制设置 viewport 指令,提交到缓存区
// 2. 设置裁剪区域
VkRect2D scissor = {};
scissor.offset = {0, 0}; // 裁剪区域的偏移量,从左上角开始
scissor.extent = swapChainExtent; // 裁剪区域的大小,通常是交换链的尺寸
vkCmdSetScissor(commandBuffer, 0, 1, &scissor); // 录制设置裁剪区域的指令
// 3. 开始渲染通道(render pass)
VkRenderPassBeginInfo renderPassInfo = {}; // 初始化 VkRenderPassBeginInfo 结构体
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; // 指定结构体类型
renderPassInfo.renderPass = renderPass; // 指定关联的渲染通道
renderPassInfo.framebuffer = framebuffer; // 指定要渲染的帧缓冲区
renderPassInfo.renderArea.offset = {0, 0}; // 渲染的起点,通常是从左上角(0,0)开始
renderPassInfo.renderArea.extent = swapChainExtent; // 渲染区域大小,通常和交换链区域一致
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; // 定义清屏颜色为黑色
renderPassInfo.clearValueCount = 1; // 清屏的值数量,这里只设置一个清屏颜色
renderPassInfo.pClearValues = &clearColor;
// 开始录制渲染通道
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // 开始渲染通道的录制
// 4. 绑定图形管线
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); // 绑定图形管线到命令缓冲区
// 5. 绑定顶点缓冲区(可选)
VkBuffer vertexBuffers[] = {vertexBuffer}; // 顶点缓冲区数组,包含顶点数据的缓冲区
VkDeviceSize offsets[] = {0}; // 偏移量数组,表示从缓冲区的哪个位置开始读取数据
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); // 绑定顶点缓冲区,设置偏移量
// 6. 绘制图元
vkCmdDraw(commandBuffer, static_cast<uint32_t>(vertexCount), 1, 0, 0); // 录制绘制指令,指定要绘制的顶点数量
// 结束渲染通道
vkCmdEndRenderPass(commandBuffer); // 结束当前渲染通道
// 结束命令缓冲区的录制
result = vkEndCommandBuffer(commandBuffer); // 结束命令缓冲区的录制
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to record command buffer!");
}
提交 command buffer
使用 VkQueueSubmit
将指令缓存提交给队列执行:
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
// wait a semaphore and trigger an fence
VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}