当前位置: 首页 > article >正文

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

帧缓冲区的创建过程通常涉及下面的步骤:

  1. 确定帧缓冲区需要兼容的 Renderpass,因为 RenderPass 定义了渲染操作的结构和执行顺序,以及需要的附件的数量、类型和格式

  2. 为每个 SwapChain 图像创建对应的 ImageView,因为渲染的图像最终需要显示在屏幕上,SwapchainImageView 就是用来呈现到屏幕上的

  3. 使用 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_pool

command buffer pool

一个应用程序的指令缓存数量是非常多的,Vulkan API 设计师为了最大的提高性能,指令缓存的分配通过 VkCommandPool 完成,从而降低多个指令缓存之间的资源创建带来的性能消耗

指令缓存可以是一直存在的,只需创建一个,就可以一直反复使用

分配指令缓存池

首先创建一个command buffer pool,用于分配指令缓存

  1. 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!");
    }
  1. 分配指令缓存
    使用 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 指令都会被记录下来
command_buffer

// 定义缓冲区的开始信息结构体
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!");
}

http://www.kler.cn/a/530722.html

相关文章:

  • 【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙
  • MiniQMT与xtquant:量化交易的利器
  • 手机连接WIFI可以上网,笔记本电脑连接WIFI却不能上网? 解决方法?
  • 无人机集群新年祝福表演技术原理详解
  • 2025最新源支付V7全套开源版+Mac云端+五合一云端
  • tf.Keras (tf-1.15)使用记录3-model.compile方法
  • 修复使用unplugin-auto-import和unplugin-vue-components后tsc-vue报错的问题
  • 寒假day10
  • 一文讲解Spring中应用的设计模式
  • Swoole如何进行错误处理
  • Linux下安装Redis详细教程
  • 6. 【Vue实战--孢子记账--Web 版开发】-- 主币种设置
  • 强化学习、深度学习、深度强化学习的区别是什么?
  • PySPARK带多组参数和标签的SparkSQL批量数据导出到S3的程序
  • Unity Shader Graph 2D - 跳动的火焰
  • 大语言模型深度研究功能:人类认知与创新的新范式
  • Pyside6异步通信测试
  • Java设计模式:行为型模式→状态模式
  • 【R语言】R语言安装包的相关操作
  • Linux第105步_基于SiI9022A芯片的RGB转HDMI实验
  • 体系自适应的物联网漏洞挖掘系统研究背景及意义:物联网漏洞挖掘概述之物联网漏洞现状及问题
  • Python网络自动化运维---批量登录设备
  • 自然语言生成(NLG)算法模型评估方案的硬件配置、系统架构设计、软件技术栈、实现流程和关键代码
  • 【router路由的配置】
  • FPGA学习篇——开篇之作
  • Linux系统之whereis命令的基本使用