Vulkan学习笔记6—渲染呈现

一、渲染循环核心

    while (!glfwWindowShouldClose(window)) {glfwPollEvents();helloTriangleApp.drawFrame(); // 绘制帧}

在 Vulkan 中渲染帧包含一组常见的步骤

  • 等待前一帧完成(vkWaitForFences)

  • 从交换链获取图像(vkAcquireNextImageKHR)

  • 录制一个命令缓冲区将场景绘制到图像上(vkBeginCommandBuffer、vkEndCommandBuffer)

  • 提交已记录的命令缓冲区(vkQueueSubmit)

  • 呈现交换链图像(vkQueuePresentKHR)

这就是一个drawFrame函数要做的主要工作。

二、同步

GPU执行需显式同步。

例如下面这些事件:

  • 从交换链获取图像(vkAcquireNextImageKHR)

  • 执行在获取的图像上绘制的命令

  • 将该图像呈现到屏幕上进行呈现,将其返回到交换链(vkQueuePresentKHR)

信号量

用于控制GPU上的同步操作。

VkCommandBuffer A, B = ... // 录制命令缓冲
VkSemaphore S = ... // 创建一个信号// 当操作 A 完成时,将发出信号量 S 的信号,而操作 B 将不会启动,直到 S 发出信号
vkQueueSubmit(work: A, signal: S, wait: None)// 在操作 B 开始执行后,信号量 S 将自动重置回未发出信号的状态,从而允许再次使用它。
vkQueueSubmit(work: B, signal: None, wait: S)

栅栏

它是用于对 CPU(也称为主机)上的执行进行排序的。如果主机需要知道 GPU 何时完成某件事,我们会使用栅栏。

例如:截屏操作

VkCommandBuffer A = ... // 记录包含传输操作的命令缓冲区
VkFence F = ... // 创建围栏对象// 将命令缓冲区A提交到队列,立即开始执行,并在完成时发出围栏F的信号
vkQueueSubmit(work: A, fence: F)vkWaitForFence(F) // 阻塞当前执行线程,直到命令缓冲区A完成执行save_screenshot_to_disk() // 必须等待传输操作完成后才能执行

三、绘制过程

创建同步对象

void HelloTriangle::createSyncObjects() {// 为每一帧预分配同步对象的存储imageAvailableSemaphores.resize(MAX_CONCURRENT_FRAMES);renderFinishedSemaphores.resize(MAX_CONCURRENT_FRAMES);inFlightFences.resize(MAX_CONCURRENT_FRAMES);// 设置信号量创建信息结构体VkSemaphoreCreateInfo semaphoreInfo{};semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;// 设置围栏创建信息结构体,初始化为已信号化状态VkFenceCreateInfo fenceInfo{};fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;  // 初始状态设为已信号化,允许第一帧立即执行// 为每一个并发帧创建一组同步对象for (size_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) {if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS|| vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS|| vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {throw std::runtime_error("Failed to create synchronization objects for a frame!");}}
}

录制命令

void HelloTriangle::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {// 1. 开始记录命令缓冲区VkCommandBufferBeginInfo beginInfo{};beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {throw std::runtime_error("Failed to begin recording command buffer!");}// 2. 设置渲染通道信息VkRenderPassBeginInfo renderPassInfo{};renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;renderPassInfo.renderPass = renderPass;  // 指定使用的渲染通道renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];  // 指定目标帧缓冲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;// 3. 开始渲染通道,使用内联子通道内容vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);// 4. 绑定图形管线vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);// 5. 设置视口 (Viewport) - 定义裁剪空间坐标到帧缓冲坐标的映射VkViewport viewport{};viewport.x = 0.0f;viewport.y = 0.0f;viewport.width = static_cast<float>(swapChainExtent.width);viewport.height = static_cast<float>(swapChainExtent.height);viewport.minDepth = 0.0f;viewport.maxDepth = 1.0f;vkCmdSetViewport(commandBuffer, 0, 1, &viewport);// 6. 设置剪刀区域 (Scissor) - 定义实际渲染的区域VkRect2D scissor{};scissor.offset = {0, 0};scissor.extent = swapChainExtent;vkCmdSetScissor(commandBuffer, 0, 1, &scissor);// 7. 执行绘制命令 - 绘制3个顶点,组成一个三角形vkCmdDraw(commandBuffer, 3, 1, 0, 0);// 8. 结束渲染通道vkCmdEndRenderPass(commandBuffer);// 9. 结束命令缓冲区记录if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {throw std::runtime_error("Failed to record command buffer!");}
}

绘制帧

void HelloTriangle::drawFrame() {// 1. 等待当前帧的围栏被信号化,确保上一帧的渲染操作已完成vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);// 重置围栏状态,为下一帧做准备vkResetFences(device, 1, &inFlightFences[currentFrame]);// 2. 从交换链获取下一可用图像,使用当前帧的"图像可用"信号量uint32_t imageIndex;vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);// 3. 重置并记录当前帧的命令缓冲区vkResetCommandBuffer(commandBuffers[currentFrame],  0);recordCommandBuffer(commandBuffers[currentFrame], imageIndex);// 4. 设置提交信息,定义命令执行的依赖关系VkSubmitInfo submitInfo{};submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;// 等待"图像可用"信号量,确保在图像可用后再开始渲染VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};submitInfo.waitSemaphoreCount = 1;submitInfo.pWaitSemaphores = &imageAvailableSemaphores[currentFrame];submitInfo.pWaitDstStageMask = waitStages;// 指定要执行的命令缓冲区submitInfo.commandBufferCount = 1;submitInfo.pCommandBuffers = &commandBuffers[currentFrame];// 当命令执行完成时,信号化"渲染完成"信号量submitInfo.signalSemaphoreCount = 1;submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame];// 提交命令到图形队列,并关联围栏以跟踪完成状态if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {throw std::runtime_error("Failed to submit draw command buffer!");}// 5. 设置呈现信息,准备将渲染结果呈现到屏幕VkPresentInfoKHR presentInfo{};presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;// 等待"渲染完成"信号量,确保渲染完成后再呈现presentInfo.waitSemaphoreCount = 1;presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame];// 指定要呈现的交换链和图像索引presentInfo.swapchainCount = 1;presentInfo.pSwapchains = &swapChain;presentInfo.pImageIndices = &imageIndex;// 提交呈现请求到呈现队列vkQueuePresentKHR(presentQueue, &presentInfo);// 6. 更新当前帧索引,循环使用预分配的同步对象currentFrame = (currentFrame + 1) % MAX_CONCURRENT_FRAMES;
}

完整的初始化过程关系图

呈现效果:

四、新增成员变量和成员函数

新增/更新的成员变量

// 全局变量
const int MAX_CONCURRENT_FRAMES = 3; // 定义绘制的最大并行帧数// 类成员变量更新
std::vector<VkCommandBuffer> commandBuffers;  // 为每一帧创建一个命令缓冲
std::vector<VkSemaphore> imageAvailableSemaphores; // 为每一帧创建一个图片信号量
std::vector<VkSemaphore> renderFinishedSemaphores;  // 为每一帧创建渲染器完成信号量
std::vector<VkFence> inFlightFences;  // 为每一帧创建一个栅栏

新增成员函数

    void createCommandBuffers();void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex);void createSyncObjects();void cleanupSwapChain()void drawFrame();

上一节和本节代码保存为分支 04_render_present

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83631.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React第六十二节 Router中 createStaticRouter 的使用详解

前言 createStaticRouter 是 React Router 专为 服务端渲染&#xff08;SSR&#xff09; 设计的 API&#xff0c;用于在服务器端处理路由匹配和数据加载。它在构建静态 HTML 响应时替代了客户端的 BrowserRouter&#xff0c;确保 SSR 和客户端 Hydration 的路由状态一致。 一…

qt 双缓冲案例对比

双缓冲 1.双缓冲原理 单缓冲&#xff1a;在paintEvent中直接绘制到屏幕&#xff0c;绘制过程被用户看到 双缓冲&#xff1a;先在redrawBuffer绘制到缓冲区&#xff0c;然后一次性显示完整结果 代码结构 单缓冲&#xff1a;所有绘制逻辑在paintEvent中 双缓冲&#xff1a;绘制…

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

ParaGraphX [特殊字符]

https://github.com/stevechampion1/paragraphx 一个基于 JAX 的、为 CPU/GPU 加速而生的超高性能图算法库。 ParaGraphX 是一个实验性的 Python 库&#xff0c;旨在利用 JAX 的即时编译 (JIT) 和大规模并行计算能力&#xff0c;为经典的图算法提供惊人的性能提升。我们的目标…

如何用4 种可靠的方法更换 iPhone(2025 年指南)

Apple 每年都会发布新版本的 iPhone。升级到新 iPhone 是一种令人兴奋的体验&#xff0c;但转移所有宝贵数据的想法有时会让人感到畏惧。幸运的是&#xff0c;我们准备了 4 种有效的更换 iPhone 的方法&#xff0c;让你可以毫不费力地更换到你的新 iPhone。 此外&#xff0c;您…

GitLab 拉取变慢的原因及排查方法

前言&#xff1a;在软件开发的快节奏世界里&#xff0c;高效协作与快速交付是制胜关键。然而&#xff0c;当开发团队兴高采烈地投入工作&#xff0c;却发现从GitLab拉取代码的速度慢如蜗牛&#xff0c;那种沮丧感简直能瞬间浇灭热情。在分布式开发环境中&#xff0c;这种情况时…

落水人员目标检测数据集(猫脸码客第253期)

落水人员目标检测&#xff1a;科技守护生命之舟 一、背景与意义 随着人类海洋活动和水上活动的日益频繁&#xff0c;海上与水域安全事故频发。每年都会开展大量的海上救援行动&#xff0c;以搜救数以万计的落难人员。在水上活动区域&#xff0c;如水库、河道等&#xff0c;溺…

JAVA_强制类型转换:

类型范围大的变量&#xff0c;不可以直接赋值给类型变量小的变量 需要进行强制类型转换&#xff1a; 想要完成类型范围大的变量传给类型范围小的变量需要先创建一个新的变量&#xff08;类型与方法的形参类型要相同&#xff09;。将类型范围大的变量前面加上&#xff08;转换类…

打卡第44天:无人机数据集分类

重复以下内容 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a; 并拆分成多个文件 import os import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader,…

个人网站大更新,还是有个总站比较好

个人网站大更新&#xff0c;还是有个总站比较好 放弃了所有框架&#xff0c;用纯htmlcssjs撸了个网站&#xff0c;这回可以想改啥改啥了。 选择了黑紫作为主色调&#xff0c;暂时看着还算可以。 为什么不用那些框架了 几个原因&#xff1a; 尝试用vuepress、vitepress、not…

高精度算法详解:从原理到加减乘除的完整实现

文章目录 一、为什么需要高精度算法二、高精度算法的数据结构设计2.1 基础工具函数2.2 高精度加法实现2.3 高精度减法实现2.4 高精度乘法实现2.5 高精度除法实现 三、完整测试程序四、总结 一、为什么需要高精度算法 在编程中&#xff0c;处理极大数值是常见需求&#xff0c;例…

排序--计数排序

一,引言 计数排序是一种针对整数数据的高效排序算法。其主要流程可分为三个步骤&#xff1a;首先计算整数数据的数值范围&#xff1b;接着按大小顺序统计各数值的出现次数&#xff1b;最后根据统计结果输出排序后的数据序列。 二,求最值 遍历现有数据&#xff0c;获取最大值…

Kubernetes安全机制深度解析(四):动态准入控制和Webhook

#作者&#xff1a;程宏斌 文章目录 动态准入控制什么是准入 Webhook&#xff1f; 尝试准入Webhook先决条件编写一个准入 Webhook 服务器部署准入 Webhook 服务即时配置准入 Webhook对 API 服务器进行身份认证 Webhook 请求与响应Webhook 配置匹配请求-规则匹配请求&#xff1a…

WDK 10.0.19041.685,可在32位win7 sp1系统下搭配vs2019使用,可以编译出xp驱动。

(14)[驱动开发]配置环境 VS2019 WDK10 写 xp驱动 (14)[驱动开发]配置环境 VS2019 WDK10 写 xp驱动_microsoft visual 2019 wdk-CSDN博客文章浏览阅读3k次&#xff0c;点赞8次&#xff0c;收藏17次。本文介绍了如何在VS2019环境下安装和配置Windows Driver Kit(WDK)&#xff0…

论坛系统自动化测试

1、项目背景与测试目标 系统定位 论坛系统作为典型的高并发Web应用&#xff0c;需支持用户注册、登录、发帖、评论、私信及个人中心管理等核心功能&#xff0c;是用户公开交流与信息共享的核心平台。其稳定性与响应效率直接影响用户体验及平台活跃度。 测试必要性 功能可靠性&…

ChipWhisperer教程(一)

一、ChipWhisperer介绍 ChipWhisperer 是一个完整的开源工具链&#xff0c;用于学习嵌入式设备上的侧信道攻击并验证这些设备的侧信道抗性。ChipWhisperer主要用于功耗分析&#xff0c;利用设备功耗泄露的信息进行攻击&#xff0c;也可用于故障攻击&#xff08;电压和时钟毛刺…

【持续更新】计算机网络试题

问题1 请简要说明TCP/IP协议栈的四层结构&#xff0c;并分别举出每一层出现的典型协议或应用。 答案 应用层&#xff1a;ping,telnet,dns 传输层&#xff1a;tcp,udp 网络层&#xff1a;ip,icmp 数据链路层&#xff1a;arp,rarp 问题2 下列协议或应用分别属于TCP/IP协议…

短剧系统开发:打造高效、创新的短视频娱乐平台 - 从0到1的完整解决方案

一、短剧市场迎来爆发式增长 - 不容错过的万亿级蓝海 随着5G技术的普及和移动互联网的深度渗透&#xff0c;短剧市场正在经历前所未有的爆发式增长。根据权威机构艾瑞咨询最新发布的《2023年中国网络短剧行业发展报告》显示&#xff1a; 市场规模&#xff1a;2023年中国短剧市…

ChipWhisperer教程(三)

——CW305目标板的波形采集 一、目标板介绍 CW305 是一款独立的 FPGA 目标板&#xff0c;搭载的FPGA芯片为Xilinx Artix-7系列。 它具有与 FPGA 通信的 USB 接口、为 FPGA 提供时钟的外部 PLL、编程 VCC-INT 电源以及用于故障注入环境的二极管保护。 CW305 电路板有多种配置&…

django中如何解析content-type=application/json的请求

django中如何解析content-typeapplication/json的请求 本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 往期文章回顾: …