Vulkan教程 - 11 帧缓冲和命令缓冲

2024-08-21 19:58
文章标签 命令 教程 缓冲 vulkan

本文主要是介绍Vulkan教程 - 11 帧缓冲和命令缓冲,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        帧缓冲我们前面的章节已经讨论很多了,而且我们已经建立了渲染通道,以便得到单个的帧缓冲,有着和交换链图像一样的格式,但是我们还没有真正创建什么东西呢。

        在渲染通道创建过程中指定的附件通过把它们包装成一个VkFramebuffer对象来绑定到一起。帧缓冲对象引用了所有表示附件的VkImageView对象。我们这里就一个附件,即颜色附件。但是,我们为了这个附件要用的图像依赖于当我们获取图像用于呈现的时候交换链返回的是哪个图像。也就是说我们要为交换链中所有的图像创建一个帧缓冲,然后使用一个和绘制时获取的图像对应的图像。

        创建一个std::vector类型的类成员,存储帧缓冲用:

std::vector<VkFramebuffer> swapChainFramebuffers;

        我们会在一个新的方法中为这个数组创建对象,这个方法是createFramebuffers,在initVulkan方法的创建图形管线之后调用。

        一开始要调整容器大小以容纳所有帧缓冲:

void createFramebuffers() {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!");}
}

        正如你看到的,创建帧缓冲是比较直白的。首先需要指定帧缓冲和哪个renderPass兼容。只能用兼容的,也就是说它们使用相同个数和类型的附件。

        attachmentCount和pAttachments参数指定在渲染通道pAttachment数组中要绑定到各自附件描述的VkImageView对象。

        width和height参数不用解释,layers指的是图像数组中的层的个数。我们这里交换链图像是单图像的,所以层个数就是1。我们应该在图像视图和渲染通道之前删除帧缓冲,但是要在完成渲染之后:

for (auto framebuffer : swapChainFramebuffers) {vkDestroyFramebuffer(device, framebuffer, nullptr);
}

        现在已经完成了渲染所需的各项要求,下一章我们将写一个真正的绘制命令。

        Vulkan中的命令,比如绘制操作和内存转移,并不是直接用方法调用来执行的。你必须把所有操作记录到命令缓冲对象中。这么做的优势是建立绘制命令这种困难的工作能够提前做好,且是多线程做的。这样,你就能告诉Vulkan来执行主循环中的命令了。

        在我们创建命令缓冲之前,必须要创建一个命令池。命令池管理用于存储缓冲的内存,命令缓冲也是从它们中分配的。添加一个新的类成员来存储VkCommandPool:

VkCommandPool commandPool;

        然后创建一个新的方法createCommandPool,然后从initVulkan中调用,调用时机是在创建帧缓冲之后。命令池创建只需要两个参数:

QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
poolInfo.flags = 0;

        命令缓冲通过提交到某个设备队列上执行,如我们获取到的图形和呈现队列。每个命令池只能分配单一类型队列中提交的命令缓冲。我们会记录命令来进行绘制,这也是为什么我们选择了图形队列族。

        命令池有两种可能的标记:

        VK_COMMAND_POOL_CREATE_TRANSIENT_BIT:表明命令缓冲经常用新的命令记录(可能改变内存分配行为);

        VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT:允许命令缓冲逐个记录,没有这个标记则它们会统一进行重置。

        我们仅仅在程序开始的时候记录命令缓冲,然后在主循环中把它们执行很多次,所以我们并不会用到上面的两种标记:

if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {throw std::runtime_error("failed to create command pool!");
}

        使用vkCreateCommandPool方法完成命令池创建。程序整个生命周期都会用到命令,因此它们要在结束的时候销毁,就放在cleanup的第一行:

vkDestroyCommandPool(device, commandPool, nullptr);

        现在我们可以分配内存缓冲了,另外记录它们的绘制命令。因为有一个绘制命令涉及到绑定正确的VkFramebuffer,我们要为交换链中的每一个图像再次记录一个命令缓冲。为此创建一个VkCommandBuffer列表,作为类成员。命令缓冲会在命令池销毁的时候自动释放,所以不用在cleanup方法中进行显式处理。

std::vector<VkCommandBuffer> commandBuffers;

        现在开始创建一个createCommandBuffers方法,它负责分配和记录每个交换链图像的命令:

void createCommandBuffers() {commandBuffers.resize(swapChainFramebuffers.size());
}

        该方法就在initVulkan的最后调用。

        命令缓冲分配用的是vkAllocateCommandBuffers方法,接收一个VkCommandBufferAllocateInfo结构体作为参数,指定命令池和要分配的缓冲个数:

VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {throw std::runtime_error("failed to allocate command buffers!");
}

        level参数表明分配的命令缓冲是是主命令缓冲,还是次要命令缓冲:

        VK_COMMAND_BUFFER_LEVEL_PRIMARY:可以提交到队列执行,但是不能从其他命令缓冲中调用;

        VK_COMMAND_BUFFER_LEVEL_SECONDARY:不能直接提交,但是可以从主命令缓冲中调用。

        我们不会用次要命令缓冲,但是你可以想象下,从主命令缓冲中重用通用的操作是很有用的。

        我们用vkBeginCommandBuffer开始记录命令缓冲,传一个小结构体VkCommandBufferBeginInfo作为其参数,指定一些命令缓冲使用的细节信息:

for (size_t i = 0; i < commandBuffers.size(); i++) {VkCommandBufferBeginInfo beginInfo = {};beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;beginInfo.pInheritanceInfo = nullptr;  // optionalif (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {throw std::runtime_error("failed to begin recording command buffer!");}
}

        flags标记参数表明了我们如何使用命令缓冲,有以下可选项:

        VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT:命令缓冲将一旦执行后就被记录;

        VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT:这是完全在一个渲染通道中的次命令缓冲;

         VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT:命令缓冲可以在挂起执行的情况下重新提交。

        我们用了最后一个标记,因为我们可能在最后一帧还没完成的时候已经为下一帧计划绘制命令了。pInheritanceInfo参数只和次命令缓冲有关。它指定了从调用的主命令缓冲的哪个状态继承。

        如果命令缓冲已经记录了一次,那么调用vkBeginCommandBuffer会隐式地重置它。后面就不可能将命令追加到缓冲中了。

        绘制就从用vkCmdBeginRenderPass开启渲染通道开始。渲染通道使用一些VkRenderPassBeginInfo结构体中的参数配置:

VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];

        第一个参数是渲染通道自身和要绑定的附件。我们为每个交换链图像创建一个帧缓冲,把它作为颜色附件。

renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = swapChainExtent;

        接下来的这两个参数定义了渲染区域大小,渲染区域定义了着色器加载和存储的地点,在此之外的像素的值将会是未定义的。它应该和附件的大小一致,以便取得最佳性能。

VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;

        最后的两个参数定义了VK_ATTACHMENT_LOAD_OP_CLEAR要用的清除值,我们用作颜色附件的加载操作。这里定义的清除颜色就是一个很简单的完全不透明的黑色。

vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

        现在渲染通道可以开始了,所有的记录命令的功能都有vkCmd前缀。它们都返回void,所以直到我们完成记录之前都不会有错误处理。

        每个命令的第一个参数一直都是要记录命令的命令缓冲。第二个参数明确了我们提供的渲染通道的细节信息。最终的参数控制渲染通道内的绘制命令如何提供。有以下两种值可选:

        VK_SUBPASS_CONTENTS_INLINE:渲染通道命令将会嵌入到主命令缓冲中,次命令缓冲不会执行;

        VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS:渲染通道命令将会从次命令缓冲执行。

        我们不用次命令缓冲,所以这里就用第一个选项。

        现在我们可以绑定图形管线了:

vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

        第二个参数说明了该管线对象是否是一个图形或者计算管线。我们现在告诉了Vulkan在图形管线中执行哪个操作以及在片段着色器中使用哪个附件,所以现在剩下的就是告诉它绘制三角形:

vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);

        实际的vkCmdDraw有些虎头蛇尾,但是它太简单了,因为所有的信息我们都提前说明了。除了命令缓冲外它还有以下参数:

        vertexCount:尽管我们没用顶点缓冲,但是严格来说还是有三个顶点要绘制的;

        instanceCount:用于实例渲染,如果没有这么做的话就设置为1;

        firstVertex:作为顶点缓冲的偏置,定义了gl_VertexIndex的最小值;

        firstInstance:作为实例渲染偏置,定义了gl_InstanceIndex的最小值。

        现在渲染通道可以结束了:

vkCmdEndRenderPass(commandBuffers[i]);

        现在已经完成了命令缓冲的记录:

if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {throw std::runtime_error("failed to record command buffer!");
}

        下一章我们会写一些代码,放在主循环中,获取交换链图像,执行正确的命令缓冲并返回完成的图像到交换链中。

这篇关于Vulkan教程 - 11 帧缓冲和命令缓冲的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094134

相关文章

2025版mysql8.0.41 winx64 手动安装详细教程

《2025版mysql8.0.41winx64手动安装详细教程》本文指导Windows系统下MySQL安装配置,包含解压、设置环境变量、my.ini配置、初始化密码获取、服务安装与手动启动等步骤,... 目录一、下载安装包二、配置环境变量三、安装配置四、启动 mysql 服务,修改密码一、下载安装包安装地

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

C语言进阶(预处理命令详解)

《C语言进阶(预处理命令详解)》文章讲解了宏定义规范、头文件包含方式及条件编译应用,强调带参宏需加括号避免计算错误,头文件应声明函数原型以便主函数调用,条件编译通过宏定义控制代码编译,适用于测试与模块... 目录1.宏定义1.1不带参宏1.2带参宏2.头文件的包含2.1头文件中的内容2.2工程结构3.条件编

电脑提示d3dx11_43.dll缺失怎么办? DLL文件丢失的多种修复教程

《电脑提示d3dx11_43.dll缺失怎么办?DLL文件丢失的多种修复教程》在使用电脑玩游戏或运行某些图形处理软件时,有时会遇到系统提示“d3dx11_43.dll缺失”的错误,下面我们就来分享超... 在计算机使用过程中,我们可能会遇到一些错误提示,其中之一就是缺失某个dll文件。其中,d3dx11_4

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.