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

Vulkan 学习(15)---- Vulkan 完整渲染流程

目录

      • 完整渲染流程(在屏渲染)
        • 创建基本上下文
        • 创建 Surface 和 SwapChain
        • WSI 说明
        • 创建 RenderPass 和 FrameBuffer
        • 创建 GraphicPipeLine
        • 创建 CommandPool 和 CommandBuffer
        • 录制 Command 命令
        • 同步提交渲染到屏幕
        • Fence 和 Semaphore 区别

完整渲染流程(在屏渲染)

初始化流程:

  • 创建 Vulkan Instance, 启用 ValidationLayer
  • 根据 Instance 创建 PhysicalDevice 同时查询并获取所需的 QueueFamilyIndex
  • 创建 Logical Device, 获取 Device Queue
  • 创建 SwapChain 获取 SwapChain 中的 vkImage,同时创建 ImageView
  • 创建 RenderPass
  • 创建 GraphicPipeLine
  • 创建 FrameBuffer
  • 创建 CommandPool, 分配CommandBuffer
  • 创建 Sync Object(Fence/Semaphore)

绘制流程:

  • 录制 commandBuffer
  • 提交 commandBuffer
  • 等待绘制完成
创建基本上下文

从创建 Vulkan Instance, 到获取 Device Queue 的过程比较基础,参考下面的博客

参考:
Vulkan Instance
Vulkan 物理设备和队列族
Vulkan 逻辑设备

创建 Surface 和 SwapChain
  1. 创建 vkSurfaceKHR 对象
    Vulkan 中,vkSurfaceKHR 是一个抽象的表面对象,用于将渲染结果呈现到屏幕上,SwapChain 依赖于 vkSurfaceKHR,因为它需要知道渲染目标表面以及如何与之交互

vkSurfaceKHR 对象的作用如下:

  • 连接 Vulkan 和窗口系统
    Windows 下使用 win32 对接,Android 下使用 ANativeWindow 对接

  • 管理呈现模式
    比如是否使用V-sync,窗口模式等

  • 查询表面能力
    在创建 SwapChain 之前,Vulkan 需要查询表面属性(比如支持的格式,大小和呈现模式等)

下面的参考代码是从 vkSurfaceKHR 对象中获取颜色和大小等信息

SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {SwapChainSupportDetails details;vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);uint32_t formatCount;vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);if (formatCount != 0) {details.formats.resize(formatCount);vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());}uint32_t presentModeCount;vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);if (presentModeCount != 0) {details.presentModes.resize(presentModeCount);vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());}return details;
}
  1. 创建 SwapChain 对象
    根据创建的 vkSurfaceKHR 对象查询出的表面属性(支持的格式,大小,色域等),创建出SwapChain
    参考:Vulkan SwapChain 创建

SwapChain 在创建成功之后,内部已经包含了 vkImage 对象,但是这些 VkImage 是由 SwapChain 管理的,不能直接访问,需要调用 Vulkan API 来获取
这些 vkImage 对象的声明周期和 SwapChain 绑定,当 SwapChain 销毁时,对应的 vkImage 对象也会销毁

使用下面的方法获取SwapChain中的vkImage

uint32_t imageCount;
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, nullptr); std::vector<VkImage> swapchainImages(imageCount);
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, swapchainImages.data()); 

需要注意的是,如果窗口变化或者失效,此时需要重建 SwapChain, 对应的 vkImage 也可能失效,需要重新获取

WSI 说明

VulkanWSIWindow System Integration,窗口系统集成)主要集中在SurfaceSwapChain,呈现(Presentation) 和平台适配层等多个部分

  • vkSurfaceKHR
    表面属性(支持的格式,大小等)

  • VkSwapchainKHR
    提供一组 vkImage 用于渲染,控制呈现模式(MailBox or Immedaite)并处理窗口大小的变化

  • 呈现(vkQueuePresentKHR
    用于将 SwapChain 中的图像呈现到屏幕

创建 RenderPass 和 FrameBuffer

RenderPass 用于描述一次完整的渲染流程,说明了对 vkImage 的操作关系,vkImage 的操作关系是以颜色,模板、深度附件的形式关联到 RenderPassRenderPass 只描述渲染流程,不包括渲染数据,渲染数据需要以 FrameBuffer 形式来获取到

参考:Vulkan RenderPass 创建

这里创建从 SwapChain 获取到的 vkImagevkImageView,在 RenderPass 中定义为颜色附件(作为渲染目标)

vkFrameBuffer 类似于一个图像的容器,里面包含一系列的vkImageViewvkImageView 定义了对应的 vkImage 的解析方式
同时也关联到了对应的vkImage,结合 RenderPassvkImage 的操作方法,就定义了一次渲染流程

参考:Vulkan FrameBuffer 创建

创建 GraphicPipeLine

GraphicPipeLine 用于对应图形渲染管线,创建 GraphicPipeLine 也就是设置了图像管线的工作状态
最重要的,GraphicPipeLine 还包含对 VertexShaderFragementShader 的设置

参考:Vulkan Pipeline 创建

创建 CommandPool 和 CommandBuffer

Vulkan 中,指令缓存(Command buffer)是用于记录和存储一系列绘制和计算指令的对象
这些指令将在 GPU 上执行,可以执行不同类型的工作,包括绑定顶点缓存、绑定流水线、录制渲染通道指令、设置视口和裁切矩形,设置绘制指令,执行图像和缓存内容的复制操作等
Command bufferCommand buffer Pool 中分配得到,Command buffer Pool是根据创建逻辑设备的queueFamilyIndex 作为参数创建的

参考:Vulkan CommandBuffer 创建

录制 Command 命令

Command 命令包括绑定顶点缓存(可选)、绑定流水线、设置视口和裁切矩形、录制渲染通道指令、设置绘制指令等
recordCommandBuffer 的过程以 vkBeginCommandBuffer 开始,以 vkEndCommandBuffer 结束

// vkCmdBeginRenderPass
// vkCmdBindPipeline
// vkCmdSetViewport vkCmdSetScissor
// vkCmdDraw
// vkCmdEndRenderPass
// vkCmdEndRenderPass
voidrecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {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!");}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;vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);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);VkRect2D scissor{};scissor.offset = { 0, 0 };scissor.extent = swapChainExtent;vkCmdSetScissor(commandBuffer, 0, 1, &scissor);vkCmdDraw(commandBuffer, 3, 1, 0, 0);vkCmdEndRenderPass(commandBuffer);if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {throw std::runtime_error("failed to record command buffer!");}
}
同步提交渲染到屏幕

Vulkan渲染-呈现 流程涉及到两个关键的信号量:
imageAvailableSemaphore(由 vkAcquireNextImageKHR 触发),表示 交换链图像已经可用(即 GPU 已完成该图像的先前呈现,可以开始渲染)
renderFinishedSemaphore(由 vkQueueSubmit 触发)表示渲染完成,可以提交给 vkQueuePresentKHR 显示
onScreenRender

vkQueuePresentKHR 为什么需要 Semaphore ?

  1. VulkanPresent 是一个队列操作(vkQueuePresentKHR 提交到 VkQueue),意味着它由 GPU 驱动调度执行
    它需要等待 GPU 渲染完成(通过 pWaitSemaphores),确保图像可用后再显示,它可能涉及 GPU 端的图像布局转换(如从 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 切换到 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

  2. 最终,vkQueuePresentKHR 会将图像提交给窗口系统的合成器,这部分操作通常由 GPU 驱动与窗口系统协作完成
    基于此,vkQueuePresentKHR 要实现 GPU 之间队列的同步,所以需要引入一个 Semaphore

除此之前在 vkAcquireNextImageKHR 之前,还需要等待 vkQueueSubmit 产生的 Fence:

为什么在 Semaphore 的前提下还需要 Fence
Vulkan 的交换链(Swapchain)通常有 2 ~ 3 张图像(VK_SWAPCHAIN_IMAGE_COUNT),用于多缓冲
vkQueuePresentKHR 提交图像后,GPU 不会立即释放它,而是等待垂直同步(VSync)或窗口系统完成显示
如果过早复用同一张图像(即 vkAcquireNextImageKHR 返回的 imageIndex 仍被 GPU 使用),会导致写入冲突或者数据竞争

vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &inFlightFence);uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain,UINT64_MAX, \imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);vkResetCommandBuffer(commandBuffer, 0);
recordCommandBuffer(commandBuffer, imageIndex);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!");
}VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;vkQueuePresentKHR(presentQueue, &presentInfo);
Fence 和 Semaphore 区别

Fence 适用于 CPU-GPU 同步,而 Semaphore 适用于 GPU-GPU 同步

  • Fence 主要用于 CPU 等待 GPU 完成某个操作(例如,确保渲染完成后再更新资源)。
  • Semaphore 主要用于 GPU 内部不同队列或操作之间的同步(例如,渲染完成后再呈现)。

vkQueuePresentKHR 本身是一个 GPU 操作,它需要等待 另一个 GPU 操作(如 vkQueueSubmit 的渲染)完成,因此 semaphore 是更合适的同步机制

http://www.xdnf.cn/news/8840.html

相关文章:

  • 怎么判断文件是否支持多线程下载
  • 【Day36】
  • Python打卡训练营学习记录Day36
  • pyhton基础【4】判断
  • 使用Cursor生成需求文档+UI设计图
  • 【扫描线 线段树】P1856 [IOI 1998 ] [USACO5.5] 矩形周长Picture|普及+
  • firfox 国外版和国内版本账号不互通问题处理
  • 理论物理:为什么在极低温(接近绝对零度)时,经典理论失效?
  • 5.25 打卡
  • 高级特性实战:死信队列、延迟队列与优先级队列(三)
  • 《1.1_3_2 电路交换、报文交换、分组交换的性能分析|精讲篇》
  • git 把一个分支A的某一个 commit 应用到另一个分支B上
  • 乐观锁与悲观锁
  • Ansible配置文件常用选项详解
  • [c语言实战]C语言多线程编程:从零开发高并发任务调度器(五)
  • 浅谈ggplot2图表美化~
  • 8:OpenCV—仿射变换和坐标映射
  • 每日Prompt:龙虎斗
  • LangChain4j 项目实战——idea快捷键搜索
  • 力扣第157场双周赛
  • NISP和CISP有什么区别,哪个更好
  • 内容中台的核心价值是什么?
  • 决策引擎与规则引擎在交易所业务风控中的建设思路、架构设
  • 【开源项目】成本50元内的开源项目
  • 只能上百度b站打不开其他网页
  • 关于 java: 2. 面向对象编程(OOP)核心概念
  • lc hot 100之:回文链表
  • 探索容器技术:Docker与Kubernetes的实践指南
  • TiDB:从快速上手到核心原理与最佳实践
  • FreeRTOS--信号量